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