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