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