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