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, UnpinnedAbsolutePackageUrl};
16use serde::{Deserialize, Serialize};
17use std::collections::{BTreeMap, HashMap, HashSet};
18use std::fs::{self, File, create_dir_all};
19use std::io::{self, BufReader, BufWriter, 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 = root_from_slice(&meta_far);
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 a BlobInfo corresponding to the top level meta.far blob and a HashMap
506    /// containing all of the blobs (both content blobs and meta.fars) from the transitive closure
507    /// of the subpackages. The HashMap will not contain the BlobInfo of the top level meta.far.
508    pub fn package_and_subpackage_blobs(
509        self,
510    ) -> Result<(BlobInfo, HashMap<Hash, BlobInfo>), PackageManifestError> {
511        let mut contents = HashMap::new();
512        let mut visited_subpackages = HashSet::new();
513
514        Self::package_and_subpackage_blobs_impl(
515            &mut contents,
516            &mut visited_subpackages,
517            self.clone(),
518        )?;
519
520        let blobs = self.into_blobs();
521        for blob in blobs {
522            if blob.path == Self::META_FAR_BLOB_PATH && contents.remove(&blob.merkle).is_some() {
523                return Ok((blob, contents));
524            }
525        }
526        Err(PackageManifestError::MetaPackage(MetaPackageError::MetaPackageMissing))
527    }
528
529    pub fn write_with_relative_paths(self, path: impl AsRef<Utf8Path>) -> anyhow::Result<Self> {
530        fn inner(this: PackageManifest, path: &Utf8Path) -> anyhow::Result<PackageManifest> {
531            let manifest = match this.0 {
532                VersionedPackageManifest::Version1(manifest) => PackageManifest(
533                    VersionedPackageManifest::Version1(manifest.with_relative_paths(path)?),
534                ),
535            };
536            let () = manifest.write(path)?;
537            Ok(manifest)
538        }
539        inner(self, path.as_ref())
540    }
541
542    pub fn write(&self, path: impl AsRef<Utf8Path>) -> anyhow::Result<()> {
543        fn inner(this: &PackageManifest, path: &Utf8Path) -> anyhow::Result<()> {
544            let mut tmp = if let Some(parent) = path.parent() {
545                create_dir_all(parent)?;
546                tempfile::NamedTempFile::new_in(parent)?
547            } else {
548                tempfile::NamedTempFile::new()?
549            };
550
551            serde_json::to_writer_pretty(BufWriter::new(&mut tmp), this)?;
552            tmp.persist_if_changed(path)
553                .with_context(|| format!("failed to persist package manifest: {path}"))?;
554
555            Ok(())
556        }
557        inner(self, path.as_ref())
558    }
559}
560
561pub struct PackageManifestBuilder {
562    manifest: PackageManifestV1,
563}
564
565impl PackageManifestBuilder {
566    pub fn new(meta_package: MetaPackage) -> Self {
567        Self {
568            manifest: PackageManifestV1 {
569                package: PackageMetadata {
570                    name: meta_package.name().to_owned(),
571                    version: meta_package.variant().to_owned(),
572                },
573                blobs: vec![],
574                repository: None,
575                blob_sources_relative: Default::default(),
576                subpackages: vec![],
577                delivery_blob_type: None,
578                abi_revision: None,
579            },
580        }
581    }
582
583    pub fn repository(mut self, repository: impl Into<String>) -> Self {
584        self.manifest.repository = Some(repository.into());
585        self
586    }
587
588    pub fn delivery_blob_type(mut self, delivery_blob_type: Option<DeliveryBlobType>) -> Self {
589        self.manifest.delivery_blob_type = delivery_blob_type;
590        self
591    }
592
593    pub fn add_blob(mut self, info: BlobInfo) -> Self {
594        self.manifest.blobs.push(info);
595        self
596    }
597
598    pub fn add_subpackage(mut self, info: SubpackageInfo) -> Self {
599        self.manifest.subpackages.push(info);
600        self
601    }
602
603    pub fn abi_revision(mut self, abi_revision: Option<AbiRevision>) -> Self {
604        self.manifest.abi_revision = abi_revision;
605        self
606    }
607
608    pub fn build(self) -> PackageManifest {
609        PackageManifest(VersionedPackageManifest::Version1(self.manifest))
610    }
611}
612
613#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
614#[serde(tag = "version")]
615enum VersionedPackageManifest {
616    #[serde(rename = "1")]
617    Version1(PackageManifestV1),
618}
619
620#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
621struct PackageManifestV1 {
622    #[serde(default, skip_serializing_if = "Option::is_none")]
623    repository: Option<String>,
624    package: PackageMetadata,
625    blobs: Vec<BlobInfo>,
626
627    /// Are the blob source_paths relative to the working dir (default, as made
628    /// by 'pm') or the file containing the serialized manifest (new, portable,
629    /// behavior)
630    #[serde(default, skip_serializing_if = "RelativeTo::is_default")]
631    // TODO(https://fxbug.dev/42066050): rename this to `paths_relative` since it applies
632    // to both blobs and subpackages. (I'd change it now, but it's encoded in
633    // JSON files so we may need a soft transition to support both at first.)
634    blob_sources_relative: RelativeTo,
635
636    #[serde(default, skip_serializing_if = "Vec::is_empty")]
637    subpackages: Vec<SubpackageInfo>,
638    /// If not None, the `source_path` of the `blobs` are delivery blobs of the given type instead of
639    /// uncompressed blobs.
640    #[serde(default, skip_serializing_if = "Option::is_none")]
641    pub delivery_blob_type: Option<DeliveryBlobType>,
642    #[serde(default, skip_serializing_if = "Option::is_none")]
643    abi_revision: Option<AbiRevision>,
644}
645
646impl PackageManifestV1 {
647    pub fn with_relative_paths(
648        self,
649        manifest_path: impl AsRef<Utf8Path>,
650    ) -> anyhow::Result<PackageManifestV1> {
651        fn inner(
652            this: PackageManifestV1,
653            manifest_path: &Utf8Path,
654        ) -> anyhow::Result<PackageManifestV1> {
655            let manifest = if let RelativeTo::WorkingDir = &this.blob_sources_relative {
656                // manifest contains working-dir relative source paths, make
657                // them relative to the file, instead.
658                let blobs = this
659                    .blobs
660                    .into_iter()
661                    .map(|blob| relativize_blob_source_path(blob, manifest_path))
662                    .collect::<anyhow::Result<_>>()?;
663                let subpackages = this
664                    .subpackages
665                    .into_iter()
666                    .map(|subpackage| {
667                        relativize_subpackage_manifest_path(subpackage, manifest_path)
668                    })
669                    .collect::<anyhow::Result<_>>()?;
670                PackageManifestV1 {
671                    blobs,
672                    subpackages,
673                    blob_sources_relative: RelativeTo::File,
674                    ..this
675                }
676            } else {
677                this
678            };
679
680            Ok(manifest)
681        }
682        inner(self, manifest_path.as_ref())
683    }
684
685    pub fn resolve_source_paths(self, manifest_path: impl AsRef<Utf8Path>) -> anyhow::Result<Self> {
686        fn inner(
687            this: PackageManifestV1,
688            manifest_path: &Utf8Path,
689        ) -> anyhow::Result<PackageManifestV1> {
690            if let RelativeTo::File = &this.blob_sources_relative {
691                let blobs = this
692                    .blobs
693                    .into_iter()
694                    .map(|blob| resolve_blob_source_path(blob, manifest_path))
695                    .collect::<anyhow::Result<_>>()?;
696                let subpackages = this
697                    .subpackages
698                    .into_iter()
699                    .map(|subpackage| resolve_subpackage_manifest_path(subpackage, manifest_path))
700                    .collect::<anyhow::Result<_>>()?;
701                let blob_sources_relative = RelativeTo::WorkingDir;
702                Ok(PackageManifestV1 { blobs, subpackages, blob_sources_relative, ..this })
703            } else {
704                Ok(this)
705            }
706        }
707        inner(self, manifest_path.as_ref())
708    }
709}
710
711/// If the path is a relative path, what is it relative from?
712///
713/// If 'RelativeTo::WorkingDir', then the path is assumed to be relative to the
714/// working dir, and can be used directly as a path.
715///
716/// If 'RelativeTo::File', then the path is relative to the file that contained
717/// the path. To use the path, it must be resolved against the path of the
718/// file.
719#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
720pub enum RelativeTo {
721    #[serde(rename = "working_dir")]
722    #[default]
723    WorkingDir,
724    #[serde(rename = "file")]
725    File,
726}
727
728impl RelativeTo {
729    pub(crate) fn is_default(&self) -> bool {
730        matches!(self, RelativeTo::WorkingDir)
731    }
732}
733
734#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
735struct PackageMetadata {
736    name: PackageName,
737    version: PackageVariant,
738}
739
740#[derive(Clone, Debug, Eq, Deserialize, Serialize, PartialOrd, Ord)]
741pub struct BlobInfo {
742    /// Path to the blob file, could be a delivery blob or uncompressed blob depending on
743    /// `delivery_blob_type` in the manifest.
744    pub source_path: String,
745    /// The virtual path of the blob in the package.
746    pub path: String,
747    pub merkle: fuchsia_merkle::Hash,
748    /// Uncompressed size of the blob.
749    pub size: u64,
750}
751
752// Write a custom PartialEq so that we ignore `source_path`.
753// Blobs are identical if their destination path and merkle are the same.
754impl PartialEq for BlobInfo {
755    fn eq(&self, other: &Self) -> bool {
756        self.path == other.path && self.merkle == other.merkle
757    }
758}
759
760#[derive(Clone, Debug, Eq, Deserialize, Serialize)]
761pub struct SubpackageInfo {
762    /// Path to a PackageManifest for the subpackage.
763    pub manifest_path: String,
764
765    /// The package-relative name of this declared subpackage.
766    pub name: String,
767
768    /// The package hash (meta.far merkle) of the subpackage.
769    pub merkle: fuchsia_merkle::Hash,
770}
771
772// Write a custom PartialEq so that we ignore `manifest_path`.
773// Subpackages are identical if their name and merkle are the same.
774impl PartialEq for SubpackageInfo {
775    fn eq(&self, other: &Self) -> bool {
776        self.name == other.name && self.merkle == other.merkle
777    }
778}
779
780fn relativize_blob_source_path(
781    blob: BlobInfo,
782    manifest_path: &Utf8Path,
783) -> anyhow::Result<BlobInfo> {
784    let source_path = path_relative_from_file(blob.source_path, manifest_path)?;
785    let source_path = source_path.into_string();
786
787    Ok(BlobInfo { source_path, ..blob })
788}
789
790fn resolve_blob_source_path(blob: BlobInfo, manifest_path: &Utf8Path) -> anyhow::Result<BlobInfo> {
791    let source_path = resolve_path_from_file(&blob.source_path, manifest_path)
792        .with_context(|| format!("Resolving blob path: {}", blob.source_path))?
793        .into_string();
794    Ok(BlobInfo { source_path, ..blob })
795}
796
797fn relativize_subpackage_manifest_path(
798    subpackage: SubpackageInfo,
799    manifest_path: &Utf8Path,
800) -> anyhow::Result<SubpackageInfo> {
801    let manifest_path = path_relative_from_file(subpackage.manifest_path, manifest_path)?;
802    let manifest_path = manifest_path.into_string();
803
804    Ok(SubpackageInfo { manifest_path, ..subpackage })
805}
806
807fn resolve_subpackage_manifest_path(
808    subpackage: SubpackageInfo,
809    manifest_path: &Utf8Path,
810) -> anyhow::Result<SubpackageInfo> {
811    let manifest_path = resolve_path_from_file(&subpackage.manifest_path, manifest_path)
812        .with_context(|| {
813            format!("Resolving subpackage manifest path: {}", subpackage.manifest_path)
814        })?
815        .into_string();
816    Ok(SubpackageInfo { manifest_path, ..subpackage })
817}
818
819#[cfg(test)]
820mod tests {
821    use super::*;
822    use crate::path_to_string::PathToStringExt;
823    use crate::{BlobEntry, MetaPackage, PackageBuilder};
824    use assert_matches::assert_matches;
825    use camino::Utf8PathBuf;
826    use fuchsia_url::RelativePackageUrl;
827    use pretty_assertions::assert_eq;
828    use serde_json::{Value, json};
829    use tempfile::{NamedTempFile, TempDir};
830
831    const FAKE_ABI_REVISION: version_history::AbiRevision =
832        version_history::AbiRevision::from_u64(0x323dd69d73d957a7);
833
834    const HASH_0: Hash = Hash::from_array([0; fuchsia_hash::HASH_SIZE]);
835    const HASH_1: Hash = Hash::from_array([1; fuchsia_hash::HASH_SIZE]);
836    const HASH_2: Hash = Hash::from_array([2; fuchsia_hash::HASH_SIZE]);
837    const HASH_3: Hash = Hash::from_array([3; fuchsia_hash::HASH_SIZE]);
838    const HASH_4: Hash = Hash::from_array([4; fuchsia_hash::HASH_SIZE]);
839
840    pub struct TestEnv {
841        pub _temp: TempDir,
842        pub dir_path: Utf8PathBuf,
843        pub manifest_path: Utf8PathBuf,
844        pub subpackage_path: Utf8PathBuf,
845        pub data_dir: Utf8PathBuf,
846    }
847
848    impl TestEnv {
849        pub fn new() -> Self {
850            let temp = TempDir::new().unwrap();
851            let dir_path = Utf8Path::from_path(temp.path()).unwrap().to_path_buf();
852
853            let manifest_dir = dir_path.join("manifest_dir");
854            std::fs::create_dir_all(&manifest_dir).unwrap();
855
856            let subpackage_dir = dir_path.join("subpackage_manifests");
857            std::fs::create_dir_all(&subpackage_dir).unwrap();
858
859            let data_dir = dir_path.join("data_source");
860            std::fs::create_dir_all(&data_dir).unwrap();
861
862            TestEnv {
863                _temp: temp,
864                dir_path,
865                manifest_path: manifest_dir.join("package_manifest.json"),
866                subpackage_path: subpackage_dir.join(HASH_0.to_string()),
867                data_dir,
868            }
869        }
870    }
871
872    #[test]
873    fn test_version1_serialization() {
874        let manifest = PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
875            package: PackageMetadata {
876                name: "example".parse().unwrap(),
877                version: "0".parse().unwrap(),
878            },
879            blobs: vec![BlobInfo {
880                source_path: "../p1".into(),
881                path: "data/p1".into(),
882                merkle: HASH_0,
883                size: 1,
884            }],
885            subpackages: vec![],
886            repository: None,
887            blob_sources_relative: Default::default(),
888            delivery_blob_type: None,
889            abi_revision: None,
890        }));
891
892        assert_eq!(
893            serde_json::to_value(manifest).unwrap(),
894            json!(
895                {
896                    "version": "1",
897                    "package": {
898                        "name": "example",
899                        "version": "0"
900                    },
901                    "blobs": [
902                        {
903                            "source_path": "../p1",
904                            "path": "data/p1",
905                            "merkle": "0000000000000000000000000000000000000000000000000000000000000000",
906                            "size": 1
907                        },
908                    ]
909                }
910            )
911        );
912
913        let manifest = PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
914            package: PackageMetadata {
915                name: "example".parse().unwrap(),
916                version: "0".parse().unwrap(),
917            },
918            blobs: vec![BlobInfo {
919                source_path: "../p1".into(),
920                path: "data/p1".into(),
921                merkle: HASH_0,
922                size: 1,
923            }],
924            subpackages: vec![],
925            repository: Some("testrepository.org".into()),
926            blob_sources_relative: RelativeTo::File,
927            delivery_blob_type: None,
928            abi_revision: Some(FAKE_ABI_REVISION),
929        }));
930
931        assert_eq!(
932            serde_json::to_value(manifest).unwrap(),
933            json!(
934                {
935                    "version": "1",
936                    "repository": "testrepository.org",
937                    "package": {
938                        "name": "example",
939                        "version": "0"
940                    },
941                    "blobs": [
942                        {
943                            "source_path": "../p1",
944                            "path": "data/p1",
945                            "merkle": HASH_0,
946                            "size": 1
947                        },
948                    ],
949                    "blob_sources_relative": "file",
950                    "abi_revision": "0x323dd69d73d957a7",
951                }
952            )
953        );
954    }
955
956    #[test]
957    fn test_version1_deserialization() {
958        let manifest = serde_json::from_value::<VersionedPackageManifest>(json!(
959            {
960                "version": "1",
961                "repository": "testrepository.org",
962                "package": {
963                    "name": "example",
964                    "version": "0"
965                },
966                "blobs": [
967                    {
968                        "source_path": "../p1",
969                        "path": "data/p1",
970                        "merkle": HASH_0,
971                        "size": 1
972                    },
973                ]
974            }
975        ))
976        .expect("valid json");
977
978        assert_eq!(
979            manifest,
980            VersionedPackageManifest::Version1(PackageManifestV1 {
981                package: PackageMetadata {
982                    name: "example".parse().unwrap(),
983                    version: "0".parse().unwrap(),
984                },
985                blobs: vec![BlobInfo {
986                    source_path: "../p1".into(),
987                    path: "data/p1".into(),
988                    merkle: HASH_0,
989                    size: 1,
990                }],
991                subpackages: vec![],
992                repository: Some("testrepository.org".into()),
993                blob_sources_relative: Default::default(),
994                delivery_blob_type: None,
995                abi_revision: None,
996            })
997        );
998
999        let manifest = serde_json::from_value::<VersionedPackageManifest>(json!(
1000            {
1001                "version": "1",
1002                "package": {
1003                    "name": "example",
1004                    "version": "0"
1005                },
1006                "blobs": [
1007                    {
1008                        "source_path": "../p1",
1009                        "path": "data/p1",
1010                        "merkle": HASH_0,
1011                        "size": 1
1012                    },
1013                ],
1014                "blob_sources_relative": "file",
1015                "abi_revision": "0x323dd69d73d957a7",
1016            }
1017        ))
1018        .expect("valid json");
1019
1020        assert_eq!(
1021            manifest,
1022            VersionedPackageManifest::Version1(PackageManifestV1 {
1023                package: PackageMetadata {
1024                    name: "example".parse().unwrap(),
1025                    version: "0".parse().unwrap(),
1026                },
1027                blobs: vec![BlobInfo {
1028                    source_path: "../p1".into(),
1029                    path: "data/p1".into(),
1030                    merkle: HASH_0,
1031                    size: 1,
1032                }],
1033                subpackages: vec![],
1034                repository: None,
1035                blob_sources_relative: RelativeTo::File,
1036                delivery_blob_type: None,
1037                abi_revision: Some(FAKE_ABI_REVISION),
1038            })
1039        )
1040    }
1041
1042    #[test]
1043    fn test_create_package_manifest_from_parts() {
1044        let meta_package = MetaPackage::from_name_and_variant_zero("package-name".parse().unwrap());
1045        let mut blobs = BTreeMap::new();
1046        blobs.insert(
1047            "bin/my_prog".to_string(),
1048            BlobEntry { source_path: "src/bin/my_prog".into(), hash: HASH_0, size: 1 },
1049        );
1050        let package_manifest =
1051            PackageManifest::from_parts(meta_package, None, blobs, vec![], FAKE_ABI_REVISION)
1052                .unwrap();
1053
1054        assert_eq!(&"package-name".parse::<PackageName>().unwrap(), package_manifest.name());
1055        assert_eq!(None, package_manifest.repository());
1056        assert_eq!(Some(FAKE_ABI_REVISION), package_manifest.abi_revision());
1057    }
1058
1059    #[test]
1060    fn test_from_blobs_dir() {
1061        let temp = TempDir::new().unwrap();
1062        let temp_dir = Utf8Path::from_path(temp.path()).unwrap();
1063
1064        let gen_dir = temp_dir.join("gen");
1065        std::fs::create_dir_all(&gen_dir).unwrap();
1066
1067        let blobs_dir = temp_dir.join("blobs/1");
1068        std::fs::create_dir_all(&blobs_dir).unwrap();
1069
1070        let manifests_dir = temp_dir.join("manifests");
1071        std::fs::create_dir_all(&manifests_dir).unwrap();
1072
1073        // Helper to write some content into a delivery blob.
1074        let write_blob = |contents| {
1075            let hash = fuchsia_merkle::root_from_slice(contents);
1076
1077            let path = blobs_dir.join(hash.to_string());
1078
1079            let blob_file = File::create(&path).unwrap();
1080            delivery_blob::generate_to(DeliveryBlobType::Type1, contents, &blob_file).unwrap();
1081
1082            (path, hash)
1083        };
1084
1085        // Create a package.
1086        let mut package_builder = PackageBuilder::new("package", FAKE_ABI_REVISION);
1087        let (file1_path, file1_hash) = write_blob(b"file 1");
1088        package_builder.add_contents_as_blob("file-1", b"file 1", &gen_dir).unwrap();
1089        let (file2_path, file2_hash) = write_blob(b"file 2");
1090        package_builder.add_contents_as_blob("file-2", b"file 2", &gen_dir).unwrap();
1091
1092        let gen_meta_far_path = temp_dir.join("meta.far");
1093        let _package_manifest = package_builder.build(&gen_dir, &gen_meta_far_path).unwrap();
1094
1095        // Compute the meta.far hash, and generate a delivery blob in the blobs/1/ directory.
1096        let meta_far_bytes = std::fs::read(&gen_meta_far_path).unwrap();
1097        let (meta_far_path, meta_far_hash) = write_blob(&meta_far_bytes);
1098
1099        // We should be able to create a manifest from the blob directory that matches the one
1100        // created by the builder.
1101        assert_eq!(
1102            PackageManifest::from_blobs_dir(
1103                blobs_dir.as_std_path().parent().unwrap(),
1104                Some(DeliveryBlobType::Type1),
1105                meta_far_hash,
1106                manifests_dir.as_std_path()
1107            )
1108            .unwrap(),
1109            PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
1110                package: PackageMetadata {
1111                    name: "package".parse().unwrap(),
1112                    version: PackageVariant::zero(),
1113                },
1114                blobs: vec![
1115                    BlobInfo {
1116                        source_path: meta_far_path.to_string(),
1117                        path: PackageManifest::META_FAR_BLOB_PATH.into(),
1118                        merkle: meta_far_hash,
1119                        size: 16384,
1120                    },
1121                    BlobInfo {
1122                        source_path: file1_path.to_string(),
1123                        path: "file-1".into(),
1124                        merkle: file1_hash,
1125                        size: 6,
1126                    },
1127                    BlobInfo {
1128                        source_path: file2_path.to_string(),
1129                        path: "file-2".into(),
1130                        merkle: file2_hash,
1131                        size: 6,
1132                    },
1133                ],
1134                subpackages: vec![],
1135                repository: None,
1136                blob_sources_relative: RelativeTo::WorkingDir,
1137                delivery_blob_type: Some(DeliveryBlobType::Type1),
1138                abi_revision: Some(FAKE_ABI_REVISION),
1139            }))
1140        );
1141    }
1142
1143    #[test]
1144    fn test_load_from_simple() {
1145        let env = TestEnv::new();
1146
1147        let expected_blob_source_path = &env.data_dir.join("p1").to_string();
1148
1149        let manifest = PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
1150            package: PackageMetadata {
1151                name: "example".parse().unwrap(),
1152                version: "0".parse().unwrap(),
1153            },
1154            blobs: vec![BlobInfo {
1155                source_path: expected_blob_source_path.clone(),
1156                path: "data/p1".into(),
1157                merkle: HASH_0,
1158                size: 1,
1159            }],
1160            subpackages: vec![SubpackageInfo {
1161                manifest_path: env.subpackage_path.to_string(),
1162                name: "subpackage0".into(),
1163                merkle: HASH_0,
1164            }],
1165            repository: None,
1166            blob_sources_relative: RelativeTo::WorkingDir,
1167            delivery_blob_type: None,
1168            abi_revision: None,
1169        }));
1170
1171        let manifest_file = File::create(&env.manifest_path).unwrap();
1172        serde_json::to_writer(manifest_file, &manifest).unwrap();
1173
1174        let loaded_manifest = PackageManifest::try_load_from(&env.manifest_path).unwrap();
1175        assert_eq!(loaded_manifest.name(), &"example".parse::<PackageName>().unwrap());
1176
1177        let (blobs, subpackages) = loaded_manifest.into_blobs_and_subpackages();
1178
1179        assert_eq!(blobs.len(), 1);
1180        let blob = blobs.first().unwrap();
1181        assert_eq!(blob.path, "data/p1");
1182
1183        assert_eq!(&blob.source_path, expected_blob_source_path);
1184
1185        assert_eq!(subpackages.len(), 1);
1186        let subpackage = subpackages.first().unwrap();
1187        assert_eq!(subpackage.name, "subpackage0");
1188        assert_eq!(&subpackage.manifest_path, &env.subpackage_path.to_string());
1189    }
1190
1191    #[test]
1192    fn test_load_from_resolves_source_paths() {
1193        let env = TestEnv::new();
1194
1195        let manifest = PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
1196            package: PackageMetadata {
1197                name: "example".parse().unwrap(),
1198                version: "0".parse().unwrap(),
1199            },
1200            blobs: vec![BlobInfo {
1201                source_path: "../data_source/p1".into(),
1202                path: "data/p1".into(),
1203                merkle: HASH_0,
1204                size: 1,
1205            }],
1206            subpackages: vec![SubpackageInfo {
1207                manifest_path: "../subpackage_manifests/0000000000000000000000000000000000000000000000000000000000000000".into(),
1208                name: "subpackage0".into(),
1209                merkle: HASH_0,
1210            }],
1211            repository: None,
1212            blob_sources_relative: RelativeTo::File,
1213            delivery_blob_type: None,
1214            abi_revision: None,
1215        }));
1216
1217        let manifest_file = File::create(&env.manifest_path).unwrap();
1218        serde_json::to_writer(manifest_file, &manifest).unwrap();
1219
1220        let loaded_manifest = PackageManifest::try_load_from(&env.manifest_path).unwrap();
1221        assert_eq!(
1222            loaded_manifest,
1223            PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
1224                package: PackageMetadata {
1225                    name: "example".parse::<PackageName>().unwrap(),
1226                    version: "0".parse().unwrap(),
1227                },
1228                blobs: vec![BlobInfo {
1229                    source_path: env.data_dir.join("p1").to_string(),
1230                    path: "data/p1".into(),
1231                    merkle: HASH_0,
1232                    size: 1,
1233                }],
1234                subpackages: vec![SubpackageInfo {
1235                    manifest_path: env.subpackage_path.to_string(),
1236                    name: "subpackage0".into(),
1237                    merkle: HASH_0,
1238                }],
1239                repository: None,
1240                blob_sources_relative: RelativeTo::WorkingDir,
1241                delivery_blob_type: None,
1242                abi_revision: None,
1243            }))
1244        );
1245    }
1246
1247    #[test]
1248    fn test_package_and_subpackage_blobs_meta_far_error() {
1249        let env = TestEnv::new();
1250
1251        let manifest = PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
1252            package: PackageMetadata {
1253                name: "example".parse().unwrap(),
1254                version: "0".parse().unwrap(),
1255            },
1256            blobs: vec![BlobInfo {
1257                source_path: "../data_source/p1".into(),
1258                path: "data/p1".into(),
1259                merkle: HASH_0,
1260                size: 1,
1261            }],
1262            subpackages: vec![SubpackageInfo {
1263                manifest_path: format!("../subpackage_manifests/{HASH_0}"),
1264                name: "subpackage0".into(),
1265                merkle: HASH_0,
1266            }],
1267            repository: None,
1268            blob_sources_relative: RelativeTo::File,
1269            delivery_blob_type: None,
1270            abi_revision: None,
1271        }));
1272
1273        let manifest_file = File::create(&env.manifest_path).unwrap();
1274        serde_json::to_writer(manifest_file, &manifest).unwrap();
1275
1276        let sub_manifest = PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
1277            package: PackageMetadata {
1278                name: "sub_manifest".parse().unwrap(),
1279                version: "0".parse().unwrap(),
1280            },
1281            blobs: vec![BlobInfo {
1282                source_path: "../data_source/p2".into(),
1283                path: "data/p2".into(),
1284                merkle: HASH_1,
1285                size: 1,
1286            }],
1287            subpackages: vec![],
1288            repository: None,
1289            blob_sources_relative: RelativeTo::File,
1290            delivery_blob_type: None,
1291            abi_revision: None,
1292        }));
1293
1294        let sub_manifest_file = File::create(&env.subpackage_path).unwrap();
1295        serde_json::to_writer(sub_manifest_file, &sub_manifest).unwrap();
1296
1297        let loaded_manifest = PackageManifest::try_load_from(&env.manifest_path).unwrap();
1298
1299        let result = loaded_manifest.package_and_subpackage_blobs();
1300        assert_matches!(
1301            result,
1302            Err(PackageManifestError::MetaPackage(MetaPackageError::MetaPackageMissing))
1303        );
1304    }
1305
1306    #[test]
1307    fn test_package_and_subpackage_blobs() {
1308        let env = TestEnv::new();
1309        let subsubpackage_dir = &env.dir_path.join("subsubpackage_manifests");
1310
1311        let expected_subsubpackage_manifest_path =
1312            subsubpackage_dir.join(HASH_0.to_string()).to_string();
1313
1314        std::fs::create_dir_all(subsubpackage_dir).unwrap();
1315
1316        let manifest = PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
1317            package: PackageMetadata {
1318                name: "example".parse().unwrap(),
1319                version: "0".parse().unwrap(),
1320            },
1321            blobs: vec![
1322                BlobInfo {
1323                    source_path: "../data_source/p0".into(),
1324                    path: "meta/".into(),
1325                    merkle: HASH_0,
1326                    size: 1,
1327                },
1328                BlobInfo {
1329                    source_path: "../data_source/p1".into(),
1330                    path: "data/p1".into(),
1331                    merkle: HASH_1,
1332                    size: 1,
1333                },
1334            ],
1335            subpackages: vec![SubpackageInfo {
1336                manifest_path: format!("../subpackage_manifests/{HASH_0}"),
1337                name: "subpackage0".into(),
1338                merkle: HASH_2,
1339            }],
1340            repository: None,
1341            blob_sources_relative: RelativeTo::File,
1342            delivery_blob_type: None,
1343            abi_revision: None,
1344        }));
1345
1346        let manifest_file = File::create(&env.manifest_path).unwrap();
1347        serde_json::to_writer(manifest_file, &manifest).unwrap();
1348
1349        let sub_manifest = PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
1350            package: PackageMetadata {
1351                name: "sub_manifest".parse().unwrap(),
1352                version: "0".parse().unwrap(),
1353            },
1354            blobs: vec![
1355                BlobInfo {
1356                    source_path: "../data_source/p2".into(),
1357                    path: "meta/".into(),
1358                    merkle: HASH_2,
1359                    size: 1,
1360                },
1361                BlobInfo {
1362                    source_path: "../data_source/p3".into(),
1363                    path: "data/p3".into(),
1364                    merkle: HASH_3,
1365                    size: 1,
1366                },
1367            ],
1368            subpackages: vec![SubpackageInfo {
1369                manifest_path: format!("../subsubpackage_manifests/{HASH_0}"),
1370                name: "subsubpackage0".into(),
1371                merkle: HASH_4,
1372            }],
1373            repository: None,
1374            blob_sources_relative: RelativeTo::File,
1375            delivery_blob_type: None,
1376            abi_revision: None,
1377        }));
1378
1379        let sub_manifest_file = File::create(&env.subpackage_path).unwrap();
1380        serde_json::to_writer(sub_manifest_file, &sub_manifest).unwrap();
1381
1382        let sub_sub_manifest =
1383            PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
1384                package: PackageMetadata {
1385                    name: "sub_sub_manifest".parse().unwrap(),
1386                    version: "0".parse().unwrap(),
1387                },
1388                blobs: vec![BlobInfo {
1389                    source_path: "../data_source/p4".into(),
1390                    path: "meta/".into(),
1391                    merkle: HASH_4,
1392                    size: 1,
1393                }],
1394                subpackages: vec![],
1395                repository: None,
1396                blob_sources_relative: RelativeTo::File,
1397                delivery_blob_type: None,
1398                abi_revision: None,
1399            }));
1400
1401        let sub_sub_manifest_file = File::create(expected_subsubpackage_manifest_path).unwrap();
1402        serde_json::to_writer(sub_sub_manifest_file, &sub_sub_manifest).unwrap();
1403
1404        let loaded_manifest = PackageManifest::try_load_from(&env.manifest_path).unwrap();
1405
1406        let (meta_far, contents) = loaded_manifest.package_and_subpackage_blobs().unwrap();
1407        assert_eq!(
1408            meta_far,
1409            BlobInfo {
1410                source_path: env.data_dir.join("p0").to_string(),
1411                path: "meta/".into(),
1412                merkle: HASH_0,
1413                size: 1,
1414            }
1415        );
1416
1417        // Does not contain top level meta.far
1418        assert_eq!(
1419            contents,
1420            HashMap::from([
1421                (
1422                    HASH_1,
1423                    BlobInfo {
1424                        source_path: env.data_dir.join("p1").to_string(),
1425                        path: "data/p1".into(),
1426                        merkle: HASH_1,
1427                        size: 1,
1428                    },
1429                ),
1430                (
1431                    HASH_2,
1432                    BlobInfo {
1433                        source_path: env.data_dir.join("p2").to_string(),
1434                        path: "meta/".into(),
1435                        merkle: HASH_2,
1436                        size: 1,
1437                    },
1438                ),
1439                (
1440                    HASH_3,
1441                    BlobInfo {
1442                        source_path: env.data_dir.join("p3").to_string(),
1443                        path: "data/p3".into(),
1444                        merkle: HASH_3,
1445                        size: 1,
1446                    },
1447                ),
1448                (
1449                    HASH_4,
1450                    BlobInfo {
1451                        source_path: env.data_dir.join("p4").to_string(),
1452                        path: "meta/".into(),
1453                        merkle: HASH_4,
1454                        size: 1,
1455                    },
1456                ),
1457            ]),
1458        );
1459    }
1460
1461    #[test]
1462    fn test_package_and_subpackage_blobs_deduped() {
1463        let env = TestEnv::new();
1464
1465        let expected_meta_far_source_path = env.data_dir.join("p0").to_string();
1466        let expected_blob_source_path_1 = env.data_dir.join("p1").to_string();
1467        let expected_blob_source_path_2 = env.data_dir.join("p2").to_string();
1468        let expected_blob_source_path_3 = env.data_dir.join("p3").to_string();
1469
1470        let manifest = PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
1471            package: PackageMetadata {
1472                name: "example".parse().unwrap(),
1473                version: "0".parse().unwrap(),
1474            },
1475            blobs: vec![
1476                BlobInfo {
1477                    source_path: "../data_source/p0".into(),
1478                    path: "meta/".into(),
1479                    merkle: HASH_0,
1480                    size: 1,
1481                },
1482                BlobInfo {
1483                    source_path: "../data_source/p1".into(),
1484                    path: "data/p1".into(),
1485                    merkle: HASH_1,
1486                    size: 1,
1487                },
1488            ],
1489            // Note that we're intentionally duplicating the subpackages with
1490            // separate names.
1491            subpackages: vec![
1492                SubpackageInfo {
1493                    manifest_path: format!("../subpackage_manifests/{HASH_0}"),
1494                    name: "subpackage0".into(),
1495                    merkle: HASH_2,
1496                },
1497                SubpackageInfo {
1498                    manifest_path: format!("../subpackage_manifests/{HASH_0}"),
1499                    name: "subpackage1".into(),
1500                    merkle: HASH_2,
1501                },
1502            ],
1503            repository: None,
1504            blob_sources_relative: RelativeTo::File,
1505            delivery_blob_type: None,
1506            abi_revision: None,
1507        }));
1508
1509        let manifest_file = File::create(&env.manifest_path).unwrap();
1510        serde_json::to_writer(manifest_file, &manifest).unwrap();
1511
1512        let sub_manifest = PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
1513            package: PackageMetadata {
1514                name: "sub_manifest".parse().unwrap(),
1515                version: "0".parse().unwrap(),
1516            },
1517            blobs: vec![
1518                BlobInfo {
1519                    source_path: "../data_source/p2".into(),
1520                    path: "meta/".into(),
1521                    merkle: HASH_2,
1522                    size: 1,
1523                },
1524                BlobInfo {
1525                    source_path: "../data_source/p3".into(),
1526                    path: "data/p3".into(),
1527                    merkle: HASH_3,
1528                    size: 1,
1529                },
1530            ],
1531            subpackages: vec![],
1532            repository: None,
1533            blob_sources_relative: RelativeTo::File,
1534            delivery_blob_type: None,
1535            abi_revision: None,
1536        }));
1537
1538        serde_json::to_writer(File::create(&env.subpackage_path).unwrap(), &sub_manifest).unwrap();
1539
1540        let loaded_manifest = PackageManifest::try_load_from(&env.manifest_path).unwrap();
1541
1542        let (meta_far, contents) = loaded_manifest.package_and_subpackage_blobs().unwrap();
1543        assert_eq!(
1544            meta_far,
1545            BlobInfo {
1546                source_path: expected_meta_far_source_path,
1547                path: "meta/".into(),
1548                merkle: HASH_0,
1549                size: 1,
1550            }
1551        );
1552
1553        // Does not contain meta.far
1554        assert_eq!(
1555            contents,
1556            HashMap::from([
1557                (
1558                    HASH_1,
1559                    BlobInfo {
1560                        source_path: expected_blob_source_path_1,
1561                        path: "data/p1".into(),
1562                        merkle: HASH_1,
1563                        size: 1,
1564                    }
1565                ),
1566                (
1567                    HASH_2,
1568                    BlobInfo {
1569                        source_path: expected_blob_source_path_2,
1570                        path: "meta/".into(),
1571                        merkle: HASH_2,
1572                        size: 1,
1573                    }
1574                ),
1575                (
1576                    HASH_3,
1577                    BlobInfo {
1578                        source_path: expected_blob_source_path_3,
1579                        path: "data/p3".into(),
1580                        merkle: HASH_3,
1581                        size: 1,
1582                    }
1583                ),
1584            ])
1585        );
1586    }
1587
1588    #[test]
1589    fn test_from_package_archive_bogus() {
1590        let temp = TempDir::new().unwrap();
1591        let temp_blobs_dir = temp.into_path();
1592
1593        let temp = TempDir::new().unwrap();
1594        let temp_manifest_dir = temp.into_path();
1595
1596        let temp_archive = TempDir::new().unwrap();
1597        let temp_archive_dir = temp_archive.path();
1598
1599        let result =
1600            PackageManifest::from_archive(temp_archive_dir, &temp_blobs_dir, &temp_manifest_dir);
1601        assert!(result.is_err())
1602    }
1603
1604    #[fuchsia_async::run_singlethreaded(test)]
1605    async fn test_from_package_manifest_archive_manifest() {
1606        let outdir = TempDir::new().unwrap();
1607
1608        let sub_outdir = outdir.path().join("subpackage_manifests");
1609        std::fs::create_dir(&sub_outdir).unwrap();
1610
1611        // Create a file to write to the sub package metafar
1612        let sub_far_source_file_path = NamedTempFile::new_in(&sub_outdir).unwrap();
1613        std::fs::write(&sub_far_source_file_path, "some data for sub far").unwrap();
1614
1615        // Create a file to include as a blob
1616        let sub_blob_source_file_path = sub_outdir.as_path().join("sub_blob_a");
1617        let blob_contents = "sub some data for blob";
1618        std::fs::write(&sub_blob_source_file_path, blob_contents).unwrap();
1619
1620        // Create a file to include as a blob
1621        let sub_blob_source_file_path2 = sub_outdir.as_path().join("sub_blob_b");
1622        let blob_contents = "sub some data for blob2";
1623        std::fs::write(&sub_blob_source_file_path2, blob_contents).unwrap();
1624
1625        // Create the sub builder
1626        let mut sub_builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1627        sub_builder
1628            .add_file_as_blob(
1629                "sub_blob_a",
1630                sub_blob_source_file_path.as_path().path_to_string().unwrap(),
1631            )
1632            .unwrap();
1633        sub_builder
1634            .add_file_as_blob(
1635                "sub_blob_b",
1636                sub_blob_source_file_path2.as_path().path_to_string().unwrap(),
1637            )
1638            .unwrap();
1639        sub_builder
1640            .add_file_to_far(
1641                "meta/some/file",
1642                sub_far_source_file_path.path().path_to_string().unwrap(),
1643            )
1644            .unwrap();
1645
1646        let sub_metafar_path = sub_outdir.as_path().join("meta.far");
1647        let sub_manifest = sub_builder.build(&sub_outdir, &sub_metafar_path).unwrap();
1648
1649        let manifest_outdir = TempDir::new().unwrap().into_path();
1650        let subpackage_manifest_path =
1651            manifest_outdir.join(format!("{}_package_manifest.json", sub_manifest.hash()));
1652
1653        serde_json::to_writer(
1654            std::fs::File::create(&subpackage_manifest_path).unwrap(),
1655            &sub_manifest,
1656        )
1657        .unwrap();
1658
1659        let subpackage_url = "subpackage_manifests".parse::<RelativePackageUrl>().unwrap();
1660
1661        let metafar_path = outdir.path().join("meta.far");
1662
1663        // Create a file to write to the package metafar
1664        let far_source_file_path = NamedTempFile::new_in(&outdir).unwrap();
1665        std::fs::write(&far_source_file_path, "some data for far").unwrap();
1666
1667        // Create a file to include as a blob
1668        let blob_source_file_path = outdir.path().join("blob_c");
1669        let blob_contents = "some data for blob";
1670        std::fs::write(&blob_source_file_path, blob_contents).unwrap();
1671
1672        // Create a file to include as a blob
1673        let blob_source_file_path2 = outdir.path().join("blob_d");
1674        let blob_contents = "some data for blob2";
1675        std::fs::write(&blob_source_file_path2, blob_contents).unwrap();
1676
1677        // Create the builder
1678        let mut builder = PackageBuilder::new("some_pkg_name", FAKE_ABI_REVISION);
1679        builder
1680            .add_file_as_blob("blob_c", blob_source_file_path.as_path().path_to_string().unwrap())
1681            .unwrap();
1682        builder
1683            .add_file_as_blob("blob_d", blob_source_file_path2.as_path().path_to_string().unwrap())
1684            .unwrap();
1685        builder
1686            .add_file_to_far(
1687                "meta/some/file",
1688                far_source_file_path.path().path_to_string().unwrap(),
1689            )
1690            .unwrap();
1691        builder
1692            .add_subpackage(&subpackage_url, sub_manifest.hash(), subpackage_manifest_path)
1693            .unwrap();
1694
1695        // Build the package
1696        let manifest = builder.build(&outdir, &metafar_path).unwrap();
1697
1698        let archive_outdir = TempDir::new().unwrap();
1699        let archive_path = archive_outdir.path().join("test.far");
1700        let archive_file = File::create(archive_path.clone()).unwrap();
1701        manifest.clone().archive(&outdir, &archive_file).await.unwrap();
1702
1703        let blobs_outdir = TempDir::new().unwrap().into_path();
1704
1705        let manifest_2 =
1706            PackageManifest::from_archive(&archive_path, &blobs_outdir, &manifest_outdir).unwrap();
1707        assert_eq!(manifest_2.package_path(), manifest.package_path());
1708
1709        let (_blob1_info, all_blobs_1) = manifest.package_and_subpackage_blobs().unwrap();
1710        let (_blob2_info, mut all_blobs_2) = manifest_2.package_and_subpackage_blobs().unwrap();
1711
1712        for (merkle, blob1) in all_blobs_1 {
1713            let blob2 = all_blobs_2.remove_entry(&merkle).unwrap().1;
1714            assert_eq!(
1715                std::fs::read(&blob1.source_path).unwrap(),
1716                std::fs::read(&blob2.source_path).unwrap(),
1717            );
1718        }
1719
1720        assert!(all_blobs_2.is_empty());
1721    }
1722
1723    #[test]
1724    fn test_write_package_manifest_already_relative() {
1725        let temp = TempDir::new().unwrap();
1726        let temp_dir = Utf8Path::from_path(temp.path()).unwrap();
1727
1728        let data_dir = temp_dir.join("data_source");
1729        let subpackage_dir = temp_dir.join("subpackage_manifests");
1730        let manifest_dir = temp_dir.join("manifest_dir");
1731        let manifest_path = manifest_dir.join("package_manifest.json");
1732
1733        std::fs::create_dir_all(&data_dir).unwrap();
1734        std::fs::create_dir_all(&subpackage_dir).unwrap();
1735        std::fs::create_dir_all(&manifest_dir).unwrap();
1736
1737        let manifest = PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
1738            package: PackageMetadata {
1739                name: "example".parse().unwrap(),
1740                version: "0".parse().unwrap(),
1741            },
1742            blobs: vec![BlobInfo {
1743                source_path: "../data_source/p1".into(),
1744                path: "data/p1".into(),
1745                merkle: HASH_0,
1746                size: 1,
1747            }],
1748            subpackages: vec![SubpackageInfo {
1749                manifest_path: format!("../subpackage_manifests/{HASH_0}"),
1750                name: "subpackage0".into(),
1751                merkle: HASH_0,
1752            }],
1753            repository: None,
1754            blob_sources_relative: RelativeTo::File,
1755            delivery_blob_type: None,
1756            abi_revision: None,
1757        }));
1758
1759        let result_manifest = manifest.clone().write_with_relative_paths(&manifest_path).unwrap();
1760
1761        // The manifest should not have been changed in this case.
1762        assert_eq!(result_manifest, manifest);
1763
1764        let parsed_manifest: Value =
1765            serde_json::from_reader(File::open(manifest_path).unwrap()).unwrap();
1766        let object = parsed_manifest.as_object().unwrap();
1767        let version = object.get("version").unwrap();
1768
1769        let blobs_value = object.get("blobs").unwrap();
1770        let blobs = blobs_value.as_array().unwrap();
1771        let blob_value = blobs.first().unwrap();
1772        let blob = blob_value.as_object().unwrap();
1773        let source_path_value = blob.get("source_path").unwrap();
1774        let source_path = source_path_value.as_str().unwrap();
1775
1776        let subpackages_value = object.get("subpackages").unwrap();
1777        let subpackages = subpackages_value.as_array().unwrap();
1778        let subpackage_value = subpackages.first().unwrap();
1779        let subpackage = subpackage_value.as_object().unwrap();
1780        let subpackage_manifest_path_value = subpackage.get("manifest_path").unwrap();
1781        let subpackage_manifest_path = subpackage_manifest_path_value.as_str().unwrap();
1782
1783        assert_eq!(version, "1");
1784        assert_eq!(source_path, "../data_source/p1");
1785        assert_eq!(subpackage_manifest_path, format!("../subpackage_manifests/{HASH_0}"));
1786    }
1787
1788    #[test]
1789    fn test_write_package_manifest_making_paths_relative() {
1790        let temp = TempDir::new().unwrap();
1791        let temp_dir = Utf8Path::from_path(temp.path()).unwrap();
1792
1793        let data_dir = temp_dir.join("data_source");
1794        let subpackage_dir = temp_dir.join("subpackage_manifests");
1795        let manifest_dir = temp_dir.join("manifest_dir");
1796        let manifest_path = manifest_dir.join("package_manifest.json");
1797        let manifest_dir_2 = temp_dir.join("subdir").join("other_manifest_dir");
1798        let manifest_path_2 = manifest_dir_2.join("package_manifest.json");
1799        let blob_source_path = data_dir.join("p2").to_string();
1800        let subpackage_manifest_path = subpackage_dir.join(HASH_1.to_string()).to_string();
1801
1802        std::fs::create_dir_all(&data_dir).unwrap();
1803        std::fs::create_dir_all(&subpackage_dir).unwrap();
1804        std::fs::create_dir_all(&manifest_dir).unwrap();
1805        std::fs::create_dir_all(&manifest_dir_2).unwrap();
1806
1807        let manifest = PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
1808            package: PackageMetadata {
1809                name: "example".parse().unwrap(),
1810                version: "0".parse().unwrap(),
1811            },
1812            blobs: vec![BlobInfo {
1813                source_path: blob_source_path,
1814                path: "data/p2".into(),
1815                merkle: HASH_0,
1816                size: 1,
1817            }],
1818            subpackages: vec![SubpackageInfo {
1819                manifest_path: subpackage_manifest_path,
1820                name: "subpackage1".into(),
1821                merkle: HASH_1,
1822            }],
1823            repository: None,
1824            blob_sources_relative: RelativeTo::WorkingDir,
1825            delivery_blob_type: None,
1826            abi_revision: None,
1827        }));
1828
1829        let result_manifest = manifest.clone().write_with_relative_paths(&manifest_path).unwrap();
1830        let result_manifest_2 =
1831            manifest.clone().write_with_relative_paths(&manifest_path_2).unwrap();
1832
1833        // The manifests should be equivalent, even if the paths have been relativized to
1834        // directories at different depths (which results in different manifest_paths).
1835        assert_eq!(result_manifest, result_manifest_2);
1836        assert_ne!(
1837            result_manifest.subpackages()[0].manifest_path,
1838            result_manifest_2.subpackages()[0].manifest_path
1839        );
1840
1841        let blob = result_manifest.blobs().first().unwrap();
1842        assert_eq!(blob.source_path, "../data_source/p2");
1843        let subpackage = result_manifest.subpackages().first().unwrap();
1844        assert_eq!(subpackage.manifest_path, format!("../subpackage_manifests/{HASH_1}"));
1845
1846        let parsed_manifest: serde_json::Value =
1847            serde_json::from_reader(File::open(manifest_path).unwrap()).unwrap();
1848
1849        let object = parsed_manifest.as_object().unwrap();
1850
1851        let blobs_value = object.get("blobs").unwrap();
1852        let blobs = blobs_value.as_array().unwrap();
1853        let blob_value = blobs.first().unwrap();
1854        let blob = blob_value.as_object().unwrap();
1855        let source_path_value = blob.get("source_path").unwrap();
1856        let source_path = source_path_value.as_str().unwrap();
1857
1858        let subpackages_value = object.get("subpackages").unwrap();
1859        let subpackages = subpackages_value.as_array().unwrap();
1860        let subpackage_value = subpackages.first().unwrap();
1861        let subpackage = subpackage_value.as_object().unwrap();
1862        let subpackage_manifest_path_value = subpackage.get("manifest_path").unwrap();
1863        let subpackage_manifest_path = subpackage_manifest_path_value.as_str().unwrap();
1864
1865        assert_eq!(source_path, "../data_source/p2");
1866        assert_eq!(subpackage_manifest_path, format!("../subpackage_manifests/{HASH_1}"));
1867    }
1868
1869    #[test]
1870    fn test_set_name() {
1871        let mut manifest = PackageManifest(VersionedPackageManifest::Version1(PackageManifestV1 {
1872            package: PackageMetadata {
1873                name: "original-name".parse().unwrap(),
1874                version: "0".parse().unwrap(),
1875            },
1876            blobs: vec![],
1877            subpackages: vec![],
1878            repository: None,
1879            blob_sources_relative: Default::default(),
1880            delivery_blob_type: None,
1881            abi_revision: None,
1882        }));
1883
1884        assert_eq!(manifest.name(), &"original-name".parse::<PackageName>().unwrap());
1885
1886        let new_name = "new-name".parse().unwrap();
1887        manifest.set_name(new_name);
1888
1889        assert_eq!(manifest.name(), &"new-name".parse::<PackageName>().unwrap());
1890    }
1891}