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