1use crate::path_to_string::PathToStringExt;
6use crate::{
7 MetaContents, MetaPackage, MetaSubpackages, PackageBuildManifest, PackageManifest, RelativeTo,
8 SubpackageEntry,
9};
10use anyhow::{Context, Result, anyhow, bail, ensure};
11use camino::Utf8PathBuf;
12use fuchsia_merkle::Hash;
13use fuchsia_url::RelativePackageUrl;
14use std::collections::BTreeMap;
15use std::fs::File;
16use std::io::{BufReader, Cursor};
17use std::path::{Path, PathBuf};
18use version_history::AbiRevision;
19
20const RESERVED_PATHS: &[&str] =
22 &[MetaContents::PATH, MetaPackage::PATH, version_history::AbiRevision::PATH];
23pub const ABI_REVISION_FILE_PATH: &str = "meta/fuchsia.abi/abi-revision";
24
25pub struct PackageBuilder {
27 name: String,
29
30 pub abi_revision: AbiRevision,
32
33 far_contents: BTreeMap<String, String>,
36
37 blobs: BTreeMap<String, String>,
39
40 manifest_path: Option<Utf8PathBuf>,
42
43 blob_sources_relative: RelativeTo,
46
47 published_name: Option<String>,
50
51 repository: Option<String>,
53
54 subpackages: BTreeMap<RelativePackageUrl, (Hash, PathBuf)>,
56
57 overwrite_files: bool,
59
60 overwrite_subpackages: bool,
62}
63
64impl PackageBuilder {
65 pub fn new(name: impl AsRef<str>, abi_revision: AbiRevision) -> Self {
70 PackageBuilder {
71 name: name.as_ref().to_string(),
72 abi_revision,
73 far_contents: BTreeMap::default(),
74 blobs: BTreeMap::default(),
75 manifest_path: None,
76 blob_sources_relative: RelativeTo::default(),
77 published_name: None,
78 repository: None,
79 subpackages: BTreeMap::default(),
80 overwrite_files: false,
81 overwrite_subpackages: false,
82 }
83 }
84
85 pub fn new_platform_internal_package(name: impl AsRef<str>) -> Self {
93 PackageBuilder::new(
94 name,
95 version_history_data::HISTORY.get_abi_revision_for_platform_components(),
96 )
97 }
98
99 pub fn from_package_build_manifest(
105 manifest: &PackageBuildManifest,
106 abi_revision: AbiRevision,
107 ) -> Result<Self> {
108 let meta_package = if let Some(path) = manifest.far_contents().get("meta/package") {
110 let f = File::open(path).with_context(|| format!("opening {path}"))?;
111
112 MetaPackage::deserialize(BufReader::new(f))?
113 } else {
114 return Err(anyhow!("package missing meta/package entry"));
115 };
116
117 ensure!(meta_package.variant().is_zero(), "package variant must be zero");
118
119 if manifest.far_contents().get("meta/fuchsia.abi/abi-revision").is_some() {
123 bail!(
124 "Manifest must not include entry for 'meta/fuchsia.abi/abi-revision'. \
125 Pass --api-level to package-tool instead."
126 )
127 };
128
129 let mut builder = PackageBuilder::new(meta_package.name(), abi_revision);
130
131 for (at_path, file) in manifest.external_contents() {
132 builder
133 .add_file_as_blob(at_path, file)
134 .with_context(|| format!("adding file {at_path} as blob {file}"))?;
135 }
136
137 for (at_path, file) in manifest.far_contents() {
138 if at_path == "meta/package" {
140 continue;
141 }
142
143 builder
144 .add_file_to_far(at_path, file)
145 .with_context(|| format!("adding file {at_path} to far {file}"))?;
146 }
147
148 Ok(builder)
149 }
150
151 pub fn from_manifest(
154 original_manifest: PackageManifest,
155 outdir: impl AsRef<Path>,
156 ) -> Result<Self> {
157 let mut abi_rev = None;
159 let mut inner_name = None;
160 let mut meta_blobs = BTreeMap::new();
161 let mut blob_paths = BTreeMap::new();
162 let mut subpackage_names = BTreeMap::new();
163 for blob in original_manifest.blobs() {
164 if blob.path == PackageManifest::META_FAR_BLOB_PATH {
165 let meta_far_contents = std::fs::read(&blob.source_path)
166 .with_context(|| format!("reading {}", blob.source_path))?;
167 let PackagedMetaFar { abi_revision, name, meta_contents, .. } =
168 PackagedMetaFar::parse(&meta_far_contents).context("parsing meta/")?;
169 abi_rev = Some(abi_revision);
170 inner_name = Some(name);
171 meta_blobs = meta_contents;
172 } else {
173 blob_paths.insert(blob.path.clone(), blob.source_path.clone());
174 }
175 }
176 for subpackage in original_manifest.subpackages() {
177 subpackage_names.insert(
178 subpackage.name.clone(),
179 (subpackage.merkle, subpackage.manifest_path.clone()),
180 );
181 }
182 let abi_rev = abi_rev
183 .ok_or_else(|| anyhow!("did not find {}", version_history::AbiRevision::PATH))?;
184 let inner_name = inner_name.ok_or_else(|| anyhow!("did not find {}", MetaPackage::PATH))?;
185
186 let mut builder = PackageBuilder::new(inner_name, abi_rev);
187 builder.published_name(original_manifest.name());
188 if let Some(repository) = original_manifest.repository() {
189 builder.repository(repository);
190 }
191
192 for (path, contents) in meta_blobs {
193 builder
194 .add_contents_to_far(&path, contents, &outdir)
195 .with_context(|| format!("adding {path} to far"))?;
196 }
197
198 for (path, source_path) in blob_paths {
199 builder
200 .add_file_as_blob(&path, &source_path)
201 .with_context(|| format!("adding {path}"))?;
202 }
203
204 for (name, (merkle, manifest_path)) in subpackage_names {
205 builder
206 .add_subpackage(
207 &name.parse().context("parsing subpackage name")?,
208 merkle,
209 manifest_path.into(),
210 )
211 .with_context(|| format!("adding {name}"))?;
212 }
213
214 Ok(builder)
215 }
216
217 pub fn manifest_path(&mut self, manifest_path: impl Into<Utf8PathBuf>) {
219 self.manifest_path = Some(manifest_path.into())
220 }
221
222 pub fn manifest_blobs_relative_to(&mut self, relative_to: RelativeTo) {
223 self.blob_sources_relative = relative_to
224 }
225
226 pub fn overwrite_files(&mut self, overwrite_files: bool) {
229 self.overwrite_files = overwrite_files
230 }
231
232 fn validate_ok_to_modify(&self, at_path: &str) -> Result<()> {
233 if RESERVED_PATHS.contains(&at_path) {
234 bail!("Cannot add '{}', it will be created by the PackageBuilder", at_path);
235 }
236
237 Ok(())
238 }
239
240 fn validate_ok_to_add_in_far(&self, at_path: impl AsRef<str>) -> Result<()> {
241 let at_path = at_path.as_ref();
242 self.validate_ok_to_modify(at_path)?;
243
244 if self.blobs.contains_key(at_path) {
246 return Err(anyhow!(
247 "Package '{}' already contains a file (as a blob) at: '{}'",
248 self.name,
249 at_path
250 ));
251 }
252
253 if self.far_contents.contains_key(at_path) && !self.overwrite_files {
254 return Err(anyhow!(
255 "Package '{}' already contains a file (in the far) at: '{}'",
256 self.name,
257 at_path
258 ));
259 }
260
261 Ok(())
262 }
263
264 fn validate_ok_to_add_as_blob(&self, at_path: impl AsRef<str>) -> Result<()> {
265 let at_path = at_path.as_ref();
266 self.validate_ok_to_modify(at_path)?;
267
268 if self.far_contents.contains_key(at_path) {
270 return Err(anyhow!(
271 "Package '{}' already contains a file (in the far) at: '{}'",
272 self.name,
273 at_path
274 ));
275 }
276 if self.blobs.contains_key(at_path) && !self.overwrite_files {
277 return Err(anyhow!(
278 "Package '{}' already contains a file (as a blob) at: '{}'",
279 self.name,
280 at_path
281 ));
282 }
283
284 Ok(())
285 }
286
287 pub fn add_file_to_far(
294 &mut self,
295 at_path: impl AsRef<str>,
296 file: impl AsRef<str>,
297 ) -> Result<()> {
298 let at_path = at_path.as_ref();
299 let file = file.as_ref();
300 self.validate_ok_to_add_in_far(at_path)?;
301
302 self.far_contents.insert(at_path.to_string(), file.to_string());
303
304 Ok(())
305 }
306
307 pub fn remove_file_from_far(&mut self, at_path: impl AsRef<str>) -> Result<()> {
313 self.validate_ok_to_modify(at_path.as_ref())?;
314 match self.far_contents.remove(at_path.as_ref()) {
315 Some(_key) => Ok(()),
316 None => Err(anyhow!("file not in meta.far")),
317 }
318 }
319
320 pub fn add_file_as_blob(
327 &mut self,
328 at_path: impl AsRef<str>,
329 file: impl AsRef<str>,
330 ) -> Result<()> {
331 let at_path = at_path.as_ref();
332 let file = file.as_ref();
333 self.validate_ok_to_add_as_blob(at_path)?;
334
335 self.blobs.insert(at_path.to_string(), file.to_string());
336
337 Ok(())
338 }
339
340 pub fn add_file_as_blob_with_dedup(
344 &mut self,
345 at_path: impl AsRef<str>,
346 file: impl AsRef<str>,
347 ) -> Result<()> {
348 let at_path = at_path.as_ref();
349 let file = file.as_ref();
350
351 if let Some(existing_file) = self.blobs.get(at_path) {
352 let existing_contents =
353 std::fs::read(existing_file).context(format!("reading {}", existing_file))?;
354 let new_contents = std::fs::read(file).context(format!("reading {}", file))?;
355
356 if existing_contents == new_contents {
359 return Ok(());
360 }
361 }
362
363 self.add_file_as_blob(at_path, file)
364 }
365
366 pub fn remove_blob_file(&mut self, at_path: impl AsRef<str>) -> Result<()> {
372 self.validate_ok_to_modify(at_path.as_ref())?;
373 match self.blobs.remove(at_path.as_ref()) {
374 Some(_key) => Ok(()),
375 None => Err(anyhow!("file not in package contents")),
376 }
377 }
378
379 pub fn add_contents_as_blob<C: AsRef<[u8]>>(
382 &mut self,
383 at_path: impl AsRef<str>,
384 contents: C,
385 gendir: impl AsRef<Path>,
386 ) -> Result<()> {
387 self.validate_ok_to_add_as_blob(&at_path)?;
389 let source_path = Self::write_contents_to_file(gendir, at_path.as_ref(), contents)?;
390 self.add_file_as_blob(at_path, source_path.path_to_string()?)
391 }
392
393 pub fn add_contents_to_far<C: AsRef<[u8]>>(
396 &mut self,
397 at_path: impl AsRef<str>,
398 contents: C,
399 gendir: impl AsRef<Path>,
400 ) -> Result<()> {
401 self.validate_ok_to_add_in_far(&at_path)?;
403 let source_path = Self::write_contents_to_file(gendir, at_path.as_ref(), contents)?;
404 self.add_file_to_far(at_path, source_path.path_to_string()?)
405 }
406
407 fn write_contents_to_file<C: AsRef<[u8]>>(
409 gendir: impl AsRef<Path>,
410 file_path: impl AsRef<Path>,
411 contents: C,
412 ) -> Result<PathBuf> {
413 let file_path = gendir.as_ref().join(file_path);
414 if let Some(parent_dir) = file_path.parent() {
415 std::fs::create_dir_all(parent_dir)
416 .context(format!("creating parent directories for {}", file_path.display()))?;
417 }
418 std::fs::write(&file_path, contents)
419 .context(format!("writing contents to file: {}", file_path.display()))?;
420 Ok(file_path)
421 }
422
423 pub fn overwrite_subpackages(&mut self, overwrite_subpackages: bool) {
426 self.overwrite_subpackages = overwrite_subpackages
427 }
428
429 pub fn add_subpackage(
431 &mut self,
432 url: &RelativePackageUrl,
433 package_hash: Hash,
434 package_manifest_path: PathBuf,
435 ) -> Result<()> {
436 if self.subpackages.contains_key(url) && !self.overwrite_subpackages {
437 return Err(anyhow!("duplicate entry for {:?}", url));
438 }
439 self.subpackages.insert(url.clone(), (package_hash, package_manifest_path));
440 Ok(())
441 }
442
443 pub fn name(&mut self, name: impl AsRef<str>) {
445 self.name = name.as_ref().to_string();
446 }
447
448 pub fn published_name(&mut self, published_name: impl AsRef<str>) {
452 self.published_name = Some(published_name.as_ref().into());
453 }
454
455 pub fn repository(&mut self, repository: impl AsRef<str>) {
457 self.repository = Some(repository.as_ref().into());
458 }
459
460 pub fn read_contents_from_far(&self, file_path: &str) -> Result<Vec<u8>> {
462 if let Some(p) = self.far_contents.get(file_path) {
463 std::fs::read(p).with_context(|| format!("reading {p}"))
464 } else {
465 bail!(
466 "couldn't find `{}` in package: {:?}",
467 file_path,
468 self.far_contents.keys().collect::<Vec<_>>()
469 );
470 }
471 }
472
473 pub fn build(
483 self,
484 gendir: impl AsRef<Path>,
485 metafar_path: impl AsRef<Path>,
486 ) -> Result<PackageManifest> {
487 let gendir = gendir.as_ref();
488 let metafar_path = metafar_path.as_ref();
489
490 let PackageBuilder {
491 name,
492 abi_revision,
493 mut far_contents,
494 blobs,
495 manifest_path,
496 blob_sources_relative,
497 published_name,
498 repository,
499 subpackages,
500 overwrite_files: _,
501 overwrite_subpackages: _,
502 } = self;
503
504 far_contents.insert(
505 MetaPackage::PATH.to_string(),
506 create_meta_package_file(gendir, &name)
507 .with_context(|| format!("Writing the {} file", MetaPackage::PATH))?,
508 );
509
510 let abi_revision_file =
511 Self::write_contents_to_file(gendir, ABI_REVISION_FILE_PATH, abi_revision.as_bytes())
512 .with_context(|| format!("Writing the {ABI_REVISION_FILE_PATH} file"))?;
513
514 far_contents.insert(
515 ABI_REVISION_FILE_PATH.to_string(),
516 abi_revision_file.path_to_string().with_context(|| {
517 format!("Adding the {ABI_REVISION_FILE_PATH} file to the package")
518 })?,
519 );
520
521 if !subpackages.is_empty() {
523 far_contents.insert(
524 MetaSubpackages::PATH.to_string(),
525 create_meta_subpackages_file(gendir, subpackages.clone()).with_context(|| {
526 format!("Adding the {} file to the package", MetaSubpackages::PATH)
527 })?,
528 );
529 }
530
531 let package_build_manifest =
532 PackageBuildManifest::from_external_and_far_contents(blobs, far_contents)
533 .with_context(|| "creating creation manifest".to_string())?;
534
535 let package_manifest = crate::build::build(
536 &package_build_manifest,
537 metafar_path,
538 published_name.unwrap_or(name),
539 subpackages
540 .into_iter()
541 .map(|(name, (merkle, package_manifest_path))| SubpackageEntry {
542 name,
543 merkle,
544 package_manifest_path,
545 })
546 .collect(),
547 repository,
548 abi_revision,
549 )
550 .with_context(|| format!("building package manifest {}", metafar_path.display()))?;
551
552 if let Some(manifest_path) = manifest_path {
553 if let RelativeTo::File = blob_sources_relative {
554 let copy = package_manifest.clone();
555 copy.write_with_relative_paths(&manifest_path).with_context(|| {
556 format!(
557 "Failed to create package manifest with relative paths at: {manifest_path}"
558 )
559 })?;
560 } else {
561 package_manifest.write(&manifest_path).with_context(|| {
563 format!("Failed to create package manifest at: {manifest_path}")
564 })?;
565 }
566 }
567 Ok(package_manifest)
568 }
569}
570
571fn create_meta_package_file(gendir: &Path, name: impl Into<String>) -> Result<String> {
575 let package_name = name.into();
576 let meta_package_path = gendir.join(MetaPackage::PATH);
577 if let Some(parent_dir) = meta_package_path.parent() {
578 std::fs::create_dir_all(parent_dir)?;
579 }
580
581 let file = std::fs::File::create(&meta_package_path)?;
582 let meta_package = MetaPackage::from_name_and_variant_zero(package_name.try_into()?);
583 meta_package.serialize(file)?;
584 meta_package_path.path_to_string()
585}
586
587struct PackagedMetaFar {
589 name: String,
591
592 abi_revision: AbiRevision,
594
595 meta_contents: BTreeMap<String, Vec<u8>>,
597}
598
599impl PackagedMetaFar {
600 fn parse(bytes: &[u8]) -> Result<Self> {
601 let mut meta_far =
602 fuchsia_archive::Utf8Reader::new(Cursor::new(bytes)).context("reading FAR")?;
603
604 let mut abi_revision = None;
605 let mut name = None;
606 let mut meta_contents = BTreeMap::new();
607
608 let meta_paths = meta_far.list().map(|e| e.path().to_owned()).collect::<Vec<_>>();
610
611 for path in meta_paths {
613 let contents = meta_far.read_file(&path).with_context(|| format!("reading {path}"))?;
614
615 if path == MetaContents::PATH {
616 continue;
617 } else if path == MetaPackage::PATH {
618 ensure!(name.is_none(), "only one name per package");
619 let mp = MetaPackage::deserialize(Cursor::new(&contents))
620 .context("deserializing meta/package")?;
621 name = Some(mp.name().to_string());
622 } else if path == ABI_REVISION_FILE_PATH {
623 ensure!(abi_revision.is_none(), "only one abi revision per package");
624 ensure!(contents.len() == 8, "ABI revision must be encoded as 8 bytes");
625 abi_revision = Some(AbiRevision::try_from(contents.as_slice()).unwrap());
626 } else {
627 meta_contents.insert(path, contents);
628 }
629 }
630 let abi_revision =
631 abi_revision.ok_or_else(|| anyhow!("did not find {}", ABI_REVISION_FILE_PATH))?;
632 let name = name.ok_or_else(|| anyhow!("did not find {}", MetaPackage::PATH))?;
633
634 Ok(Self { name, abi_revision, meta_contents })
635 }
636}
637
638fn create_meta_subpackages_file(
642 gendir: &Path,
643 subpackages: BTreeMap<RelativePackageUrl, (Hash, PathBuf)>,
644) -> Result<String> {
645 let meta_subpackages_path = gendir.join(MetaSubpackages::PATH);
646 if let Some(parent_dir) = meta_subpackages_path.parent() {
647 std::fs::create_dir_all(parent_dir)?;
648 }
649
650 let meta_subpackages = MetaSubpackages::from_iter(
651 subpackages.into_iter().map(|(name, (merkle, _))| (name, merkle)),
652 );
653 let file = std::fs::File::create(&meta_subpackages_path)?;
654 meta_subpackages.serialize(file)?;
655 meta_subpackages_path.path_to_string()
656}
657
658#[cfg(test)]
659mod tests {
660 use super::*;
661 use camino::Utf8Path;
662 use tempfile::{NamedTempFile, TempDir};
663
664 const FAKE_ABI_REVISION: AbiRevision = AbiRevision::from_u64(0x5836508c2defac54);
665
666 #[test]
667 fn test_create_meta_package_file() {
668 let gen_dir = TempDir::new().unwrap();
669 let name = "some_test_package";
670 let meta_package_path = gen_dir.as_ref().join("meta/package");
671 let created_path = create_meta_package_file(gen_dir.path(), name).unwrap();
672 assert_eq!(created_path, meta_package_path.path_to_string().unwrap());
673
674 let raw_contents = std::fs::read(meta_package_path).unwrap();
675 let meta_package = MetaPackage::deserialize(std::io::Cursor::new(raw_contents)).unwrap();
676 assert_eq!(meta_package.name().as_ref(), "some_test_package");
677 assert!(meta_package.variant().is_zero());
678 }
679
680 #[test]
681 fn test_builder() {
682 let outdir = TempDir::new().unwrap();
683 let metafar_path = outdir.path().join("meta.far");
684
685 let far_source_file_path = NamedTempFile::new_in(&outdir).unwrap();
687 std::fs::write(&far_source_file_path, "some data for far").unwrap();
688
689 let blob_source_file_path = NamedTempFile::new_in(&outdir).unwrap();
691 let blob_contents = "some data for blob";
692 std::fs::write(&blob_source_file_path, blob_contents).unwrap();
693
694 let blob_hash = fuchsia_merkle::from_slice(blob_contents.as_bytes()).root();
696
697 let subpackage_url = "subpackage0".parse::<RelativePackageUrl>().unwrap();
698 let subpackage_hash = Hash::from([0; fuchsia_hash::HASH_SIZE]);
699 let subpackage_package_manifest_path = "subpackages/package_manifest.json";
700
701 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
703 builder
704 .add_file_as_blob("some/blob", blob_source_file_path.path().path_to_string().unwrap())
705 .unwrap();
706 builder
707 .add_file_to_far(
708 "meta/some/file",
709 far_source_file_path.path().path_to_string().unwrap(),
710 )
711 .unwrap();
712 builder
713 .add_subpackage(
714 &subpackage_url,
715 subpackage_hash,
716 subpackage_package_manifest_path.into(),
717 )
718 .unwrap();
719
720 let manifest = builder.build(&outdir, &metafar_path).unwrap();
722
723 assert_eq!(manifest.name().as_ref(), "some_pkg_name");
725
726 let (blobs, subpackages) = manifest.into_blobs_and_subpackages();
727
728 let blob_info = blobs.iter().find(|info| info.path == "some/blob").unwrap().clone();
730 assert_eq!(blob_hash, blob_info.merkle);
731 assert_eq!(blob_contents, std::fs::read_to_string(blob_info.source_path).unwrap());
732
733 let subpackage_info =
735 subpackages.iter().find(|info| info.name == "subpackage0").unwrap().clone();
736 assert_eq!(subpackage_hash, subpackage_info.merkle);
737 assert_eq!(subpackage_package_manifest_path, subpackage_info.manifest_path);
738
739 let mut metafar = std::fs::File::open(metafar_path).unwrap();
741 let mut far_reader = fuchsia_archive::Utf8Reader::new(&mut metafar).unwrap();
742 let far_file_data = far_reader.read_file("meta/some/file").unwrap();
743 let far_file_data = std::str::from_utf8(far_file_data.as_slice()).unwrap();
744 assert_eq!(far_file_data, "some data for far");
745
746 let abi_revision_data = far_reader.read_file("meta/fuchsia.abi/abi-revision").unwrap();
748 let abi_revision_data: [u8; 8] = abi_revision_data.try_into().unwrap();
749 let abi_revision = AbiRevision::from_bytes(abi_revision_data);
750 assert_eq!(abi_revision, FAKE_ABI_REVISION);
751 }
752
753 #[test]
754 fn test_from_manifest() {
755 let first_outdir = TempDir::new().unwrap();
756
757 let inner_name = "some_pkg_name";
759 let mut first_builder = PackageBuilder::new(inner_name, FAKE_ABI_REVISION);
760 let published_name = "some_other_pkg_name";
762 first_builder.published_name(published_name);
763
764 let first_far_source_file_path = NamedTempFile::new_in(&first_outdir).unwrap();
766 let first_far_contents = "some data for far";
767 std::fs::write(&first_far_source_file_path, first_far_contents).unwrap();
768 first_builder
769 .add_file_to_far("meta/some/file", first_far_source_file_path.path().to_string_lossy())
770 .unwrap();
771
772 let first_blob_source_file_path = NamedTempFile::new_in(&first_outdir).unwrap();
774 let first_blob_contents = "some data for blob";
775 std::fs::write(&first_blob_source_file_path, first_blob_contents).unwrap();
776 first_builder
777 .add_file_as_blob("some/blob", first_blob_source_file_path.path().to_string_lossy())
778 .unwrap();
779
780 let first_subpackage_url = "subpackage0".parse::<RelativePackageUrl>().unwrap();
781 let first_subpackage_hash = Hash::from([0; fuchsia_hash::HASH_SIZE]);
782 let first_subpackage_package_manifest_path = "subpackages/package_manifest.json";
783
784 first_builder
785 .add_subpackage(
786 &first_subpackage_url,
787 first_subpackage_hash,
788 first_subpackage_package_manifest_path.into(),
789 )
790 .unwrap();
791
792 let first_manifest =
794 first_builder.build(&first_outdir, first_outdir.path().join("meta.far")).unwrap();
795 assert_eq!(
796 first_manifest.blobs().len(),
797 2,
798 "package should have a meta.far and a single blob"
799 );
800 assert_eq!(
801 first_manifest.subpackages().len(),
802 1,
803 "package should have a single subpackage"
804 );
805 let blob_info = first_manifest
806 .blobs()
807 .iter()
808 .find(|blob_info| blob_info.path == PackageManifest::META_FAR_BLOB_PATH)
809 .unwrap();
810 let mut metafar = std::fs::File::open(&blob_info.source_path).unwrap();
811 let far_reader = fuchsia_archive::Utf8Reader::new(&mut metafar).unwrap();
812 let first_paths_in_far =
813 far_reader.list().map(|e| e.path().to_string()).collect::<Vec<_>>();
814
815 let second_outdir = TempDir::new().unwrap();
817 let mut second_builder =
818 PackageBuilder::from_manifest(first_manifest.clone(), second_outdir.path()).unwrap();
819
820 let second_far_source_file_path = NamedTempFile::new_in(&second_outdir).unwrap();
822 let second_far_contents = "some more data for far";
823 std::fs::write(&second_far_source_file_path, second_far_contents).unwrap();
824 second_builder
825 .add_file_to_far(
826 "meta/some/other/file",
827 second_far_source_file_path.path().to_string_lossy(),
828 )
829 .unwrap();
830
831 let second_blob_source_file_path = NamedTempFile::new_in(&second_outdir).unwrap();
833 let second_blob_contents = "some more data for blobs";
834 std::fs::write(&second_blob_source_file_path, second_blob_contents).unwrap();
835 second_builder
836 .add_file_as_blob(
837 "some/other/blob",
838 second_blob_source_file_path.path().to_string_lossy(),
839 )
840 .unwrap();
841
842 let second_metafar_path = second_outdir.path().join("meta.far");
844 let second_manifest = second_builder.build(&second_outdir, second_metafar_path).unwrap();
845 assert_eq!(first_manifest.name(), second_manifest.name(), "package names must match");
846 assert_eq!(
847 second_manifest.blobs().len(),
848 3,
849 "package should have a meta.far and two blobs"
850 );
851 assert_eq!(
852 second_manifest.subpackages().len(),
853 1,
854 "package should STILL have a single subpackage"
855 );
856
857 for blob_info in second_manifest.blobs() {
859 match &*blob_info.path {
860 PackageManifest::META_FAR_BLOB_PATH => {
861 let mut metafar = std::fs::File::open(&blob_info.source_path).unwrap();
863 let mut far_reader = fuchsia_archive::Utf8Reader::new(&mut metafar).unwrap();
864 let paths_in_far =
865 far_reader.list().map(|e| e.path().to_string()).collect::<Vec<_>>();
866 assert_eq!(
867 paths_in_far.len(),
868 first_paths_in_far.len() + 1,
869 "must have the original files and one added one"
870 );
871
872 for far_path in paths_in_far {
873 let far_bytes = far_reader.read_file(&far_path).unwrap();
874 match &*far_path {
875 MetaContents::PATH => (), MetaPackage::PATH => {
877 let mp = MetaPackage::deserialize(Cursor::new(&far_bytes)).unwrap();
878 assert_eq!(mp.name().as_ref(), inner_name);
879 }
880 MetaSubpackages::PATH => {
881 let ms =
882 MetaSubpackages::deserialize(Cursor::new(&far_bytes)).unwrap();
883 assert_eq!(ms.subpackages().len(), 1);
884 let (url, hash) = ms.subpackages().iter().next().unwrap();
885 assert_eq!(url, &first_subpackage_url);
886 assert_eq!(hash, &first_subpackage_hash);
887 }
888 ABI_REVISION_FILE_PATH => {
889 assert_eq!(far_bytes, FAKE_ABI_REVISION.as_bytes());
890 }
891 "meta/some/file" => {
892 assert_eq!(far_bytes, first_far_contents.as_bytes());
893 }
894 "meta/some/other/file" => {
895 assert_eq!(far_bytes, second_far_contents.as_bytes());
896 }
897 other => panic!("unrecognized file in meta.far: {other}"),
898 }
899 }
900 }
901 "some/blob" => {
902 assert_eq!(
903 std::fs::read_to_string(&blob_info.source_path).unwrap(),
904 first_blob_contents,
905 );
906 }
907 "some/other/blob" => {
908 assert_eq!(
909 std::fs::read_to_string(&blob_info.source_path).unwrap(),
910 second_blob_contents,
911 )
912 }
913 other => panic!("unrecognized path in blobs `{other}`"),
914 }
915 }
916 }
917
918 #[test]
919 fn test_removes() {
920 let gendir = TempDir::new().unwrap();
921 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
922 assert!(builder.add_contents_to_far("meta/foo", "foo", gendir.path()).is_ok());
923 assert!(builder.add_contents_to_far("meta/bar", "bar", gendir.path()).is_ok());
924
925 assert!(builder.add_contents_as_blob("baz", "baz", gendir.path()).is_ok());
926 assert!(builder.add_contents_as_blob("boom", "boom", gendir.path()).is_ok());
927
928 assert!(builder.remove_file_from_far("meta/foo").is_ok());
929 assert!(builder.remove_file_from_far("meta/does_not_exist").is_err());
930
931 assert!(builder.remove_blob_file("baz").is_ok());
932 assert!(builder.remove_blob_file("does_not_exist").is_err());
933
934 let outdir = TempDir::new().unwrap();
935 let metafar_path = outdir.path().join("meta.far");
936
937 let pkg_manifest = builder.build(&outdir, &metafar_path).unwrap();
938
939 for blob_info in pkg_manifest.blobs() {
942 match &*blob_info.path {
943 PackageManifest::META_FAR_BLOB_PATH => {
944 let mut metafar = std::fs::File::open(&blob_info.source_path).unwrap();
945 let mut far_reader = fuchsia_archive::Utf8Reader::new(&mut metafar).unwrap();
946 let paths_in_far =
947 far_reader.list().map(|e| e.path().to_string()).collect::<Vec<_>>();
948
949 for far_path in paths_in_far {
950 let far_bytes = far_reader.read_file(&far_path).unwrap();
951 match &*far_path {
952 MetaContents::PATH => (), MetaPackage::PATH => (),
954 MetaSubpackages::PATH => (),
955 ABI_REVISION_FILE_PATH => (),
956 "meta/bar" => {
957 assert_eq!(far_bytes, "bar".as_bytes());
958 }
959 other => panic!("unrecognized file in meta.far: {other}"),
960 }
961 }
962 }
963 "boom" => {
964 assert_eq!(std::fs::read_to_string(&blob_info.source_path).unwrap(), "boom",);
965 }
966 other => panic!("unrecognized path in blobs `{other}`"),
967 }
968 }
969 }
970
971 #[test]
972 fn test_overwrite_abi_revision() {
973 const OTHER_FAKE_ABI_REVISION: AbiRevision = AbiRevision::from_u64(0x1234);
974
975 let outdir = TempDir::new().unwrap();
976 let metafar_path = outdir.path().join("meta.far");
977
978 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
980
981 assert_eq!(builder.abi_revision, FAKE_ABI_REVISION);
982
983 builder.abi_revision = OTHER_FAKE_ABI_REVISION;
985
986 let _ = builder.build(&outdir, &metafar_path).unwrap();
988
989 let mut metafar = std::fs::File::open(metafar_path).unwrap();
991 let mut far_reader = fuchsia_archive::Utf8Reader::new(&mut metafar).unwrap();
992
993 let abi_revision_data = far_reader.read_file("meta/fuchsia.abi/abi-revision").unwrap();
994 let abi_revision_data: [u8; 8] = abi_revision_data.try_into().unwrap();
995 let abi_revision = AbiRevision::from_bytes(abi_revision_data);
996 assert_eq!(abi_revision, OTHER_FAKE_ABI_REVISION);
997 }
998
999 #[test]
1000 fn test_build_rejects_meta_contents() {
1001 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1002 assert!(builder.add_file_to_far("meta/contents", "some/src/file").is_err());
1003 assert!(builder.add_file_as_blob("meta/contents", "some/src/file").is_err());
1004 }
1005
1006 #[test]
1007 fn test_build_rejects_meta_package() {
1008 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1009 assert!(builder.add_file_to_far("meta/package", "some/src/file").is_err());
1010 assert!(builder.add_file_as_blob("meta/package", "some/src/file").is_err());
1011 }
1012
1013 #[test]
1014 fn test_build_rejects_abi_revision() {
1015 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1016 assert!(builder.add_file_to_far("meta/fuchsia.abi/abi-revision", "some/src/file").is_err());
1017 assert!(
1018 builder.add_file_as_blob("meta/fuchsia.abi/abi-revision", "some/src/file").is_err()
1019 );
1020 }
1021
1022 #[test]
1023 fn test_builder_rejects_path_in_far_when_existing_path_in_far() {
1024 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1025 builder.add_file_to_far("some/far/file", "some/src/file").unwrap();
1026 assert!(builder.add_file_to_far("some/far/file", "some/src/file").is_err());
1027 }
1028
1029 #[test]
1030 fn test_builder_allows_overwrite_path_in_far_when_flag_set() {
1031 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1032 builder.overwrite_files(true);
1033 builder.add_file_to_far("some/far/file", "some/src/file").unwrap();
1034 assert!(builder.add_file_to_far("some/far/file", "some/src/file").is_ok());
1035 }
1036
1037 #[test]
1038 fn test_builder_rejects_path_as_blob_when_existing_path_in_far() {
1039 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1040 builder.add_file_to_far("some/far/file", "some/src/file").unwrap();
1041 assert!(builder.add_file_as_blob("some/far/file", "some/src/file").is_err());
1042 }
1043
1044 #[test]
1045 fn test_builder_rejects_path_as_blob_when_existing_path_in_far_and_overwrite_set() {
1046 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1048 builder.overwrite_files(true);
1049 builder.add_file_to_far("some/far/file", "some/src/file").unwrap();
1050 assert!(builder.add_file_as_blob("some/far/file", "some/src/file").is_err());
1051 }
1052
1053 #[test]
1054 fn test_builder_rejects_path_in_far_when_existing_path_as_blob() {
1055 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1056 builder.add_file_as_blob("some/far/file", "some/src/file").unwrap();
1057 assert!(builder.add_file_to_far("some/far/file", "some/src/file").is_err());
1058 }
1059
1060 #[test]
1061 fn test_builder_rejects_path_in_far_when_existing_path_as_blob_and_overwrite_set() {
1062 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1064 builder.overwrite_files(true);
1065 builder.add_file_as_blob("some/far/file", "some/src/file").unwrap();
1066 assert!(builder.add_file_to_far("some/far/file", "some/src/file").is_err());
1067 }
1068
1069 #[test]
1070 fn test_builder_rejects_path_in_blob_when_existing_path_as_blob() {
1071 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1072 builder.add_file_as_blob("some/far/file", "some/src/file").unwrap();
1073 assert!(builder.add_file_as_blob("some/far/file", "some/src/file").is_err());
1074 }
1075
1076 #[test]
1077 fn test_builder_allows_overwrite_path_as_blob_when_flag_set() {
1078 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1079 builder.overwrite_files(true);
1080 builder.add_file_as_blob("some/far/file", "some/src/file").unwrap();
1081 assert!(builder.add_file_as_blob("some/far/file", "some/src/file").is_ok());
1082 }
1083
1084 fn add_blob_with_dedup(
1090 builder: &mut PackageBuilder,
1091 contents_1: &str,
1092 contents_2: &str,
1093 ) -> Result<()> {
1094 let tmp = TempDir::new().unwrap();
1096 let outdir = Utf8Path::from_path(tmp.path()).unwrap();
1097
1098 let file_1 = NamedTempFile::new_in(outdir).unwrap();
1099 let file_2 = NamedTempFile::new_in(outdir).unwrap();
1100 std::fs::write(&file_1, contents_1).unwrap();
1101 std::fs::write(&file_2, contents_2).unwrap();
1102 let path_1 = file_1.path().to_str().unwrap();
1103 let path_2 = file_2.path().to_str().unwrap();
1104
1105 builder.add_file_as_blob("path/to/blob", path_1).unwrap();
1107 builder.add_file_as_blob_with_dedup("path/to/blob", path_2)
1108 }
1109
1110 #[test]
1111 fn test_builder_add_file_as_blob_dedup() {
1112 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1113
1114 assert!(add_blob_with_dedup(&mut builder, "blob contents", "blob contents").is_ok());
1116 }
1117
1118 #[test]
1119 fn test_builder_add_file_as_blob_dedup_rejects_different_contents() {
1120 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1121
1122 assert!(add_blob_with_dedup(&mut builder, "blob contents", "other blob contents").is_err());
1124 }
1125
1126 #[test]
1127 fn test_builder_add_file_as_blob_dedup_respects_overwrite_flag() {
1128 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1129 builder.overwrite_files(true);
1130
1131 assert!(add_blob_with_dedup(&mut builder, "blob contents", "other blob contents").is_ok());
1133 }
1134
1135 #[test]
1136 fn test_builder_makes_file_relative_manifests_when_asked() {
1137 let tmp = TempDir::new().unwrap();
1138 let outdir = Utf8Path::from_path(tmp.path()).unwrap();
1139
1140 let metafar_path = outdir.join("meta.far");
1141 let manifest_path = outdir.join("package_manifest.json");
1142
1143 let far_source_file_path = NamedTempFile::new_in(outdir).unwrap();
1145 std::fs::write(&far_source_file_path, "some data for far").unwrap();
1146
1147 let blob_source_file_path = outdir.join("contents/data_file");
1149 std::fs::create_dir_all(blob_source_file_path.parent().unwrap()).unwrap();
1150 let blob_contents = "some data for blob";
1151 std::fs::write(&blob_source_file_path, blob_contents).unwrap();
1152
1153 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1155 builder.add_file_as_blob("some/blob", &blob_source_file_path).unwrap();
1156 builder
1157 .add_file_to_far(
1158 "meta/some/file",
1159 far_source_file_path.path().path_to_string().unwrap(),
1160 )
1161 .unwrap();
1162
1163 builder.manifest_path(manifest_path);
1165 builder.manifest_blobs_relative_to(RelativeTo::File);
1166
1167 let manifest = builder.build(outdir, metafar_path).unwrap();
1169
1170 manifest
1173 .blobs()
1174 .iter()
1175 .find(|b| b.source_path == blob_source_file_path)
1176 .expect("The manifest should have paths relative to the working directory");
1177
1178 }
1180
1181 #[test]
1182 fn test_builder_add_subpackages() {
1183 let outdir = TempDir::new().unwrap();
1184 let metafar_path = outdir.path().join("meta.far");
1185
1186 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1187
1188 let pkg1_url = "pkg1".parse::<RelativePackageUrl>().unwrap();
1189 let pkg1_hash = Hash::from([0; fuchsia_hash::HASH_SIZE]);
1190 let pkg1_package_manifest_path = outdir.path().join("path1/package_manifest.json");
1191
1192 let pkg2_url = "pkg2".parse::<RelativePackageUrl>().unwrap();
1193 let pkg2_hash = Hash::from([1; fuchsia_hash::HASH_SIZE]);
1194 let pkg2_package_manifest_path = outdir.path().join("path2/package_manifest.json");
1195
1196 builder.add_subpackage(&pkg1_url, pkg1_hash, pkg1_package_manifest_path).unwrap();
1197 builder.add_subpackage(&pkg2_url, pkg2_hash, pkg2_package_manifest_path).unwrap();
1198
1199 builder.build(&outdir, &metafar_path).unwrap();
1201
1202 let mut metafar = std::fs::File::open(metafar_path).unwrap();
1204 let mut far_reader = fuchsia_archive::Utf8Reader::new(&mut metafar).unwrap();
1205 let far_file_data = far_reader.read_file(MetaSubpackages::PATH).unwrap();
1206
1207 assert_eq!(
1208 MetaSubpackages::deserialize(Cursor::new(&far_file_data)).unwrap(),
1209 MetaSubpackages::from_iter([(pkg1_url, pkg1_hash), (pkg2_url, pkg2_hash)])
1210 );
1211 }
1212
1213 #[test]
1214 fn test_builder_rejects_subpackages_collisions() {
1215 let url = "pkg".parse::<RelativePackageUrl>().unwrap();
1216 let hash1 = Hash::from([0; fuchsia_hash::HASH_SIZE]);
1217 let package_manifest_path1 = PathBuf::from("path1/package_manifest.json");
1218 let hash2 = Hash::from([0; fuchsia_hash::HASH_SIZE]);
1219 let package_manifest_path2 = PathBuf::from("path2/package_manifest.json");
1220
1221 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1222 builder.add_subpackage(&url, hash1, package_manifest_path1).unwrap();
1223 assert!(builder.add_subpackage(&url, hash2, package_manifest_path2).is_err());
1224 }
1225
1226 #[test]
1227 fn test_builder_allows_overwrite_subpackages_when_flag_set() {
1228 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1229 builder.overwrite_subpackages(true);
1230
1231 let url = "pkg".parse::<RelativePackageUrl>().unwrap();
1232 let package_hash: fuchsia_hash::GenericDigest<fuchsia_hash::FuchsiaMerkleMarker> =
1233 Hash::from([0; fuchsia_hash::HASH_SIZE]);
1234 let package_manifest_path = PathBuf::from("path/package_manifest.json");
1235
1236 let package_hash2: fuchsia_hash::GenericDigest<fuchsia_hash::FuchsiaMerkleMarker> =
1237 Hash::from([0; fuchsia_hash::HASH_SIZE]);
1238 let package_manifest_path2 = PathBuf::from("path2/package_manifest.json");
1239
1240 builder.add_subpackage(&url, package_hash, package_manifest_path).unwrap();
1241 assert!(
1242 builder.add_subpackage(&url, package_hash2, package_manifest_path2.clone()).is_ok()
1243 );
1244 assert!(builder.subpackages.get(&url).unwrap().0 == package_hash2);
1245 assert!(builder.subpackages.get(&url).unwrap().1 == package_manifest_path2);
1246 }
1247}