pyoxidizerlib/py_packaging/
resource.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5/*!
6Defines primitives representing Python resources.
7*/
8
9use {
10    anyhow::Result,
11    python_packaging::{
12        module_util::{packages_from_module_name, resolve_path_for_module},
13        resource::{
14            PythonExtensionModule, PythonModuleSource, PythonPackageDistributionResource,
15            PythonPackageResource,
16        },
17    },
18    simple_file_manifest::{FileEntry, FileManifest},
19};
20
21pub trait AddToFileManifest {
22    /// Add the object to a FileManifest instance.
23    fn add_to_file_manifest(&self, manifest: &mut FileManifest, prefix: &str) -> Result<()>;
24}
25
26impl AddToFileManifest for PythonModuleSource {
27    fn add_to_file_manifest(&self, manifest: &mut FileManifest, prefix: &str) -> Result<()> {
28        let content = self.source.resolve_content()?;
29
30        manifest.add_file_entry(&self.resolve_path(prefix), content)?;
31
32        for package in packages_from_module_name(&self.name) {
33            let package_path = resolve_path_for_module(prefix, &package, true, None);
34
35            if !manifest.has_path(&package_path) {
36                manifest.add_file_entry(&package_path, vec![])?;
37            }
38        }
39
40        Ok(())
41    }
42}
43
44impl AddToFileManifest for PythonPackageResource {
45    fn add_to_file_manifest(&self, manifest: &mut FileManifest, prefix: &str) -> Result<()> {
46        let dest_path = self.resolve_path(prefix);
47
48        manifest.add_file_entry(&dest_path, self.data.resolve_content()?)?;
49
50        Ok(())
51    }
52}
53
54impl AddToFileManifest for PythonPackageDistributionResource {
55    fn add_to_file_manifest(&self, manifest: &mut FileManifest, prefix: &str) -> Result<()> {
56        let dest_path = self.resolve_path(prefix);
57
58        manifest.add_file_entry(&dest_path, self.data.resolve_content()?)?;
59
60        Ok(())
61    }
62}
63
64impl AddToFileManifest for PythonExtensionModule {
65    fn add_to_file_manifest(&self, manifest: &mut FileManifest, prefix: &str) -> Result<()> {
66        if let Some(data) = &self.shared_library {
67            manifest.add_file_entry(
68                &self.resolve_path(prefix),
69                FileEntry::new_from_data(data.resolve_content()?, true),
70            )?;
71        }
72
73        Ok(())
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use {super::*, itertools::Itertools, simple_file_manifest::FileData, std::path::PathBuf};
80
81    const DEFAULT_CACHE_TAG: &str = "cpython-39";
82
83    #[test]
84    fn test_source_module_add_to_manifest_top_level() -> Result<()> {
85        let mut m = FileManifest::default();
86
87        PythonModuleSource {
88            name: "foo".to_string(),
89            source: FileData::Memory(vec![]),
90            is_package: false,
91            cache_tag: DEFAULT_CACHE_TAG.to_string(),
92            is_stdlib: false,
93            is_test: false,
94        }
95        .add_to_file_manifest(&mut m, ".")?;
96
97        PythonModuleSource {
98            name: "bar".to_string(),
99            source: FileData::Memory(vec![]),
100            is_package: false,
101            cache_tag: DEFAULT_CACHE_TAG.to_string(),
102            is_stdlib: false,
103            is_test: false,
104        }
105        .add_to_file_manifest(&mut m, ".")?;
106
107        let entries = m.iter_entries().collect_vec();
108        assert_eq!(entries.len(), 2);
109        assert_eq!(entries[0].0, &PathBuf::from("./bar.py"));
110        assert_eq!(entries[1].0, &PathBuf::from("./foo.py"));
111
112        Ok(())
113    }
114
115    #[test]
116    fn test_source_module_add_to_manifest_top_level_package() -> Result<()> {
117        let mut m = FileManifest::default();
118
119        PythonModuleSource {
120            name: "foo".to_string(),
121            source: FileData::Memory(vec![]),
122            is_package: true,
123            cache_tag: DEFAULT_CACHE_TAG.to_string(),
124            is_stdlib: false,
125            is_test: false,
126        }
127        .add_to_file_manifest(&mut m, ".")?;
128
129        let entries = m.iter_entries().collect_vec();
130        assert_eq!(entries.len(), 1);
131        assert_eq!(entries[0].0, &PathBuf::from("./foo/__init__.py"));
132
133        Ok(())
134    }
135
136    #[test]
137    fn test_source_module_add_to_manifest_missing_parent() -> Result<()> {
138        let mut m = FileManifest::default();
139
140        PythonModuleSource {
141            name: "root.parent.child".to_string(),
142            source: FileData::Memory(vec![]),
143            is_package: false,
144            cache_tag: DEFAULT_CACHE_TAG.to_string(),
145            is_stdlib: false,
146            is_test: false,
147        }
148        .add_to_file_manifest(&mut m, ".")?;
149
150        let entries = m.iter_entries().collect_vec();
151        assert_eq!(entries.len(), 3);
152        assert_eq!(entries[0].0, &PathBuf::from("./root/__init__.py"));
153        assert_eq!(entries[1].0, &PathBuf::from("./root/parent/__init__.py"));
154        assert_eq!(entries[2].0, &PathBuf::from("./root/parent/child.py"));
155
156        Ok(())
157    }
158
159    #[test]
160    fn test_source_module_add_to_manifest_missing_parent_package() -> Result<()> {
161        let mut m = FileManifest::default();
162
163        PythonModuleSource {
164            name: "root.parent.child".to_string(),
165            source: FileData::Memory(vec![]),
166            is_package: true,
167            cache_tag: DEFAULT_CACHE_TAG.to_string(),
168            is_stdlib: false,
169            is_test: false,
170        }
171        .add_to_file_manifest(&mut m, ".")?;
172
173        let entries = m.iter_entries().collect_vec();
174        assert_eq!(entries.len(), 3);
175        assert_eq!(entries[0].0, &PathBuf::from("./root/__init__.py"));
176        assert_eq!(entries[1].0, &PathBuf::from("./root/parent/__init__.py"));
177        assert_eq!(
178            entries[2].0,
179            &PathBuf::from("./root/parent/child/__init__.py")
180        );
181
182        Ok(())
183    }
184}