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