fuchsia_pkg/
package_manifest_list.rs

1// Copyright 2021 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::RelativeTo;
6use anyhow::Result;
7use camino::{Utf8Path, Utf8PathBuf};
8use serde::{Deserialize, Serialize};
9use std::fs::{create_dir_all, File};
10use std::{slice, vec};
11use utf8_path::{path_relative_from_file, resolve_path_from_file};
12
13/// [PackageManifestList] is a construct that points at a path that contains a
14/// package manifest list. This will be used by the packaging tooling to
15/// understand when packages have changed.
16#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
17#[serde(transparent)]
18pub struct PackageManifestList(VersionedPackageManifestList);
19
20#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
21#[serde(tag = "version", content = "content", deny_unknown_fields)]
22enum VersionedPackageManifestList {
23    #[serde(rename = "1")]
24    Version1(PackageManifestListV1),
25}
26
27#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
28struct PackageManifestListV1 {
29    /// Are the blob source_paths relative to the working dir (default, as made
30    /// by 'pm') or the file containing the serialized manifest (new, portable,
31    /// behavior)
32    #[serde(default, skip_serializing_if = "RelativeTo::is_default")]
33    paths_relative: RelativeTo,
34    #[serde(default, skip_serializing_if = "Vec::is_empty")]
35    manifests: Vec<Utf8PathBuf>,
36}
37
38impl PackageManifestList {
39    /// Construct a new [PackageManifestList].
40    #[allow(clippy::new_without_default)]
41    pub fn new() -> Self {
42        PackageManifestList(VersionedPackageManifestList::Version1(PackageManifestListV1 {
43            manifests: vec![],
44            paths_relative: RelativeTo::default(),
45        }))
46    }
47
48    /// Push a package manifest path to the end of the [PackageManifestList].
49    pub fn push(&mut self, package_manifest_path: Utf8PathBuf) {
50        match &mut self.0 {
51            VersionedPackageManifestList::Version1(ref mut package_manifest_list_v1) => {
52                package_manifest_list_v1.manifests.push(package_manifest_path)
53            }
54        }
55    }
56
57    /// Returns an iterator over the package manifest path entries.
58    pub fn iter(&self) -> Iter<'_> {
59        match &self.0 {
60            VersionedPackageManifestList::Version1(package_manifest_list_v1) => {
61                Iter(package_manifest_list_v1.manifests.iter())
62            }
63        }
64    }
65
66    pub fn from_reader(
67        manifest_list_path: &Utf8Path,
68        mut reader: impl std::io::Read,
69    ) -> anyhow::Result<Self> {
70        let mut bytes = Vec::new();
71        reader.read_to_end(&mut bytes)?;
72
73        let versioned_list = match serde_json::from_slice(bytes.as_slice())? {
74            VersionedPackageManifestList::Version1(manifest_list) => {
75                VersionedPackageManifestList::Version1(
76                    manifest_list.resolve_source_paths(manifest_list_path)?,
77                )
78            }
79        };
80
81        Ok(Self(versioned_list))
82    }
83
84    pub fn write_with_relative_paths(self, path: &Utf8Path) -> anyhow::Result<Self> {
85        let versioned = match &self.0 {
86            VersionedPackageManifestList::Version1(package_manifest_list) => {
87                VersionedPackageManifestList::Version1(
88                    package_manifest_list.clone().write_with_relative_paths(path)?,
89                )
90            }
91        };
92
93        Ok(PackageManifestList(versioned))
94    }
95
96    /// Write the package list manifest to this path.
97    pub fn to_writer(&self, writer: impl std::io::Write) -> Result<(), std::io::Error> {
98        serde_json::to_writer(writer, &self.0).unwrap();
99        Ok(())
100    }
101}
102
103impl PackageManifestListV1 {
104    pub fn write_with_relative_paths(self, manifest_list_path: &Utf8Path) -> anyhow::Result<Self> {
105        // If necessary, adjust manifest files to relative paths
106        let manifest_list = if let RelativeTo::WorkingDir = &self.paths_relative {
107            let manifests = self
108                .manifests
109                .into_iter()
110                .map(|manifest_path| {
111                    path_relative_from_file(manifest_path, manifest_list_path).unwrap()
112                })
113                .collect::<Vec<_>>();
114            Self { manifests, paths_relative: RelativeTo::File }
115        } else {
116            self
117        };
118
119        let versioned_manifest = VersionedPackageManifestList::Version1(manifest_list.clone());
120
121        create_dir_all(manifest_list_path.parent().unwrap())?;
122        let file = File::create(manifest_list_path)?;
123        serde_json::to_writer(file, &versioned_manifest)?;
124
125        Ok(manifest_list)
126    }
127
128    pub fn resolve_source_paths(self, manifest_list_path: &Utf8Path) -> anyhow::Result<Self> {
129        if let RelativeTo::File = self.paths_relative {
130            let manifests = self
131                .manifests
132                .into_iter()
133                .map(|manifest_path| {
134                    resolve_path_from_file(manifest_path, manifest_list_path).unwrap()
135                })
136                .collect::<Vec<_>>();
137            Ok(Self { manifests, ..self })
138        } else {
139            Ok(self)
140        }
141    }
142}
143
144impl IntoIterator for PackageManifestList {
145    type Item = Utf8PathBuf;
146    type IntoIter = IntoIter;
147
148    fn into_iter(self) -> Self::IntoIter {
149        match self.0 {
150            VersionedPackageManifestList::Version1(package_manifest_list_v1) => {
151                IntoIter(package_manifest_list_v1.manifests.into_iter())
152            }
153        }
154    }
155}
156
157impl From<Vec<Utf8PathBuf>> for PackageManifestList {
158    fn from(package_manifest_list: Vec<Utf8PathBuf>) -> Self {
159        Self(VersionedPackageManifestList::Version1(PackageManifestListV1 {
160            manifests: package_manifest_list,
161            paths_relative: RelativeTo::default(),
162        }))
163    }
164}
165
166impl From<PackageManifestList> for Vec<Utf8PathBuf> {
167    fn from(package_manifest_list: PackageManifestList) -> Self {
168        match package_manifest_list.0 {
169            VersionedPackageManifestList::Version1(package_manifest_list_v1) => {
170                package_manifest_list_v1.manifests
171            }
172        }
173    }
174}
175
176impl FromIterator<Utf8PathBuf> for PackageManifestList {
177    fn from_iter<T>(iter: T) -> Self
178    where
179        T: IntoIterator<Item = Utf8PathBuf>,
180    {
181        Self(VersionedPackageManifestList::Version1(PackageManifestListV1 {
182            manifests: iter.into_iter().collect(),
183            paths_relative: RelativeTo::default(),
184        }))
185    }
186}
187
188/// Immutable iterator over the package manifest paths.
189pub struct Iter<'a>(slice::Iter<'a, Utf8PathBuf>);
190
191impl<'a> Iterator for Iter<'a> {
192    type Item = &'a Utf8PathBuf;
193
194    fn next(&mut self) -> Option<Self::Item> {
195        self.0.next()
196    }
197}
198
199/// An iterator that moves out of the [PackageManifestList].
200pub struct IntoIter(vec::IntoIter<Utf8PathBuf>);
201
202impl Iterator for IntoIter {
203    type Item = Utf8PathBuf;
204
205    fn next(&mut self) -> Option<Self::Item> {
206        self.0.next()
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213    use serde_json::json;
214    use tempfile::TempDir;
215
216    #[test]
217    fn test_serialize() {
218        let package_manifest_list = PackageManifestList::from(vec![
219            "obj/build/images/config-data/package_manifest.json".into(),
220            "obj/build/images/shell-commands/package_manifest.json".into(),
221            "obj/src/sys/component_index/component_index/package_manifest.json".into(),
222        ]);
223
224        assert_eq!(
225            serde_json::to_value(package_manifest_list).unwrap(),
226            json!(
227                {
228                    "content": {
229                        "manifests": [
230                            "obj/build/images/config-data/package_manifest.json",
231                            "obj/build/images/shell-commands/package_manifest.json",
232                            "obj/src/sys/component_index/component_index/package_manifest.json",
233                        ]
234                    },
235                    "version": "1"
236                }
237            ),
238        );
239    }
240
241    #[test]
242    fn test_iter_from_reader_json_v1() {
243        let temp = TempDir::new().unwrap();
244        let temp_dir = Utf8Path::from_path(temp.path()).unwrap();
245
246        let manifest_dir = temp_dir.join("manifest_dir");
247        let manifest_path = manifest_dir.join("package_manifest_list.json");
248        std::fs::create_dir_all(&manifest_dir).unwrap();
249
250        // RelativeTo::WorkingDir
251        let raw_package_manifest_list_json_v1 = r#"{
252            "version": "1",
253            "content": {
254                "paths_relative": "working_dir",
255                "manifests": [
256                    "obj/build/images/config-data/package_manifest.json",
257                    "obj/build/images/shell-commands/package_manifest.json",
258                    "obj/src/sys/component_index/component_index/package_manifest.json"
259                ]
260            }
261        }"#;
262        std::fs::write(&manifest_path, raw_package_manifest_list_json_v1).unwrap();
263
264        let package_manifest_list =
265            PackageManifestList::from_reader(&manifest_path, File::open(&manifest_path).unwrap())
266                .unwrap();
267        assert_eq!(
268            PackageManifestList(VersionedPackageManifestList::Version1(PackageManifestListV1 {
269                paths_relative: RelativeTo::WorkingDir,
270                manifests: vec![
271                    "obj/build/images/config-data/package_manifest.json".into(),
272                    "obj/build/images/shell-commands/package_manifest.json".into(),
273                    "obj/src/sys/component_index/component_index/package_manifest.json".into(),
274                ]
275            })),
276            package_manifest_list
277        );
278
279        assert_eq!(
280            package_manifest_list.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
281            vec![
282                "obj/build/images/config-data/package_manifest.json",
283                "obj/build/images/shell-commands/package_manifest.json",
284                "obj/src/sys/component_index/component_index/package_manifest.json",
285            ]
286        );
287
288        // RelativeTo::File
289        let raw_package_manifest_list_json_v1 = r#"{
290            "version": "1",
291            "content": {
292                "paths_relative": "file",
293                "manifests": [
294                    "obj/build/images/config-data/package_manifest.json",
295                    "obj/build/images/shell-commands/package_manifest.json",
296                    "obj/src/sys/component_index/component_index/package_manifest.json"
297                ]
298            }
299        }"#;
300        std::fs::write(&manifest_path, raw_package_manifest_list_json_v1).unwrap();
301
302        let package_manifest_list =
303            PackageManifestList::from_reader(&manifest_path, File::open(&manifest_path).unwrap())
304                .unwrap();
305        assert_eq!(
306            PackageManifestList(VersionedPackageManifestList::Version1(PackageManifestListV1 {
307                paths_relative: RelativeTo::File,
308                manifests: vec![
309                    manifest_dir.join("obj/build/images/config-data/package_manifest.json"),
310                    manifest_dir.join("obj/build/images/shell-commands/package_manifest.json"),
311                    manifest_dir
312                        .join("obj/src/sys/component_index/component_index/package_manifest.json"),
313                ]
314            })),
315            package_manifest_list
316        );
317
318        assert_eq!(
319            package_manifest_list.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
320            vec![
321                manifest_dir.join("obj/build/images/config-data/package_manifest.json"),
322                manifest_dir.join("obj/build/images/shell-commands/package_manifest.json"),
323                manifest_dir
324                    .join("obj/src/sys/component_index/component_index/package_manifest.json"),
325            ]
326        );
327    }
328
329    #[test]
330    fn test_to_writer() {
331        let package_manifest_list = PackageManifestList::from(vec![
332            "obj/build/images/config-data/package_manifest.json".into(),
333            "obj/build/images/shell-commands/package_manifest.json".into(),
334            "obj/src/sys/component_index/component_index/package_manifest.json".into(),
335        ]);
336
337        let mut out = vec![];
338        package_manifest_list.to_writer(&mut out).unwrap();
339
340        assert_eq!(
341            String::from_utf8(out).unwrap(),
342            "{\
343              \"version\":\"1\",\
344              \"content\":{\
345                  \"manifests\":[\
346                      \"obj/build/images/config-data/package_manifest.json\",\
347                      \"obj/build/images/shell-commands/package_manifest.json\",\
348                      \"obj/src/sys/component_index/component_index/package_manifest.json\"\
349                  ]\
350              }\
351          }"
352        );
353    }
354
355    #[test]
356    fn test_write_package_manifest_list_making_paths_relative() {
357        let temp = TempDir::new().unwrap();
358        let temp_dir = Utf8Path::from_path(temp.path()).unwrap();
359
360        let other_dir = temp_dir.join("other_dir");
361        let manifest_dir = temp_dir.join("manifest_dir");
362        let manifest_path = manifest_dir.join("package_manifest_list.json");
363
364        std::fs::create_dir_all(&manifest_dir).unwrap();
365        std::fs::create_dir_all(&other_dir).unwrap();
366
367        let package_manifest_list =
368            PackageManifestList(VersionedPackageManifestList::Version1(PackageManifestListV1 {
369                paths_relative: RelativeTo::WorkingDir,
370                manifests: vec![
371                    other_dir.join("foo.package_manifest.json"),
372                    other_dir.join("bar.package_manifest.json"),
373                ],
374            }));
375
376        // manifests will now have relative paths
377        let result_manifest_list =
378            package_manifest_list.write_with_relative_paths(&manifest_path).unwrap();
379
380        // TODO(https://fxbug.dev/42077054): Add "re-read" test once writer is functioning.
381        let path_relative_package_manifest_list =
382            PackageManifestList(VersionedPackageManifestList::Version1(PackageManifestListV1 {
383                paths_relative: RelativeTo::File,
384                manifests: vec![
385                    "../other_dir/foo.package_manifest.json".into(),
386                    "../other_dir/bar.package_manifest.json".into(),
387                ],
388            }));
389
390        assert_eq!(result_manifest_list, path_relative_package_manifest_list);
391    }
392
393    #[test]
394    fn test_write_package_manifest_list_already_relative() {
395        let temp = TempDir::new().unwrap();
396        let temp_dir = Utf8Path::from_path(temp.path()).unwrap();
397
398        let manifest_dir = temp_dir.join("manifest_dir");
399        let manifest_path = manifest_dir.join("package_manifest_list.json");
400
401        std::fs::create_dir_all(&manifest_dir).unwrap();
402
403        let package_manifest_list =
404            PackageManifestList(VersionedPackageManifestList::Version1(PackageManifestListV1 {
405                paths_relative: RelativeTo::File,
406                manifests: vec![
407                    "../other_dir/foo.package_manifest.json".into(),
408                    "../other_dir/bar.package_manifest.json".into(),
409                ],
410            }));
411
412        // manifests will be untouched, as paths are already relative.
413        let result_manifest_list =
414            package_manifest_list.clone().write_with_relative_paths(&manifest_path).unwrap();
415
416        // TODO(https://fxbug.dev/42077054): Add "re-read" test once writer is functioning.
417        assert_eq!(result_manifest_list, package_manifest_list);
418    }
419
420    #[test]
421    fn test_iter() {
422        let package_manifest_list = PackageManifestList::from(vec![
423            "obj/build/images/config-data/package_manifest.json".into(),
424            "obj/build/images/shell-commands/package_manifest.json".into(),
425            "obj/src/sys/component_index/component_index/package_manifest.json".into(),
426        ]);
427
428        assert_eq!(
429            package_manifest_list.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
430            vec![
431                "obj/build/images/config-data/package_manifest.json",
432                "obj/build/images/shell-commands/package_manifest.json",
433                "obj/src/sys/component_index/component_index/package_manifest.json",
434            ]
435        );
436    }
437
438    #[test]
439    fn test_into_iter() {
440        let entries = vec![
441            "obj/build/images/config-data/package_manifest.json".into(),
442            "obj/build/images/shell-commands/package_manifest.json".into(),
443            "obj/src/sys/component_index/component_index/package_manifest.json".into(),
444        ];
445        let package_manifest_list = PackageManifestList::from(entries.clone());
446
447        assert_eq!(package_manifest_list.into_iter().collect::<Vec<_>>(), entries,);
448    }
449}