fuchsia_pkg/
subpackages_build_manifest.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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/// Helper type for reading the build-time information based on the subpackage
15/// declarations declared in a build file (such as the `subpackages` list in
16/// a `fuchsia_package()` target, in `BUILD.gn`).
17#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct SubpackagesBuildManifest(SubpackagesBuildManifestV0);
19
20impl SubpackagesBuildManifest {
21    /// Return the subpackage manifest entries.
22    pub fn entries(&self) -> &[SubpackagesBuildManifestEntry] {
23        &self.0.entries
24    }
25
26    /// Open up each entry in the manifest and return the subpackage url and hash.
27    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            // Read the merkle from the package manifest.
31            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            // Read the name from the package manifest, but override with the name if provided.
40            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    /// Deserializes a `SubpackagesBuildManifest` from json.
54    pub fn deserialize(reader: impl io::BufRead) -> Result<Self> {
55        Ok(SubpackagesBuildManifest(serde_json::from_reader(reader)?))
56    }
57
58    /// Serializes a `SubpackagesBuildManifest` to json.
59    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    /// The subpackages build manifest entry's [EntryKind].
115    pub kind: SubpackagesBuildManifestEntryKind,
116
117    /// The package_manifest.json of the subpackage.
118    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    /// Construct a new [SubpackagesBuildManifestEntry].
152    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        // Generate a subpackages build manifest.
186        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        // Write out all the files.
198        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        // Make sure we can deserialize from the manifest format.
212        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        // Make sure we can convert the manifest into subpackages.
257        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        // We should error out if the package manifest file doesn't exist.
294        assert_matches!(
295            manifest.to_subpackages(),
296            Err(err) if err.downcast_ref::<io::Error>().unwrap().kind() == io::ErrorKind::NotFound
297        );
298
299        // It should work once we write the files.
300        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        // We should error out if the package manifest file doesn't exist.
345        assert_matches!(
346            manifest.to_subpackages(),
347            Err(err) if err.downcast_ref::<io::Error>().unwrap().kind() == io::ErrorKind::NotFound
348        );
349
350        // It should work once we write the files.
351        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}