Skip to main content

fuchsia_pkg/
package_manifest.rs

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