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}