1use crate::PackageManifest;
6use anyhow::{Context as _, Result};
7use camino::Utf8PathBuf;
8use fuchsia_merkle::Hash;
9use fuchsia_url::RelativePackageUrl;
10use serde::de::Deserializer;
11use serde::{Deserialize, Serialize};
12use std::io;
13
14#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct SubpackagesBuildManifest(SubpackagesBuildManifestV0);
19
20impl SubpackagesBuildManifest {
21 pub fn entries(&self) -> &[SubpackagesBuildManifestEntry] {
23 &self.0.entries
24 }
25
26 pub fn to_subpackages(&self) -> Result<Vec<(RelativePackageUrl, Hash, Utf8PathBuf)>> {
28 let mut entries = Vec::with_capacity(self.0.entries.len());
29 for entry in &self.0.entries {
30 let manifest = PackageManifest::try_load_from(&entry.package_manifest_file)
32 .with_context(|| format!("reading {}", &entry.package_manifest_file))?;
33 let meta_far_blob = manifest
34 .blobs()
35 .iter()
36 .find(|b| b.path == "meta/")
37 .with_context(|| format!("finding meta/in {}", &entry.package_manifest_file))?;
38
39 let url = match &entry.kind {
41 SubpackagesBuildManifestEntryKind::Url(url) => url.clone(),
42 SubpackagesBuildManifestEntryKind::Empty
43 | SubpackagesBuildManifestEntryKind::MetaPackageFile(_) => {
44 manifest.name().clone().into()
45 }
46 };
47
48 entries.push((url, meta_far_blob.merkle, entry.package_manifest_file.clone()));
49 }
50 Ok(entries)
51 }
52
53 pub fn deserialize(reader: impl io::BufRead) -> Result<Self> {
55 Ok(SubpackagesBuildManifest(serde_json::from_reader(reader)?))
56 }
57
58 pub fn serialize(&self, writer: impl io::Write) -> Result<()> {
60 Ok(serde_json::to_writer(writer, &self.0.entries)?)
61 }
62}
63
64#[derive(Clone, Debug, PartialEq, Eq)]
65struct SubpackagesBuildManifestV0 {
66 entries: Vec<SubpackagesBuildManifestEntry>,
67}
68
69impl From<Vec<SubpackagesBuildManifestEntry>> for SubpackagesBuildManifest {
70 fn from(entries: Vec<SubpackagesBuildManifestEntry>) -> Self {
71 SubpackagesBuildManifest(SubpackagesBuildManifestV0 { entries })
72 }
73}
74
75impl<'de> Deserialize<'de> for SubpackagesBuildManifestV0 {
76 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
77 where
78 D: Deserializer<'de>,
79 {
80 #[derive(Deserialize)]
81 struct Helper {
82 #[serde(flatten)]
83 helper_kind: Option<HelperKind>,
84 package_manifest_file: Utf8PathBuf,
85 }
86
87 #[derive(Deserialize)]
88 #[serde(untagged)]
89 enum HelperKind {
90 Name { name: RelativePackageUrl },
91 File { meta_package_file: Utf8PathBuf },
92 }
93
94 let manifest_entries = Vec::<Helper>::deserialize(deserializer)?;
95
96 let mut entries = vec![];
97 for Helper { helper_kind, package_manifest_file } in manifest_entries {
98 let kind = match helper_kind {
99 Some(HelperKind::Name { name }) => SubpackagesBuildManifestEntryKind::Url(name),
100 Some(HelperKind::File { meta_package_file }) => {
101 SubpackagesBuildManifestEntryKind::MetaPackageFile(meta_package_file)
102 }
103 None => SubpackagesBuildManifestEntryKind::Empty,
104 };
105 entries.push(SubpackagesBuildManifestEntry { kind, package_manifest_file });
106 }
107
108 Ok(SubpackagesBuildManifestV0 { entries })
109 }
110}
111
112#[derive(Clone, Debug, PartialEq, Eq)]
113pub struct SubpackagesBuildManifestEntry {
114 pub kind: SubpackagesBuildManifestEntryKind,
116
117 pub package_manifest_file: Utf8PathBuf,
119}
120
121impl Serialize for SubpackagesBuildManifestEntry {
122 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
123 where
124 S: serde::Serializer,
125 {
126 #[derive(Serialize)]
127 struct Helper<'a> {
128 #[serde(skip_serializing_if = "Option::is_none")]
129 name: Option<&'a RelativePackageUrl>,
130 #[serde(skip_serializing_if = "Option::is_none")]
131 meta_package_file: Option<&'a Utf8PathBuf>,
132 package_manifest_file: &'a Utf8PathBuf,
133 }
134 let mut helper = Helper {
135 name: None,
136 meta_package_file: None,
137 package_manifest_file: &self.package_manifest_file,
138 };
139 match &self.kind {
140 SubpackagesBuildManifestEntryKind::Empty => {}
141 SubpackagesBuildManifestEntryKind::Url(url) => helper.name = Some(url),
142 SubpackagesBuildManifestEntryKind::MetaPackageFile(path) => {
143 helper.meta_package_file = Some(path)
144 }
145 }
146 helper.serialize(serializer)
147 }
148}
149
150impl SubpackagesBuildManifestEntry {
151 pub fn new(
153 kind: SubpackagesBuildManifestEntryKind,
154 package_manifest_file: Utf8PathBuf,
155 ) -> Self {
156 Self { kind, package_manifest_file }
157 }
158}
159
160#[derive(Clone, Debug, PartialEq, Eq)]
161pub enum SubpackagesBuildManifestEntryKind {
162 Empty,
163 Url(RelativePackageUrl),
164 MetaPackageFile(Utf8PathBuf),
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170 use crate::{MetaPackage, PackageBuilder};
171 use assert_matches::assert_matches;
172 use camino::Utf8Path;
173 use fuchsia_url::PackageName;
174 use serde_json::json;
175 use std::fs::File;
176
177 const FAKE_ABI_REVISION: version_history::AbiRevision =
178 version_history::AbiRevision::from_u64(0xdc6e746980ce30a9);
179
180 #[test]
181 fn test_deserialize() {
182 let tmp = tempfile::tempdir().unwrap();
183 let dir = Utf8Path::from_path(tmp.path()).unwrap();
184
185 let pkg1_name = PackageName::try_from("pkg1".to_string()).unwrap();
187 let pkg1_url = RelativePackageUrl::from(pkg1_name.clone());
188 let pkg1_package_manifest_file = dir.join("pkg1-package_manifest.json");
189 let pkg1_meta_far_file = dir.join("pkg1-meta.far");
190
191 let pkg2_name = PackageName::try_from("pkg2".to_string()).unwrap();
192 let pkg2_url = RelativePackageUrl::from(pkg2_name.clone());
193 let pkg2_meta_package_file = dir.join("pkg2-meta-package");
194 let pkg2_package_manifest_file = dir.join("pkg2-package_manifest.json");
195 let pkg2_meta_far_file = dir.join("pkg2-meta.far");
196
197 let meta_package = MetaPackage::from_name_and_variant_zero(pkg2_name.clone());
199 meta_package.serialize(File::create(&pkg2_meta_package_file).unwrap()).unwrap();
200
201 let mut builder = PackageBuilder::new(&pkg1_name, FAKE_ABI_REVISION);
202 builder.manifest_path(&pkg1_package_manifest_file);
203 let manifest = builder.build(dir, pkg1_meta_far_file).unwrap();
204 let pkg1_hash = manifest.blobs().iter().find(|b| b.path == "meta/").unwrap().merkle;
205
206 let mut builder = PackageBuilder::new(&pkg2_name, FAKE_ABI_REVISION);
207 builder.manifest_path(&pkg2_package_manifest_file);
208 let manifest = builder.build(dir, pkg2_meta_far_file).unwrap();
209 let pkg2_hash = manifest.blobs().iter().find(|b| b.path == "meta/").unwrap().merkle;
210
211 let subpackages_build_manifest_path = dir.join("subpackages-build-manifest");
213 serde_json::to_writer(
214 File::create(&subpackages_build_manifest_path).unwrap(),
215 &json!([
216 {
217 "package_manifest_file": pkg1_package_manifest_file.to_string(),
218 },
219 {
220 "name": pkg1_name.to_string(),
221 "package_manifest_file": pkg1_package_manifest_file.to_string(),
222 },
223 {
224 "meta_package_file": pkg2_meta_package_file.to_string(),
225 "package_manifest_file": pkg2_package_manifest_file.to_string(),
226 },
227 ]),
228 )
229 .unwrap();
230
231 let manifest = SubpackagesBuildManifest::deserialize(io::BufReader::new(
232 File::open(&subpackages_build_manifest_path).unwrap(),
233 ))
234 .unwrap();
235
236 assert_eq!(
237 manifest.0.entries,
238 vec![
239 SubpackagesBuildManifestEntry {
240 kind: SubpackagesBuildManifestEntryKind::Empty,
241 package_manifest_file: pkg1_package_manifest_file.clone(),
242 },
243 SubpackagesBuildManifestEntry {
244 kind: SubpackagesBuildManifestEntryKind::Url(pkg1_url.clone()),
245 package_manifest_file: pkg1_package_manifest_file.clone(),
246 },
247 SubpackagesBuildManifestEntry {
248 kind: SubpackagesBuildManifestEntryKind::MetaPackageFile(
249 pkg2_meta_package_file
250 ),
251 package_manifest_file: pkg2_package_manifest_file.clone(),
252 },
253 ]
254 );
255
256 assert_eq!(
258 manifest.to_subpackages().unwrap(),
259 vec![
260 (pkg1_url.clone(), pkg1_hash, pkg1_package_manifest_file.clone()),
261 (pkg1_url, pkg1_hash, pkg1_package_manifest_file),
262 (pkg2_url, pkg2_hash, pkg2_package_manifest_file),
263 ]
264 );
265 }
266
267 #[test]
268 fn test_meta_package_not_found() {
269 let tmp = tempfile::tempdir().unwrap();
270 let dir = Utf8Path::from_path(tmp.path()).unwrap();
271
272 let pkg_meta_package_file = dir.join("pkg-meta-package");
273 let pkg_package_manifest_file = dir.join("package_manifest.json");
274 let pkg_meta_far_file = dir.join("meta.far");
275
276 let subpackages_build_manifest_path = dir.join("subpackages-build-manifest");
277 serde_json::to_writer(
278 File::create(&subpackages_build_manifest_path).unwrap(),
279 &json!([
280 {
281 "meta_package_file": pkg_meta_package_file.to_string(),
282 "package_manifest_file": pkg_package_manifest_file.to_string(),
283 },
284 ]),
285 )
286 .unwrap();
287
288 let manifest = SubpackagesBuildManifest::deserialize(io::BufReader::new(
289 File::open(&subpackages_build_manifest_path).unwrap(),
290 ))
291 .unwrap();
292
293 assert_matches!(
295 manifest.to_subpackages(),
296 Err(err) if err.downcast_ref::<io::Error>().unwrap().kind() == io::ErrorKind::NotFound
297 );
298
299 let mut builder = PackageBuilder::new("pkg", FAKE_ABI_REVISION);
301 builder.manifest_path(&pkg_package_manifest_file);
302 let package_manifest = builder.build(dir, pkg_meta_far_file).unwrap();
303 let pkg_hash = package_manifest.blobs().iter().find(|b| b.path == "meta/").unwrap().merkle;
304
305 let pkg_name = PackageName::try_from("pkg".to_string()).unwrap();
306 MetaPackage::from_name_and_variant_zero(pkg_name.clone())
307 .serialize(File::create(&pkg_meta_package_file).unwrap())
308 .unwrap();
309 let pkg_url = RelativePackageUrl::from(pkg_name);
310
311 assert_eq!(
312 manifest.to_subpackages().unwrap(),
313 vec![(pkg_url, pkg_hash, pkg_package_manifest_file)]
314 );
315 }
316
317 #[test]
318 fn test_merkle_file_not_found() {
319 let tmp = tempfile::tempdir().unwrap();
320 let dir = Utf8Path::from_path(tmp.path()).unwrap();
321
322 let pkg_name = PackageName::try_from("pkg".to_string()).unwrap();
323 let pkg_url = RelativePackageUrl::from(pkg_name);
324 let pkg_package_manifest_file = dir.join("package_manifest.json");
325 let pkg_meta_far_file = dir.join("meta.far");
326
327 let subpackages_build_manifest_path = dir.join("subpackages-build-manifest");
328 serde_json::to_writer(
329 File::create(&subpackages_build_manifest_path).unwrap(),
330 &json!([
331 {
332 "name": pkg_url.to_string(),
333 "package_manifest_file": pkg_package_manifest_file.to_string(),
334 },
335 ]),
336 )
337 .unwrap();
338
339 let manifest = SubpackagesBuildManifest::deserialize(io::BufReader::new(
340 File::open(&subpackages_build_manifest_path).unwrap(),
341 ))
342 .unwrap();
343
344 assert_matches!(
346 manifest.to_subpackages(),
347 Err(err) if err.downcast_ref::<io::Error>().unwrap().kind() == io::ErrorKind::NotFound
348 );
349
350 let mut builder = PackageBuilder::new("pkg", FAKE_ABI_REVISION);
352 builder.manifest_path(&pkg_package_manifest_file);
353 let package_manifest = builder.build(dir, pkg_meta_far_file).unwrap();
354 let pkg_hash = package_manifest.blobs().iter().find(|b| b.path == "meta/").unwrap().merkle;
355
356 assert_eq!(
357 manifest.to_subpackages().unwrap(),
358 vec![(pkg_url, pkg_hash, pkg_package_manifest_file)]
359 );
360 }
361
362 #[test]
363 fn test_serialize() {
364 let entries = vec![
365 SubpackagesBuildManifestEntry::new(
366 SubpackagesBuildManifestEntryKind::Empty,
367 "package-manifest-path-0".into(),
368 ),
369 SubpackagesBuildManifestEntry::new(
370 SubpackagesBuildManifestEntryKind::Url("subpackage-name".parse().unwrap()),
371 "package-manifest-path-0".into(),
372 ),
373 SubpackagesBuildManifestEntry::new(
374 SubpackagesBuildManifestEntryKind::MetaPackageFile("file-path".into()),
375 "package-manifest-path-1".into(),
376 ),
377 ];
378 let manifest = SubpackagesBuildManifest::from(entries);
379
380 let mut bytes = vec![];
381 let () = manifest.serialize(&mut bytes).unwrap();
382 let actual_json: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
383
384 assert_eq!(
385 actual_json,
386 json!([
387 {
388 "package_manifest_file": "package-manifest-path-0"
389 },
390 {
391 "name": "subpackage-name",
392 "package_manifest_file": "package-manifest-path-0"
393 },
394 {
395 "meta_package_file": "file-path",
396 "package_manifest_file": "package-manifest-path-1"
397 },
398 ])
399 );
400 }
401
402 #[test]
403 fn test_serialize_deserialize() {
404 let entries = vec![
405 SubpackagesBuildManifestEntry::new(
406 SubpackagesBuildManifestEntryKind::Empty,
407 "package-manifest-path-0".into(),
408 ),
409 SubpackagesBuildManifestEntry::new(
410 SubpackagesBuildManifestEntryKind::Url("subpackage-name".parse().unwrap()),
411 "package-manifest-path-0".into(),
412 ),
413 SubpackagesBuildManifestEntry::new(
414 SubpackagesBuildManifestEntryKind::MetaPackageFile("file-path".into()),
415 "package-manifest-path-1".into(),
416 ),
417 ];
418 let manifest = SubpackagesBuildManifest::from(entries);
419
420 let mut bytes = vec![];
421 let () = manifest.serialize(&mut bytes).unwrap();
422 let deserialized =
423 SubpackagesBuildManifest::deserialize(io::BufReader::new(bytes.as_slice())).unwrap();
424
425 assert_eq!(deserialized, manifest);
426 }
427}