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