fuchsia_pkg/
package_builder.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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
22/// Paths which will be generated by `PackageBuilder` itself and which should not be manually added.
23const 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
27/// A builder for Fuchsia Packages
28pub struct PackageBuilder {
29    /// The name of the package being created.
30    name: String,
31
32    /// The abi_revision to embed in the package.
33    pub abi_revision: AbiRevision,
34
35    /// The contents that are to be placed inside the FAR itself, not as
36    /// separate blobs.
37    far_contents: BTreeMap<String, String>,
38
39    /// The contents that are to be attached to the package as external blobs.
40    blobs: BTreeMap<String, String>,
41
42    /// Optional path to serialize the PackageManifest to
43    manifest_path: Option<Utf8PathBuf>,
44
45    /// Optionally make the blob 'source_path's relative to the path the
46    /// PackageManifest is serialized to.
47    blob_sources_relative: RelativeTo,
48
49    /// Optional (possibly different) name to publish the package under.
50    /// This changes the name that's placed in the output package manifest.
51    published_name: Option<String>,
52
53    /// Optional package repository.
54    repository: Option<String>,
55
56    /// Metafile of subpackages.
57    subpackages: BTreeMap<RelativePackageUrl, (Hash, PathBuf)>,
58
59    /// Whether new files with the same path should overwrwite previous files at that path.
60    overwrite_files: bool,
61
62    /// Whether new subpackages with the same url should overwrite previous subpackages at that url.
63    overwrite_subpackages: bool,
64}
65
66impl PackageBuilder {
67    /// Create a new PackageBuilder.
68    ///
69    /// The ABI revision is included in the package exactly as given - we don't
70    /// validate it here.
71    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    /// Create a new PackageBuilder with the platform-internal ABI revision.
88    ///
89    /// The platform-internal ABI revision is only appropriate for packages that
90    /// are only ever read by binaries from the exact same release. For packages
91    /// that can be built by tools from one release and then run on an OS from a
92    /// different release, a different ABI revision that defines the
93    /// compatibility guarantees must be selected.
94    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    /// Create a PackageBuilder from a PackageBuildManifest.
102    ///
103    /// The ABI revision is included in the package exactly as given - we don't
104    /// validate it here. Returns an error if the given manifest already
105    /// specified an ABI revision.
106    pub fn from_package_build_manifest(
107        manifest: &PackageBuildManifest,
108        abi_revision: AbiRevision,
109    ) -> Result<Self> {
110        // Read the package name from `meta/package`, or error out if it's missing.
111        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        // Ensure the manifest doesn't include `meta/fuchsia.abi/abi-revision` -
122        // if it did, it could conflict with the value of --api-level from the
123        // command line.
124        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            // Ignore files that the package builder will automatically create.
141            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    /// Create a PackageBuilder from an existing manifest. Requires an out directory for temporarily
154    /// unpacking `meta.far` contents.
155    pub fn from_manifest(
156        original_manifest: PackageManifest,
157        outdir: impl AsRef<Path>,
158    ) -> Result<Self> {
159        // parse the existing manifest, copying everything over
160        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    /// Specify a path to write out the json package manifest to.
220    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    /// Specify whether new additions of a file that have already been added should overwrite,
229    /// or fail
230    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        // Never allow overwriting a blob path if we think we're writing into a far
247        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        // Never allow overwriting a path in the far if we think we're writing a blob
271        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    /// Add a file to the package's far.
290    ///
291    /// Errors
292    ///
293    /// Will return an error if the path for the file is already being used.
294    /// Will return an error if any special package metadata paths are used.
295    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    /// Remove a file from the package's meta.far.
310    ///
311    /// Errors
312    ///
313    /// Will return an error if the file is not already in the meta.far
314    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    /// Add a file to the package as a blob itself.
323    ///
324    /// Errors
325    ///
326    /// Will return an error if the path for the file is already being used.
327    /// Will return an error if any special package metadata paths are used.
328    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    /// Same as [add_file_as_blob], but deduplicates identical images at the same path.
343    ///
344    /// Deduplication is based on image contents, not the `file` source path.
345    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 the exact contents already exist, we're done. Otherwise continue on to the default
359            // behavior which may either error out or override the existing blob.
360            if existing_contents == new_contents {
361                return Ok(());
362            }
363        }
364
365        self.add_file_as_blob(at_path, file)
366    }
367
368    /// Remove a file currently in the package as a blob
369    ///
370    /// Errors
371    ///
372    /// Will return an error if the file is not already in the package contents
373    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    /// Write the contents to a file, and add that file as a blob at the given
382    /// path within the package.
383    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        // Preflight that the file paths are valid before attempting to write.
390        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    /// Write the contents to a file, and add that file to the metafar at the
396    /// given path within the package.
397    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        // Preflight that the file paths are valid before attempting to write.
404        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    /// Helper fn to write the contents to a file, creating the parent dirs as needed when doing so.
410    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    /// Specify whether new additions of a subpackage that have already been added should overwrite,
426    /// or fail
427    pub fn overwrite_subpackages(&mut self, overwrite_subpackages: bool) {
428        self.overwrite_subpackages = overwrite_subpackages
429    }
430
431    /// Helper fn to include a subpackage into this package.
432    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    /// Set the name of the package.
446    pub fn name(&mut self, name: impl AsRef<str>) {
447        self.name = name.as_ref().to_string();
448    }
449
450    /// Set a different name for the package to be published by (and to be
451    /// included in the generated PackageManifest), than the one embedded in the
452    /// package itself.
453    pub fn published_name(&mut self, published_name: impl AsRef<str>) {
454        self.published_name = Some(published_name.as_ref().into());
455    }
456
457    /// Set a repository for the package to be included in the generated PackageManifest.
458    pub fn repository(&mut self, repository: impl AsRef<str>) {
459        self.repository = Some(repository.as_ref().into());
460    }
461
462    /// Read the contents of a file already added to the builder's meta.far.
463    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    /// Build the package, using the specified dir, returning the
476    /// PackageManifest.
477    ///
478    /// If a path for the manifest was specified, the PackageManifest will also
479    /// be written to there.
480    ///
481    /// The `gendir` param is assumed to be a path to folder which is only used
482    /// by this package's creation, so this fn does not try to create paths
483    /// within it that are unique across different packages.
484    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        // Only add the subpackages file if we were configured with any subpackages.
524        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                // Write the package manifest to a file.
566                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
589/// Construct a meta/package file in `gendir`.
590///
591/// Returns the path that the file was created at.
592fn 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
605/// Results of parsing an existing meta.far for repackaging or testing purposes.
606struct PackagedMetaFar {
607    /// Package's name.
608    name: String,
609
610    /// Package's ABI revision.
611    abi_revision: AbiRevision,
612
613    /// Map of package paths to blob contents.
614    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        // collect paths separately, we need mutable access to reader for the bytes of each
627        let meta_paths = meta_far.list().map(|e| e.path().to_owned()).collect::<Vec<_>>();
628
629        // copy the contents of the meta.far, skipping files that PackageBuilder will write
630        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
656/// Construct a meta/fuchsia.pkg/subpackages file in `gendir`.
657///
658/// Returns the path that the file was created at.
659fn 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        // Create a file to write to the package metafar
704        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        // Create a file to include as a blob
708        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        // Pre-calculate the blob's hash
713        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        // Create the builder
720        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        // Build the package
739        let manifest = builder.build(&outdir, &metafar_path).unwrap();
740
741        // Validate the returned manifest
742        assert_eq!(manifest.name().as_ref(), "some_pkg_name");
743
744        let (blobs, subpackages) = manifest.into_blobs_and_subpackages();
745
746        // Validate that the blob has the correct hash and contents
747        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        // Validate that the subpackage has the correct hash and manifest path
752        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        // Validate that the metafar contains the additional file in meta
758        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        // Validate that the abi_revision was written correctly
765        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        // Create an initial package with non-default outputs for generated files
776        let inner_name = "some_pkg_name";
777        let mut first_builder = PackageBuilder::new(inner_name, FAKE_ABI_REVISION);
778        // Set a different published name
779        let published_name = "some_other_pkg_name";
780        first_builder.published_name(published_name);
781
782        // Create a file to write to the package metafar
783        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        // Create a file to include as a blob
791        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        // Build the package
811        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        // Re-parse the package into a builder for further modification
834        let second_outdir = TempDir::new().unwrap();
835        let mut second_builder =
836            PackageBuilder::from_manifest(first_manifest.clone(), second_outdir.path()).unwrap();
837
838        // Create another file to write to the package metafar
839        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        // Create a file to include as a blob
850        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        // Write the package again after we've modified its contents
861        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        // Validate the contents of the package after re-writing
876        for blob_info in second_manifest.blobs() {
877            match &*blob_info.path {
878                PackageManifest::META_FAR_BLOB_PATH => {
879                    // Validate that the metafar contains the additional file in meta
880                    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 => (), // separate tests check this matches blobs
894                            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        // We should be able to build the package, and it should not have our
958        // removed files in either the meta.far or the contents.
959        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 => (), // we have separate tests for the meta.far metadata
971                            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        // Create the builder
997        let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
998
999        assert_eq!(builder.abi_revision, FAKE_ABI_REVISION);
1000
1001        // Replace the ABI revision.
1002        builder.abi_revision = OTHER_FAKE_ABI_REVISION;
1003
1004        // Build the package
1005        let _ = builder.build(&outdir, &metafar_path).unwrap();
1006
1007        // Validate that the new abi_revision was written correctly
1008        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        // even if we set the overwrite flag, we shouldn't allow a blob to overwrite a file in the far
1065        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        // even if we set the overwrite flag, we shouldn't allow a far file to overwrite a blob
1081        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    /// Helper to wrap common boilerplate for testing [add_file_as_blob_with_dedup]:
1103    ///
1104    /// 1. Creates 2 temporary files containing the given contents
1105    /// 2. Calls [add_file_as_blob] on the first file, which must succeed
1106    /// 3. Calls [add_file_as_blob_with_dedup] on the second file, returning the result
1107    fn add_blob_with_dedup(
1108        builder: &mut PackageBuilder,
1109        contents_1: &str,
1110        contents_2: &str,
1111    ) -> Result<()> {
1112        // Write to real files since the contents need to be compared, not the paths.
1113        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        // The first blob should always succeed, return the second blob status.
1124        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        // Same contents: add should succeed with second blob deduplicated.
1133        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        // Different contents: second blob add should fail.
1141        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        // Second addition should now succeed since we've set the overwrite flag.
1150        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        // Create a file to write to the package metafar
1162        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        // Create a file to include as a blob
1166        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        // Create the builder
1172        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        // set it to write a manifest, with file-relative paths.
1182        builder.manifest_path(manifest_path);
1183        builder.manifest_blobs_relative_to(RelativeTo::File);
1184
1185        // Build the package
1186        let manifest = builder.build(outdir, metafar_path).unwrap();
1187
1188        // Ensure that the loaded manifest has paths still relative to the working directory, even
1189        // though serialized paths should be relative to the manifest itself.
1190        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        // The written manifest is tested in [crate::package_manifest::host_tests]
1197    }
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        // Build the package.
1218        builder.build(&outdir, &metafar_path).unwrap();
1219
1220        // Validate that the metafar contains the subpackages.
1221        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}