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, Cursor};
17use std::path::{Path, PathBuf};
18use version_history::AbiRevision;
19
20/// Paths which will be generated by `PackageBuilder` itself and which should not be manually added.
21const RESERVED_PATHS: &[&str] =
22    &[MetaContents::PATH, MetaPackage::PATH, version_history::AbiRevision::PATH];
23pub const ABI_REVISION_FILE_PATH: &str = "meta/fuchsia.abi/abi-revision";
24
25/// A builder for Fuchsia Packages
26pub struct PackageBuilder {
27    /// The name of the package being created.
28    name: String,
29
30    /// The abi_revision to embed in the package.
31    pub abi_revision: AbiRevision,
32
33    /// The contents that are to be placed inside the FAR itself, not as
34    /// separate blobs.
35    far_contents: BTreeMap<String, String>,
36
37    /// The contents that are to be attached to the package as external blobs.
38    blobs: BTreeMap<String, String>,
39
40    /// Optional path to serialize the PackageManifest to
41    manifest_path: Option<Utf8PathBuf>,
42
43    /// Optionally make the blob 'source_path's relative to the path the
44    /// PackageManifest is serialized to.
45    blob_sources_relative: RelativeTo,
46
47    /// Optional (possibly different) name to publish the package under.
48    /// This changes the name that's placed in the output package manifest.
49    published_name: Option<String>,
50
51    /// Optional package repository.
52    repository: Option<String>,
53
54    /// Metafile of subpackages.
55    subpackages: BTreeMap<RelativePackageUrl, (Hash, PathBuf)>,
56
57    /// Whether new files with the same path should overwrwite previous files at that path.
58    overwrite_files: bool,
59
60    /// Whether new subpackages with the same url should overwrite previous subpackages at that url.
61    overwrite_subpackages: bool,
62}
63
64impl PackageBuilder {
65    /// Create a new PackageBuilder.
66    ///
67    /// The ABI revision is included in the package exactly as given - we don't
68    /// validate it here.
69    pub fn new(name: impl AsRef<str>, abi_revision: AbiRevision) -> Self {
70        PackageBuilder {
71            name: name.as_ref().to_string(),
72            abi_revision,
73            far_contents: BTreeMap::default(),
74            blobs: BTreeMap::default(),
75            manifest_path: None,
76            blob_sources_relative: RelativeTo::default(),
77            published_name: None,
78            repository: None,
79            subpackages: BTreeMap::default(),
80            overwrite_files: false,
81            overwrite_subpackages: false,
82        }
83    }
84
85    /// Create a new PackageBuilder with the platform-internal ABI revision.
86    ///
87    /// The platform-internal ABI revision is only appropriate for packages that
88    /// are only ever read by binaries from the exact same release. For packages
89    /// that can be built by tools from one release and then run on an OS from a
90    /// different release, a different ABI revision that defines the
91    /// compatibility guarantees must be selected.
92    pub fn new_platform_internal_package(name: impl AsRef<str>) -> Self {
93        PackageBuilder::new(
94            name,
95            version_history_data::HISTORY.get_abi_revision_for_platform_components(),
96        )
97    }
98
99    /// Create a PackageBuilder from a PackageBuildManifest.
100    ///
101    /// The ABI revision is included in the package exactly as given - we don't
102    /// validate it here. Returns an error if the given manifest already
103    /// specified an ABI revision.
104    pub fn from_package_build_manifest(
105        manifest: &PackageBuildManifest,
106        abi_revision: AbiRevision,
107    ) -> Result<Self> {
108        // Read the package name from `meta/package`, or error out if it's missing.
109        let meta_package = if let Some(path) = manifest.far_contents().get("meta/package") {
110            let f = File::open(path).with_context(|| format!("opening {path}"))?;
111
112            MetaPackage::deserialize(BufReader::new(f))?
113        } else {
114            return Err(anyhow!("package missing meta/package entry"));
115        };
116
117        ensure!(meta_package.variant().is_zero(), "package variant must be zero");
118
119        // Ensure the manifest doesn't include `meta/fuchsia.abi/abi-revision` -
120        // if it did, it could conflict with the value of --api-level from the
121        // command line.
122        if manifest.far_contents().get("meta/fuchsia.abi/abi-revision").is_some() {
123            bail!(
124                "Manifest must not include entry for 'meta/fuchsia.abi/abi-revision'. \
125                Pass --api-level to package-tool instead."
126            )
127        };
128
129        let mut builder = PackageBuilder::new(meta_package.name(), abi_revision);
130
131        for (at_path, file) in manifest.external_contents() {
132            builder
133                .add_file_as_blob(at_path, file)
134                .with_context(|| format!("adding file {at_path} as blob {file}"))?;
135        }
136
137        for (at_path, file) in manifest.far_contents() {
138            // Ignore files that the package builder will automatically create.
139            if at_path == "meta/package" {
140                continue;
141            }
142
143            builder
144                .add_file_to_far(at_path, file)
145                .with_context(|| format!("adding file {at_path} to far {file}"))?;
146        }
147
148        Ok(builder)
149    }
150
151    /// Create a PackageBuilder from an existing manifest. Requires an out directory for temporarily
152    /// unpacking `meta.far` contents.
153    pub fn from_manifest(
154        original_manifest: PackageManifest,
155        outdir: impl AsRef<Path>,
156    ) -> Result<Self> {
157        // parse the existing manifest, copying everything over
158        let mut abi_rev = None;
159        let mut inner_name = None;
160        let mut meta_blobs = BTreeMap::new();
161        let mut blob_paths = BTreeMap::new();
162        let mut subpackage_names = BTreeMap::new();
163        for blob in original_manifest.blobs() {
164            if blob.path == PackageManifest::META_FAR_BLOB_PATH {
165                let meta_far_contents = std::fs::read(&blob.source_path)
166                    .with_context(|| format!("reading {}", blob.source_path))?;
167                let PackagedMetaFar { abi_revision, name, meta_contents, .. } =
168                    PackagedMetaFar::parse(&meta_far_contents).context("parsing meta/")?;
169                abi_rev = Some(abi_revision);
170                inner_name = Some(name);
171                meta_blobs = meta_contents;
172            } else {
173                blob_paths.insert(blob.path.clone(), blob.source_path.clone());
174            }
175        }
176        for subpackage in original_manifest.subpackages() {
177            subpackage_names.insert(
178                subpackage.name.clone(),
179                (subpackage.merkle, subpackage.manifest_path.clone()),
180            );
181        }
182        let abi_rev = abi_rev
183            .ok_or_else(|| anyhow!("did not find {}", version_history::AbiRevision::PATH))?;
184        let inner_name = inner_name.ok_or_else(|| anyhow!("did not find {}", MetaPackage::PATH))?;
185
186        let mut builder = PackageBuilder::new(inner_name, abi_rev);
187        builder.published_name(original_manifest.name());
188        if let Some(repository) = original_manifest.repository() {
189            builder.repository(repository);
190        }
191
192        for (path, contents) in meta_blobs {
193            builder
194                .add_contents_to_far(&path, contents, &outdir)
195                .with_context(|| format!("adding {path} to far"))?;
196        }
197
198        for (path, source_path) in blob_paths {
199            builder
200                .add_file_as_blob(&path, &source_path)
201                .with_context(|| format!("adding {path}"))?;
202        }
203
204        for (name, (merkle, manifest_path)) in subpackage_names {
205            builder
206                .add_subpackage(
207                    &name.parse().context("parsing subpackage name")?,
208                    merkle,
209                    manifest_path.into(),
210                )
211                .with_context(|| format!("adding {name}"))?;
212        }
213
214        Ok(builder)
215    }
216
217    /// Specify a path to write out the json package manifest to.
218    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    /// Specify whether new additions of a file that have already been added should overwrite,
227    /// or fail
228    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        // Never allow overwriting a blob path if we think we're writing into a far
245        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        // Never allow overwriting a path in the far if we think we're writing a blob
269        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    /// Add a file to the package's far.
288    ///
289    /// Errors
290    ///
291    /// Will return an error if the path for the file is already being used.
292    /// Will return an error if any special package metadata paths are used.
293    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    /// Remove a file from the package's meta.far.
308    ///
309    /// Errors
310    ///
311    /// Will return an error if the file is not already in the meta.far
312    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    /// Add a file to the package as a blob itself.
321    ///
322    /// Errors
323    ///
324    /// Will return an error if the path for the file is already being used.
325    /// Will return an error if any special package metadata paths are used.
326    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    /// Same as [add_file_as_blob], but deduplicates identical images at the same path.
341    ///
342    /// Deduplication is based on image contents, not the `file` source path.
343    pub fn add_file_as_blob_with_dedup(
344        &mut self,
345        at_path: impl AsRef<str>,
346        file: impl AsRef<str>,
347    ) -> Result<()> {
348        let at_path = at_path.as_ref();
349        let file = file.as_ref();
350
351        if let Some(existing_file) = self.blobs.get(at_path) {
352            let existing_contents =
353                std::fs::read(existing_file).context(format!("reading {}", existing_file))?;
354            let new_contents = std::fs::read(file).context(format!("reading {}", file))?;
355
356            // If the exact contents already exist, we're done. Otherwise continue on to the default
357            // behavior which may either error out or override the existing blob.
358            if existing_contents == new_contents {
359                return Ok(());
360            }
361        }
362
363        self.add_file_as_blob(at_path, file)
364    }
365
366    /// Remove a file currently in the package as a blob
367    ///
368    /// Errors
369    ///
370    /// Will return an error if the file is not already in the package contents
371    pub fn remove_blob_file(&mut self, at_path: impl AsRef<str>) -> Result<()> {
372        self.validate_ok_to_modify(at_path.as_ref())?;
373        match self.blobs.remove(at_path.as_ref()) {
374            Some(_key) => Ok(()),
375            None => Err(anyhow!("file not in package contents")),
376        }
377    }
378
379    /// Write the contents to a file, and add that file as a blob at the given
380    /// path within the package.
381    pub fn add_contents_as_blob<C: AsRef<[u8]>>(
382        &mut self,
383        at_path: impl AsRef<str>,
384        contents: C,
385        gendir: impl AsRef<Path>,
386    ) -> Result<()> {
387        // Preflight that the file paths are valid before attempting to write.
388        self.validate_ok_to_add_as_blob(&at_path)?;
389        let source_path = Self::write_contents_to_file(gendir, at_path.as_ref(), contents)?;
390        self.add_file_as_blob(at_path, source_path.path_to_string()?)
391    }
392
393    /// Write the contents to a file, and add that file to the metafar at the
394    /// given path within the package.
395    pub fn add_contents_to_far<C: AsRef<[u8]>>(
396        &mut self,
397        at_path: impl AsRef<str>,
398        contents: C,
399        gendir: impl AsRef<Path>,
400    ) -> Result<()> {
401        // Preflight that the file paths are valid before attempting to write.
402        self.validate_ok_to_add_in_far(&at_path)?;
403        let source_path = Self::write_contents_to_file(gendir, at_path.as_ref(), contents)?;
404        self.add_file_to_far(at_path, source_path.path_to_string()?)
405    }
406
407    /// Helper fn to write the contents to a file, creating the parent dirs as needed when doing so.
408    fn write_contents_to_file<C: AsRef<[u8]>>(
409        gendir: impl AsRef<Path>,
410        file_path: impl AsRef<Path>,
411        contents: C,
412    ) -> Result<PathBuf> {
413        let file_path = gendir.as_ref().join(file_path);
414        if let Some(parent_dir) = file_path.parent() {
415            std::fs::create_dir_all(parent_dir)
416                .context(format!("creating parent directories for {}", file_path.display()))?;
417        }
418        std::fs::write(&file_path, contents)
419            .context(format!("writing contents to file: {}", file_path.display()))?;
420        Ok(file_path)
421    }
422
423    /// Specify whether new additions of a subpackage that have already been added should overwrite,
424    /// or fail
425    pub fn overwrite_subpackages(&mut self, overwrite_subpackages: bool) {
426        self.overwrite_subpackages = overwrite_subpackages
427    }
428
429    /// Helper fn to include a subpackage into this package.
430    pub fn add_subpackage(
431        &mut self,
432        url: &RelativePackageUrl,
433        package_hash: Hash,
434        package_manifest_path: PathBuf,
435    ) -> Result<()> {
436        if self.subpackages.contains_key(url) && !self.overwrite_subpackages {
437            return Err(anyhow!("duplicate entry for {:?}", url));
438        }
439        self.subpackages.insert(url.clone(), (package_hash, package_manifest_path));
440        Ok(())
441    }
442
443    /// Set the name of the package.
444    pub fn name(&mut self, name: impl AsRef<str>) {
445        self.name = name.as_ref().to_string();
446    }
447
448    /// Set a different name for the package to be published by (and to be
449    /// included in the generated PackageManifest), than the one embedded in the
450    /// package itself.
451    pub fn published_name(&mut self, published_name: impl AsRef<str>) {
452        self.published_name = Some(published_name.as_ref().into());
453    }
454
455    /// Set a repository for the package to be included in the generated PackageManifest.
456    pub fn repository(&mut self, repository: impl AsRef<str>) {
457        self.repository = Some(repository.as_ref().into());
458    }
459
460    /// Read the contents of a file already added to the builder's meta.far.
461    pub fn read_contents_from_far(&self, file_path: &str) -> Result<Vec<u8>> {
462        if let Some(p) = self.far_contents.get(file_path) {
463            std::fs::read(p).with_context(|| format!("reading {p}"))
464        } else {
465            bail!(
466                "couldn't find `{}` in package: {:?}",
467                file_path,
468                self.far_contents.keys().collect::<Vec<_>>()
469            );
470        }
471    }
472
473    /// Build the package, using the specified dir, returning the
474    /// PackageManifest.
475    ///
476    /// If a path for the manifest was specified, the PackageManifest will also
477    /// be written to there.
478    ///
479    /// The `gendir` param is assumed to be a path to folder which is only used
480    /// by this package's creation, so this fn does not try to create paths
481    /// within it that are unique across different packages.
482    pub fn build(
483        self,
484        gendir: impl AsRef<Path>,
485        metafar_path: impl AsRef<Path>,
486    ) -> Result<PackageManifest> {
487        let gendir = gendir.as_ref();
488        let metafar_path = metafar_path.as_ref();
489
490        let PackageBuilder {
491            name,
492            abi_revision,
493            mut far_contents,
494            blobs,
495            manifest_path,
496            blob_sources_relative,
497            published_name,
498            repository,
499            subpackages,
500            overwrite_files: _,
501            overwrite_subpackages: _,
502        } = self;
503
504        far_contents.insert(
505            MetaPackage::PATH.to_string(),
506            create_meta_package_file(gendir, &name)
507                .with_context(|| format!("Writing the {} file", MetaPackage::PATH))?,
508        );
509
510        let abi_revision_file =
511            Self::write_contents_to_file(gendir, ABI_REVISION_FILE_PATH, abi_revision.as_bytes())
512                .with_context(|| format!("Writing the {ABI_REVISION_FILE_PATH} file"))?;
513
514        far_contents.insert(
515            ABI_REVISION_FILE_PATH.to_string(),
516            abi_revision_file.path_to_string().with_context(|| {
517                format!("Adding the {ABI_REVISION_FILE_PATH} file to the package")
518            })?,
519        );
520
521        // Only add the subpackages file if we were configured with any subpackages.
522        if !subpackages.is_empty() {
523            far_contents.insert(
524                MetaSubpackages::PATH.to_string(),
525                create_meta_subpackages_file(gendir, subpackages.clone()).with_context(|| {
526                    format!("Adding the {} file to the package", MetaSubpackages::PATH)
527                })?,
528            );
529        }
530
531        let package_build_manifest =
532            PackageBuildManifest::from_external_and_far_contents(blobs, far_contents)
533                .with_context(|| "creating creation manifest".to_string())?;
534
535        let package_manifest = crate::build::build(
536            &package_build_manifest,
537            metafar_path,
538            published_name.unwrap_or(name),
539            subpackages
540                .into_iter()
541                .map(|(name, (merkle, package_manifest_path))| SubpackageEntry {
542                    name,
543                    merkle,
544                    package_manifest_path,
545                })
546                .collect(),
547            repository,
548            abi_revision,
549        )
550        .with_context(|| format!("building package manifest {}", metafar_path.display()))?;
551
552        if let Some(manifest_path) = manifest_path {
553            if let RelativeTo::File = blob_sources_relative {
554                let copy = package_manifest.clone();
555                copy.write_with_relative_paths(&manifest_path).with_context(|| {
556                    format!(
557                        "Failed to create package manifest with relative paths at: {manifest_path}"
558                    )
559                })?;
560            } else {
561                // Write the package manifest to a file.
562                package_manifest.write(&manifest_path).with_context(|| {
563                    format!("Failed to create package manifest at: {manifest_path}")
564                })?;
565            }
566        }
567        Ok(package_manifest)
568    }
569}
570
571/// Construct a meta/package file in `gendir`.
572///
573/// Returns the path that the file was created at.
574fn create_meta_package_file(gendir: &Path, name: impl Into<String>) -> Result<String> {
575    let package_name = name.into();
576    let meta_package_path = gendir.join(MetaPackage::PATH);
577    if let Some(parent_dir) = meta_package_path.parent() {
578        std::fs::create_dir_all(parent_dir)?;
579    }
580
581    let file = std::fs::File::create(&meta_package_path)?;
582    let meta_package = MetaPackage::from_name_and_variant_zero(package_name.try_into()?);
583    meta_package.serialize(file)?;
584    meta_package_path.path_to_string()
585}
586
587/// Results of parsing an existing meta.far for repackaging or testing purposes.
588struct PackagedMetaFar {
589    /// Package's name.
590    name: String,
591
592    /// Package's ABI revision.
593    abi_revision: AbiRevision,
594
595    /// Map of package paths to blob contents.
596    meta_contents: BTreeMap<String, Vec<u8>>,
597}
598
599impl PackagedMetaFar {
600    fn parse(bytes: &[u8]) -> Result<Self> {
601        let mut meta_far =
602            fuchsia_archive::Utf8Reader::new(Cursor::new(bytes)).context("reading FAR")?;
603
604        let mut abi_revision = None;
605        let mut name = None;
606        let mut meta_contents = BTreeMap::new();
607
608        // collect paths separately, we need mutable access to reader for the bytes of each
609        let meta_paths = meta_far.list().map(|e| e.path().to_owned()).collect::<Vec<_>>();
610
611        // copy the contents of the meta.far, skipping files that PackageBuilder will write
612        for path in meta_paths {
613            let contents = meta_far.read_file(&path).with_context(|| format!("reading {path}"))?;
614
615            if path == MetaContents::PATH {
616                continue;
617            } else if path == MetaPackage::PATH {
618                ensure!(name.is_none(), "only one name per package");
619                let mp = MetaPackage::deserialize(Cursor::new(&contents))
620                    .context("deserializing meta/package")?;
621                name = Some(mp.name().to_string());
622            } else if path == ABI_REVISION_FILE_PATH {
623                ensure!(abi_revision.is_none(), "only one abi revision per package");
624                ensure!(contents.len() == 8, "ABI revision must be encoded as 8 bytes");
625                abi_revision = Some(AbiRevision::try_from(contents.as_slice()).unwrap());
626            } else {
627                meta_contents.insert(path, contents);
628            }
629        }
630        let abi_revision =
631            abi_revision.ok_or_else(|| anyhow!("did not find {}", ABI_REVISION_FILE_PATH))?;
632        let name = name.ok_or_else(|| anyhow!("did not find {}", MetaPackage::PATH))?;
633
634        Ok(Self { name, abi_revision, meta_contents })
635    }
636}
637
638/// Construct a meta/fuchsia.pkg/subpackages file in `gendir`.
639///
640/// Returns the path that the file was created at.
641fn create_meta_subpackages_file(
642    gendir: &Path,
643    subpackages: BTreeMap<RelativePackageUrl, (Hash, PathBuf)>,
644) -> Result<String> {
645    let meta_subpackages_path = gendir.join(MetaSubpackages::PATH);
646    if let Some(parent_dir) = meta_subpackages_path.parent() {
647        std::fs::create_dir_all(parent_dir)?;
648    }
649
650    let meta_subpackages = MetaSubpackages::from_iter(
651        subpackages.into_iter().map(|(name, (merkle, _))| (name, merkle)),
652    );
653    let file = std::fs::File::create(&meta_subpackages_path)?;
654    meta_subpackages.serialize(file)?;
655    meta_subpackages_path.path_to_string()
656}
657
658#[cfg(test)]
659mod tests {
660    use super::*;
661    use camino::Utf8Path;
662    use tempfile::{NamedTempFile, TempDir};
663
664    const FAKE_ABI_REVISION: AbiRevision = AbiRevision::from_u64(0x5836508c2defac54);
665
666    #[test]
667    fn test_create_meta_package_file() {
668        let gen_dir = TempDir::new().unwrap();
669        let name = "some_test_package";
670        let meta_package_path = gen_dir.as_ref().join("meta/package");
671        let created_path = create_meta_package_file(gen_dir.path(), name).unwrap();
672        assert_eq!(created_path, meta_package_path.path_to_string().unwrap());
673
674        let raw_contents = std::fs::read(meta_package_path).unwrap();
675        let meta_package = MetaPackage::deserialize(std::io::Cursor::new(raw_contents)).unwrap();
676        assert_eq!(meta_package.name().as_ref(), "some_test_package");
677        assert!(meta_package.variant().is_zero());
678    }
679
680    #[test]
681    fn test_builder() {
682        let outdir = TempDir::new().unwrap();
683        let metafar_path = outdir.path().join("meta.far");
684
685        // Create a file to write to the package metafar
686        let far_source_file_path = NamedTempFile::new_in(&outdir).unwrap();
687        std::fs::write(&far_source_file_path, "some data for far").unwrap();
688
689        // Create a file to include as a blob
690        let blob_source_file_path = NamedTempFile::new_in(&outdir).unwrap();
691        let blob_contents = "some data for blob";
692        std::fs::write(&blob_source_file_path, blob_contents).unwrap();
693
694        // Pre-calculate the blob's hash
695        let blob_hash = fuchsia_merkle::from_slice(blob_contents.as_bytes()).root();
696
697        let subpackage_url = "subpackage0".parse::<RelativePackageUrl>().unwrap();
698        let subpackage_hash = Hash::from([0; fuchsia_hash::HASH_SIZE]);
699        let subpackage_package_manifest_path = "subpackages/package_manifest.json";
700
701        // Create the builder
702        let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
703        builder
704            .add_file_as_blob("some/blob", blob_source_file_path.path().path_to_string().unwrap())
705            .unwrap();
706        builder
707            .add_file_to_far(
708                "meta/some/file",
709                far_source_file_path.path().path_to_string().unwrap(),
710            )
711            .unwrap();
712        builder
713            .add_subpackage(
714                &subpackage_url,
715                subpackage_hash,
716                subpackage_package_manifest_path.into(),
717            )
718            .unwrap();
719
720        // Build the package
721        let manifest = builder.build(&outdir, &metafar_path).unwrap();
722
723        // Validate the returned manifest
724        assert_eq!(manifest.name().as_ref(), "some_pkg_name");
725
726        let (blobs, subpackages) = manifest.into_blobs_and_subpackages();
727
728        // Validate that the blob has the correct hash and contents
729        let blob_info = blobs.iter().find(|info| info.path == "some/blob").unwrap().clone();
730        assert_eq!(blob_hash, blob_info.merkle);
731        assert_eq!(blob_contents, std::fs::read_to_string(blob_info.source_path).unwrap());
732
733        // Validate that the subpackage has the correct hash and manifest path
734        let subpackage_info =
735            subpackages.iter().find(|info| info.name == "subpackage0").unwrap().clone();
736        assert_eq!(subpackage_hash, subpackage_info.merkle);
737        assert_eq!(subpackage_package_manifest_path, subpackage_info.manifest_path);
738
739        // Validate that the metafar contains the additional file in meta
740        let mut metafar = std::fs::File::open(metafar_path).unwrap();
741        let mut far_reader = fuchsia_archive::Utf8Reader::new(&mut metafar).unwrap();
742        let far_file_data = far_reader.read_file("meta/some/file").unwrap();
743        let far_file_data = std::str::from_utf8(far_file_data.as_slice()).unwrap();
744        assert_eq!(far_file_data, "some data for far");
745
746        // Validate that the abi_revision was written correctly
747        let abi_revision_data = far_reader.read_file("meta/fuchsia.abi/abi-revision").unwrap();
748        let abi_revision_data: [u8; 8] = abi_revision_data.try_into().unwrap();
749        let abi_revision = AbiRevision::from_bytes(abi_revision_data);
750        assert_eq!(abi_revision, FAKE_ABI_REVISION);
751    }
752
753    #[test]
754    fn test_from_manifest() {
755        let first_outdir = TempDir::new().unwrap();
756
757        // Create an initial package with non-default outputs for generated files
758        let inner_name = "some_pkg_name";
759        let mut first_builder = PackageBuilder::new(inner_name, FAKE_ABI_REVISION);
760        // Set a different published name
761        let published_name = "some_other_pkg_name";
762        first_builder.published_name(published_name);
763
764        // Create a file to write to the package metafar
765        let first_far_source_file_path = NamedTempFile::new_in(&first_outdir).unwrap();
766        let first_far_contents = "some data for far";
767        std::fs::write(&first_far_source_file_path, first_far_contents).unwrap();
768        first_builder
769            .add_file_to_far("meta/some/file", first_far_source_file_path.path().to_string_lossy())
770            .unwrap();
771
772        // Create a file to include as a blob
773        let first_blob_source_file_path = NamedTempFile::new_in(&first_outdir).unwrap();
774        let first_blob_contents = "some data for blob";
775        std::fs::write(&first_blob_source_file_path, first_blob_contents).unwrap();
776        first_builder
777            .add_file_as_blob("some/blob", first_blob_source_file_path.path().to_string_lossy())
778            .unwrap();
779
780        let first_subpackage_url = "subpackage0".parse::<RelativePackageUrl>().unwrap();
781        let first_subpackage_hash = Hash::from([0; fuchsia_hash::HASH_SIZE]);
782        let first_subpackage_package_manifest_path = "subpackages/package_manifest.json";
783
784        first_builder
785            .add_subpackage(
786                &first_subpackage_url,
787                first_subpackage_hash,
788                first_subpackage_package_manifest_path.into(),
789            )
790            .unwrap();
791
792        // Build the package
793        let first_manifest =
794            first_builder.build(&first_outdir, first_outdir.path().join("meta.far")).unwrap();
795        assert_eq!(
796            first_manifest.blobs().len(),
797            2,
798            "package should have a meta.far and a single blob"
799        );
800        assert_eq!(
801            first_manifest.subpackages().len(),
802            1,
803            "package should have a single subpackage"
804        );
805        let blob_info = first_manifest
806            .blobs()
807            .iter()
808            .find(|blob_info| blob_info.path == PackageManifest::META_FAR_BLOB_PATH)
809            .unwrap();
810        let mut metafar = std::fs::File::open(&blob_info.source_path).unwrap();
811        let far_reader = fuchsia_archive::Utf8Reader::new(&mut metafar).unwrap();
812        let first_paths_in_far =
813            far_reader.list().map(|e| e.path().to_string()).collect::<Vec<_>>();
814
815        // Re-parse the package into a builder for further modification
816        let second_outdir = TempDir::new().unwrap();
817        let mut second_builder =
818            PackageBuilder::from_manifest(first_manifest.clone(), second_outdir.path()).unwrap();
819
820        // Create another file to write to the package metafar
821        let second_far_source_file_path = NamedTempFile::new_in(&second_outdir).unwrap();
822        let second_far_contents = "some more data for far";
823        std::fs::write(&second_far_source_file_path, second_far_contents).unwrap();
824        second_builder
825            .add_file_to_far(
826                "meta/some/other/file",
827                second_far_source_file_path.path().to_string_lossy(),
828            )
829            .unwrap();
830
831        // Create a file to include as a blob
832        let second_blob_source_file_path = NamedTempFile::new_in(&second_outdir).unwrap();
833        let second_blob_contents = "some more data for blobs";
834        std::fs::write(&second_blob_source_file_path, second_blob_contents).unwrap();
835        second_builder
836            .add_file_as_blob(
837                "some/other/blob",
838                second_blob_source_file_path.path().to_string_lossy(),
839            )
840            .unwrap();
841
842        // Write the package again after we've modified its contents
843        let second_metafar_path = second_outdir.path().join("meta.far");
844        let second_manifest = second_builder.build(&second_outdir, second_metafar_path).unwrap();
845        assert_eq!(first_manifest.name(), second_manifest.name(), "package names must match");
846        assert_eq!(
847            second_manifest.blobs().len(),
848            3,
849            "package should have a meta.far and two blobs"
850        );
851        assert_eq!(
852            second_manifest.subpackages().len(),
853            1,
854            "package should STILL have a single subpackage"
855        );
856
857        // Validate the contents of the package after re-writing
858        for blob_info in second_manifest.blobs() {
859            match &*blob_info.path {
860                PackageManifest::META_FAR_BLOB_PATH => {
861                    // Validate that the metafar contains the additional file in meta
862                    let mut metafar = std::fs::File::open(&blob_info.source_path).unwrap();
863                    let mut far_reader = fuchsia_archive::Utf8Reader::new(&mut metafar).unwrap();
864                    let paths_in_far =
865                        far_reader.list().map(|e| e.path().to_string()).collect::<Vec<_>>();
866                    assert_eq!(
867                        paths_in_far.len(),
868                        first_paths_in_far.len() + 1,
869                        "must have the original files and one added one"
870                    );
871
872                    for far_path in paths_in_far {
873                        let far_bytes = far_reader.read_file(&far_path).unwrap();
874                        match &*far_path {
875                            MetaContents::PATH => (), // separate tests check this matches blobs
876                            MetaPackage::PATH => {
877                                let mp = MetaPackage::deserialize(Cursor::new(&far_bytes)).unwrap();
878                                assert_eq!(mp.name().as_ref(), inner_name);
879                            }
880                            MetaSubpackages::PATH => {
881                                let ms =
882                                    MetaSubpackages::deserialize(Cursor::new(&far_bytes)).unwrap();
883                                assert_eq!(ms.subpackages().len(), 1);
884                                let (url, hash) = ms.subpackages().iter().next().unwrap();
885                                assert_eq!(url, &first_subpackage_url);
886                                assert_eq!(hash, &first_subpackage_hash);
887                            }
888                            ABI_REVISION_FILE_PATH => {
889                                assert_eq!(far_bytes, FAKE_ABI_REVISION.as_bytes());
890                            }
891                            "meta/some/file" => {
892                                assert_eq!(far_bytes, first_far_contents.as_bytes());
893                            }
894                            "meta/some/other/file" => {
895                                assert_eq!(far_bytes, second_far_contents.as_bytes());
896                            }
897                            other => panic!("unrecognized file in meta.far: {other}"),
898                        }
899                    }
900                }
901                "some/blob" => {
902                    assert_eq!(
903                        std::fs::read_to_string(&blob_info.source_path).unwrap(),
904                        first_blob_contents,
905                    );
906                }
907                "some/other/blob" => {
908                    assert_eq!(
909                        std::fs::read_to_string(&blob_info.source_path).unwrap(),
910                        second_blob_contents,
911                    )
912                }
913                other => panic!("unrecognized path in blobs `{other}`"),
914            }
915        }
916    }
917
918    #[test]
919    fn test_removes() {
920        let gendir = TempDir::new().unwrap();
921        let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
922        assert!(builder.add_contents_to_far("meta/foo", "foo", gendir.path()).is_ok());
923        assert!(builder.add_contents_to_far("meta/bar", "bar", gendir.path()).is_ok());
924
925        assert!(builder.add_contents_as_blob("baz", "baz", gendir.path()).is_ok());
926        assert!(builder.add_contents_as_blob("boom", "boom", gendir.path()).is_ok());
927
928        assert!(builder.remove_file_from_far("meta/foo").is_ok());
929        assert!(builder.remove_file_from_far("meta/does_not_exist").is_err());
930
931        assert!(builder.remove_blob_file("baz").is_ok());
932        assert!(builder.remove_blob_file("does_not_exist").is_err());
933
934        let outdir = TempDir::new().unwrap();
935        let metafar_path = outdir.path().join("meta.far");
936
937        let pkg_manifest = builder.build(&outdir, &metafar_path).unwrap();
938
939        // We should be able to build the package, and it should not have our
940        // removed files in either the meta.far or the contents.
941        for blob_info in pkg_manifest.blobs() {
942            match &*blob_info.path {
943                PackageManifest::META_FAR_BLOB_PATH => {
944                    let mut metafar = std::fs::File::open(&blob_info.source_path).unwrap();
945                    let mut far_reader = fuchsia_archive::Utf8Reader::new(&mut metafar).unwrap();
946                    let paths_in_far =
947                        far_reader.list().map(|e| e.path().to_string()).collect::<Vec<_>>();
948
949                    for far_path in paths_in_far {
950                        let far_bytes = far_reader.read_file(&far_path).unwrap();
951                        match &*far_path {
952                            MetaContents::PATH => (), // we have separate tests for the meta.far metadata
953                            MetaPackage::PATH => (),
954                            MetaSubpackages::PATH => (),
955                            ABI_REVISION_FILE_PATH => (),
956                            "meta/bar" => {
957                                assert_eq!(far_bytes, "bar".as_bytes());
958                            }
959                            other => panic!("unrecognized file in meta.far: {other}"),
960                        }
961                    }
962                }
963                "boom" => {
964                    assert_eq!(std::fs::read_to_string(&blob_info.source_path).unwrap(), "boom",);
965                }
966                other => panic!("unrecognized path in blobs `{other}`"),
967            }
968        }
969    }
970
971    #[test]
972    fn test_overwrite_abi_revision() {
973        const OTHER_FAKE_ABI_REVISION: AbiRevision = AbiRevision::from_u64(0x1234);
974
975        let outdir = TempDir::new().unwrap();
976        let metafar_path = outdir.path().join("meta.far");
977
978        // Create the builder
979        let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
980
981        assert_eq!(builder.abi_revision, FAKE_ABI_REVISION);
982
983        // Replace the ABI revision.
984        builder.abi_revision = OTHER_FAKE_ABI_REVISION;
985
986        // Build the package
987        let _ = builder.build(&outdir, &metafar_path).unwrap();
988
989        // Validate that the new abi_revision was written correctly
990        let mut metafar = std::fs::File::open(metafar_path).unwrap();
991        let mut far_reader = fuchsia_archive::Utf8Reader::new(&mut metafar).unwrap();
992
993        let abi_revision_data = far_reader.read_file("meta/fuchsia.abi/abi-revision").unwrap();
994        let abi_revision_data: [u8; 8] = abi_revision_data.try_into().unwrap();
995        let abi_revision = AbiRevision::from_bytes(abi_revision_data);
996        assert_eq!(abi_revision, OTHER_FAKE_ABI_REVISION);
997    }
998
999    #[test]
1000    fn test_build_rejects_meta_contents() {
1001        let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1002        assert!(builder.add_file_to_far("meta/contents", "some/src/file").is_err());
1003        assert!(builder.add_file_as_blob("meta/contents", "some/src/file").is_err());
1004    }
1005
1006    #[test]
1007    fn test_build_rejects_meta_package() {
1008        let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1009        assert!(builder.add_file_to_far("meta/package", "some/src/file").is_err());
1010        assert!(builder.add_file_as_blob("meta/package", "some/src/file").is_err());
1011    }
1012
1013    #[test]
1014    fn test_build_rejects_abi_revision() {
1015        let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1016        assert!(builder.add_file_to_far("meta/fuchsia.abi/abi-revision", "some/src/file").is_err());
1017        assert!(
1018            builder.add_file_as_blob("meta/fuchsia.abi/abi-revision", "some/src/file").is_err()
1019        );
1020    }
1021
1022    #[test]
1023    fn test_builder_rejects_path_in_far_when_existing_path_in_far() {
1024        let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1025        builder.add_file_to_far("some/far/file", "some/src/file").unwrap();
1026        assert!(builder.add_file_to_far("some/far/file", "some/src/file").is_err());
1027    }
1028
1029    #[test]
1030    fn test_builder_allows_overwrite_path_in_far_when_flag_set() {
1031        let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1032        builder.overwrite_files(true);
1033        builder.add_file_to_far("some/far/file", "some/src/file").unwrap();
1034        assert!(builder.add_file_to_far("some/far/file", "some/src/file").is_ok());
1035    }
1036
1037    #[test]
1038    fn test_builder_rejects_path_as_blob_when_existing_path_in_far() {
1039        let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1040        builder.add_file_to_far("some/far/file", "some/src/file").unwrap();
1041        assert!(builder.add_file_as_blob("some/far/file", "some/src/file").is_err());
1042    }
1043
1044    #[test]
1045    fn test_builder_rejects_path_as_blob_when_existing_path_in_far_and_overwrite_set() {
1046        // even if we set the overwrite flag, we shouldn't allow a blob to overwrite a file in the far
1047        let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1048        builder.overwrite_files(true);
1049        builder.add_file_to_far("some/far/file", "some/src/file").unwrap();
1050        assert!(builder.add_file_as_blob("some/far/file", "some/src/file").is_err());
1051    }
1052
1053    #[test]
1054    fn test_builder_rejects_path_in_far_when_existing_path_as_blob() {
1055        let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1056        builder.add_file_as_blob("some/far/file", "some/src/file").unwrap();
1057        assert!(builder.add_file_to_far("some/far/file", "some/src/file").is_err());
1058    }
1059
1060    #[test]
1061    fn test_builder_rejects_path_in_far_when_existing_path_as_blob_and_overwrite_set() {
1062        // even if we set the overwrite flag, we shouldn't allow a far file to overwrite a blob
1063        let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1064        builder.overwrite_files(true);
1065        builder.add_file_as_blob("some/far/file", "some/src/file").unwrap();
1066        assert!(builder.add_file_to_far("some/far/file", "some/src/file").is_err());
1067    }
1068
1069    #[test]
1070    fn test_builder_rejects_path_in_blob_when_existing_path_as_blob() {
1071        let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1072        builder.add_file_as_blob("some/far/file", "some/src/file").unwrap();
1073        assert!(builder.add_file_as_blob("some/far/file", "some/src/file").is_err());
1074    }
1075
1076    #[test]
1077    fn test_builder_allows_overwrite_path_as_blob_when_flag_set() {
1078        let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1079        builder.overwrite_files(true);
1080        builder.add_file_as_blob("some/far/file", "some/src/file").unwrap();
1081        assert!(builder.add_file_as_blob("some/far/file", "some/src/file").is_ok());
1082    }
1083
1084    /// Helper to wrap common boilerplate for testing [add_file_as_blob_with_dedup]:
1085    ///
1086    /// 1. Creates 2 temporary files containing the given contents
1087    /// 2. Calls [add_file_as_blob] on the first file, which must succeed
1088    /// 3. Calls [add_file_as_blob_with_dedup] on the second file, returning the result
1089    fn add_blob_with_dedup(
1090        builder: &mut PackageBuilder,
1091        contents_1: &str,
1092        contents_2: &str,
1093    ) -> Result<()> {
1094        // Write to real files since the contents need to be compared, not the paths.
1095        let tmp = TempDir::new().unwrap();
1096        let outdir = Utf8Path::from_path(tmp.path()).unwrap();
1097
1098        let file_1 = NamedTempFile::new_in(outdir).unwrap();
1099        let file_2 = NamedTempFile::new_in(outdir).unwrap();
1100        std::fs::write(&file_1, contents_1).unwrap();
1101        std::fs::write(&file_2, contents_2).unwrap();
1102        let path_1 = file_1.path().to_str().unwrap();
1103        let path_2 = file_2.path().to_str().unwrap();
1104
1105        // The first blob should always succeed, return the second blob status.
1106        builder.add_file_as_blob("path/to/blob", path_1).unwrap();
1107        builder.add_file_as_blob_with_dedup("path/to/blob", path_2)
1108    }
1109
1110    #[test]
1111    fn test_builder_add_file_as_blob_dedup() {
1112        let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1113
1114        // Same contents: add should succeed with second blob deduplicated.
1115        assert!(add_blob_with_dedup(&mut builder, "blob contents", "blob contents").is_ok());
1116    }
1117
1118    #[test]
1119    fn test_builder_add_file_as_blob_dedup_rejects_different_contents() {
1120        let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1121
1122        // Different contents: second blob add should fail.
1123        assert!(add_blob_with_dedup(&mut builder, "blob contents", "other blob contents").is_err());
1124    }
1125
1126    #[test]
1127    fn test_builder_add_file_as_blob_dedup_respects_overwrite_flag() {
1128        let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1129        builder.overwrite_files(true);
1130
1131        // Second addition should now succeed since we've set the overwrite flag.
1132        assert!(add_blob_with_dedup(&mut builder, "blob contents", "other blob contents").is_ok());
1133    }
1134
1135    #[test]
1136    fn test_builder_makes_file_relative_manifests_when_asked() {
1137        let tmp = TempDir::new().unwrap();
1138        let outdir = Utf8Path::from_path(tmp.path()).unwrap();
1139
1140        let metafar_path = outdir.join("meta.far");
1141        let manifest_path = outdir.join("package_manifest.json");
1142
1143        // Create a file to write to the package metafar
1144        let far_source_file_path = NamedTempFile::new_in(outdir).unwrap();
1145        std::fs::write(&far_source_file_path, "some data for far").unwrap();
1146
1147        // Create a file to include as a blob
1148        let blob_source_file_path = outdir.join("contents/data_file");
1149        std::fs::create_dir_all(blob_source_file_path.parent().unwrap()).unwrap();
1150        let blob_contents = "some data for blob";
1151        std::fs::write(&blob_source_file_path, blob_contents).unwrap();
1152
1153        // Create the builder
1154        let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1155        builder.add_file_as_blob("some/blob", &blob_source_file_path).unwrap();
1156        builder
1157            .add_file_to_far(
1158                "meta/some/file",
1159                far_source_file_path.path().path_to_string().unwrap(),
1160            )
1161            .unwrap();
1162
1163        // set it to write a manifest, with file-relative paths.
1164        builder.manifest_path(manifest_path);
1165        builder.manifest_blobs_relative_to(RelativeTo::File);
1166
1167        // Build the package
1168        let manifest = builder.build(outdir, metafar_path).unwrap();
1169
1170        // Ensure that the loaded manifest has paths still relative to the working directory, even
1171        // though serialized paths should be relative to the manifest itself.
1172        manifest
1173            .blobs()
1174            .iter()
1175            .find(|b| b.source_path == blob_source_file_path)
1176            .expect("The manifest should have paths relative to the working directory");
1177
1178        // The written manifest is tested in [crate::package_manifest::host_tests]
1179    }
1180
1181    #[test]
1182    fn test_builder_add_subpackages() {
1183        let outdir = TempDir::new().unwrap();
1184        let metafar_path = outdir.path().join("meta.far");
1185
1186        let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1187
1188        let pkg1_url = "pkg1".parse::<RelativePackageUrl>().unwrap();
1189        let pkg1_hash = Hash::from([0; fuchsia_hash::HASH_SIZE]);
1190        let pkg1_package_manifest_path = outdir.path().join("path1/package_manifest.json");
1191
1192        let pkg2_url = "pkg2".parse::<RelativePackageUrl>().unwrap();
1193        let pkg2_hash = Hash::from([1; fuchsia_hash::HASH_SIZE]);
1194        let pkg2_package_manifest_path = outdir.path().join("path2/package_manifest.json");
1195
1196        builder.add_subpackage(&pkg1_url, pkg1_hash, pkg1_package_manifest_path).unwrap();
1197        builder.add_subpackage(&pkg2_url, pkg2_hash, pkg2_package_manifest_path).unwrap();
1198
1199        // Build the package.
1200        builder.build(&outdir, &metafar_path).unwrap();
1201
1202        // Validate that the metafar contains the subpackages.
1203        let mut metafar = std::fs::File::open(metafar_path).unwrap();
1204        let mut far_reader = fuchsia_archive::Utf8Reader::new(&mut metafar).unwrap();
1205        let far_file_data = far_reader.read_file(MetaSubpackages::PATH).unwrap();
1206
1207        assert_eq!(
1208            MetaSubpackages::deserialize(Cursor::new(&far_file_data)).unwrap(),
1209            MetaSubpackages::from_iter([(pkg1_url, pkg1_hash), (pkg2_url, pkg2_hash)])
1210        );
1211    }
1212
1213    #[test]
1214    fn test_builder_rejects_subpackages_collisions() {
1215        let url = "pkg".parse::<RelativePackageUrl>().unwrap();
1216        let hash1 = Hash::from([0; fuchsia_hash::HASH_SIZE]);
1217        let package_manifest_path1 = PathBuf::from("path1/package_manifest.json");
1218        let hash2 = Hash::from([0; fuchsia_hash::HASH_SIZE]);
1219        let package_manifest_path2 = PathBuf::from("path2/package_manifest.json");
1220
1221        let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1222        builder.add_subpackage(&url, hash1, package_manifest_path1).unwrap();
1223        assert!(builder.add_subpackage(&url, hash2, package_manifest_path2).is_err());
1224    }
1225
1226    #[test]
1227    fn test_builder_allows_overwrite_subpackages_when_flag_set() {
1228        let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1229        builder.overwrite_subpackages(true);
1230
1231        let url = "pkg".parse::<RelativePackageUrl>().unwrap();
1232        let package_hash: fuchsia_hash::GenericDigest<fuchsia_hash::FuchsiaMerkleMarker> =
1233            Hash::from([0; fuchsia_hash::HASH_SIZE]);
1234        let package_manifest_path = PathBuf::from("path/package_manifest.json");
1235
1236        let package_hash2: fuchsia_hash::GenericDigest<fuchsia_hash::FuchsiaMerkleMarker> =
1237            Hash::from([0; fuchsia_hash::HASH_SIZE]);
1238        let package_manifest_path2 = PathBuf::from("path2/package_manifest.json");
1239
1240        builder.add_subpackage(&url, package_hash, package_manifest_path).unwrap();
1241        assert!(
1242            builder.add_subpackage(&url, package_hash2, package_manifest_path2.clone()).is_ok()
1243        );
1244        assert!(builder.subpackages.get(&url).unwrap().0 == package_hash2);
1245        assert!(builder.subpackages.get(&url).unwrap().1 == package_manifest_path2);
1246    }
1247}