pyoxidizerlib/starlark/
python_module_source.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
5use {
6    super::python_resource::ResourceCollectionContext,
7    python_packaging::{
8        resource::{PythonModuleSource, PythonResource},
9        resource_collection::PythonResourceAddCollectionContext,
10    },
11    starlark::values::{
12        error::{RuntimeError, UnsupportedOperation, ValueError},
13        {Mutable, TypedValue, Value, ValueResult},
14    },
15    std::sync::{Arc, Mutex, MutexGuard},
16};
17
18#[derive(Debug)]
19pub struct PythonModuleSourceWrapper {
20    pub m: PythonModuleSource,
21    pub add_context: Option<PythonResourceAddCollectionContext>,
22}
23
24/// Starlark value wrapper for `PythonModuleSource`.
25#[derive(Debug, Clone)]
26pub struct PythonModuleSourceValue {
27    inner: Arc<Mutex<PythonModuleSourceWrapper>>,
28    name: String,
29}
30
31impl PythonModuleSourceValue {
32    pub fn new(module: PythonModuleSource) -> Self {
33        let name = module.name.clone();
34
35        Self {
36            inner: Arc::new(Mutex::new(PythonModuleSourceWrapper {
37                m: module,
38                add_context: None,
39            })),
40            name,
41        }
42    }
43
44    pub fn inner(&self, label: &str) -> Result<MutexGuard<PythonModuleSourceWrapper>, ValueError> {
45        self.inner.try_lock().map_err(|e| {
46            ValueError::Runtime(RuntimeError {
47                code: "PYTHON_MODULE_SOURCE",
48                message: format!("failed to acquire lock: {}", e),
49                label: label.to_string(),
50            })
51        })
52    }
53}
54
55impl ResourceCollectionContext for PythonModuleSourceValue {
56    fn add_collection_context(
57        &self,
58    ) -> Result<Option<PythonResourceAddCollectionContext>, ValueError> {
59        Ok(self
60            .inner("PythonModuleSource.add_collection_context()")?
61            .add_context
62            .clone())
63    }
64
65    fn replace_add_collection_context(
66        &mut self,
67        context: PythonResourceAddCollectionContext,
68    ) -> Result<Option<PythonResourceAddCollectionContext>, ValueError> {
69        Ok(self
70            .inner("PythonModuleSource.replace_add_collection_context()")?
71            .add_context
72            .replace(context))
73    }
74
75    fn as_python_resource(&self) -> Result<PythonResource<'_>, ValueError> {
76        Ok(PythonResource::from(
77            self.inner("PythonModuleSource.as_python_resource()")?
78                .m
79                .clone(),
80        ))
81    }
82}
83
84impl TypedValue for PythonModuleSourceValue {
85    type Holder = Mutable<PythonModuleSourceValue>;
86    const TYPE: &'static str = "PythonModuleSource";
87
88    fn values_for_descendant_check_and_freeze(&self) -> Box<dyn Iterator<Item = Value>> {
89        Box::new(std::iter::empty())
90    }
91
92    fn to_str(&self) -> String {
93        format!("{}<name={}>", Self::TYPE, self.name)
94    }
95
96    fn to_repr(&self) -> String {
97        self.to_str()
98    }
99
100    fn get_attr(&self, attribute: &str) -> ValueResult {
101        let inner = self.inner(&format!("PythonModuleSource.{}", attribute))?;
102
103        let v = match attribute {
104            "is_stdlib" => Value::from(inner.m.is_stdlib),
105            "name" => Value::new(inner.m.name.clone()),
106            "source" => {
107                let source = inner.m.source.resolve_content().map_err(|e| {
108                    ValueError::from(RuntimeError {
109                        code: "PYOXIDIZER_SOURCE_ERROR",
110                        message: format!("error resolving source code: {}", e),
111                        label: "source".to_string(),
112                    })
113                })?;
114
115                let source = String::from_utf8(source).map_err(|_| {
116                    ValueError::from(RuntimeError {
117                        code: "PYOXIDIZER_SOURCE_ERROR",
118                        message: "error converting source code to UTF-8".to_string(),
119                        label: "source".to_string(),
120                    })
121                })?;
122
123                Value::new(source)
124            }
125            "is_package" => Value::new(inner.m.is_package),
126            attr => {
127                drop(inner);
128
129                return if self.add_collection_context_attrs().contains(&attr) {
130                    self.get_attr_add_collection_context(attr)
131                } else {
132                    Err(ValueError::OperationNotSupported {
133                        op: UnsupportedOperation::GetAttr(attr.to_string()),
134                        left: Self::TYPE.to_string(),
135                        right: None,
136                    })
137                };
138            }
139        };
140
141        Ok(v)
142    }
143
144    fn has_attr(&self, attribute: &str) -> Result<bool, ValueError> {
145        Ok(match attribute {
146            "name" => true,
147            "source" => true,
148            "is_package" => true,
149            "is_stdlib" => true,
150            attr => self.add_collection_context_attrs().contains(&attr),
151        })
152    }
153
154    fn set_attr(&mut self, attribute: &str, value: Value) -> Result<(), ValueError> {
155        if self.add_collection_context_attrs().contains(&attribute) {
156            self.set_attr_add_collection_context(attribute, value)
157        } else {
158            Err(ValueError::OperationNotSupported {
159                op: UnsupportedOperation::SetAttr(attribute.to_string()),
160                left: Self::TYPE.to_owned(),
161                right: None,
162            })
163        }
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use {
170        super::*,
171        crate::starlark::{python_distribution::PythonDistributionValue, testutil::*},
172        anyhow::Result,
173        starlark::values::none::NoneType,
174    };
175
176    #[test]
177    fn test_source_module_attrs() -> Result<()> {
178        let mut env = test_evaluation_context_builder()?.into_context()?;
179        add_exe(&mut env)?;
180
181        let dist_value = env.eval("dist")?;
182        let dist_ref = dist_value
183            .downcast_ref::<PythonDistributionValue>()
184            .unwrap();
185        let dist = dist_ref.distribution.as_ref().unwrap();
186
187        let mut m = env.eval("exe.make_python_module_source('foo', 'import bar')")?;
188
189        assert_eq!(m.get_type(), PythonModuleSourceValue::TYPE);
190        assert!(m.has_attr("name").unwrap());
191        assert_eq!(m.get_attr("name").unwrap().to_str(), "foo");
192
193        assert!(m.has_attr("source").unwrap());
194        assert_eq!(m.get_attr("source").unwrap().to_str(), "import bar");
195
196        assert!(m.has_attr("is_package").unwrap());
197        assert!(!m.get_attr("is_package").unwrap().to_bool());
198
199        assert!(m.has_attr("add_include").unwrap());
200        assert_eq!(m.get_attr("add_include").unwrap().get_type(), "bool");
201        assert!(m.get_attr("add_include").unwrap().to_bool());
202        m.set_attr("add_include", Value::new(false)).unwrap();
203        assert!(!m.get_attr("add_include").unwrap().to_bool());
204
205        assert!(m.has_attr("add_location").unwrap());
206        assert_eq!(m.get_attr("add_location").unwrap().to_str(), "in-memory");
207
208        m.set_attr("add_location", Value::from("in-memory"))
209            .unwrap();
210        assert_eq!(m.get_attr("add_location").unwrap().to_str(), "in-memory");
211
212        m.set_attr("add_location", Value::from("filesystem-relative:lib"))
213            .unwrap();
214        assert_eq!(
215            m.get_attr("add_location").unwrap().to_str(),
216            "filesystem-relative:lib"
217        );
218
219        assert!(m.has_attr("add_location_fallback").unwrap());
220
221        assert_eq!(
222            m.get_attr("add_location_fallback").unwrap().get_type(),
223            if dist.supports_in_memory_shared_library_loading() {
224                "string"
225            } else {
226                "NoneType"
227            }
228        );
229
230        m.set_attr("add_location_fallback", Value::from("in-memory"))
231            .unwrap();
232        assert_eq!(
233            m.get_attr("add_location_fallback").unwrap().to_str(),
234            "in-memory"
235        );
236
237        m.set_attr(
238            "add_location_fallback",
239            Value::from("filesystem-relative:lib"),
240        )
241        .unwrap();
242        assert_eq!(
243            m.get_attr("add_location_fallback").unwrap().to_str(),
244            "filesystem-relative:lib"
245        );
246
247        m.set_attr("add_location_fallback", Value::from(NoneType::None))
248            .unwrap();
249        assert_eq!(
250            m.get_attr("add_location_fallback").unwrap().get_type(),
251            "NoneType"
252        );
253
254        assert!(m.has_attr("add_source").unwrap());
255        assert_eq!(m.get_attr("add_source").unwrap().get_type(), "bool");
256        assert!(m.get_attr("add_source").unwrap().to_bool());
257        m.set_attr("add_source", Value::new(false)).unwrap();
258        assert!(!m.get_attr("add_source").unwrap().to_bool());
259
260        assert!(m.has_attr("add_bytecode_optimization_level_zero").unwrap());
261        assert_eq!(
262            m.get_attr("add_bytecode_optimization_level_zero")
263                .unwrap()
264                .get_type(),
265            "bool"
266        );
267        assert!(m
268            .get_attr("add_bytecode_optimization_level_zero")
269            .unwrap()
270            .to_bool(),);
271        m.set_attr("add_bytecode_optimization_level_zero", Value::new(false))
272            .unwrap();
273        assert!(!m
274            .get_attr("add_bytecode_optimization_level_zero")
275            .unwrap()
276            .to_bool());
277
278        assert!(m.has_attr("add_bytecode_optimization_level_one").unwrap());
279        assert_eq!(
280            m.get_attr("add_bytecode_optimization_level_one")
281                .unwrap()
282                .get_type(),
283            "bool"
284        );
285        assert!(!m
286            .get_attr("add_bytecode_optimization_level_one")
287            .unwrap()
288            .to_bool());
289        m.set_attr("add_bytecode_optimization_level_one", Value::new(true))
290            .unwrap();
291        assert!(m
292            .get_attr("add_bytecode_optimization_level_one")
293            .unwrap()
294            .to_bool(),);
295
296        assert!(m.has_attr("add_bytecode_optimization_level_two").unwrap());
297        assert_eq!(
298            m.get_attr("add_bytecode_optimization_level_two")
299                .unwrap()
300                .get_type(),
301            "bool"
302        );
303        assert!(!m
304            .get_attr("add_bytecode_optimization_level_two")
305            .unwrap()
306            .to_bool());
307        m.set_attr("add_bytecode_optimization_level_two", Value::new(true))
308            .unwrap();
309        assert!(m
310            .get_attr("add_bytecode_optimization_level_two")
311            .unwrap()
312            .to_bool());
313
314        Ok(())
315    }
316}