pyoxidizerlib/py_packaging/
binary.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/*!
6Defining and manipulating binaries embedding Python.
7*/
8
9use {
10    crate::{
11        environment::Environment,
12        py_packaging::{distribution::AppleSdkInfo, embedding::EmbeddedPythonContext},
13    },
14    anyhow::Result,
15    python_packaging::{
16        licensing::{LicensedComponent, LicensedComponents},
17        policy::PythonPackagingPolicy,
18        resource::{
19            PythonExtensionModule, PythonModuleSource, PythonPackageDistributionResource,
20            PythonPackageResource, PythonResource,
21        },
22        resource_collection::{
23            AddResourceAction, PrePackagedResource, PythonResourceAddCollectionContext,
24        },
25    },
26    simple_file_manifest::File,
27    std::{collections::HashMap, path::Path, sync::Arc},
28    tugger_windows::VcRedistributablePlatform,
29};
30
31/// How a binary should link against libpython.
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub enum LibpythonLinkMode {
34    /// Libpython will be statically linked into the binary.
35    Static,
36    /// The binary will dynamically link against libpython.
37    Dynamic,
38}
39
40/// Determines how packed resources are loaded by the generated binary.
41///
42/// This effectively controls how resources file are written to disk
43/// and what `pyembed::PackedResourcesSource` will get serialized in the
44/// configuration.
45#[derive(Clone, Debug, PartialEq, Eq)]
46pub enum PackedResourcesLoadMode {
47    /// Packed resources will not be loaded.
48    None,
49
50    /// Resources data will be embedded in the binary.
51    ///
52    /// The data will be referenced via an `include_bytes!()` and the
53    /// stored path controls the name of the file that will be materialized
54    /// in the artifacts directory.
55    EmbeddedInBinary(String),
56
57    /// Resources data will be serialized to a file relative to the built binary.
58    ///
59    /// The configuration will reference the file via a relative path using
60    /// `$ORIGIN` expansion. Memory mapped I/O will be used to read the file.
61    BinaryRelativePathMemoryMapped(String),
62}
63
64impl ToString for PackedResourcesLoadMode {
65    fn to_string(&self) -> String {
66        match self {
67            Self::None => "none".to_string(),
68            Self::EmbeddedInBinary(filename) => format!("embedded:{}", filename),
69            Self::BinaryRelativePathMemoryMapped(path) => {
70                format!("binary-relative-memory-mapped:{}", path)
71            }
72        }
73    }
74}
75
76impl TryFrom<&str> for PackedResourcesLoadMode {
77    type Error = String;
78
79    fn try_from(value: &str) -> Result<Self, Self::Error> {
80        if value == "none" {
81            Ok(Self::None)
82        } else {
83            let parts = value.splitn(2, ':').collect::<Vec<_>>();
84            if parts.len() != 2 {
85                Err(
86                    "resources load mode value not recognized; must have form `type:value`"
87                        .to_string(),
88                )
89            } else {
90                let prefix = parts[0];
91                let value = parts[1];
92
93                match prefix {
94                    "embedded" => {
95                        Ok(Self::EmbeddedInBinary(value.to_string()))
96                    }
97                    "binary-relative-memory-mapped" => {
98                        Ok(Self::BinaryRelativePathMemoryMapped(value.to_string()))
99                    }
100                    _ => Err(format!("{} is not a valid prefix; must be 'embedded' or 'binary-relative-memory-mapped'", prefix))
101                }
102            }
103        }
104    }
105}
106
107/// Describes how Windows Runtime DLLs (e.g. vcruntime140.dll) should be handled during builds.
108#[derive(Clone, Copy, Debug, PartialEq, Eq)]
109pub enum WindowsRuntimeDllsMode {
110    /// Never attempt to install Windows Runtime DLLs.
111    ///
112    /// A binary will be generated with no runtime DLLs next to it.
113    Never,
114
115    /// Install Windows Runtime DLLs if they can be located. Do nothing if not.
116    WhenPresent,
117
118    /// Always install Windows Runtime DLLs and fail if they can't be found.
119    Always,
120}
121
122impl ToString for WindowsRuntimeDllsMode {
123    fn to_string(&self) -> String {
124        match self {
125            Self::Never => "never",
126            Self::WhenPresent => "when-present",
127            Self::Always => "always",
128        }
129        .to_string()
130    }
131}
132
133impl TryFrom<&str> for WindowsRuntimeDllsMode {
134    type Error = String;
135
136    fn try_from(value: &str) -> Result<Self, Self::Error> {
137        match value {
138            "never" => Ok(Self::Never),
139            "when-present" => Ok(Self::WhenPresent),
140            "always" => Ok(Self::Always),
141            _ => Err(format!("{} is not a valid mode; must be 'never'", value)),
142        }
143    }
144}
145
146/// A callable that can influence PythonResourceAddCollectionContext.
147pub type ResourceAddCollectionContextCallback<'a> = Box<
148    dyn Fn(
149            &PythonPackagingPolicy,
150            &PythonResource,
151            &mut PythonResourceAddCollectionContext,
152        ) -> Result<()>
153        + 'a,
154>;
155
156/// Describes a generic way to build a Python binary.
157///
158/// Binary here means an executable or library containing or linking to a
159/// Python interpreter. It also includes embeddable resources within that
160/// binary.
161///
162/// Concrete implementations can be turned into build artifacts or binaries
163/// themselves.
164pub trait PythonBinaryBuilder {
165    /// Clone self into a Box'ed trait object.
166    fn clone_trait(&self) -> Arc<dyn PythonBinaryBuilder>;
167
168    /// The name of the binary.
169    fn name(&self) -> String;
170
171    /// How the binary will link against libpython.
172    fn libpython_link_mode(&self) -> LibpythonLinkMode;
173
174    /// Rust target triple the binary will run on.
175    fn target_triple(&self) -> &str;
176
177    /// Obtain run-time requirements for the Visual C++ Redistributable.
178    ///
179    /// If `None`, there is no dependency on `vcruntimeXXX.dll` files. If `Some`,
180    /// the returned tuple declares the VC++ Redistributable major version string
181    /// (e.g. `14`) and the VC++ Redistributable platform variant that is required.
182    fn vc_runtime_requirements(&self) -> Option<(String, VcRedistributablePlatform)>;
183
184    /// Obtain the cache tag to apply to Python bytecode modules.
185    fn cache_tag(&self) -> &str;
186
187    /// Obtain the `PythonPackagingPolicy` for the builder.
188    fn python_packaging_policy(&self) -> &PythonPackagingPolicy;
189
190    /// Path to Python executable that can be used to derive info at build time.
191    ///
192    /// The produced binary is effectively a clone of the Python distribution behind the
193    /// returned executable.
194    fn host_python_exe_path(&self) -> &Path;
195
196    /// Path to Python executable that is native to the target architecture.
197    // TODO this should not need to exist if we properly supported cross-compiling.
198    fn target_python_exe_path(&self) -> &Path;
199
200    /// Apple SDK build/targeting information.
201    fn apple_sdk_info(&self) -> Option<&AppleSdkInfo>;
202
203    /// Obtain how Windows runtime DLLs will be handled during builds.
204    ///
205    /// See the enum's documentation for behavior.
206    ///
207    /// This setting is ignored for binaries that don't need the Windows runtime
208    /// DLLs.
209    fn windows_runtime_dlls_mode(&self) -> &WindowsRuntimeDllsMode;
210
211    /// Set the value for `windows_runtime_dlls_mode()`.
212    fn set_windows_runtime_dlls_mode(&mut self, value: WindowsRuntimeDllsMode);
213
214    /// The directory to install tcl/tk files into.
215    fn tcl_files_path(&self) -> &Option<String>;
216
217    /// Set the directory to install tcl/tk files into.
218    fn set_tcl_files_path(&mut self, value: Option<String>);
219
220    /// The value of the `windows_subsystem` Rust attribute for the generated Rust project.
221    fn windows_subsystem(&self) -> &str;
222
223    /// Set the value of the `windows_subsystem` Rust attribute for generated Rust projects.
224    fn set_windows_subsystem(&mut self, value: &str) -> Result<()>;
225
226    /// Obtain the path of a filename to write containing a licensing report.
227    fn licenses_filename(&self) -> Option<&str>;
228
229    /// Set the path of a filename to write containing a licensing report.
230    fn set_licenses_filename(&mut self, value: Option<String>);
231
232    /// How packed Python resources will be loaded by the binary.
233    fn packed_resources_load_mode(&self) -> &PackedResourcesLoadMode;
234
235    /// Set how packed Python resources will be loaded by the binary.
236    fn set_packed_resources_load_mode(&mut self, load_mode: PackedResourcesLoadMode);
237
238    /// Obtain an iterator over all resource entries that will be embedded in the binary.
239    ///
240    /// This likely does not return extension modules that are statically linked
241    /// into the binary. For those, see `builtin_extension_module_names()`.
242    fn iter_resources<'a>(
243        &'a self,
244    ) -> Box<dyn Iterator<Item = (&'a String, &'a PrePackagedResource)> + 'a>;
245
246    /// Resolve license metadata from an iterable of `PythonResource` and store that data.
247    ///
248    /// The resolved license data can later be used to ensure packages conform
249    /// to license restrictions. This method can safely be called on resources
250    /// that aren't added to the instance / resource collector: it simply
251    /// registers the license metadata so it can be consulted later.
252    fn index_package_license_info_from_resources<'a>(
253        &mut self,
254        resources: &[PythonResource<'a>],
255    ) -> Result<()>;
256
257    /// Runs `pip download` using the binary builder's settings.
258    ///
259    /// Returns resources discovered from the Python packages downloaded.
260    fn pip_download(
261        &mut self,
262        env: &Environment,
263        verbose: bool,
264        args: &[String],
265    ) -> Result<Vec<PythonResource>>;
266
267    /// Runs `pip install` using the binary builder's settings.
268    ///
269    /// Returns resources discovered as part of performing an install.
270    fn pip_install(
271        &mut self,
272        env: &Environment,
273        verbose: bool,
274        install_args: &[String],
275        extra_envs: &HashMap<String, String>,
276    ) -> Result<Vec<PythonResource>>;
277
278    /// Reads Python resources from the filesystem.
279    fn read_package_root(
280        &mut self,
281        path: &Path,
282        packages: &[String],
283    ) -> Result<Vec<PythonResource>>;
284
285    /// Read Python resources from a populated virtualenv directory.
286    fn read_virtualenv(&mut self, path: &Path) -> Result<Vec<PythonResource>>;
287
288    /// Runs `python setup.py install` using the binary builder's settings.
289    ///
290    /// Returns resources discovered as part of performing an install.
291    fn setup_py_install(
292        &mut self,
293        env: &Environment,
294        package_path: &Path,
295        verbose: bool,
296        extra_envs: &HashMap<String, String>,
297        extra_global_arguments: &[String],
298    ) -> Result<Vec<PythonResource>>;
299
300    /// Add resources from the Python distribution to the builder.
301    ///
302    /// This method should likely be called soon after object construction
303    /// in order to finish adding state from the Python distribution to the
304    /// builder.
305    ///
306    /// The boundary between what distribution state should be initialized
307    /// at binary construction time versus this method is not well-defined
308    /// and is up to implementations. However, it is strongly recommended for
309    /// the division to be handling of core/required interpreter state at
310    /// construction time and all optional/standard library state in this
311    /// method.
312    ///
313    /// `callback` defines an optional function which can be called between
314    /// resource creation and adding that resource to the builder. This
315    /// gives the caller an opportunity to influence how resources are added
316    /// to the binary builder.
317    fn add_distribution_resources(
318        &mut self,
319        callback: Option<ResourceAddCollectionContextCallback>,
320    ) -> Result<Vec<AddResourceAction>>;
321
322    /// Add a `PythonModuleSource` to the resources collection.
323    ///
324    /// The location to load the resource from is optional. If specified, it
325    /// will be used. If not, an appropriate location based on the resources
326    /// policy will be chosen.
327    fn add_python_module_source(
328        &mut self,
329        module: &PythonModuleSource,
330        add_context: Option<PythonResourceAddCollectionContext>,
331    ) -> Result<Vec<AddResourceAction>>;
332
333    /// Add a `PythonPackageResource` to the resources collection.
334    ///
335    /// The location to load the resource from is optional. If specified, it will
336    /// be used. If not, an appropriate location based on the resources policy
337    /// will be chosen.
338    fn add_python_package_resource(
339        &mut self,
340        resource: &PythonPackageResource,
341        add_context: Option<PythonResourceAddCollectionContext>,
342    ) -> Result<Vec<AddResourceAction>>;
343
344    /// Add a `PythonPackageDistributionResource` to the resources collection.
345    ///
346    /// The location to load the resource from is optional. If specified, it will
347    /// be used. If not, an appropriate location based on the resources policy
348    /// will be chosen.
349    fn add_python_package_distribution_resource(
350        &mut self,
351        resource: &PythonPackageDistributionResource,
352        add_context: Option<PythonResourceAddCollectionContext>,
353    ) -> Result<Vec<AddResourceAction>>;
354
355    /// Add a `PythonExtensionModule` to make available.
356    ///
357    /// The location to load the extension module from can be specified. However,
358    /// different builders have different capabilities. And the location may be
359    /// ignored in some cases. For example, when adding an extension module that
360    /// is compiled into libpython itself, the location will always be inside
361    /// libpython and it isn't possible to materialize the extension module as
362    /// a standalone file.
363    fn add_python_extension_module(
364        &mut self,
365        extension_module: &PythonExtensionModule,
366        add_context: Option<PythonResourceAddCollectionContext>,
367    ) -> Result<Vec<AddResourceAction>>;
368
369    /// Add a `File` to the resource collection.
370    fn add_file_data(
371        &mut self,
372        file: &File,
373        add_context: Option<PythonResourceAddCollectionContext>,
374    ) -> Result<Vec<AddResourceAction>>;
375
376    /// Filter embedded resources against names in files.
377    ///
378    /// `files` is files to read names from.
379    ///
380    /// `glob_patterns` is file patterns of files to read names from.
381    fn filter_resources_from_files(
382        &mut self,
383        files: &[&Path],
384        glob_patterns: &[&str],
385    ) -> Result<()>;
386
387    /// Whether the binary requires the jemalloc library.
388    fn requires_jemalloc(&self) -> bool;
389
390    /// Whether the binary requires the Mimalloc library.
391    fn requires_mimalloc(&self) -> bool;
392
393    /// Whether the binary requires the Snmalloc library.
394    fn requires_snmalloc(&self) -> bool;
395
396    /// Obtain software licensing information.
397    fn licensed_components(&self) -> Result<LicensedComponents>;
398
399    /// Add a licensed software component to the instance.
400    ///
401    /// Calling this effectively conveys that the software will be built into
402    /// the final binary and its licensing should be captured in order to
403    /// generate a licensing report.
404    fn add_licensed_component(&mut self, component: LicensedComponent) -> Result<()>;
405
406    /// Obtain an `EmbeddedPythonContext` instance from this one.
407    fn to_embedded_python_context(
408        &self,
409        env: &Environment,
410        opt_level: &str,
411    ) -> Result<EmbeddedPythonContext>;
412}
413
414#[cfg(test)]
415mod tests {
416    use super::*;
417
418    #[test]
419    fn test_resources_load_mode_serialization() {
420        assert_eq!(
421            PackedResourcesLoadMode::None.to_string(),
422            "none".to_string()
423        );
424        assert_eq!(
425            PackedResourcesLoadMode::EmbeddedInBinary("resources".into()).to_string(),
426            "embedded:resources".to_string()
427        );
428        assert_eq!(
429            PackedResourcesLoadMode::BinaryRelativePathMemoryMapped("relative-resources".into())
430                .to_string(),
431            "binary-relative-memory-mapped:relative-resources".to_string()
432        );
433    }
434
435    #[test]
436    fn test_resources_load_mode_parsing() -> Result<()> {
437        assert_eq!(
438            PackedResourcesLoadMode::try_from("none").unwrap(),
439            PackedResourcesLoadMode::None
440        );
441        assert_eq!(
442            PackedResourcesLoadMode::try_from("embedded:resources").unwrap(),
443            PackedResourcesLoadMode::EmbeddedInBinary("resources".into())
444        );
445        assert_eq!(
446            PackedResourcesLoadMode::try_from("binary-relative-memory-mapped:relative").unwrap(),
447            PackedResourcesLoadMode::BinaryRelativePathMemoryMapped("relative".into())
448        );
449
450        Ok(())
451    }
452}