1use crate::path_to_string::PathToStringExt;
6use crate::{
7 MetaContents, MetaPackage, MetaSubpackages, PackageBuildManifest, PackageManifest, RelativeTo,
8 SubpackageEntry,
9};
10use anyhow::{anyhow, bail, ensure, Context, Result};
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] = &[MetaContents::PATH, MetaPackage::PATH, ABI_REVISION_FILE_PATH];
24pub const ABI_REVISION_FILE_PATH: &str = "meta/fuchsia.abi/abi-revision";
25
26pub struct PackageBuilder {
28 name: String,
30
31 pub abi_revision: AbiRevision,
33
34 far_contents: BTreeMap<String, String>,
37
38 blobs: BTreeMap<String, String>,
40
41 manifest_path: Option<Utf8PathBuf>,
43
44 blob_sources_relative: RelativeTo,
47
48 published_name: Option<String>,
51
52 repository: Option<String>,
54
55 subpackages: BTreeMap<RelativePackageUrl, (Hash, PathBuf)>,
57
58 overwrite_files: bool,
60
61 overwrite_subpackages: bool,
63}
64
65impl PackageBuilder {
66 pub fn new(name: impl AsRef<str>, abi_revision: AbiRevision) -> Self {
71 PackageBuilder {
72 name: name.as_ref().to_string(),
73 abi_revision,
74 far_contents: BTreeMap::default(),
75 blobs: BTreeMap::default(),
76 manifest_path: None,
77 blob_sources_relative: RelativeTo::default(),
78 published_name: None,
79 repository: None,
80 subpackages: BTreeMap::default(),
81 overwrite_files: false,
82 overwrite_subpackages: false,
83 }
84 }
85
86 pub fn new_platform_internal_package(name: impl AsRef<str>) -> Self {
94 PackageBuilder::new(
95 name,
96 version_history_data::HISTORY.get_abi_revision_for_platform_components(),
97 )
98 }
99
100 pub fn from_package_build_manifest(
106 manifest: &PackageBuildManifest,
107 abi_revision: AbiRevision,
108 ) -> Result<Self> {
109 let meta_package = if let Some(path) = manifest.far_contents().get("meta/package") {
111 let f = File::open(path).with_context(|| format!("opening {path}"))?;
112
113 MetaPackage::deserialize(BufReader::new(f))?
114 } else {
115 return Err(anyhow!("package missing meta/package entry"));
116 };
117
118 ensure!(meta_package.variant().is_zero(), "package variant must be zero");
119
120 if manifest.far_contents().get("meta/fuchsia.abi/abi-revision").is_some() {
124 bail!(
125 "Manifest must not include entry for 'meta/fuchsia.abi/abi-revision'. \
126 Pass --api-level to package-tool instead."
127 )
128 };
129
130 let mut builder = PackageBuilder::new(meta_package.name(), abi_revision);
131
132 for (at_path, file) in manifest.external_contents() {
133 builder
134 .add_file_as_blob(at_path, file)
135 .with_context(|| format!("adding file {at_path} as blob {file}"))?;
136 }
137
138 for (at_path, file) in manifest.far_contents() {
139 if at_path == "meta/package" {
141 continue;
142 }
143
144 builder
145 .add_file_to_far(at_path, file)
146 .with_context(|| format!("adding file {at_path} to far {file}"))?;
147 }
148
149 Ok(builder)
150 }
151
152 pub fn from_manifest(
155 original_manifest: PackageManifest,
156 outdir: impl AsRef<Path>,
157 ) -> Result<Self> {
158 let mut abi_rev = None;
160 let mut inner_name = None;
161 let mut meta_blobs = BTreeMap::new();
162 let mut blob_paths = BTreeMap::new();
163 let mut subpackage_names = BTreeMap::new();
164 for blob in original_manifest.blobs() {
165 if blob.path == PackageManifest::META_FAR_BLOB_PATH {
166 let meta_far_contents = std::fs::read(&blob.source_path)
167 .with_context(|| format!("reading {}", blob.source_path))?;
168 let PackagedMetaFar { abi_revision, name, meta_contents, .. } =
169 PackagedMetaFar::parse(&meta_far_contents).context("parsing meta/")?;
170 abi_rev = Some(abi_revision);
171 inner_name = Some(name);
172 meta_blobs = meta_contents;
173 } else {
174 blob_paths.insert(blob.path.clone(), blob.source_path.clone());
175 }
176 }
177 for subpackage in original_manifest.subpackages() {
178 subpackage_names.insert(
179 subpackage.name.clone(),
180 (subpackage.merkle, subpackage.manifest_path.clone()),
181 );
182 }
183 let abi_rev = abi_rev.ok_or_else(|| anyhow!("did not find {}", ABI_REVISION_FILE_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 remove_blob_file(&mut self, at_path: impl AsRef<str>) -> Result<()> {
346 self.validate_ok_to_modify(at_path.as_ref())?;
347 match self.blobs.remove(at_path.as_ref()) {
348 Some(_key) => Ok(()),
349 None => Err(anyhow!("file not in package contents")),
350 }
351 }
352
353 pub fn add_contents_as_blob<C: AsRef<[u8]>>(
356 &mut self,
357 at_path: impl AsRef<str>,
358 contents: C,
359 gendir: impl AsRef<Path>,
360 ) -> Result<()> {
361 self.validate_ok_to_add_as_blob(&at_path)?;
363 let source_path = Self::write_contents_to_file(gendir, at_path.as_ref(), contents)?;
364 self.add_file_as_blob(at_path, source_path.path_to_string()?)
365 }
366
367 pub fn add_contents_to_far<C: AsRef<[u8]>>(
370 &mut self,
371 at_path: impl AsRef<str>,
372 contents: C,
373 gendir: impl AsRef<Path>,
374 ) -> Result<()> {
375 self.validate_ok_to_add_in_far(&at_path)?;
377 let source_path = Self::write_contents_to_file(gendir, at_path.as_ref(), contents)?;
378 self.add_file_to_far(at_path, source_path.path_to_string()?)
379 }
380
381 fn write_contents_to_file<C: AsRef<[u8]>>(
383 gendir: impl AsRef<Path>,
384 file_path: impl AsRef<Path>,
385 contents: C,
386 ) -> Result<PathBuf> {
387 let file_path = gendir.as_ref().join(file_path);
388 if let Some(parent_dir) = file_path.parent() {
389 std::fs::create_dir_all(parent_dir)
390 .context(format!("creating parent directories for {}", file_path.display()))?;
391 }
392 std::fs::write(&file_path, contents)
393 .context(format!("writing contents to file: {}", file_path.display()))?;
394 Ok(file_path)
395 }
396
397 pub fn overwrite_subpackages(&mut self, overwrite_subpackages: bool) {
400 self.overwrite_subpackages = overwrite_subpackages
401 }
402
403 pub fn add_subpackage(
405 &mut self,
406 url: &RelativePackageUrl,
407 package_hash: Hash,
408 package_manifest_path: PathBuf,
409 ) -> Result<()> {
410 if self.subpackages.contains_key(url) && !self.overwrite_subpackages {
411 return Err(anyhow!("duplicate entry for {:?}", url));
412 }
413 self.subpackages.insert(url.clone(), (package_hash, package_manifest_path));
414 Ok(())
415 }
416
417 pub fn name(&mut self, name: impl AsRef<str>) {
419 self.name = name.as_ref().to_string();
420 }
421
422 pub fn published_name(&mut self, published_name: impl AsRef<str>) {
426 self.published_name = Some(published_name.as_ref().into());
427 }
428
429 pub fn repository(&mut self, repository: impl AsRef<str>) {
431 self.repository = Some(repository.as_ref().into());
432 }
433
434 pub fn read_contents_from_far(&self, file_path: &str) -> Result<Vec<u8>> {
436 if let Some(p) = self.far_contents.get(file_path) {
437 std::fs::read(p).with_context(|| format!("reading {p}"))
438 } else {
439 bail!(
440 "couldn't find `{}` in package: {:?}",
441 file_path,
442 self.far_contents.keys().collect::<Vec<_>>()
443 );
444 }
445 }
446
447 pub fn build(
457 self,
458 gendir: impl AsRef<Path>,
459 metafar_path: impl AsRef<Path>,
460 ) -> Result<PackageManifest> {
461 let gendir = gendir.as_ref();
462 let metafar_path = metafar_path.as_ref();
463
464 let PackageBuilder {
465 name,
466 abi_revision,
467 mut far_contents,
468 blobs,
469 manifest_path,
470 blob_sources_relative,
471 published_name,
472 repository,
473 subpackages,
474 overwrite_files: _,
475 overwrite_subpackages: _,
476 } = self;
477
478 far_contents.insert(
479 MetaPackage::PATH.to_string(),
480 create_meta_package_file(gendir, &name)
481 .with_context(|| format!("Writing the {} file", MetaPackage::PATH))?,
482 );
483
484 let abi_revision_file =
485 Self::write_contents_to_file(gendir, ABI_REVISION_FILE_PATH, abi_revision.as_bytes())
486 .with_context(|| format!("Writing the {ABI_REVISION_FILE_PATH} file"))?;
487
488 far_contents.insert(
489 ABI_REVISION_FILE_PATH.to_string(),
490 abi_revision_file.path_to_string().with_context(|| {
491 format!("Adding the {ABI_REVISION_FILE_PATH} file to the package")
492 })?,
493 );
494
495 if !subpackages.is_empty() {
497 far_contents.insert(
498 MetaSubpackages::PATH.to_string(),
499 create_meta_subpackages_file(gendir, subpackages.clone()).with_context(|| {
500 format!("Adding the {} file to the package", MetaSubpackages::PATH)
501 })?,
502 );
503 }
504
505 let package_build_manifest =
506 PackageBuildManifest::from_external_and_far_contents(blobs, far_contents)
507 .with_context(|| "creating creation manifest".to_string())?;
508
509 let package_manifest = crate::build::build(
510 &package_build_manifest,
511 metafar_path,
512 published_name.unwrap_or(name),
513 subpackages
514 .into_iter()
515 .map(|(name, (merkle, package_manifest_path))| SubpackageEntry {
516 name,
517 merkle,
518 package_manifest_path,
519 })
520 .collect(),
521 repository,
522 )
523 .with_context(|| format!("building package manifest {}", metafar_path.display()))?;
524
525 Ok(if let Some(manifest_path) = manifest_path {
526 if let RelativeTo::File = blob_sources_relative {
527 let copy = package_manifest.clone();
528 copy.write_with_relative_paths(&manifest_path).with_context(|| {
529 format!(
530 "Failed to create package manifest with relative paths at: {manifest_path}"
531 )
532 })?;
533
534 package_manifest
535 } else {
536 let mut tmp = if let Some(parent) = manifest_path.parent() {
538 NamedTempFile::new_in(parent)?
539 } else {
540 NamedTempFile::new()?
541 };
542
543 serde_json::ser::to_writer(BufWriter::new(&mut tmp), &package_manifest)
544 .with_context(|| {
545 format!("writing package manifest to {}", tmp.path().display())
546 })?;
547
548 tmp.persist_if_changed(&manifest_path).with_context(|| {
549 format!("Failed to persist package manifest: {manifest_path}")
550 })?;
551
552 package_manifest
553 }
554 } else {
555 package_manifest
556 })
557 }
558}
559
560fn create_meta_package_file(gendir: &Path, name: impl Into<String>) -> Result<String> {
564 let package_name = name.into();
565 let meta_package_path = gendir.join(MetaPackage::PATH);
566 if let Some(parent_dir) = meta_package_path.parent() {
567 std::fs::create_dir_all(parent_dir)?;
568 }
569
570 let file = std::fs::File::create(&meta_package_path)?;
571 let meta_package = MetaPackage::from_name_and_variant_zero(package_name.try_into()?);
572 meta_package.serialize(file)?;
573 meta_package_path.path_to_string()
574}
575
576struct PackagedMetaFar {
578 name: String,
580
581 abi_revision: AbiRevision,
583
584 meta_contents: BTreeMap<String, Vec<u8>>,
586}
587
588impl PackagedMetaFar {
589 fn parse(bytes: &[u8]) -> Result<Self> {
590 let mut meta_far =
591 fuchsia_archive::Utf8Reader::new(Cursor::new(bytes)).context("reading FAR")?;
592
593 let mut abi_revision = None;
594 let mut name = None;
595 let mut meta_contents = BTreeMap::new();
596
597 let meta_paths = meta_far.list().map(|e| e.path().to_owned()).collect::<Vec<_>>();
599
600 for path in meta_paths {
602 let contents = meta_far.read_file(&path).with_context(|| format!("reading {path}"))?;
603
604 if path == MetaContents::PATH {
605 continue;
606 } else if path == MetaPackage::PATH {
607 ensure!(name.is_none(), "only one name per package");
608 let mp = MetaPackage::deserialize(Cursor::new(&contents))
609 .context("deserializing meta/package")?;
610 name = Some(mp.name().to_string());
611 } else if path == ABI_REVISION_FILE_PATH {
612 ensure!(abi_revision.is_none(), "only one abi revision per package");
613 ensure!(contents.len() == 8, "ABI revision must be encoded as 8 bytes");
614 abi_revision = Some(AbiRevision::try_from(contents.as_slice()).unwrap());
615 } else {
616 meta_contents.insert(path, contents);
617 }
618 }
619 let abi_revision =
620 abi_revision.ok_or_else(|| anyhow!("did not find {}", ABI_REVISION_FILE_PATH))?;
621 let name = name.ok_or_else(|| anyhow!("did not find {}", MetaPackage::PATH))?;
622
623 Ok(Self { name, abi_revision, meta_contents })
624 }
625}
626
627fn create_meta_subpackages_file(
631 gendir: &Path,
632 subpackages: BTreeMap<RelativePackageUrl, (Hash, PathBuf)>,
633) -> Result<String> {
634 let meta_subpackages_path = gendir.join(MetaSubpackages::PATH);
635 if let Some(parent_dir) = meta_subpackages_path.parent() {
636 std::fs::create_dir_all(parent_dir)?;
637 }
638
639 let meta_subpackages = MetaSubpackages::from_iter(
640 subpackages.into_iter().map(|(name, (merkle, _))| (name, merkle)),
641 );
642 let file = std::fs::File::create(&meta_subpackages_path)?;
643 meta_subpackages.serialize(file)?;
644 meta_subpackages_path.path_to_string()
645}
646
647#[cfg(test)]
648mod tests {
649 use super::*;
650 use camino::Utf8Path;
651 use tempfile::TempDir;
652
653 const FAKE_ABI_REVISION: AbiRevision = AbiRevision::from_u64(0x5836508c2defac54);
654
655 #[test]
656 fn test_create_meta_package_file() {
657 let gen_dir = TempDir::new().unwrap();
658 let name = "some_test_package";
659 let meta_package_path = gen_dir.as_ref().join("meta/package");
660 let created_path = create_meta_package_file(gen_dir.path(), name).unwrap();
661 assert_eq!(created_path, meta_package_path.path_to_string().unwrap());
662
663 let raw_contents = std::fs::read(meta_package_path).unwrap();
664 let meta_package = MetaPackage::deserialize(std::io::Cursor::new(raw_contents)).unwrap();
665 assert_eq!(meta_package.name().as_ref(), "some_test_package");
666 assert!(meta_package.variant().is_zero());
667 }
668
669 #[test]
670 fn test_builder() {
671 let outdir = TempDir::new().unwrap();
672 let metafar_path = outdir.path().join("meta.far");
673
674 let far_source_file_path = NamedTempFile::new_in(&outdir).unwrap();
676 std::fs::write(&far_source_file_path, "some data for far").unwrap();
677
678 let blob_source_file_path = NamedTempFile::new_in(&outdir).unwrap();
680 let blob_contents = "some data for blob";
681 std::fs::write(&blob_source_file_path, blob_contents).unwrap();
682
683 let blob_hash = fuchsia_merkle::from_slice(blob_contents.as_bytes()).root();
685
686 let subpackage_url = "subpackage0".parse::<RelativePackageUrl>().unwrap();
687 let subpackage_hash = Hash::from([0; fuchsia_hash::HASH_SIZE]);
688 let subpackage_package_manifest_path = "subpackages/package_manifest.json";
689
690 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
692 builder
693 .add_file_as_blob("some/blob", blob_source_file_path.path().path_to_string().unwrap())
694 .unwrap();
695 builder
696 .add_file_to_far(
697 "meta/some/file",
698 far_source_file_path.path().path_to_string().unwrap(),
699 )
700 .unwrap();
701 builder
702 .add_subpackage(
703 &subpackage_url,
704 subpackage_hash,
705 subpackage_package_manifest_path.into(),
706 )
707 .unwrap();
708
709 let manifest = builder.build(&outdir, &metafar_path).unwrap();
711
712 assert_eq!(manifest.name().as_ref(), "some_pkg_name");
714
715 let (blobs, subpackages) = manifest.into_blobs_and_subpackages();
716
717 let blob_info = blobs.iter().find(|info| info.path == "some/blob").unwrap().clone();
719 assert_eq!(blob_hash, blob_info.merkle);
720 assert_eq!(blob_contents, std::fs::read_to_string(blob_info.source_path).unwrap());
721
722 let subpackage_info =
724 subpackages.iter().find(|info| info.name == "subpackage0").unwrap().clone();
725 assert_eq!(subpackage_hash, subpackage_info.merkle);
726 assert_eq!(subpackage_package_manifest_path, subpackage_info.manifest_path);
727
728 let mut metafar = std::fs::File::open(metafar_path).unwrap();
730 let mut far_reader = fuchsia_archive::Utf8Reader::new(&mut metafar).unwrap();
731 let far_file_data = far_reader.read_file("meta/some/file").unwrap();
732 let far_file_data = std::str::from_utf8(far_file_data.as_slice()).unwrap();
733 assert_eq!(far_file_data, "some data for far");
734
735 let abi_revision_data = far_reader.read_file("meta/fuchsia.abi/abi-revision").unwrap();
737 let abi_revision_data: [u8; 8] = abi_revision_data.try_into().unwrap();
738 let abi_revision = AbiRevision::from_bytes(abi_revision_data);
739 assert_eq!(abi_revision, FAKE_ABI_REVISION);
740 }
741
742 #[test]
743 fn test_from_manifest() {
744 let first_outdir = TempDir::new().unwrap();
745
746 let inner_name = "some_pkg_name";
748 let mut first_builder = PackageBuilder::new(inner_name, FAKE_ABI_REVISION);
749 let published_name = "some_other_pkg_name";
751 first_builder.published_name(published_name);
752
753 let first_far_source_file_path = NamedTempFile::new_in(&first_outdir).unwrap();
755 let first_far_contents = "some data for far";
756 std::fs::write(&first_far_source_file_path, first_far_contents).unwrap();
757 first_builder
758 .add_file_to_far("meta/some/file", first_far_source_file_path.path().to_string_lossy())
759 .unwrap();
760
761 let first_blob_source_file_path = NamedTempFile::new_in(&first_outdir).unwrap();
763 let first_blob_contents = "some data for blob";
764 std::fs::write(&first_blob_source_file_path, first_blob_contents).unwrap();
765 first_builder
766 .add_file_as_blob("some/blob", first_blob_source_file_path.path().to_string_lossy())
767 .unwrap();
768
769 let first_subpackage_url = "subpackage0".parse::<RelativePackageUrl>().unwrap();
770 let first_subpackage_hash = Hash::from([0; fuchsia_hash::HASH_SIZE]);
771 let first_subpackage_package_manifest_path = "subpackages/package_manifest.json";
772
773 first_builder
774 .add_subpackage(
775 &first_subpackage_url,
776 first_subpackage_hash,
777 first_subpackage_package_manifest_path.into(),
778 )
779 .unwrap();
780
781 let first_manifest =
783 first_builder.build(&first_outdir, first_outdir.path().join("meta.far")).unwrap();
784 assert_eq!(
785 first_manifest.blobs().len(),
786 2,
787 "package should have a meta.far and a single blob"
788 );
789 assert_eq!(
790 first_manifest.subpackages().len(),
791 1,
792 "package should have a single subpackage"
793 );
794 let blob_info = first_manifest
795 .blobs()
796 .iter()
797 .find(|blob_info| blob_info.path == PackageManifest::META_FAR_BLOB_PATH)
798 .unwrap();
799 let mut metafar = std::fs::File::open(&blob_info.source_path).unwrap();
800 let far_reader = fuchsia_archive::Utf8Reader::new(&mut metafar).unwrap();
801 let first_paths_in_far =
802 far_reader.list().map(|e| e.path().to_string()).collect::<Vec<_>>();
803
804 let second_outdir = TempDir::new().unwrap();
806 let mut second_builder =
807 PackageBuilder::from_manifest(first_manifest.clone(), second_outdir.path()).unwrap();
808
809 let second_far_source_file_path = NamedTempFile::new_in(&second_outdir).unwrap();
811 let second_far_contents = "some more data for far";
812 std::fs::write(&second_far_source_file_path, second_far_contents).unwrap();
813 second_builder
814 .add_file_to_far(
815 "meta/some/other/file",
816 second_far_source_file_path.path().to_string_lossy(),
817 )
818 .unwrap();
819
820 let second_blob_source_file_path = NamedTempFile::new_in(&second_outdir).unwrap();
822 let second_blob_contents = "some more data for blobs";
823 std::fs::write(&second_blob_source_file_path, second_blob_contents).unwrap();
824 second_builder
825 .add_file_as_blob(
826 "some/other/blob",
827 second_blob_source_file_path.path().to_string_lossy(),
828 )
829 .unwrap();
830
831 let second_metafar_path = second_outdir.path().join("meta.far");
833 let second_manifest = second_builder.build(&second_outdir, second_metafar_path).unwrap();
834 assert_eq!(first_manifest.name(), second_manifest.name(), "package names must match");
835 assert_eq!(
836 second_manifest.blobs().len(),
837 3,
838 "package should have a meta.far and two blobs"
839 );
840 assert_eq!(
841 second_manifest.subpackages().len(),
842 1,
843 "package should STILL have a single subpackage"
844 );
845
846 for blob_info in second_manifest.blobs() {
848 match &*blob_info.path {
849 PackageManifest::META_FAR_BLOB_PATH => {
850 let mut metafar = std::fs::File::open(&blob_info.source_path).unwrap();
852 let mut far_reader = fuchsia_archive::Utf8Reader::new(&mut metafar).unwrap();
853 let paths_in_far =
854 far_reader.list().map(|e| e.path().to_string()).collect::<Vec<_>>();
855 assert_eq!(
856 paths_in_far.len(),
857 first_paths_in_far.len() + 1,
858 "must have the original files and one added one"
859 );
860
861 for far_path in paths_in_far {
862 let far_bytes = far_reader.read_file(&far_path).unwrap();
863 match &*far_path {
864 MetaContents::PATH => (), MetaPackage::PATH => {
866 let mp = MetaPackage::deserialize(Cursor::new(&far_bytes)).unwrap();
867 assert_eq!(mp.name().as_ref(), inner_name);
868 }
869 MetaSubpackages::PATH => {
870 let ms =
871 MetaSubpackages::deserialize(Cursor::new(&far_bytes)).unwrap();
872 assert_eq!(ms.subpackages().len(), 1);
873 let (url, hash) = ms.subpackages().iter().next().unwrap();
874 assert_eq!(url, &first_subpackage_url);
875 assert_eq!(hash, &first_subpackage_hash);
876 }
877 ABI_REVISION_FILE_PATH => {
878 assert_eq!(far_bytes, FAKE_ABI_REVISION.as_bytes());
879 }
880 "meta/some/file" => {
881 assert_eq!(far_bytes, first_far_contents.as_bytes());
882 }
883 "meta/some/other/file" => {
884 assert_eq!(far_bytes, second_far_contents.as_bytes());
885 }
886 other => panic!("unrecognized file in meta.far: {other}"),
887 }
888 }
889 }
890 "some/blob" => {
891 assert_eq!(
892 std::fs::read_to_string(&blob_info.source_path).unwrap(),
893 first_blob_contents,
894 );
895 }
896 "some/other/blob" => {
897 assert_eq!(
898 std::fs::read_to_string(&blob_info.source_path).unwrap(),
899 second_blob_contents,
900 )
901 }
902 other => panic!("unrecognized path in blobs `{other}`"),
903 }
904 }
905 }
906
907 #[test]
908 fn test_removes() {
909 let gendir = TempDir::new().unwrap();
910 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
911 assert!(builder.add_contents_to_far("meta/foo", "foo", gendir.path()).is_ok());
912 assert!(builder.add_contents_to_far("meta/bar", "bar", gendir.path()).is_ok());
913
914 assert!(builder.add_contents_as_blob("baz", "baz", gendir.path()).is_ok());
915 assert!(builder.add_contents_as_blob("boom", "boom", gendir.path()).is_ok());
916
917 assert!(builder.remove_file_from_far("meta/foo").is_ok());
918 assert!(builder.remove_file_from_far("meta/does_not_exist").is_err());
919
920 assert!(builder.remove_blob_file("baz").is_ok());
921 assert!(builder.remove_blob_file("does_not_exist").is_err());
922
923 let outdir = TempDir::new().unwrap();
924 let metafar_path = outdir.path().join("meta.far");
925
926 let pkg_manifest = builder.build(&outdir, &metafar_path).unwrap();
927
928 for blob_info in pkg_manifest.blobs() {
931 match &*blob_info.path {
932 PackageManifest::META_FAR_BLOB_PATH => {
933 let mut metafar = std::fs::File::open(&blob_info.source_path).unwrap();
934 let mut far_reader = fuchsia_archive::Utf8Reader::new(&mut metafar).unwrap();
935 let paths_in_far =
936 far_reader.list().map(|e| e.path().to_string()).collect::<Vec<_>>();
937
938 for far_path in paths_in_far {
939 let far_bytes = far_reader.read_file(&far_path).unwrap();
940 match &*far_path {
941 MetaContents::PATH => (), MetaPackage::PATH => (),
943 MetaSubpackages::PATH => (),
944 ABI_REVISION_FILE_PATH => (),
945 "meta/bar" => {
946 assert_eq!(far_bytes, "bar".as_bytes());
947 }
948 other => panic!("unrecognized file in meta.far: {other}"),
949 }
950 }
951 }
952 "boom" => {
953 assert_eq!(std::fs::read_to_string(&blob_info.source_path).unwrap(), "boom",);
954 }
955 other => panic!("unrecognized path in blobs `{other}`"),
956 }
957 }
958 }
959
960 #[test]
961 fn test_overwrite_abi_revision() {
962 const OTHER_FAKE_ABI_REVISION: AbiRevision = AbiRevision::from_u64(0x1234);
963
964 let outdir = TempDir::new().unwrap();
965 let metafar_path = outdir.path().join("meta.far");
966
967 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
969
970 assert_eq!(builder.abi_revision, FAKE_ABI_REVISION);
971
972 builder.abi_revision = OTHER_FAKE_ABI_REVISION;
974
975 let _ = builder.build(&outdir, &metafar_path).unwrap();
977
978 let mut metafar = std::fs::File::open(metafar_path).unwrap();
980 let mut far_reader = fuchsia_archive::Utf8Reader::new(&mut metafar).unwrap();
981
982 let abi_revision_data = far_reader.read_file("meta/fuchsia.abi/abi-revision").unwrap();
983 let abi_revision_data: [u8; 8] = abi_revision_data.try_into().unwrap();
984 let abi_revision = AbiRevision::from_bytes(abi_revision_data);
985 assert_eq!(abi_revision, OTHER_FAKE_ABI_REVISION);
986 }
987
988 #[test]
989 fn test_build_rejects_meta_contents() {
990 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
991 assert!(builder.add_file_to_far("meta/contents", "some/src/file").is_err());
992 assert!(builder.add_file_as_blob("meta/contents", "some/src/file").is_err());
993 }
994
995 #[test]
996 fn test_build_rejects_meta_package() {
997 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
998 assert!(builder.add_file_to_far("meta/package", "some/src/file").is_err());
999 assert!(builder.add_file_as_blob("meta/package", "some/src/file").is_err());
1000 }
1001
1002 #[test]
1003 fn test_build_rejects_abi_revision() {
1004 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1005 assert!(builder.add_file_to_far("meta/fuchsia.abi/abi-revision", "some/src/file").is_err());
1006 assert!(builder
1007 .add_file_as_blob("meta/fuchsia.abi/abi-revision", "some/src/file")
1008 .is_err());
1009 }
1010
1011 #[test]
1012 fn test_builder_rejects_path_in_far_when_existing_path_in_far() {
1013 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1014 builder.add_file_to_far("some/far/file", "some/src/file").unwrap();
1015 assert!(builder.add_file_to_far("some/far/file", "some/src/file").is_err());
1016 }
1017
1018 #[test]
1019 fn test_builder_allows_overwrite_path_in_far_when_flag_set() {
1020 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1021 builder.overwrite_files(true);
1022 builder.add_file_to_far("some/far/file", "some/src/file").unwrap();
1023 assert!(builder.add_file_to_far("some/far/file", "some/src/file").is_ok());
1024 }
1025
1026 #[test]
1027 fn test_builder_rejects_path_as_blob_when_existing_path_in_far() {
1028 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1029 builder.add_file_to_far("some/far/file", "some/src/file").unwrap();
1030 assert!(builder.add_file_as_blob("some/far/file", "some/src/file").is_err());
1031 }
1032
1033 #[test]
1034 fn test_builder_rejects_path_as_blob_when_existing_path_in_far_and_overwrite_set() {
1035 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1037 builder.overwrite_files(true);
1038 builder.add_file_to_far("some/far/file", "some/src/file").unwrap();
1039 assert!(builder.add_file_as_blob("some/far/file", "some/src/file").is_err());
1040 }
1041
1042 #[test]
1043 fn test_builder_rejects_path_in_far_when_existing_path_as_blob() {
1044 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1045 builder.add_file_as_blob("some/far/file", "some/src/file").unwrap();
1046 assert!(builder.add_file_to_far("some/far/file", "some/src/file").is_err());
1047 }
1048
1049 #[test]
1050 fn test_builder_rejects_path_in_far_when_existing_path_as_blob_and_overwrite_set() {
1051 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1053 builder.overwrite_files(true);
1054 builder.add_file_as_blob("some/far/file", "some/src/file").unwrap();
1055 assert!(builder.add_file_to_far("some/far/file", "some/src/file").is_err());
1056 }
1057
1058 #[test]
1059 fn test_builder_rejects_path_in_blob_when_existing_path_as_blob() {
1060 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1061 builder.add_file_as_blob("some/far/file", "some/src/file").unwrap();
1062 assert!(builder.add_file_as_blob("some/far/file", "some/src/file").is_err());
1063 }
1064
1065 #[test]
1066 fn test_builder_allows_overwrite_path_as_blob_when_flag_set() {
1067 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1068 builder.overwrite_files(true);
1069 builder.add_file_as_blob("some/far/file", "some/src/file").unwrap();
1070 assert!(builder.add_file_as_blob("some/far/file", "some/src/file").is_ok());
1071 }
1072
1073 #[test]
1074 fn test_builder_makes_file_relative_manifests_when_asked() {
1075 let tmp = TempDir::new().unwrap();
1076 let outdir = Utf8Path::from_path(tmp.path()).unwrap();
1077
1078 let metafar_path = outdir.join("meta.far");
1079 let manifest_path = outdir.join("package_manifest.json");
1080
1081 let far_source_file_path = NamedTempFile::new_in(outdir).unwrap();
1083 std::fs::write(&far_source_file_path, "some data for far").unwrap();
1084
1085 let blob_source_file_path = outdir.join("contents/data_file");
1087 std::fs::create_dir_all(blob_source_file_path.parent().unwrap()).unwrap();
1088 let blob_contents = "some data for blob";
1089 std::fs::write(&blob_source_file_path, blob_contents).unwrap();
1090
1091 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1093 builder.add_file_as_blob("some/blob", &blob_source_file_path).unwrap();
1094 builder
1095 .add_file_to_far(
1096 "meta/some/file",
1097 far_source_file_path.path().path_to_string().unwrap(),
1098 )
1099 .unwrap();
1100
1101 builder.manifest_path(manifest_path);
1103 builder.manifest_blobs_relative_to(RelativeTo::File);
1104
1105 let manifest = builder.build(outdir, metafar_path).unwrap();
1107
1108 manifest
1111 .blobs()
1112 .iter()
1113 .find(|b| b.source_path == blob_source_file_path)
1114 .expect("The manifest should have paths relative to the working directory");
1115
1116 }
1118
1119 #[test]
1120 fn test_builder_add_subpackages() {
1121 let outdir = TempDir::new().unwrap();
1122 let metafar_path = outdir.path().join("meta.far");
1123
1124 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1125
1126 let pkg1_url = "pkg1".parse::<RelativePackageUrl>().unwrap();
1127 let pkg1_hash = Hash::from([0; fuchsia_hash::HASH_SIZE]);
1128 let pkg1_package_manifest_path = outdir.path().join("path1/package_manifest.json");
1129
1130 let pkg2_url = "pkg2".parse::<RelativePackageUrl>().unwrap();
1131 let pkg2_hash = Hash::from([1; fuchsia_hash::HASH_SIZE]);
1132 let pkg2_package_manifest_path = outdir.path().join("path2/package_manifest.json");
1133
1134 builder.add_subpackage(&pkg1_url, pkg1_hash, pkg1_package_manifest_path).unwrap();
1135 builder.add_subpackage(&pkg2_url, pkg2_hash, pkg2_package_manifest_path).unwrap();
1136
1137 builder.build(&outdir, &metafar_path).unwrap();
1139
1140 let mut metafar = std::fs::File::open(metafar_path).unwrap();
1142 let mut far_reader = fuchsia_archive::Utf8Reader::new(&mut metafar).unwrap();
1143 let far_file_data = far_reader.read_file(MetaSubpackages::PATH).unwrap();
1144
1145 assert_eq!(
1146 MetaSubpackages::deserialize(Cursor::new(&far_file_data)).unwrap(),
1147 MetaSubpackages::from_iter([(pkg1_url, pkg1_hash), (pkg2_url, pkg2_hash)])
1148 );
1149 }
1150
1151 #[test]
1152 fn test_builder_rejects_subpackages_collisions() {
1153 let url = "pkg".parse::<RelativePackageUrl>().unwrap();
1154 let hash1 = Hash::from([0; fuchsia_hash::HASH_SIZE]);
1155 let package_manifest_path1 = PathBuf::from("path1/package_manifest.json");
1156 let hash2 = Hash::from([0; fuchsia_hash::HASH_SIZE]);
1157 let package_manifest_path2 = PathBuf::from("path2/package_manifest.json");
1158
1159 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1160 builder.add_subpackage(&url, hash1, package_manifest_path1).unwrap();
1161 assert!(builder.add_subpackage(&url, hash2, package_manifest_path2).is_err());
1162 }
1163
1164 #[test]
1165 fn test_builder_allows_overwrite_subpackages_when_flag_set() {
1166 let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1167 builder.overwrite_subpackages(true);
1168
1169 let url = "pkg".parse::<RelativePackageUrl>().unwrap();
1170 let package_hash: fuchsia_hash::GenericDigest<fuchsia_hash::FuchsiaMerkleMarker> =
1171 Hash::from([0; fuchsia_hash::HASH_SIZE]);
1172 let package_manifest_path = PathBuf::from("path/package_manifest.json");
1173
1174 let package_hash2: fuchsia_hash::GenericDigest<fuchsia_hash::FuchsiaMerkleMarker> =
1175 Hash::from([0; fuchsia_hash::HASH_SIZE]);
1176 let package_manifest_path2 = PathBuf::from("path2/package_manifest.json");
1177
1178 builder.add_subpackage(&url, package_hash, package_manifest_path).unwrap();
1179 assert!(builder
1180 .add_subpackage(&url, package_hash2, package_manifest_path2.clone())
1181 .is_ok());
1182 assert!(builder.subpackages.get(&url).unwrap().0 == package_hash2);
1183 assert!(builder.subpackages.get(&url).unwrap().1 == package_manifest_path2);
1184 }
1185}