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