Skip to main content

fuchsia_pkg/
package_manifest.rs

1// Copyright 2019 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::{
6    BlobEntry, MetaContents, MetaPackage, MetaPackageError, MetaSubpackages, PackageArchiveBuilder,
7    PackageManifestError, PackageName, PackagePath, PackageVariant,
8};
9use anyhow::{Context, Result};
10use camino::Utf8Path;
11use delivery_blob::DeliveryBlobType;
12use fuchsia_archive::Utf8Reader;
13use fuchsia_hash::Hash;
14use fuchsia_merkle::root_from_slice;
15use fuchsia_url::RepositoryUrl;
16use fuchsia_url::fuchsia_pkg::UnpinnedAbsolutePackageUrl;
17use serde::{Deserialize, Serialize};
18use std::collections::{BTreeMap, HashMap, HashSet};
19use std::fs::{self, File, create_dir_all};
20use std::io::{self, BufReader, BufWriter, Read, Seek, SeekFrom, Write};
21use std::path::Path;
22use std::str;
23use tempfile_ext::NamedTempFileExt as _;
24use utf8_path::{path_relative_from_file, resolve_path_from_file};
25use version_history::AbiRevision;
26
27#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
28#[serde(transparent)]
29pub struct PackageManifest(VersionedPackageManifest);
30
31impl PackageManifest {
32    /// Blob path used in package manifests to indicate the `meta.far`.
33    pub const META_FAR_BLOB_PATH: &'static str = "meta/";
34
35    /// Return a reference vector of blobs in this PackageManifest.
36    ///
37    /// NB: Does not include blobs referenced by possible subpackages.
38    pub fn blobs(&self) -> &[BlobInfo] {
39        match &self.0 {
40            VersionedPackageManifest::Version1(manifest) => &manifest.blobs,
41        }
42    }
43
44    /// Returns a reference vector of SubpackageInfo in this PackageManifest.
45    pub fn subpackages(&self) -> &[SubpackageInfo] {
46        match &self.0 {
47            VersionedPackageManifest::Version1(manifest) => &manifest.subpackages,
48        }
49    }
50
51    /// Returns a vector of the blobs in the current PackageManifest.
52    pub fn into_blobs(self) -> Vec<BlobInfo> {
53        match self.0 {
54            VersionedPackageManifest::Version1(manifest) => manifest.blobs,
55        }
56    }
57
58    /// Returns a tuple of the current PackageManifest's blobs and subpackages.
59    /// `blobs` does not include blobs referenced by the subpackages.
60    pub fn into_blobs_and_subpackages(self) -> (Vec<BlobInfo>, Vec<SubpackageInfo>) {
61        match self.0 {
62            VersionedPackageManifest::Version1(manifest) => (manifest.blobs, manifest.subpackages),
63        }
64    }
65
66    /// Returns the name from the PackageMetadata.
67    pub fn name(&self) -> &PackageName {
68        match &self.0 {
69            VersionedPackageManifest::Version1(manifest) => &manifest.package.name,
70        }
71    }
72
73    /// Sets the name of the package.
74    pub fn set_name(&mut self, name: PackageName) {
75        match &mut self.0 {
76            VersionedPackageManifest::Version1(manifest) => {
77                manifest.package.name = name;
78            }
79        }
80    }
81
82    /// Write a package archive into the `out` file. The source files are relative to the `root_dir`
83    /// directory.
84    pub async fn archive(
85        self,
86        root_dir: impl AsRef<Path>,
87        out: impl Write,
88    ) -> Result<(), PackageManifestError> {
89        let root_dir = root_dir.as_ref();
90
91        let (meta_far_blob_info, all_blobs) = Self::package_and_subpackage_blobs(self)?;
92
93        let source_path = root_dir.join(&meta_far_blob_info.source_path);
94        let mut meta_far_blob = File::open(&source_path).map_err(|err| {
95            PackageManifestError::IoErrorWithPath { cause: err, path: source_path }
96        })?;
97        meta_far_blob.seek(SeekFrom::Start(0))?;
98        let mut archive_builder = PackageArchiveBuilder::with_meta_far(
99            meta_far_blob.metadata()?.len(),
100            Box::new(meta_far_blob),
101        );
102
103        for (_merkle_key, blob_info) in all_blobs.iter() {
104            let source_path = root_dir.join(&blob_info.source_path);
105
106            let blob_file = File::open(&source_path).map_err(|err| {
107                PackageManifestError::IoErrorWithPath { cause: err, path: source_path }
108            })?;
109            archive_builder.add_blob(
110                blob_info.merkle,
111                blob_file.metadata()?.len(),
112                Box::new(blob_file),
113            );
114        }
115
116        archive_builder.build(out)?;
117        Ok(())
118    }
119
120    /// Returns a `PackagePath` formatted from the metadata of the PackageManifest.
121    pub fn package_path(&self) -> PackagePath {
122        match &self.0 {
123            VersionedPackageManifest::Version1(manifest) => PackagePath::from_name_and_variant(
124                manifest.package.name.to_owned(),
125                manifest.package.version.to_owned(),
126            ),
127        }
128    }
129
130    pub fn repository(&self) -> Option<&str> {
131        match &self.0 {
132            VersionedPackageManifest::Version1(manifest) => manifest.repository.as_deref(),
133        }
134    }
135
136    pub fn set_repository(&mut self, repository: Option<String>) {
137        match &mut self.0 {
138            VersionedPackageManifest::Version1(manifest) => {
139                manifest.repository = repository;
140            }
141        }
142    }
143
144    pub fn package_url(&self) -> Result<Option<UnpinnedAbsolutePackageUrl>> {
145        if let Some(url) = self.repository() {
146            let repo = RepositoryUrl::parse_host(url.to_string())?;
147            return Ok(Some(UnpinnedAbsolutePackageUrl::new(repo, self.name().clone(), None)));
148        };
149        Ok(None)
150    }
151
152    /// Returns the merkle root of the meta.far.
153    ///
154    /// # Panics
155    ///
156    /// Panics if the PackageManifest is missing a "meta/" entry
157    pub fn hash(&self) -> Hash {
158        self.blobs().iter().find(|blob| blob.path == Self::META_FAR_BLOB_PATH).unwrap().merkle
159    }
160
161    pub fn abi_revision(&self) -> Option<AbiRevision> {
162        match &self.0 {
163            VersionedPackageManifest::Version1(manifest) => manifest.abi_revision,
164        }
165    }
166
167    pub fn delivery_blob_type(&self) -> Option<DeliveryBlobType> {
168        match &self.0 {
169            VersionedPackageManifest::Version1(manifest) => manifest.delivery_blob_type,
170        }
171    }
172
173    /// Create a `PackageManifest` and populate a manifest directory given a blobs directory and the
174    /// top level meta.far hash.
175    ///
176    /// The `blobs_dir_root` directory must contain all the package blobs either uncompressed in
177    /// root, or delivery blobs in a sub directory.
178    ///
179    /// The `out_manifest_dir` will be a flat file populated with JSON representations of
180    /// PackageManifests corresponding to the subpackages.
181    pub fn from_blobs_dir(
182        blobs_dir_root: &Path,
183        delivery_blob_type: Option<DeliveryBlobType>,
184        meta_far_hash: Hash,
185        out_manifest_dir: &Path,
186    ) -> Result<Self, PackageManifestError> {
187        let blobs_dir = if let Some(delivery_blob_type) = delivery_blob_type {
188            blobs_dir_root.join(u32::from(delivery_blob_type).to_string())
189        } else {
190            blobs_dir_root.to_path_buf()
191        };
192        let meta_far_path = blobs_dir.join(meta_far_hash.to_string());
193        let (meta_far_blob, meta_far_size) = if delivery_blob_type.is_some() {
194            let meta_far_delivery_blob = std::fs::read(&meta_far_path).map_err(|e| {
195                PackageManifestError::IoErrorWithPath { cause: e, path: meta_far_path.clone() }
196            })?;
197            let meta_far_blob =
198                delivery_blob::decompress(&meta_far_delivery_blob).map_err(|e| {
199                    PackageManifestError::DecompressDeliveryBlob {
200                        cause: e,
201                        path: meta_far_path.clone(),
202                    }
203                })?;
204            let meta_far_size = meta_far_blob.len().try_into().expect("meta.far size fits in u64");
205            (meta_far_blob, meta_far_size)
206        } else {
207            let mut meta_far_file = File::open(&meta_far_path).map_err(|e| {
208                PackageManifestError::IoErrorWithPath { cause: e, path: meta_far_path.clone() }
209            })?;
210
211            let mut meta_far_blob = vec![];
212            meta_far_file.read_to_end(&mut meta_far_blob)?;
213            (meta_far_blob, meta_far_file.metadata()?.len())
214        };
215        let mut meta_far = fuchsia_archive::Utf8Reader::new(std::io::Cursor::new(meta_far_blob))?;
216
217        let meta_contents = meta_far.read_file(MetaContents::PATH)?;
218        let meta_contents = MetaContents::deserialize(meta_contents.as_slice())?.into_contents();
219
220        // The meta contents are unordered, so sort them to keep things consistent.
221        let meta_contents = meta_contents.into_iter().collect::<BTreeMap<_, _>>();
222
223        let meta_package = meta_far.read_file(MetaPackage::PATH)?;
224        let meta_package = MetaPackage::deserialize(meta_package.as_slice())?;
225
226        let abi_revision = match meta_far.read_file(AbiRevision::PATH) {
227            Ok(bytes) => Some(AbiRevision::from_bytes(
228                bytes.as_slice().try_into().map_err(crate::errors::AbiRevisionError::from)?,
229            )),
230            Err(fuchsia_archive::Error::PathNotPresent(_)) => {
231                return Err(PackageManifestError::AbiRevision(
232                    crate::errors::AbiRevisionError::Missing,
233                ));
234            }
235            Err(e) => return Err(e.into()),
236        };
237
238        let meta_subpackages = match meta_far.read_file(MetaSubpackages::PATH) {
239            Ok(meta_subpackages) => {
240                let meta_subpackages =
241                    MetaSubpackages::deserialize(meta_subpackages.as_slice())?.into_subpackages();
242
243                // The meta subpackages are unordered, so sort them to keep things consistent.
244                meta_subpackages.into_iter().collect::<BTreeMap<_, _>>()
245            }
246            Err(fuchsia_archive::Error::PathNotPresent(_)) => BTreeMap::new(),
247            Err(e) => return Err(e.into()),
248        };
249
250        let mut sub_packages = vec![];
251        for (name, hash) in meta_subpackages {
252            let sub_package_manifest =
253                Self::from_blobs_dir(blobs_dir_root, delivery_blob_type, hash, out_manifest_dir)?;
254
255            let source_pathbuf = out_manifest_dir.join(format!("{}_package_manifest.json", &hash));
256            let source_path = source_pathbuf.as_path();
257
258            let relative_path = Utf8Path::from_path(source_path).unwrap();
259
260            let _ = sub_package_manifest
261                .write_with_relative_paths(relative_path)
262                .map_err(PackageManifestError::RelativeWrite)?;
263            sub_packages.push((name, hash, source_path.to_owned()));
264        }
265
266        // Build the PackageManifest of this package.
267        let mut builder = PackageManifestBuilder::new(meta_package)
268            .delivery_blob_type(delivery_blob_type)
269            .abi_revision(abi_revision);
270
271        // Add the meta.far blob. We add this first since some scripts assume the first entry is the
272        // meta.far entry.
273        builder = builder.add_blob(BlobInfo {
274            source_path: meta_far_path.into_os_string().into_string().map_err(|source_path| {
275                PackageManifestError::InvalidBlobPath {
276                    merkle: meta_far_hash,
277                    source_path: source_path.into(),
278                }
279            })?,
280            path: Self::META_FAR_BLOB_PATH.into(),
281            merkle: meta_far_hash,
282            size: meta_far_size,
283        });
284
285        for (blob_path, merkle) in meta_contents.into_iter() {
286            let source_path = blobs_dir.join(merkle.to_string());
287
288            if !source_path.exists() {
289                return Err(PackageManifestError::IoErrorWithPath {
290                    cause: io::ErrorKind::NotFound.into(),
291                    path: source_path,
292                });
293            }
294
295            let size = if delivery_blob_type.is_some() {
296                let file = File::open(&source_path)?;
297                delivery_blob::decompressed_size_from_reader(file).map_err(|e| {
298                    PackageManifestError::DecompressDeliveryBlob {
299                        cause: e,
300                        path: source_path.clone(),
301                    }
302                })?
303            } else {
304                fs::metadata(&source_path)?.len()
305            };
306
307            builder = builder.add_blob(BlobInfo {
308                source_path: source_path.into_os_string().into_string().map_err(|source_path| {
309                    PackageManifestError::InvalidBlobPath {
310                        merkle,
311                        source_path: source_path.into(),
312                    }
313                })?,
314                path: blob_path,
315                merkle,
316                size,
317            });
318        }
319
320        for (name, merkle, path) in sub_packages {
321            builder = builder.add_subpackage(SubpackageInfo {
322                manifest_path: path.to_str().expect("better work").to_string(),
323                name: name.to_string(),
324                merkle,
325            });
326        }
327
328        Ok(builder.build())
329    }
330
331    /// Extract the package blobs from `archive_path` into the `blobs_dir` directory and
332    /// extracts all the JSON representations of the subpackages' PackageManifests and
333    /// top level PackageManifest into `out_manifest_dir`.
334    ///
335    /// Returns an in-memory `PackageManifest` for these files.
336    pub fn from_archive(
337        archive_path: &Path,
338        blobs_dir: &Path,
339        out_manifest_dir: &Path,
340    ) -> Result<Self, PackageManifestError> {
341        let archive_file = File::open(archive_path)?;
342        let mut archive_reader = Utf8Reader::new(&archive_file)?;
343
344        let far_paths =
345            archive_reader.list().map(|entry| entry.path().to_owned()).collect::<Vec<_>>();
346
347        for path in far_paths {
348            let blob_path = blobs_dir.join(&path);
349
350            if &path != "meta.far" && !blob_path.as_path().exists() {
351                let contents = archive_reader.read_file(&path)?;
352                let mut tmp = tempfile::NamedTempFile::new_in(blobs_dir)?;
353                tmp.write_all(&contents)?;
354                tmp.persist_if_changed(&blob_path)
355                    .map_err(|err| PackageManifestError::Persist { cause: err, path: blob_path })?;
356            }
357        }
358
359        let meta_far = archive_reader.read_file("meta.far")?;
360        let meta_far_hash = root_from_slice(&meta_far);
361
362        let meta_far_path = blobs_dir.join(meta_far_hash.to_string());
363        let mut tmp = tempfile::NamedTempFile::new_in(blobs_dir)?;
364        tmp.write_all(&meta_far)?;
365        tmp.persist_if_changed(&meta_far_path)
366            .map_err(|err| PackageManifestError::Persist { cause: err, path: meta_far_path })?;
367
368        PackageManifest::from_blobs_dir(blobs_dir, None, meta_far_hash, out_manifest_dir)
369    }
370
371    /// Verify that all blob and subpackage paths are valid and return the PackageManifest.
372    pub(crate) fn from_parts(
373        meta_package: MetaPackage,
374        repository: Option<String>,
375        mut package_blobs: BTreeMap<String, BlobEntry>,
376        package_subpackages: Vec<crate::SubpackageEntry>,
377        abi_revision: AbiRevision,
378    ) -> Result<Self, PackageManifestError> {
379        let mut blobs = Vec::with_capacity(package_blobs.len());
380
381        let mut push_blob = |blob_path, blob_entry: BlobEntry| {
382            let source_path = blob_entry.source_path;
383
384            blobs.push(BlobInfo {
385                source_path: source_path.into_os_string().into_string().map_err(|source_path| {
386                    PackageManifestError::InvalidBlobPath {
387                        merkle: blob_entry.hash,
388                        source_path: source_path.into(),
389                    }
390                })?,
391                path: blob_path,
392                merkle: blob_entry.hash,
393                size: blob_entry.size,
394            });
395
396            Ok::<(), PackageManifestError>(())
397        };
398
399        // Add the meta.far blob. We add this first since some scripts assume the first entry is the
400        // meta.far entry.
401        if let Some((blob_path, blob_entry)) = package_blobs.remove_entry(Self::META_FAR_BLOB_PATH)
402        {
403            push_blob(blob_path, blob_entry)?;
404        }
405
406        for (blob_path, blob_entry) in package_blobs {
407            push_blob(blob_path, blob_entry)?;
408        }
409
410        let mut subpackages = Vec::with_capacity(package_subpackages.len());
411
412        for subpackage in package_subpackages {
413            subpackages.push(SubpackageInfo {
414                manifest_path: subpackage
415                    .package_manifest_path
416                    .into_os_string()
417                    .into_string()
418                    .map_err(|package_manifest_path| {
419                        PackageManifestError::InvalidSubpackagePath {
420                            merkle: subpackage.merkle,
421                            path: package_manifest_path.into(),
422                        }
423                    })?,
424                name: subpackage.name.to_string(),
425                merkle: subpackage.merkle,
426            });
427        }
428
429        let manifest_v1 = PackageManifestV1 {
430            package: PackageMetadata {
431                name: meta_package.name().to_owned(),
432                version: meta_package.variant().to_owned(),
433            },
434            blobs,
435            repository,
436            blob_sources_relative: Default::default(),
437            subpackages,
438            delivery_blob_type: None,
439            abi_revision: Some(abi_revision),
440        };
441        Ok(PackageManifest(VersionedPackageManifest::Version1(manifest_v1)))
442    }
443
444    pub fn try_load_from(manifest_path: impl AsRef<Utf8Path>) -> anyhow::Result<Self> {
445        fn inner(manifest_path: &Utf8Path) -> anyhow::Result<PackageManifest> {
446            let file = File::open(manifest_path)
447                .with_context(|| format!("Opening package manifest: {manifest_path}"))?;
448
449            PackageManifest::from_reader(manifest_path, BufReader::new(file))
450        }
451        inner(manifest_path.as_ref())
452    }
453
454    pub fn from_reader(
455        manifest_path: impl AsRef<Utf8Path>,
456        reader: impl std::io::Read,
457    ) -> anyhow::Result<Self> {
458        fn inner(
459            manifest_path: &Utf8Path,
460            reader: impl std::io::Read,
461        ) -> anyhow::Result<PackageManifest> {
462            let versioned: VersionedPackageManifest = serde_json::from_reader(reader)?;
463
464            let versioned = match versioned {
465                VersionedPackageManifest::Version1(manifest) => VersionedPackageManifest::Version1(
466                    manifest.resolve_source_paths(manifest_path)?,
467                ),
468            };
469
470            Ok(PackageManifest(versioned))
471        }
472        inner(manifest_path.as_ref(), reader)
473    }
474
475    fn package_and_subpackage_blobs_impl(
476        contents: &mut HashMap<Hash, BlobInfo>,
477        visited_subpackages: &mut HashSet<Hash>,
478        package_manifest: Self,
479    ) -> Result<(), PackageManifestError> {
480        let (blobs, subpackages) = package_manifest.into_blobs_and_subpackages();
481        for blob in blobs {
482            contents.insert(blob.merkle, blob);
483        }
484
485        for sp in subpackages {
486            let key = sp.merkle;
487
488            if visited_subpackages.insert(key) {
489                let package_manifest = Self::try_load_from(&sp.manifest_path).map_err(|_| {
490                    PackageManifestError::InvalidSubpackagePath {
491                        merkle: sp.merkle,
492                        path: sp.manifest_path.into(),
493                    }
494                })?;
495
496                Self::package_and_subpackage_blobs_impl(
497                    contents,
498                    visited_subpackages,
499                    package_manifest,
500                )?;
501            }
502        }
503        Ok(())
504    }
505
506    /// Returns a tuple of a BlobInfo corresponding to the top level meta.far blob and a HashMap
507    /// containing all of the blobs (both content blobs and meta.fars) from the transitive closure
508    /// of the subpackages. The HashMap will not contain the BlobInfo of the top level meta.far.
509    pub fn package_and_subpackage_blobs(
510        self,
511    ) -> Result<(BlobInfo, HashMap<Hash, BlobInfo>), PackageManifestError> {
512        let mut contents = HashMap::new();
513        let mut visited_subpackages = HashSet::new();
514
515        Self::package_and_subpackage_blobs_impl(
516            &mut contents,
517            &mut visited_subpackages,
518            self.clone(),
519        )?;
520
521        let blobs = self.into_blobs();
522        for blob in blobs {
523            if blob.path == Self::META_FAR_BLOB_PATH && contents.remove(&blob.merkle).is_some() {
524                return Ok((blob, contents));
525            }
526        }
527        Err(PackageManifestError::MetaPackage(MetaPackageError::MetaPackageMissing))
528    }
529
530    pub fn write_with_relative_paths(self, path: impl AsRef<Utf8Path>) -> anyhow::Result<Self> {
531        fn inner(this: PackageManifest, path: &Utf8Path) -> anyhow::Result<PackageManifest> {
532            let manifest = match this.0 {
533                VersionedPackageManifest::Version1(manifest) => PackageManifest(
534                    VersionedPackageManifest::Version1(manifest.with_relative_paths(path)?),
535                ),
536            };
537            let () = manifest.write(path)?;
538            Ok(manifest)
539        }
540        inner(self, path.as_ref())
541    }
542
543    pub fn write(&self, path: impl AsRef<Utf8Path>) -> anyhow::Result<()> {
544        fn inner(this: &PackageManifest, path: &Utf8Path) -> anyhow::Result<()> {
545            let mut tmp = if let Some(parent) = path.parent() {
546                create_dir_all(parent)?;
547                tempfile::NamedTempFile::new_in(parent)?
548            } else {
549                tempfile::NamedTempFile::new()?
550            };
551
552            serde_json::to_writer_pretty(BufWriter::new(&mut tmp), this)?;
553            tmp.persist_if_changed(path)
554                .with_context(|| format!("failed to persist package manifest: {path}"))?;
555
556            Ok(())
557        }
558        inner(self, path.as_ref())
559    }
560}
561
562pub struct PackageManifestBuilder {
563    manifest: PackageManifestV1,
564}
565
566impl PackageManifestBuilder {
567    pub fn new(meta_package: MetaPackage) -> Self {
568        Self {
569            manifest: PackageManifestV1 {
570                package: PackageMetadata {
571                    name: meta_package.name().to_owned(),
572                    version: meta_package.variant().to_owned(),
573                },
574                blobs: vec![],
575                repository: None,
576                blob_sources_relative: Default::default(),
577                subpackages: vec![],
578                delivery_blob_type: None,
579                abi_revision: None,
580            },
581        }
582    }
583
584    pub fn repository(mut self, repository: impl Into<String>) -> Self {
585        self.manifest.repository = Some(repository.into());
586        self
587    }
588
589    pub fn delivery_blob_type(mut self, delivery_blob_type: Option<DeliveryBlobType>) -> Self {
590        self.manifest.delivery_blob_type = delivery_blob_type;
591        self
592    }
593
594    pub fn add_blob(mut self, info: BlobInfo) -> Self {
595        self.manifest.blobs.push(info);
596        self
597    }
598
599    pub fn add_subpackage(mut self, info: SubpackageInfo) -> Self {
600        self.manifest.subpackages.push(info);
601        self
602    }
603
604    pub fn abi_revision(mut self, abi_revision: Option<AbiRevision>) -> Self {
605        self.manifest.abi_revision = abi_revision;
606        self
607    }
608
609    pub fn build(self) -> PackageManifest {
610        PackageManifest(VersionedPackageManifest::Version1(self.manifest))
611    }
612}
613
614#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
615#[serde(tag = "version")]
616enum VersionedPackageManifest {
617    #[serde(rename = "1")]
618    Version1(PackageManifestV1),
619}
620
621#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
622struct PackageManifestV1 {
623    #[serde(default, skip_serializing_if = "Option::is_none")]
624    repository: Option<String>,
625    package: PackageMetadata,
626    blobs: Vec<BlobInfo>,
627
628    /// Are the blob source_paths relative to the working dir (default, as made
629    /// by 'pm') or the file containing the serialized manifest (new, portable,
630    /// behavior)
631    #[serde(default, skip_serializing_if = "RelativeTo::is_default")]
632    // TODO(https://fxbug.dev/42066050): rename this to `paths_relative` since it applies
633    // to both blobs and subpackages. (I'd change it now, but it's encoded in
634    // JSON files so we may need a soft transition to support both at first.)
635    blob_sources_relative: RelativeTo,
636
637    #[serde(default, skip_serializing_if = "Vec::is_empty")]
638    subpackages: Vec<SubpackageInfo>,
639    /// If not None, the `source_path` of the `blobs` are delivery blobs of the given type instead of
640    /// uncompressed blobs.
641    #[serde(default, skip_serializing_if = "Option::is_none")]
642    pub delivery_blob_type: Option<DeliveryBlobType>,
643    #[serde(default, skip_serializing_if = "Option::is_none")]
644    abi_revision: Option<AbiRevision>,
645}
646
647impl PackageManifestV1 {
648    pub fn with_relative_paths(
649        self,
650        manifest_path: impl AsRef<Utf8Path>,
651    ) -> anyhow::Result<PackageManifestV1> {
652        fn inner(
653            this: PackageManifestV1,
654            manifest_path: &Utf8Path,
655        ) -> anyhow::Result<PackageManifestV1> {
656            let manifest = if let RelativeTo::WorkingDir = &this.blob_sources_relative {
657                // manifest contains working-dir relative source paths, make
658                // them relative to the file, instead.
659                let blobs = this
660                    .blobs
661                    .into_iter()
662                    .map(|blob| relativize_blob_source_path(blob, manifest_path))
663                    .collect::<anyhow::Result<_>>()?;
664                let subpackages = this
665                    .subpackages
666                    .into_iter()
667                    .map(|subpackage| {
668                        relativize_subpackage_manifest_path(subpackage, manifest_path)
669                    })
670                    .collect::<anyhow::Result<_>>()?;
671                PackageManifestV1 {
672                    blobs,
673                    subpackages,
674                    blob_sources_relative: RelativeTo::File,
675                    ..this
676                }
677            } else {
678                this
679            };
680
681            Ok(manifest)
682        }
683        inner(self, manifest_path.as_ref())
684    }
685
686    pub fn resolve_source_paths(self, manifest_path: impl AsRef<Utf8Path>) -> anyhow::Result<Self> {
687        fn inner(
688            this: PackageManifestV1,
689            manifest_path: &Utf8Path,
690        ) -> anyhow::Result<PackageManifestV1> {
691            if let RelativeTo::File = &this.blob_sources_relative {
692                let blobs = this
693                    .blobs
694                    .into_iter()
695                    .map(|blob| resolve_blob_source_path(blob, manifest_path))
696                    .collect::<anyhow::Result<_>>()?;
697                let subpackages = this
698                    .subpackages
699                    .into_iter()
700                    .map(|subpackage| resolve_subpackage_manifest_path(subpackage, manifest_path))
701                    .collect::<anyhow::Result<_>>()?;
702                let blob_sources_relative = RelativeTo::WorkingDir;
703                Ok(PackageManifestV1 { blobs, subpackages, blob_sources_relative, ..this })
704            } else {
705                Ok(this)
706            }
707        }
708        inner(self, manifest_path.as_ref())
709    }
710}
711
712/// If the path is a relative path, what is it relative from?
713///
714/// If 'RelativeTo::WorkingDir', then the path is assumed to be relative to the
715/// working dir, and can be used directly as a path.
716///
717/// If 'RelativeTo::File', then the path is relative to the file that contained
718/// the path. To use the path, it must be resolved against the path of the
719/// file.
720#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
721pub enum RelativeTo {
722    #[serde(rename = "working_dir")]
723    #[default]
724    WorkingDir,
725    #[serde(rename = "file")]
726    File,
727}
728
729impl RelativeTo {
730    pub(crate) fn is_default(&self) -> bool {
731        matches!(self, RelativeTo::WorkingDir)
732    }
733}
734
735#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
736struct PackageMetadata {
737    name: PackageName,
738    version: PackageVariant,
739}
740
741#[derive(Clone, Debug, Eq, Deserialize, Serialize, PartialOrd, Ord)]
742pub struct BlobInfo {
743    /// Path to the blob file, could be a delivery blob or uncompressed blob depending on
744    /// `delivery_blob_type` in the manifest.
745    pub source_path: String,
746    /// The virtual path of the blob in the package.
747    pub path: String,
748    pub merkle: fuchsia_merkle::Hash,
749    /// Uncompressed size of the blob.
750    pub size: u64,
751}
752
753// Write a custom PartialEq so that we ignore `source_path`.
754// Blobs are identical if their destination path and merkle are the same.
755impl PartialEq for BlobInfo {
756    fn eq(&self, other: &Self) -> bool {
757        self.path == other.path && self.merkle == other.merkle
758    }
759}
760
761#[derive(Clone, Debug, Eq, Deserialize, Serialize)]
762pub struct SubpackageInfo {
763    /// Path to a PackageManifest for the subpackage.
764    pub manifest_path: String,
765
766    /// The package-relative name of this declared subpackage.
767    pub name: String,
768
769    /// The package hash (meta.far merkle) of the subpackage.
770    pub merkle: fuchsia_merkle::Hash,
771}
772
773// Write a custom PartialEq so that we ignore `manifest_path`.
774// Subpackages are identical if their name and merkle are the same.
775impl PartialEq for SubpackageInfo {
776    fn eq(&self, other: &Self) -> bool {
777        self.name == other.name && self.merkle == other.merkle
778    }
779}
780
781fn relativize_blob_source_path(
782    blob: BlobInfo,
783    manifest_path: &Utf8Path,
784) -> anyhow::Result<BlobInfo> {
785    let source_path = path_relative_from_file(blob.source_path, manifest_path)?;
786    let source_path = source_path.into_string();
787
788    Ok(BlobInfo { source_path, ..blob })
789}
790
791fn resolve_blob_source_path(blob: BlobInfo, manifest_path: &Utf8Path) -> anyhow::Result<BlobInfo> {
792    let source_path = resolve_path_from_file(&blob.source_path, manifest_path)
793        .with_context(|| format!("Resolving blob path: {}", blob.source_path))?
794        .into_string();
795    Ok(BlobInfo { source_path, ..blob })
796}
797
798fn relativize_subpackage_manifest_path(
799    subpackage: SubpackageInfo,
800    manifest_path: &Utf8Path,
801) -> anyhow::Result<SubpackageInfo> {
802    let manifest_path = path_relative_from_file(subpackage.manifest_path, manifest_path)?;
803    let manifest_path = manifest_path.into_string();
804
805    Ok(SubpackageInfo { manifest_path, ..subpackage })
806}
807
808fn resolve_subpackage_manifest_path(
809    subpackage: SubpackageInfo,
810    manifest_path: &Utf8Path,
811) -> anyhow::Result<SubpackageInfo> {
812    let manifest_path = resolve_path_from_file(&subpackage.manifest_path, manifest_path)
813        .with_context(|| {
814            format!("Resolving subpackage manifest path: {}", subpackage.manifest_path)
815        })?
816        .into_string();
817    Ok(SubpackageInfo { manifest_path, ..subpackage })
818}
819
820#[cfg(test)]
821mod tests {
822    use super::*;
823    use crate::path_to_string::PathToStringExt;
824    use crate::{BlobEntry, MetaPackage, PackageBuilder};
825    use assert_matches::assert_matches;
826    use camino::Utf8PathBuf;
827    use fuchsia_url::RelativePackageUrl;
828    use pretty_assertions::assert_eq;
829    use serde_json::{Value, json};
830    use tempfile::{NamedTempFile, TempDir};
831
832    const FAKE_ABI_REVISION: version_history::AbiRevision =
833        version_history::AbiRevision::from_u64(0x323dd69d73d957a7);
834
835    const HASH_0: Hash = Hash::from_array([0; fuchsia_hash::HASH_SIZE]);
836    const HASH_1: Hash = Hash::from_array([1; fuchsia_hash::HASH_SIZE]);
837    const HASH_2: Hash = Hash::from_array([2; fuchsia_hash::HASH_SIZE]);
838    const HASH_3: Hash = Hash::from_array([3; fuchsia_hash::HASH_SIZE]);
839    const HASH_4: Hash = Hash::from_array([4; fuchsia_hash::HASH_SIZE]);
840
841    pub struct TestEnv {
842        pub _temp: TempDir,
843        pub dir_path: Utf8PathBuf,
844        pub manifest_path: Utf8PathBuf,
845        pub subpackage_path: Utf8PathBuf,
846        pub data_dir: Utf8PathBuf,
847    }
848
849    impl TestEnv {
850        pub fn new() -> Self {
851            let temp = TempDir::new().unwrap();
852            let dir_path = Utf8Path::from_path(temp.path()).unwrap().to_path_buf();
853
854            let manifest_dir = dir_path.join("manifest_dir");
855            std::fs::create_dir_all(&manifest_dir).unwrap();
856
857            let subpackage_dir = dir_path.join("subpackage_manifests");
858            std::fs::create_dir_all(&subpackage_dir).unwrap();
859
860            let data_dir = dir_path.join("data_source");
861            std::fs::create_dir_all(&data_dir).unwrap();
862
863            TestEnv {
864                _temp: temp,
865                dir_path,
866                manifest_path: manifest_dir.join("package_manifest.json"),
867                subpackage_path: subpackage_dir.join(HASH_0.to_string()),
868                data_dir,
869            }
870        }
871    }
872
873    #[test]
874    fn test_version1_serialization() {
875        let manifest = PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
876            package: PackageMetadata {
877                name: "example".parse().unwrap(),
878                version: "0".parse().unwrap(),
879            },
880            blobs: vec![BlobInfo {
881                source_path: "../p1".into(),
882                path: "data/p1".into(),
883                merkle: HASH_0,
884                size: 1,
885            }],
886            subpackages: vec![],
887            repository: None,
888            blob_sources_relative: Default::default(),
889            delivery_blob_type: None,
890            abi_revision: None,
891        }));
892
893        assert_eq!(
894            serde_json::to_value(manifest).unwrap(),
895            json!(
896                {
897                    "version": "1",
898                    "package": {
899                        "name": "example",
900                        "version": "0"
901                    },
902                    "blobs": [
903                        {
904                            "source_path": "../p1",
905                            "path": "data/p1",
906                            "merkle": "0000000000000000000000000000000000000000000000000000000000000000",
907                            "size": 1
908                        },
909                    ]
910                }
911            )
912        );
913
914        let manifest = PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
915            package: PackageMetadata {
916                name: "example".parse().unwrap(),
917                version: "0".parse().unwrap(),
918            },
919            blobs: vec![BlobInfo {
920                source_path: "../p1".into(),
921                path: "data/p1".into(),
922                merkle: HASH_0,
923                size: 1,
924            }],
925            subpackages: vec![],
926            repository: Some("testrepository.org".into()),
927            blob_sources_relative: RelativeTo::File,
928            delivery_blob_type: None,
929            abi_revision: Some(FAKE_ABI_REVISION),
930        }));
931
932        assert_eq!(
933            serde_json::to_value(manifest).unwrap(),
934            json!(
935                {
936                    "version": "1",
937                    "repository": "testrepository.org",
938                    "package": {
939                        "name": "example",
940                        "version": "0"
941                    },
942                    "blobs": [
943                        {
944                            "source_path": "../p1",
945                            "path": "data/p1",
946                            "merkle": HASH_0,
947                            "size": 1
948                        },
949                    ],
950                    "blob_sources_relative": "file",
951                    "abi_revision": "0x323dd69d73d957a7",
952                }
953            )
954        );
955    }
956
957    #[test]
958    fn test_version1_deserialization() {
959        let manifest = serde_json::from_value::<VersionedPackageManifest>(json!(
960            {
961                "version": "1",
962                "repository": "testrepository.org",
963                "package": {
964                    "name": "example",
965                    "version": "0"
966                },
967                "blobs": [
968                    {
969                        "source_path": "../p1",
970                        "path": "data/p1",
971                        "merkle": HASH_0,
972                        "size": 1
973                    },
974                ]
975            }
976        ))
977        .expect("valid json");
978
979        assert_eq!(
980            manifest,
981            VersionedPackageManifest::Version1(PackageManifestV1 {
982                package: PackageMetadata {
983                    name: "example".parse().unwrap(),
984                    version: "0".parse().unwrap(),
985                },
986                blobs: vec![BlobInfo {
987                    source_path: "../p1".into(),
988                    path: "data/p1".into(),
989                    merkle: HASH_0,
990                    size: 1,
991                }],
992                subpackages: vec![],
993                repository: Some("testrepository.org".into()),
994                blob_sources_relative: Default::default(),
995                delivery_blob_type: None,
996                abi_revision: None,
997            })
998        );
999
1000        let manifest = serde_json::from_value::<VersionedPackageManifest>(json!(
1001            {
1002                "version": "1",
1003                "package": {
1004                    "name": "example",
1005                    "version": "0"
1006                },
1007                "blobs": [
1008                    {
1009                        "source_path": "../p1",
1010                        "path": "data/p1",
1011                        "merkle": HASH_0,
1012                        "size": 1
1013                    },
1014                ],
1015                "blob_sources_relative": "file",
1016                "abi_revision": "0x323dd69d73d957a7",
1017            }
1018        ))
1019        .expect("valid json");
1020
1021        assert_eq!(
1022            manifest,
1023            VersionedPackageManifest::Version1(PackageManifestV1 {
1024                package: PackageMetadata {
1025                    name: "example".parse().unwrap(),
1026                    version: "0".parse().unwrap(),
1027                },
1028                blobs: vec![BlobInfo {
1029                    source_path: "../p1".into(),
1030                    path: "data/p1".into(),
1031                    merkle: HASH_0,
1032                    size: 1,
1033                }],
1034                subpackages: vec![],
1035                repository: None,
1036                blob_sources_relative: RelativeTo::File,
1037                delivery_blob_type: None,
1038                abi_revision: Some(FAKE_ABI_REVISION),
1039            })
1040        )
1041    }
1042
1043    #[test]
1044    fn test_create_package_manifest_from_parts() {
1045        let meta_package = MetaPackage::from_name_and_variant_zero("package-name".parse().unwrap());
1046        let mut blobs = BTreeMap::new();
1047        blobs.insert(
1048            "bin/my_prog".to_string(),
1049            BlobEntry { source_path: "src/bin/my_prog".into(), hash: HASH_0, size: 1 },
1050        );
1051        let package_manifest =
1052            PackageManifest::from_parts(meta_package, None, blobs, vec![], FAKE_ABI_REVISION)
1053                .unwrap();
1054
1055        assert_eq!(&"package-name".parse::<PackageName>().unwrap(), package_manifest.name());
1056        assert_eq!(None, package_manifest.repository());
1057        assert_eq!(Some(FAKE_ABI_REVISION), package_manifest.abi_revision());
1058    }
1059
1060    #[test]
1061    fn test_from_blobs_dir() {
1062        let temp = TempDir::new().unwrap();
1063        let temp_dir = Utf8Path::from_path(temp.path()).unwrap();
1064
1065        let gen_dir = temp_dir.join("gen");
1066        std::fs::create_dir_all(&gen_dir).unwrap();
1067
1068        let blobs_dir = temp_dir.join("blobs/1");
1069        std::fs::create_dir_all(&blobs_dir).unwrap();
1070
1071        let manifests_dir = temp_dir.join("manifests");
1072        std::fs::create_dir_all(&manifests_dir).unwrap();
1073
1074        // Helper to write some content into a delivery blob.
1075        let write_blob = |contents| {
1076            let hash = fuchsia_merkle::root_from_slice(contents);
1077
1078            let path = blobs_dir.join(hash.to_string());
1079
1080            let blob_file = File::create(&path).unwrap();
1081            delivery_blob::generate_to(DeliveryBlobType::Type1, contents, &blob_file).unwrap();
1082
1083            (path, hash)
1084        };
1085
1086        // Create a package.
1087        let mut package_builder = PackageBuilder::new("package", FAKE_ABI_REVISION);
1088        let (file1_path, file1_hash) = write_blob(b"file 1");
1089        package_builder.add_contents_as_blob("file-1", b"file 1", &gen_dir).unwrap();
1090        let (file2_path, file2_hash) = write_blob(b"file 2");
1091        package_builder.add_contents_as_blob("file-2", b"file 2", &gen_dir).unwrap();
1092
1093        let gen_meta_far_path = temp_dir.join("meta.far");
1094        let _package_manifest = package_builder.build(&gen_dir, &gen_meta_far_path).unwrap();
1095
1096        // Compute the meta.far hash, and generate a delivery blob in the blobs/1/ directory.
1097        let meta_far_bytes = std::fs::read(&gen_meta_far_path).unwrap();
1098        let (meta_far_path, meta_far_hash) = write_blob(&meta_far_bytes);
1099
1100        // We should be able to create a manifest from the blob directory that matches the one
1101        // created by the builder.
1102        assert_eq!(
1103            PackageManifest::from_blobs_dir(
1104                blobs_dir.as_std_path().parent().unwrap(),
1105                Some(DeliveryBlobType::Type1),
1106                meta_far_hash,
1107                manifests_dir.as_std_path()
1108            )
1109            .unwrap(),
1110            PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
1111                package: PackageMetadata {
1112                    name: "package".parse().unwrap(),
1113                    version: PackageVariant::zero(),
1114                },
1115                blobs: vec![
1116                    BlobInfo {
1117                        source_path: meta_far_path.to_string(),
1118                        path: PackageManifest::META_FAR_BLOB_PATH.into(),
1119                        merkle: meta_far_hash,
1120                        size: 16384,
1121                    },
1122                    BlobInfo {
1123                        source_path: file1_path.to_string(),
1124                        path: "file-1".into(),
1125                        merkle: file1_hash,
1126                        size: 6,
1127                    },
1128                    BlobInfo {
1129                        source_path: file2_path.to_string(),
1130                        path: "file-2".into(),
1131                        merkle: file2_hash,
1132                        size: 6,
1133                    },
1134                ],
1135                subpackages: vec![],
1136                repository: None,
1137                blob_sources_relative: RelativeTo::WorkingDir,
1138                delivery_blob_type: Some(DeliveryBlobType::Type1),
1139                abi_revision: Some(FAKE_ABI_REVISION),
1140            }))
1141        );
1142    }
1143
1144    #[test]
1145    fn test_load_from_simple() {
1146        let env = TestEnv::new();
1147
1148        let expected_blob_source_path = &env.data_dir.join("p1").to_string();
1149
1150        let manifest = PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
1151            package: PackageMetadata {
1152                name: "example".parse().unwrap(),
1153                version: "0".parse().unwrap(),
1154            },
1155            blobs: vec![BlobInfo {
1156                source_path: expected_blob_source_path.clone(),
1157                path: "data/p1".into(),
1158                merkle: HASH_0,
1159                size: 1,
1160            }],
1161            subpackages: vec![SubpackageInfo {
1162                manifest_path: env.subpackage_path.to_string(),
1163                name: "subpackage0".into(),
1164                merkle: HASH_0,
1165            }],
1166            repository: None,
1167            blob_sources_relative: RelativeTo::WorkingDir,
1168            delivery_blob_type: None,
1169            abi_revision: None,
1170        }));
1171
1172        let manifest_file = File::create(&env.manifest_path).unwrap();
1173        serde_json::to_writer(manifest_file, &manifest).unwrap();
1174
1175        let loaded_manifest = PackageManifest::try_load_from(&env.manifest_path).unwrap();
1176        assert_eq!(loaded_manifest.name(), &"example".parse::<PackageName>().unwrap());
1177
1178        let (blobs, subpackages) = loaded_manifest.into_blobs_and_subpackages();
1179
1180        assert_eq!(blobs.len(), 1);
1181        let blob = blobs.first().unwrap();
1182        assert_eq!(blob.path, "data/p1");
1183
1184        assert_eq!(&blob.source_path, expected_blob_source_path);
1185
1186        assert_eq!(subpackages.len(), 1);
1187        let subpackage = subpackages.first().unwrap();
1188        assert_eq!(subpackage.name, "subpackage0");
1189        assert_eq!(&subpackage.manifest_path, &env.subpackage_path.to_string());
1190    }
1191
1192    #[test]
1193    fn test_load_from_resolves_source_paths() {
1194        let env = TestEnv::new();
1195
1196        let manifest = PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
1197            package: PackageMetadata {
1198                name: "example".parse().unwrap(),
1199                version: "0".parse().unwrap(),
1200            },
1201            blobs: vec![BlobInfo {
1202                source_path: "../data_source/p1".into(),
1203                path: "data/p1".into(),
1204                merkle: HASH_0,
1205                size: 1,
1206            }],
1207            subpackages: vec![SubpackageInfo {
1208                manifest_path: "../subpackage_manifests/0000000000000000000000000000000000000000000000000000000000000000".into(),
1209                name: "subpackage0".into(),
1210                merkle: HASH_0,
1211            }],
1212            repository: None,
1213            blob_sources_relative: RelativeTo::File,
1214            delivery_blob_type: None,
1215            abi_revision: None,
1216        }));
1217
1218        let manifest_file = File::create(&env.manifest_path).unwrap();
1219        serde_json::to_writer(manifest_file, &manifest).unwrap();
1220
1221        let loaded_manifest = PackageManifest::try_load_from(&env.manifest_path).unwrap();
1222        assert_eq!(
1223            loaded_manifest,
1224            PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
1225                package: PackageMetadata {
1226                    name: "example".parse::<PackageName>().unwrap(),
1227                    version: "0".parse().unwrap(),
1228                },
1229                blobs: vec![BlobInfo {
1230                    source_path: env.data_dir.join("p1").to_string(),
1231                    path: "data/p1".into(),
1232                    merkle: HASH_0,
1233                    size: 1,
1234                }],
1235                subpackages: vec![SubpackageInfo {
1236                    manifest_path: env.subpackage_path.to_string(),
1237                    name: "subpackage0".into(),
1238                    merkle: HASH_0,
1239                }],
1240                repository: None,
1241                blob_sources_relative: RelativeTo::WorkingDir,
1242                delivery_blob_type: None,
1243                abi_revision: None,
1244            }))
1245        );
1246    }
1247
1248    #[test]
1249    fn test_package_and_subpackage_blobs_meta_far_error() {
1250        let env = TestEnv::new();
1251
1252        let manifest = PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
1253            package: PackageMetadata {
1254                name: "example".parse().unwrap(),
1255                version: "0".parse().unwrap(),
1256            },
1257            blobs: vec![BlobInfo {
1258                source_path: "../data_source/p1".into(),
1259                path: "data/p1".into(),
1260                merkle: HASH_0,
1261                size: 1,
1262            }],
1263            subpackages: vec![SubpackageInfo {
1264                manifest_path: format!("../subpackage_manifests/{HASH_0}"),
1265                name: "subpackage0".into(),
1266                merkle: HASH_0,
1267            }],
1268            repository: None,
1269            blob_sources_relative: RelativeTo::File,
1270            delivery_blob_type: None,
1271            abi_revision: None,
1272        }));
1273
1274        let manifest_file = File::create(&env.manifest_path).unwrap();
1275        serde_json::to_writer(manifest_file, &manifest).unwrap();
1276
1277        let sub_manifest = PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
1278            package: PackageMetadata {
1279                name: "sub_manifest".parse().unwrap(),
1280                version: "0".parse().unwrap(),
1281            },
1282            blobs: vec![BlobInfo {
1283                source_path: "../data_source/p2".into(),
1284                path: "data/p2".into(),
1285                merkle: HASH_1,
1286                size: 1,
1287            }],
1288            subpackages: vec![],
1289            repository: None,
1290            blob_sources_relative: RelativeTo::File,
1291            delivery_blob_type: None,
1292            abi_revision: None,
1293        }));
1294
1295        let sub_manifest_file = File::create(&env.subpackage_path).unwrap();
1296        serde_json::to_writer(sub_manifest_file, &sub_manifest).unwrap();
1297
1298        let loaded_manifest = PackageManifest::try_load_from(&env.manifest_path).unwrap();
1299
1300        let result = loaded_manifest.package_and_subpackage_blobs();
1301        assert_matches!(
1302            result,
1303            Err(PackageManifestError::MetaPackage(MetaPackageError::MetaPackageMissing))
1304        );
1305    }
1306
1307    #[test]
1308    fn test_package_and_subpackage_blobs() {
1309        let env = TestEnv::new();
1310        let subsubpackage_dir = &env.dir_path.join("subsubpackage_manifests");
1311
1312        let expected_subsubpackage_manifest_path =
1313            subsubpackage_dir.join(HASH_0.to_string()).to_string();
1314
1315        std::fs::create_dir_all(subsubpackage_dir).unwrap();
1316
1317        let manifest = PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
1318            package: PackageMetadata {
1319                name: "example".parse().unwrap(),
1320                version: "0".parse().unwrap(),
1321            },
1322            blobs: vec![
1323                BlobInfo {
1324                    source_path: "../data_source/p0".into(),
1325                    path: "meta/".into(),
1326                    merkle: HASH_0,
1327                    size: 1,
1328                },
1329                BlobInfo {
1330                    source_path: "../data_source/p1".into(),
1331                    path: "data/p1".into(),
1332                    merkle: HASH_1,
1333                    size: 1,
1334                },
1335            ],
1336            subpackages: vec![SubpackageInfo {
1337                manifest_path: format!("../subpackage_manifests/{HASH_0}"),
1338                name: "subpackage0".into(),
1339                merkle: HASH_2,
1340            }],
1341            repository: None,
1342            blob_sources_relative: RelativeTo::File,
1343            delivery_blob_type: None,
1344            abi_revision: None,
1345        }));
1346
1347        let manifest_file = File::create(&env.manifest_path).unwrap();
1348        serde_json::to_writer(manifest_file, &manifest).unwrap();
1349
1350        let sub_manifest = PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
1351            package: PackageMetadata {
1352                name: "sub_manifest".parse().unwrap(),
1353                version: "0".parse().unwrap(),
1354            },
1355            blobs: vec![
1356                BlobInfo {
1357                    source_path: "../data_source/p2".into(),
1358                    path: "meta/".into(),
1359                    merkle: HASH_2,
1360                    size: 1,
1361                },
1362                BlobInfo {
1363                    source_path: "../data_source/p3".into(),
1364                    path: "data/p3".into(),
1365                    merkle: HASH_3,
1366                    size: 1,
1367                },
1368            ],
1369            subpackages: vec![SubpackageInfo {
1370                manifest_path: format!("../subsubpackage_manifests/{HASH_0}"),
1371                name: "subsubpackage0".into(),
1372                merkle: HASH_4,
1373            }],
1374            repository: None,
1375            blob_sources_relative: RelativeTo::File,
1376            delivery_blob_type: None,
1377            abi_revision: None,
1378        }));
1379
1380        let sub_manifest_file = File::create(&env.subpackage_path).unwrap();
1381        serde_json::to_writer(sub_manifest_file, &sub_manifest).unwrap();
1382
1383        let sub_sub_manifest =
1384            PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
1385                package: PackageMetadata {
1386                    name: "sub_sub_manifest".parse().unwrap(),
1387                    version: "0".parse().unwrap(),
1388                },
1389                blobs: vec![BlobInfo {
1390                    source_path: "../data_source/p4".into(),
1391                    path: "meta/".into(),
1392                    merkle: HASH_4,
1393                    size: 1,
1394                }],
1395                subpackages: vec![],
1396                repository: None,
1397                blob_sources_relative: RelativeTo::File,
1398                delivery_blob_type: None,
1399                abi_revision: None,
1400            }));
1401
1402        let sub_sub_manifest_file = File::create(expected_subsubpackage_manifest_path).unwrap();
1403        serde_json::to_writer(sub_sub_manifest_file, &sub_sub_manifest).unwrap();
1404
1405        let loaded_manifest = PackageManifest::try_load_from(&env.manifest_path).unwrap();
1406
1407        let (meta_far, contents) = loaded_manifest.package_and_subpackage_blobs().unwrap();
1408        assert_eq!(
1409            meta_far,
1410            BlobInfo {
1411                source_path: env.data_dir.join("p0").to_string(),
1412                path: "meta/".into(),
1413                merkle: HASH_0,
1414                size: 1,
1415            }
1416        );
1417
1418        // Does not contain top level meta.far
1419        assert_eq!(
1420            contents,
1421            HashMap::from([
1422                (
1423                    HASH_1,
1424                    BlobInfo {
1425                        source_path: env.data_dir.join("p1").to_string(),
1426                        path: "data/p1".into(),
1427                        merkle: HASH_1,
1428                        size: 1,
1429                    },
1430                ),
1431                (
1432                    HASH_2,
1433                    BlobInfo {
1434                        source_path: env.data_dir.join("p2").to_string(),
1435                        path: "meta/".into(),
1436                        merkle: HASH_2,
1437                        size: 1,
1438                    },
1439                ),
1440                (
1441                    HASH_3,
1442                    BlobInfo {
1443                        source_path: env.data_dir.join("p3").to_string(),
1444                        path: "data/p3".into(),
1445                        merkle: HASH_3,
1446                        size: 1,
1447                    },
1448                ),
1449                (
1450                    HASH_4,
1451                    BlobInfo {
1452                        source_path: env.data_dir.join("p4").to_string(),
1453                        path: "meta/".into(),
1454                        merkle: HASH_4,
1455                        size: 1,
1456                    },
1457                ),
1458            ]),
1459        );
1460    }
1461
1462    #[test]
1463    fn test_package_and_subpackage_blobs_deduped() {
1464        let env = TestEnv::new();
1465
1466        let expected_meta_far_source_path = env.data_dir.join("p0").to_string();
1467        let expected_blob_source_path_1 = env.data_dir.join("p1").to_string();
1468        let expected_blob_source_path_2 = env.data_dir.join("p2").to_string();
1469        let expected_blob_source_path_3 = env.data_dir.join("p3").to_string();
1470
1471        let manifest = PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
1472            package: PackageMetadata {
1473                name: "example".parse().unwrap(),
1474                version: "0".parse().unwrap(),
1475            },
1476            blobs: vec![
1477                BlobInfo {
1478                    source_path: "../data_source/p0".into(),
1479                    path: "meta/".into(),
1480                    merkle: HASH_0,
1481                    size: 1,
1482                },
1483                BlobInfo {
1484                    source_path: "../data_source/p1".into(),
1485                    path: "data/p1".into(),
1486                    merkle: HASH_1,
1487                    size: 1,
1488                },
1489            ],
1490            // Note that we're intentionally duplicating the subpackages with
1491            // separate names.
1492            subpackages: vec![
1493                SubpackageInfo {
1494                    manifest_path: format!("../subpackage_manifests/{HASH_0}"),
1495                    name: "subpackage0".into(),
1496                    merkle: HASH_2,
1497                },
1498                SubpackageInfo {
1499                    manifest_path: format!("../subpackage_manifests/{HASH_0}"),
1500                    name: "subpackage1".into(),
1501                    merkle: HASH_2,
1502                },
1503            ],
1504            repository: None,
1505            blob_sources_relative: RelativeTo::File,
1506            delivery_blob_type: None,
1507            abi_revision: None,
1508        }));
1509
1510        let manifest_file = File::create(&env.manifest_path).unwrap();
1511        serde_json::to_writer(manifest_file, &manifest).unwrap();
1512
1513        let sub_manifest = PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
1514            package: PackageMetadata {
1515                name: "sub_manifest".parse().unwrap(),
1516                version: "0".parse().unwrap(),
1517            },
1518            blobs: vec![
1519                BlobInfo {
1520                    source_path: "../data_source/p2".into(),
1521                    path: "meta/".into(),
1522                    merkle: HASH_2,
1523                    size: 1,
1524                },
1525                BlobInfo {
1526                    source_path: "../data_source/p3".into(),
1527                    path: "data/p3".into(),
1528                    merkle: HASH_3,
1529                    size: 1,
1530                },
1531            ],
1532            subpackages: vec![],
1533            repository: None,
1534            blob_sources_relative: RelativeTo::File,
1535            delivery_blob_type: None,
1536            abi_revision: None,
1537        }));
1538
1539        serde_json::to_writer(File::create(&env.subpackage_path).unwrap(), &sub_manifest).unwrap();
1540
1541        let loaded_manifest = PackageManifest::try_load_from(&env.manifest_path).unwrap();
1542
1543        let (meta_far, contents) = loaded_manifest.package_and_subpackage_blobs().unwrap();
1544        assert_eq!(
1545            meta_far,
1546            BlobInfo {
1547                source_path: expected_meta_far_source_path,
1548                path: "meta/".into(),
1549                merkle: HASH_0,
1550                size: 1,
1551            }
1552        );
1553
1554        // Does not contain meta.far
1555        assert_eq!(
1556            contents,
1557            HashMap::from([
1558                (
1559                    HASH_1,
1560                    BlobInfo {
1561                        source_path: expected_blob_source_path_1,
1562                        path: "data/p1".into(),
1563                        merkle: HASH_1,
1564                        size: 1,
1565                    }
1566                ),
1567                (
1568                    HASH_2,
1569                    BlobInfo {
1570                        source_path: expected_blob_source_path_2,
1571                        path: "meta/".into(),
1572                        merkle: HASH_2,
1573                        size: 1,
1574                    }
1575                ),
1576                (
1577                    HASH_3,
1578                    BlobInfo {
1579                        source_path: expected_blob_source_path_3,
1580                        path: "data/p3".into(),
1581                        merkle: HASH_3,
1582                        size: 1,
1583                    }
1584                ),
1585            ])
1586        );
1587    }
1588
1589    #[test]
1590    fn test_from_package_archive_bogus() {
1591        let temp = TempDir::new().unwrap();
1592        let temp_blobs_dir = temp.into_path();
1593
1594        let temp = TempDir::new().unwrap();
1595        let temp_manifest_dir = temp.into_path();
1596
1597        let temp_archive = TempDir::new().unwrap();
1598        let temp_archive_dir = temp_archive.path();
1599
1600        let result =
1601            PackageManifest::from_archive(temp_archive_dir, &temp_blobs_dir, &temp_manifest_dir);
1602        assert!(result.is_err())
1603    }
1604
1605    #[fuchsia_async::run_singlethreaded(test)]
1606    async fn test_from_package_manifest_archive_manifest() {
1607        let outdir = TempDir::new().unwrap();
1608
1609        let sub_outdir = outdir.path().join("subpackage_manifests");
1610        std::fs::create_dir(&sub_outdir).unwrap();
1611
1612        // Create a file to write to the sub package metafar
1613        let sub_far_source_file_path = NamedTempFile::new_in(&sub_outdir).unwrap();
1614        std::fs::write(&sub_far_source_file_path, "some data for sub far").unwrap();
1615
1616        // Create a file to include as a blob
1617        let sub_blob_source_file_path = sub_outdir.as_path().join("sub_blob_a");
1618        let blob_contents = "sub some data for blob";
1619        std::fs::write(&sub_blob_source_file_path, blob_contents).unwrap();
1620
1621        // Create a file to include as a blob
1622        let sub_blob_source_file_path2 = sub_outdir.as_path().join("sub_blob_b");
1623        let blob_contents = "sub some data for blob2";
1624        std::fs::write(&sub_blob_source_file_path2, blob_contents).unwrap();
1625
1626        // Create the sub builder
1627        let mut sub_builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1628        sub_builder
1629            .add_file_as_blob(
1630                "sub_blob_a",
1631                sub_blob_source_file_path.as_path().path_to_string().unwrap(),
1632            )
1633            .unwrap();
1634        sub_builder
1635            .add_file_as_blob(
1636                "sub_blob_b",
1637                sub_blob_source_file_path2.as_path().path_to_string().unwrap(),
1638            )
1639            .unwrap();
1640        sub_builder
1641            .add_file_to_far(
1642                "meta/some/file",
1643                sub_far_source_file_path.path().path_to_string().unwrap(),
1644            )
1645            .unwrap();
1646
1647        let sub_metafar_path = sub_outdir.as_path().join("meta.far");
1648        let sub_manifest = sub_builder.build(&sub_outdir, &sub_metafar_path).unwrap();
1649
1650        let manifest_outdir = TempDir::new().unwrap().into_path();
1651        let subpackage_manifest_path =
1652            manifest_outdir.join(format!("{}_package_manifest.json", sub_manifest.hash()));
1653
1654        serde_json::to_writer(
1655            std::fs::File::create(&subpackage_manifest_path).unwrap(),
1656            &sub_manifest,
1657        )
1658        .unwrap();
1659
1660        let subpackage_url = "subpackage_manifests".parse::<RelativePackageUrl>().unwrap();
1661
1662        let metafar_path = outdir.path().join("meta.far");
1663
1664        // Create a file to write to the package metafar
1665        let far_source_file_path = NamedTempFile::new_in(&outdir).unwrap();
1666        std::fs::write(&far_source_file_path, "some data for far").unwrap();
1667
1668        // Create a file to include as a blob
1669        let blob_source_file_path = outdir.path().join("blob_c");
1670        let blob_contents = "some data for blob";
1671        std::fs::write(&blob_source_file_path, blob_contents).unwrap();
1672
1673        // Create a file to include as a blob
1674        let blob_source_file_path2 = outdir.path().join("blob_d");
1675        let blob_contents = "some data for blob2";
1676        std::fs::write(&blob_source_file_path2, blob_contents).unwrap();
1677
1678        // Create the builder
1679        let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1680        builder
1681            .add_file_as_blob("blob_c", blob_source_file_path.as_path().path_to_string().unwrap())
1682            .unwrap();
1683        builder
1684            .add_file_as_blob("blob_d", blob_source_file_path2.as_path().path_to_string().unwrap())
1685            .unwrap();
1686        builder
1687            .add_file_to_far(
1688                "meta/some/file",
1689                far_source_file_path.path().path_to_string().unwrap(),
1690            )
1691            .unwrap();
1692        builder
1693            .add_subpackage(&subpackage_url, sub_manifest.hash(), subpackage_manifest_path)
1694            .unwrap();
1695
1696        // Build the package
1697        let manifest = builder.build(&outdir, &metafar_path).unwrap();
1698
1699        let archive_outdir = TempDir::new().unwrap();
1700        let archive_path = archive_outdir.path().join("test.far");
1701        let archive_file = File::create(archive_path.clone()).unwrap();
1702        manifest.clone().archive(&outdir, &archive_file).await.unwrap();
1703
1704        let blobs_outdir = TempDir::new().unwrap().into_path();
1705
1706        let manifest_2 =
1707            PackageManifest::from_archive(&archive_path, &blobs_outdir, &manifest_outdir).unwrap();
1708        assert_eq!(manifest_2.package_path(), manifest.package_path());
1709
1710        let (_blob1_info, all_blobs_1) = manifest.package_and_subpackage_blobs().unwrap();
1711        let (_blob2_info, mut all_blobs_2) = manifest_2.package_and_subpackage_blobs().unwrap();
1712
1713        for (merkle, blob1) in all_blobs_1 {
1714            let blob2 = all_blobs_2.remove_entry(&merkle).unwrap().1;
1715            assert_eq!(
1716                std::fs::read(&blob1.source_path).unwrap(),
1717                std::fs::read(&blob2.source_path).unwrap(),
1718            );
1719        }
1720
1721        assert!(all_blobs_2.is_empty());
1722    }
1723
1724    #[test]
1725    fn test_write_package_manifest_already_relative() {
1726        let temp = TempDir::new().unwrap();
1727        let temp_dir = Utf8Path::from_path(temp.path()).unwrap();
1728
1729        let data_dir = temp_dir.join("data_source");
1730        let subpackage_dir = temp_dir.join("subpackage_manifests");
1731        let manifest_dir = temp_dir.join("manifest_dir");
1732        let manifest_path = manifest_dir.join("package_manifest.json");
1733
1734        std::fs::create_dir_all(&data_dir).unwrap();
1735        std::fs::create_dir_all(&subpackage_dir).unwrap();
1736        std::fs::create_dir_all(&manifest_dir).unwrap();
1737
1738        let manifest = PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
1739            package: PackageMetadata {
1740                name: "example".parse().unwrap(),
1741                version: "0".parse().unwrap(),
1742            },
1743            blobs: vec![BlobInfo {
1744                source_path: "../data_source/p1".into(),
1745                path: "data/p1".into(),
1746                merkle: HASH_0,
1747                size: 1,
1748            }],
1749            subpackages: vec![SubpackageInfo {
1750                manifest_path: format!("../subpackage_manifests/{HASH_0}"),
1751                name: "subpackage0".into(),
1752                merkle: HASH_0,
1753            }],
1754            repository: None,
1755            blob_sources_relative: RelativeTo::File,
1756            delivery_blob_type: None,
1757            abi_revision: None,
1758        }));
1759
1760        let result_manifest = manifest.clone().write_with_relative_paths(&manifest_path).unwrap();
1761
1762        // The manifest should not have been changed in this case.
1763        assert_eq!(result_manifest, manifest);
1764
1765        let parsed_manifest: Value =
1766            serde_json::from_reader(File::open(manifest_path).unwrap()).unwrap();
1767        let object = parsed_manifest.as_object().unwrap();
1768        let version = object.get("version").unwrap();
1769
1770        let blobs_value = object.get("blobs").unwrap();
1771        let blobs = blobs_value.as_array().unwrap();
1772        let blob_value = blobs.first().unwrap();
1773        let blob = blob_value.as_object().unwrap();
1774        let source_path_value = blob.get("source_path").unwrap();
1775        let source_path = source_path_value.as_str().unwrap();
1776
1777        let subpackages_value = object.get("subpackages").unwrap();
1778        let subpackages = subpackages_value.as_array().unwrap();
1779        let subpackage_value = subpackages.first().unwrap();
1780        let subpackage = subpackage_value.as_object().unwrap();
1781        let subpackage_manifest_path_value = subpackage.get("manifest_path").unwrap();
1782        let subpackage_manifest_path = subpackage_manifest_path_value.as_str().unwrap();
1783
1784        assert_eq!(version, "1");
1785        assert_eq!(source_path, "../data_source/p1");
1786        assert_eq!(subpackage_manifest_path, format!("../subpackage_manifests/{HASH_0}"));
1787    }
1788
1789    #[test]
1790    fn test_write_package_manifest_making_paths_relative() {
1791        let temp = TempDir::new().unwrap();
1792        let temp_dir = Utf8Path::from_path(temp.path()).unwrap();
1793
1794        let data_dir = temp_dir.join("data_source");
1795        let subpackage_dir = temp_dir.join("subpackage_manifests");
1796        let manifest_dir = temp_dir.join("manifest_dir");
1797        let manifest_path = manifest_dir.join("package_manifest.json");
1798        let manifest_dir_2 = temp_dir.join("subdir").join("other_manifest_dir");
1799        let manifest_path_2 = manifest_dir_2.join("package_manifest.json");
1800        let blob_source_path = data_dir.join("p2").to_string();
1801        let subpackage_manifest_path = subpackage_dir.join(HASH_1.to_string()).to_string();
1802
1803        std::fs::create_dir_all(&data_dir).unwrap();
1804        std::fs::create_dir_all(&subpackage_dir).unwrap();
1805        std::fs::create_dir_all(&manifest_dir).unwrap();
1806        std::fs::create_dir_all(&manifest_dir_2).unwrap();
1807
1808        let manifest = PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
1809            package: PackageMetadata {
1810                name: "example".parse().unwrap(),
1811                version: "0".parse().unwrap(),
1812            },
1813            blobs: vec![BlobInfo {
1814                source_path: blob_source_path,
1815                path: "data/p2".into(),
1816                merkle: HASH_0,
1817                size: 1,
1818            }],
1819            subpackages: vec![SubpackageInfo {
1820                manifest_path: subpackage_manifest_path,
1821                name: "subpackage1".into(),
1822                merkle: HASH_1,
1823            }],
1824            repository: None,
1825            blob_sources_relative: RelativeTo::WorkingDir,
1826            delivery_blob_type: None,
1827            abi_revision: None,
1828        }));
1829
1830        let result_manifest = manifest.clone().write_with_relative_paths(&manifest_path).unwrap();
1831        let result_manifest_2 =
1832            manifest.clone().write_with_relative_paths(&manifest_path_2).unwrap();
1833
1834        // The manifests should be equivalent, even if the paths have been relativized to
1835        // directories at different depths (which results in different manifest_paths).
1836        assert_eq!(result_manifest, result_manifest_2);
1837        assert_ne!(
1838            result_manifest.subpackages()[0].manifest_path,
1839            result_manifest_2.subpackages()[0].manifest_path
1840        );
1841
1842        let blob = result_manifest.blobs().first().unwrap();
1843        assert_eq!(blob.source_path, "../data_source/p2");
1844        let subpackage = result_manifest.subpackages().first().unwrap();
1845        assert_eq!(subpackage.manifest_path, format!("../subpackage_manifests/{HASH_1}"));
1846
1847        let parsed_manifest: serde_json::Value =
1848            serde_json::from_reader(File::open(manifest_path).unwrap()).unwrap();
1849
1850        let object = parsed_manifest.as_object().unwrap();
1851
1852        let blobs_value = object.get("blobs").unwrap();
1853        let blobs = blobs_value.as_array().unwrap();
1854        let blob_value = blobs.first().unwrap();
1855        let blob = blob_value.as_object().unwrap();
1856        let source_path_value = blob.get("source_path").unwrap();
1857        let source_path = source_path_value.as_str().unwrap();
1858
1859        let subpackages_value = object.get("subpackages").unwrap();
1860        let subpackages = subpackages_value.as_array().unwrap();
1861        let subpackage_value = subpackages.first().unwrap();
1862        let subpackage = subpackage_value.as_object().unwrap();
1863        let subpackage_manifest_path_value = subpackage.get("manifest_path").unwrap();
1864        let subpackage_manifest_path = subpackage_manifest_path_value.as_str().unwrap();
1865
1866        assert_eq!(source_path, "../data_source/p2");
1867        assert_eq!(subpackage_manifest_path, format!("../subpackage_manifests/{HASH_1}"));
1868    }
1869
1870    #[test]
1871    fn test_set_name() {
1872        let mut manifest = PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
1873            package: PackageMetadata {
1874                name: "original-name".parse().unwrap(),
1875                version: "0".parse().unwrap(),
1876            },
1877            blobs: vec![],
1878            subpackages: vec![],
1879            repository: None,
1880            blob_sources_relative: Default::default(),
1881            delivery_blob_type: None,
1882            abi_revision: None,
1883        }));
1884
1885        assert_eq!(manifest.name(), &"original-name".parse::<PackageName>().unwrap());
1886
1887        let new_name = "new-name".parse().unwrap();
1888        manifest.set_name(new_name);
1889
1890        assert_eq!(manifest.name(), &"new-name".parse::<PackageName>().unwrap());
1891    }
1892}