pyoxidizerlib/starlark/
file_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
5use {
6    super::{
7        python_executable::PythonExecutableValue,
8        python_extension_module::PythonExtensionModuleValue,
9        python_module_source::PythonModuleSourceValue,
10        python_package_distribution_resource::PythonPackageDistributionResourceValue,
11        python_package_resource::PythonPackageResourceValue,
12    },
13    crate::{
14        project_building::build_python_executable,
15        py_packaging::{binary::PythonBinaryBuilder, resource::AddToFileManifest},
16    },
17    anyhow::{anyhow, Context, Result},
18    log::warn,
19    simple_file_manifest::{FileEntry, FileManifest},
20    starlark::{
21        environment::TypeValues,
22        values::{
23            error::{RuntimeError, ValueError, INCORRECT_PARAMETER_TYPE_ERROR_CODE},
24            none::NoneType,
25            {Value, ValueResult},
26        },
27        {
28            starlark_fun, starlark_module, starlark_parse_param_type, starlark_signature,
29            starlark_signature_extraction, starlark_signatures,
30        },
31    },
32    std::{ops::DerefMut, path::Path},
33    tugger::starlark::file_manifest::FileManifestValue,
34};
35
36#[allow(clippy::too_many_arguments)]
37pub fn file_manifest_add_python_executable(
38    manifest: &mut FileManifestValue,
39    env: &crate::environment::Environment,
40    prefix: &str,
41    exe: &dyn PythonBinaryBuilder,
42    target: &str,
43    release: bool,
44    opt_level: &str,
45) -> Result<()> {
46    const LABEL: &str = "FileManifest.add_python_executable()";
47
48    let build = build_python_executable(env, &exe.name(), exe, target, opt_level, release)
49        .context("building Python executable")?;
50
51    let content = FileEntry::new_from_data(build.exe_data.clone(), true);
52
53    let use_prefix = if prefix == "." { "" } else { prefix };
54
55    let path = Path::new(use_prefix).join(build.exe_name);
56    let mut inner = manifest.inner(LABEL).map_err(|e| anyhow!("{:?}", e))?;
57    inner
58        .add_file_entry(&path, content)
59        .context("adding exe content to manifest")?;
60
61    // Add any additional files that the exe builder requires.
62    let mut extra_files = FileManifest::default();
63
64    for (path, entry) in build.binary_data.extra_files.iter_entries() {
65        warn!("adding extra file {} to {}", path.display(), prefix);
66        extra_files.add_file_entry(&Path::new(use_prefix).join(path), entry.clone())?;
67    }
68
69    inner.add_manifest(&extra_files)?;
70    drop(inner);
71
72    // Make the last added Python executable the default run target.
73    manifest.run_path = Some(path);
74
75    Ok(())
76}
77
78/// FileManifest.add_python_resource(prefix, resource)
79pub fn file_manifest_add_python_resource(
80    manifest: &mut FileManifestValue,
81    type_values: &TypeValues,
82    prefix: String,
83    resource: &Value,
84) -> ValueResult {
85    const LABEL: &str = "FileManifest.add_python_resource()";
86
87    match resource.get_type() {
88        "PythonModuleSource" => {
89            let m = match resource.downcast_ref::<PythonModuleSourceValue>() {
90                Some(m) => Ok(m.inner(LABEL)?.m.clone()),
91                None => Err(ValueError::IncorrectParameterType),
92            }?;
93            warn!("adding source module {} to {}", m.name, prefix);
94
95            m.add_to_file_manifest(manifest.inner(LABEL)?.deref_mut(), &prefix)
96                .map_err(|e| {
97                    ValueError::from(RuntimeError {
98                        code: INCORRECT_PARAMETER_TYPE_ERROR_CODE,
99                        message: format!("{:?}", e),
100                        label: e.to_string(),
101                    })
102                })
103        }
104        "PythonPackageResource" => {
105            let m = match resource.downcast_ref::<PythonPackageResourceValue>() {
106                Some(m) => Ok(m.inner(LABEL)?.r.clone()),
107                None => Err(ValueError::IncorrectParameterType),
108            }?;
109
110            warn!("adding resource file {} to {}", m.symbolic_name(), prefix);
111            m.add_to_file_manifest(manifest.inner(LABEL)?.deref_mut(), &prefix)
112                .map_err(|e| {
113                    RuntimeError {
114                        code: INCORRECT_PARAMETER_TYPE_ERROR_CODE,
115                        message: format!("{:?}", e),
116                        label: e.to_string(),
117                    }
118                    .into()
119                })
120        }
121        "PythonPackageDistributionResource" => {
122            let m = match resource.downcast_ref::<PythonPackageDistributionResourceValue>() {
123                Some(m) => Ok(m.inner(LABEL)?.r.clone()),
124                None => Err(ValueError::IncorrectParameterType),
125            }?;
126            warn!(
127                "adding package distribution resource file {}:{} to {}",
128                m.package, m.name, prefix
129            );
130            m.add_to_file_manifest(manifest.inner(LABEL)?.deref_mut(), &prefix)
131                .map_err(|e| {
132                    ValueError::from(RuntimeError {
133                        code: INCORRECT_PARAMETER_TYPE_ERROR_CODE,
134                        message: format!("{:?}", e),
135                        label: e.to_string(),
136                    })
137                })
138        }
139        "PythonExtensionModule" => {
140            let extension = match resource.downcast_ref::<PythonExtensionModuleValue>() {
141                Some(e) => Ok(e.inner(LABEL)?.em.clone()),
142                None => Err(ValueError::IncorrectParameterType),
143            }?;
144
145            warn!("adding extension module {} to {}", extension.name, prefix);
146            extension
147                .add_to_file_manifest(manifest.inner(LABEL)?.deref_mut(), &prefix)
148                .map_err(|e| {
149                    ValueError::from(RuntimeError {
150                        code: "PYOXIDIZER_BUILD",
151                        message: format!("{:?}", e),
152                        label: "add_python_resource".to_string(),
153                    })
154                })
155        }
156
157        "PythonExecutable" => match resource.downcast_ref::<PythonExecutableValue>() {
158            Some(exe) => {
159                warn!(
160                    "adding Python executable {} to {}",
161                    exe.inner(LABEL)?.name(),
162                    prefix
163                );
164                let exe_manifest_value = exe.to_file_manifest(type_values, prefix)?;
165                let exe_manifest = exe_manifest_value
166                    .downcast_ref::<FileManifestValue>()
167                    .unwrap();
168                manifest
169                    .inner(LABEL)?
170                    .add_manifest(&*exe_manifest.inner(LABEL)?)
171                    .map_err(|e| {
172                        ValueError::from(RuntimeError {
173                            code: "PYOXIDIZER_BUILD",
174                            message: format!("{:?}", e),
175                            label: "add_python_resource".to_string(),
176                        })
177                    })?;
178                manifest.run_path = exe_manifest.run_path.clone();
179
180                Ok(())
181            }
182            None => Err(ValueError::IncorrectParameterType),
183        },
184
185        t => Err(ValueError::from(RuntimeError {
186            code: INCORRECT_PARAMETER_TYPE_ERROR_CODE,
187            message: format!("resource should be a Python resource type; got {}", t),
188            label: "bad argument type".to_string(),
189        })),
190    }?;
191
192    Ok(Value::new(NoneType::None))
193}
194
195/// FileManifest.add_python_resources(prefix, resources)
196pub fn file_manifest_add_python_resources(
197    manifest: &mut FileManifestValue,
198    type_values: &TypeValues,
199    prefix: String,
200    resources: &Value,
201) -> ValueResult {
202    for resource in &resources.iter()? {
203        file_manifest_add_python_resource(manifest, type_values, prefix.clone(), &resource)?;
204    }
205
206    Ok(Value::new(NoneType::None))
207}
208
209starlark_module! { file_resource_env =>
210    FileManifest.add_python_resource(env env, this, prefix: String, resource) {
211        let mut this = this.downcast_mut::<FileManifestValue>().unwrap().unwrap();
212        file_manifest_add_python_resource(&mut this, env, prefix, &resource)
213    }
214
215    FileManifest.add_python_resources(env env, this, prefix: String, resources) {
216        let mut this = this.downcast_mut::<FileManifestValue>().unwrap().unwrap();
217        file_manifest_add_python_resources(&mut this, env, prefix, &resources)
218    }
219}
220
221#[cfg(test)]
222mod tests {
223    use {
224        super::super::testutil::*,
225        super::*,
226        python_packaging::resource::{PythonModuleSource, PythonPackageResource},
227        simple_file_manifest::FileData,
228        std::path::PathBuf,
229    };
230
231    const DEFAULT_CACHE_TAG: &str = "cpython-39";
232
233    #[test]
234    fn test_add_python_source_module() -> Result<()> {
235        let m = FileManifestValue::new_from_args().unwrap();
236
237        let v = Value::new(PythonModuleSourceValue::new(PythonModuleSource {
238            name: "foo.bar".to_string(),
239            source: FileData::Memory(vec![]),
240            is_package: false,
241            cache_tag: DEFAULT_CACHE_TAG.to_string(),
242            is_stdlib: false,
243            is_test: false,
244        }));
245
246        let mut env = test_evaluation_context_builder()?.into_context()?;
247        env.set_var("m", m).unwrap();
248        env.set_var("v", v).unwrap();
249
250        env.eval("m.add_python_resource('lib', v)")?;
251
252        let m = env.get_var("m").unwrap();
253        let m = m.downcast_ref::<FileManifestValue>().unwrap();
254        let inner = m.inner("ignored").unwrap();
255
256        let mut entries = inner.iter_entries();
257
258        let (p, c) = entries.next().unwrap();
259        assert_eq!(p, &PathBuf::from("lib/foo/__init__.py"));
260        assert_eq!(c, &vec![].into());
261
262        let (p, c) = entries.next().unwrap();
263        assert_eq!(p, &PathBuf::from("lib/foo/bar.py"));
264        assert_eq!(c, &vec![].into());
265
266        assert!(entries.next().is_none());
267
268        Ok(())
269    }
270
271    #[test]
272    fn test_add_python_resource_data() -> Result<()> {
273        let m = FileManifestValue::new_from_args().unwrap();
274
275        let v = Value::new(PythonPackageResourceValue::new(PythonPackageResource {
276            leaf_package: "foo.bar".to_string(),
277            relative_name: "resource.txt".to_string(),
278            data: FileData::Memory(vec![]),
279            is_stdlib: false,
280            is_test: false,
281        }));
282
283        let mut env = test_evaluation_context_builder()?.into_context()?;
284        env.set_var("m", m).unwrap();
285        env.set_var("v", v).unwrap();
286
287        env.eval("m.add_python_resource('lib', v)")?;
288
289        let m = env.get_var("m").unwrap();
290        let m = m.downcast_ref::<FileManifestValue>().unwrap();
291        let inner = m.inner("ignored").unwrap();
292
293        let mut entries = inner.iter_entries();
294        let (p, c) = entries.next().unwrap();
295
296        assert_eq!(p, &PathBuf::from("lib/foo/bar/resource.txt"));
297        assert_eq!(c, &vec![].into());
298
299        assert!(entries.next().is_none());
300
301        Ok(())
302    }
303
304    #[test]
305    fn test_add_python_resources() {
306        starlark_ok("dist = default_python_distribution(); m = FileManifest(); m.add_python_resources('lib', dist.python_resources())");
307    }
308
309    #[test]
310    fn test_add_python_executable() -> Result<()> {
311        let mut env = test_evaluation_context_builder()?.into_context()?;
312        add_exe(&mut env)?;
313
314        let m = FileManifestValue::new_from_args().unwrap();
315
316        env.set_var("m", m).unwrap();
317        env.eval("m.add_python_resource('bin', exe)")?;
318
319        Ok(())
320    }
321
322    #[test]
323    // Python 3.8 not supported on aarch64.
324    #[cfg(not(target_arch = "aarch64"))]
325    fn test_add_python_executable_38() -> Result<()> {
326        let mut env = test_evaluation_context_builder()?.into_context()?;
327
328        env.eval("dist = default_python_distribution(python_version='3.8')")?;
329        env.eval("exe = dist.to_python_executable('testapp')")?;
330
331        let m = FileManifestValue::new_from_args().unwrap();
332
333        env.set_var("m", m).unwrap();
334        env.eval("m.add_python_resource('bin', exe)")?;
335
336        Ok(())
337    }
338
339    #[test]
340    fn test_add_python_executable_310() -> Result<()> {
341        let mut env = test_evaluation_context_builder()?.into_context()?;
342
343        env.eval("dist = default_python_distribution(python_version='3.10')")?;
344        env.eval("exe = dist.to_python_executable('testapp')")?;
345
346        let m = FileManifestValue::new_from_args().unwrap();
347
348        env.set_var("m", m).unwrap();
349        env.eval("m.add_python_resource('bin', exe)")?;
350
351        Ok(())
352    }
353
354    #[test]
355    fn test_install() -> Result<()> {
356        let mut env = test_evaluation_context_builder()?.into_context()?;
357        add_exe(&mut env)?;
358
359        let m = FileManifestValue::new_from_args().unwrap();
360
361        env.set_var("m", m).unwrap();
362
363        env.eval("m.add_python_resource('bin', exe)")?;
364        env.eval("m.install('myapp')")?;
365
366        let dest_path = env.build_path().unwrap().join("myapp");
367        assert!(dest_path.exists());
368
369        // There should be an executable at myapp/bin/testapp[.exe].
370        let app_exe = if cfg!(windows) {
371            dest_path.join("bin").join("testapp.exe")
372        } else {
373            dest_path.join("bin").join("testapp")
374        };
375
376        assert!(app_exe.exists());
377
378        Ok(())
379    }
380}