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