pyoxidizerlib/py_packaging/
standalone_builder.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        binary::{
8            LibpythonLinkMode, PackedResourcesLoadMode, PythonBinaryBuilder,
9            ResourceAddCollectionContextCallback, WindowsRuntimeDllsMode,
10        },
11        config::{PyembedPackedResourcesSource, PyembedPythonInterpreterConfig},
12        distribution::{AppleSdkInfo, BinaryLibpythonLinkMode, PythonDistribution},
13        embedding::{
14            EmbeddedPythonContext, LibpythonLinkSettings, LinkSharedLibraryPath,
15            LinkStaticLibraryData, LinkingAnnotation,
16        },
17        filtering::{filter_btreemap, resolve_resource_names_from_files},
18        libpython::link_libpython,
19        packaging_tool::{
20            find_resources, pip_download, pip_install, read_virtualenv, setup_py_install,
21        },
22        standalone_distribution::StandaloneDistribution,
23    },
24    crate::environment::Environment,
25    anyhow::{anyhow, Context, Result},
26    log::warn,
27    once_cell::sync::Lazy,
28    pyo3_build_config::{BuildFlag, BuildFlags, PythonImplementation, PythonVersion},
29    python_packaging::{
30        bytecode::BytecodeCompiler,
31        interpreter::MemoryAllocatorBackend,
32        libpython::LibPythonBuildContext,
33        licensing::{
34            derive_package_license_infos, ComponentFlavor, LicensedComponent, LicensedComponents,
35        },
36        location::AbstractResourceLocation,
37        policy::PythonPackagingPolicy,
38        resource::{
39            PythonExtensionModule, PythonModuleSource, PythonPackageDistributionResource,
40            PythonPackageResource, PythonResource,
41        },
42        resource_collection::{
43            AddResourceAction, PrePackagedResource, PythonResourceAddCollectionContext,
44            PythonResourceCollector,
45        },
46    },
47    simple_file_manifest::{File, FileData, FileEntry, FileManifest},
48    std::{
49        collections::{BTreeMap, BTreeSet, HashMap},
50        path::{Path, PathBuf},
51        str::FromStr,
52        sync::Arc,
53    },
54    tugger_windows::{find_visual_cpp_redistributable, VcRedistributablePlatform},
55};
56
57/// Libraries that we should not link against on Linux.
58static LINUX_IGNORE_LIBRARIES: Lazy<Vec<&'static str>> = Lazy::new(|| vec!["dl", "m"]);
59
60/// Libraries that we should not link against on macOS.
61static MACOS_IGNORE_LIBRARIES: Lazy<Vec<&'static str>> = Lazy::new(|| vec!["dl", "m"]);
62
63/// Obtain a list of ignored libraries for a given target triple.
64fn ignored_libraries_for_target(target_triple: &str) -> Vec<&'static str> {
65    if crate::environment::LINUX_TARGET_TRIPLES.contains(&target_triple) {
66        LINUX_IGNORE_LIBRARIES.clone()
67    } else if crate::environment::MACOS_TARGET_TRIPLES.contains(&target_triple) {
68        MACOS_IGNORE_LIBRARIES.clone()
69    } else {
70        vec![]
71    }
72}
73
74/// A self-contained Python executable before it is compiled.
75#[derive(Clone)]
76pub struct StandalonePythonExecutableBuilder {
77    /// The target triple we are running on.
78    host_triple: String,
79
80    /// The target triple we are building for.
81    target_triple: String,
82
83    /// The name of the executable to build.
84    exe_name: String,
85
86    /// The Python distribution being used to build this executable.
87    host_distribution: Arc<dyn PythonDistribution>,
88
89    /// The Python distribution this executable is targeting.
90    target_distribution: Arc<StandaloneDistribution>,
91
92    /// How libpython should be linked.
93    link_mode: LibpythonLinkMode,
94
95    /// Whether the built binary is capable of loading dynamically linked
96    /// extension modules from memory.
97    #[allow(dead_code)]
98    supports_in_memory_dynamically_linked_extension_loading: bool,
99
100    /// Policy to apply to added resources.
101    packaging_policy: PythonPackagingPolicy,
102
103    /// Python resources to be embedded in the binary.
104    resources_collector: PythonResourceCollector,
105
106    /// How packed resources will be loaded at run-time.
107    resources_load_mode: PackedResourcesLoadMode,
108
109    /// Holds state necessary to link libpython.
110    core_build_context: LibPythonBuildContext,
111
112    /// Holds linking context for individual extensions.
113    ///
114    /// We need to track per-extension state separately since we need
115    /// to support filtering extensions as part of building.
116    extension_build_contexts: BTreeMap<String, LibPythonBuildContext>,
117
118    /// Configuration of the embedded Python interpreter.
119    config: PyembedPythonInterpreterConfig,
120
121    /// Path to python executable that can be invoked at build time.
122    host_python_exe: PathBuf,
123
124    /// Filename to write out with licensing information.
125    licenses_filename: Option<String>,
126
127    /// Value for the `windows_subsystem` Rust attribute for generated Rust projects.
128    windows_subsystem: String,
129
130    /// Path to install tcl/tk files into.
131    tcl_files_path: Option<String>,
132
133    /// Describes how Windows runtime DLLs should be handled during builds.
134    windows_runtime_dlls_mode: WindowsRuntimeDllsMode,
135}
136
137impl StandalonePythonExecutableBuilder {
138    #[allow(clippy::too_many_arguments)]
139    pub fn from_distribution(
140        host_distribution: Arc<dyn PythonDistribution>,
141        target_distribution: Arc<StandaloneDistribution>,
142        host_triple: String,
143        target_triple: String,
144        exe_name: String,
145        link_mode: BinaryLibpythonLinkMode,
146        packaging_policy: PythonPackagingPolicy,
147        config: PyembedPythonInterpreterConfig,
148    ) -> Result<Box<Self>> {
149        let host_python_exe = host_distribution.python_exe_path().to_path_buf();
150
151        let (supports_static_libpython, supports_dynamic_libpython) =
152            target_distribution.libpython_link_support();
153
154        let link_mode = match link_mode {
155            BinaryLibpythonLinkMode::Default => {
156                if supports_static_libpython {
157                    LibpythonLinkMode::Static
158                } else if supports_dynamic_libpython {
159                    LibpythonLinkMode::Dynamic
160                } else {
161                    return Err(anyhow!("no link modes supported; please report this bug"));
162                }
163            }
164            BinaryLibpythonLinkMode::Static => {
165                if !supports_static_libpython {
166                    return Err(anyhow!(
167                        "Python distribution does not support statically linking libpython"
168                    ));
169                }
170
171                LibpythonLinkMode::Static
172            }
173            BinaryLibpythonLinkMode::Dynamic => {
174                if !supports_dynamic_libpython {
175                    return Err(anyhow!(
176                        "Python distribution does not support dynamically linking libpython"
177                    ));
178                }
179
180                LibpythonLinkMode::Dynamic
181            }
182        };
183
184        let supports_in_memory_dynamically_linked_extension_loading =
185            target_distribution.supports_in_memory_shared_library_loading();
186
187        let mut allowed_locations = vec![AbstractResourceLocation::from(
188            packaging_policy.resources_location(),
189        )];
190        if let Some(fallback) = packaging_policy.resources_location_fallback() {
191            allowed_locations.push(AbstractResourceLocation::from(fallback));
192        }
193
194        let mut allowed_extension_module_locations = vec![];
195
196        if supports_in_memory_dynamically_linked_extension_loading
197            && packaging_policy.allow_in_memory_shared_library_loading()
198        {
199            allowed_extension_module_locations.push(AbstractResourceLocation::InMemory);
200        }
201
202        if target_distribution.is_extension_module_file_loadable() {
203            allowed_extension_module_locations.push(AbstractResourceLocation::RelativePath);
204        }
205
206        let allow_new_builtin_extension_modules = link_mode == LibpythonLinkMode::Static;
207
208        let mut builder = Box::new(Self {
209            host_triple,
210            target_triple,
211            exe_name,
212            host_distribution,
213            target_distribution,
214            link_mode,
215            supports_in_memory_dynamically_linked_extension_loading,
216            packaging_policy: packaging_policy.clone(),
217            resources_collector: PythonResourceCollector::new(
218                allowed_locations,
219                allowed_extension_module_locations,
220                allow_new_builtin_extension_modules,
221                packaging_policy.allow_files(),
222            ),
223            resources_load_mode: PackedResourcesLoadMode::EmbeddedInBinary(
224                "packed-resources".to_string(),
225            ),
226            core_build_context: LibPythonBuildContext::default(),
227            extension_build_contexts: BTreeMap::new(),
228            config,
229            host_python_exe,
230            licenses_filename: Some("COPYING.txt".into()),
231            windows_subsystem: "console".to_string(),
232            tcl_files_path: None,
233            windows_runtime_dlls_mode: WindowsRuntimeDllsMode::WhenPresent,
234        });
235
236        builder.add_distribution_core_state()?;
237
238        Ok(builder)
239    }
240
241    fn add_distribution_core_state(&mut self) -> Result<()> {
242        self.core_build_context.inittab_cflags =
243            Some(self.target_distribution.inittab_cflags.clone());
244
245        for (name, path) in &self.target_distribution.includes {
246            self.core_build_context
247                .includes
248                .insert(PathBuf::from(name), FileData::Path(path.clone()));
249        }
250
251        // Add the distribution's object files from Python core to linking context.
252        for fs_path in self.target_distribution.objs_core.values() {
253            // libpython generation derives its own `_PyImport_Inittab`. So ignore
254            // the object file containing it.
255            if fs_path == &self.target_distribution.inittab_object {
256                continue;
257            }
258
259            self.core_build_context
260                .object_files
261                .push(FileData::Path(fs_path.clone()));
262        }
263
264        for entry in &self.target_distribution.links_core {
265            if entry.framework {
266                self.core_build_context
267                    .frameworks
268                    .insert(entry.name.clone());
269            } else if entry.system {
270                self.core_build_context
271                    .system_libraries
272                    .insert(entry.name.clone());
273            }
274            // TODO handle static/dynamic libraries.
275        }
276
277        for path in self.target_distribution.libraries.values() {
278            self.core_build_context.library_search_paths.insert(
279                path.parent()
280                    .ok_or_else(|| anyhow!("unable to resolve parent directory"))?
281                    .to_path_buf(),
282            );
283        }
284
285        // Windows requires dynamic linking against msvcrt. Ensure that happens.
286        if crate::environment::WINDOWS_TARGET_TRIPLES.contains(&self.target_triple.as_str()) {
287            self.core_build_context
288                .system_libraries
289                .insert("msvcrt".to_string());
290        }
291
292        if let Some(component) = &self.target_distribution.core_license {
293            self.core_build_context
294                .licensed_components
295                .add_component(component.clone());
296        }
297
298        Ok(())
299    }
300
301    /// Resolve a Python library suitable for linking.
302    ///
303    /// This will take the underlying distribution, resources, and configuration and resolve
304    /// linking info.
305    ///
306    /// If we need to derive a custom libpython, a static library will be built.
307    fn resolve_python_link_settings(
308        &self,
309        env: &Environment,
310        opt_level: &str,
311    ) -> Result<LibpythonLinkSettings> {
312        match self.link_mode {
313            LibpythonLinkMode::Static => {
314                warn!("generating custom link library containing Python...");
315
316                let mut link_contexts = vec![&self.core_build_context];
317                for c in self.extension_build_contexts.values() {
318                    link_contexts.push(c);
319                }
320
321                let library_info = link_libpython(
322                    env,
323                    &LibPythonBuildContext::merge(&link_contexts),
324                    &self.host_triple,
325                    &self.target_triple,
326                    opt_level,
327                    self.apple_sdk_info(),
328                )?;
329
330                Ok(LinkStaticLibraryData {
331                    library_data: library_info.libpython_data,
332                    linking_annotations: library_info.linking_annotations,
333                }
334                .into())
335            }
336
337            LibpythonLinkMode::Dynamic => {
338                let library_path = self
339                    .target_distribution
340                    .libpython_shared_library
341                    .clone()
342                    .ok_or_else(|| {
343                        anyhow!("target Python distribution does not have a shared libpython")
344                    })?;
345
346                let filename = library_path
347                    .file_name()
348                    .ok_or_else(|| anyhow!("unable to resolve shared library filename"))?
349                    .to_string_lossy();
350
351                let library_search_path = library_path
352                    .parent()
353                    .ok_or_else(|| anyhow!("unable to obtain shared library directory"))?;
354
355                // On Windows, the linker needs the .lib files, which are in a separate directory.
356                let library_search_path = if filename.ends_with(".dll") {
357                    library_search_path.join("libs")
358                } else {
359                    library_search_path.to_path_buf()
360                };
361
362                let linking_annotations =
363                    vec![LinkingAnnotation::SearchNative(library_search_path)];
364
365                Ok(LinkSharedLibraryPath {
366                    library_path,
367
368                    linking_annotations,
369                }
370                .into())
371            }
372        }
373    }
374
375    /// Resolves Windows runtime DLLs file needed for this binary given current settings.
376    fn resolve_windows_runtime_dll_files(&self) -> Result<FileManifest> {
377        let mut manifest = FileManifest::default();
378
379        // If we require Windows CRT DLLs and we're told to install them, do that.
380        if let Some((version, platform)) = self.vc_runtime_requirements() {
381            if matches!(
382                self.windows_runtime_dlls_mode(),
383                WindowsRuntimeDllsMode::WhenPresent | WindowsRuntimeDllsMode::Always
384            ) {
385                match find_visual_cpp_redistributable(&version, platform) {
386                    Ok(paths) => {
387                        for path in paths {
388                            let file_name = PathBuf::from(
389                                path.file_name()
390                                    .ok_or_else(|| anyhow!("could not determine file name"))?,
391                            );
392                            manifest
393                                .add_file_entry(file_name, FileEntry::new_from_path(path, true))?;
394                        }
395                    }
396                    Err(err) => {
397                        // Non-fatal in WhenPresent mode.
398                        if matches!(
399                            self.windows_runtime_dlls_mode(),
400                            WindowsRuntimeDllsMode::Always
401                        ) {
402                            return Err(anyhow!(
403                                "Windows Runtime DLLs mode of 'always' failed to locate files: {}",
404                                err
405                            ));
406                        }
407                    }
408                }
409            }
410        }
411
412        Ok(manifest)
413    }
414}
415
416impl PythonBinaryBuilder for StandalonePythonExecutableBuilder {
417    fn clone_trait(&self) -> Arc<dyn PythonBinaryBuilder> {
418        Arc::new(self.clone())
419    }
420
421    fn name(&self) -> String {
422        self.exe_name.clone()
423    }
424
425    fn libpython_link_mode(&self) -> LibpythonLinkMode {
426        self.link_mode
427    }
428
429    fn target_triple(&self) -> &str {
430        &self.target_triple
431    }
432
433    fn vc_runtime_requirements(&self) -> Option<(String, VcRedistributablePlatform)> {
434        let platform = if self.target_triple.starts_with("i686-") {
435            VcRedistributablePlatform::X86
436        } else if self.target_triple.starts_with("x86_64-") {
437            VcRedistributablePlatform::X64
438        } else if self.target_triple.starts_with("aarch64-") {
439            VcRedistributablePlatform::Arm64
440        } else {
441            return None;
442        };
443
444        self.target_distribution
445            .crt_features
446            .iter()
447            .find(|s| s.starts_with("vcruntime:"))
448            .map(|s| (s.split(':').nth(1).unwrap()[0..2].to_string(), platform))
449    }
450
451    fn cache_tag(&self) -> &str {
452        self.target_distribution.cache_tag()
453    }
454
455    fn python_packaging_policy(&self) -> &PythonPackagingPolicy {
456        &self.packaging_policy
457    }
458
459    fn host_python_exe_path(&self) -> &Path {
460        &self.host_python_exe
461    }
462
463    fn target_python_exe_path(&self) -> &Path {
464        self.target_distribution.python_exe_path()
465    }
466
467    fn apple_sdk_info(&self) -> Option<&AppleSdkInfo> {
468        self.target_distribution.apple_sdk_info()
469    }
470
471    fn windows_runtime_dlls_mode(&self) -> &WindowsRuntimeDllsMode {
472        &self.windows_runtime_dlls_mode
473    }
474
475    fn set_windows_runtime_dlls_mode(&mut self, value: WindowsRuntimeDllsMode) {
476        self.windows_runtime_dlls_mode = value;
477    }
478
479    fn tcl_files_path(&self) -> &Option<String> {
480        &self.tcl_files_path
481    }
482
483    fn set_tcl_files_path(&mut self, value: Option<String>) {
484        self.tcl_files_path = value;
485
486        self.config.tcl_library = if let Some(path) = &self.tcl_files_path {
487            Some(
488                PathBuf::from("$ORIGIN").join(path).join(
489                    self.target_distribution
490                        .tcl_library_path_directory()
491                        .expect("should have a tcl library path directory"),
492                ),
493            )
494        } else {
495            None
496        };
497    }
498
499    fn windows_subsystem(&self) -> &str {
500        &self.windows_subsystem
501    }
502
503    fn set_windows_subsystem(&mut self, value: &str) -> Result<()> {
504        self.windows_subsystem = value.to_string();
505
506        Ok(())
507    }
508
509    fn licenses_filename(&self) -> Option<&str> {
510        self.licenses_filename.as_deref()
511    }
512
513    fn set_licenses_filename(&mut self, value: Option<String>) {
514        self.licenses_filename = value;
515    }
516
517    fn packed_resources_load_mode(&self) -> &PackedResourcesLoadMode {
518        &self.resources_load_mode
519    }
520
521    fn set_packed_resources_load_mode(&mut self, load_mode: PackedResourcesLoadMode) {
522        self.resources_load_mode = load_mode;
523    }
524
525    fn iter_resources<'a>(
526        &'a self,
527    ) -> Box<dyn Iterator<Item = (&'a String, &'a PrePackagedResource)> + 'a> {
528        Box::new(self.resources_collector.iter_resources())
529    }
530
531    fn index_package_license_info_from_resources<'a>(
532        &mut self,
533        resources: &[PythonResource<'a>],
534    ) -> Result<()> {
535        for info in derive_package_license_infos(resources.iter())? {
536            self.resources_collector
537                .add_licensed_component(info.try_into()?)?;
538        }
539
540        Ok(())
541    }
542
543    fn pip_download(
544        &mut self,
545        env: &Environment,
546        verbose: bool,
547        args: &[String],
548    ) -> Result<Vec<PythonResource>> {
549        let resources = pip_download(
550            env,
551            &*self.host_distribution,
552            &*self.target_distribution,
553            self.python_packaging_policy(),
554            verbose,
555            args,
556        )
557        .context("calling pip download")?;
558
559        self.index_package_license_info_from_resources(&resources)
560            .context("indexing package license metadata")?;
561
562        Ok(resources)
563    }
564
565    fn pip_install(
566        &mut self,
567        env: &Environment,
568        verbose: bool,
569        install_args: &[String],
570        extra_envs: &HashMap<String, String>,
571    ) -> Result<Vec<PythonResource>> {
572        let resources = pip_install(
573            env,
574            &*self.target_distribution,
575            self.python_packaging_policy(),
576            self.link_mode,
577            verbose,
578            install_args,
579            extra_envs,
580        )
581        .context("calling pip install")?;
582
583        self.index_package_license_info_from_resources(&resources)
584            .context("indexing package license metadata")?;
585
586        Ok(resources)
587    }
588
589    fn read_package_root(
590        &mut self,
591        path: &Path,
592        packages: &[String],
593    ) -> Result<Vec<PythonResource>> {
594        let resources = find_resources(
595            &*self.target_distribution,
596            self.python_packaging_policy(),
597            path,
598            None,
599        )
600        .context("finding resources")?
601        .iter()
602        .filter_map(|x| {
603            if x.is_in_packages(packages) {
604                Some(x.clone())
605            } else {
606                None
607            }
608        })
609        .collect::<Vec<_>>();
610
611        self.index_package_license_info_from_resources(&resources)
612            .context("indexing package license metadata")?;
613
614        Ok(resources)
615    }
616
617    fn read_virtualenv(&mut self, path: &Path) -> Result<Vec<PythonResource>> {
618        let resources = read_virtualenv(
619            &*self.target_distribution,
620            self.python_packaging_policy(),
621            path,
622        )
623        .context("reading virtualenv")?;
624
625        self.index_package_license_info_from_resources(&resources)
626            .context("indexing package license metadata")?;
627
628        Ok(resources)
629    }
630
631    fn setup_py_install(
632        &mut self,
633        env: &Environment,
634        package_path: &Path,
635        verbose: bool,
636        extra_envs: &HashMap<String, String>,
637        extra_global_arguments: &[String],
638    ) -> Result<Vec<PythonResource>> {
639        let resources = setup_py_install(
640            env,
641            &*self.target_distribution,
642            self.python_packaging_policy(),
643            self.link_mode,
644            package_path,
645            verbose,
646            extra_envs,
647            extra_global_arguments,
648        )
649        .context("running setup.py install")?;
650
651        self.index_package_license_info_from_resources(&resources)
652            .context("indexing package license metadata")?;
653
654        Ok(resources)
655    }
656
657    fn add_distribution_resources(
658        &mut self,
659        callback: Option<ResourceAddCollectionContextCallback>,
660    ) -> Result<Vec<AddResourceAction>> {
661        let mut actions = vec![];
662
663        let core_license =
664            if let Some(core_license) = self.target_distribution.core_license.as_ref() {
665                self.resources_collector
666                    .add_licensed_component(core_license.clone())?;
667                core_license.clone()
668            } else {
669                return Err(anyhow!("could not resolve Python standard library license"));
670            };
671
672        // TODO consolidate into loop below.
673        for ext in self.packaging_policy.resolve_python_extension_modules(
674            self.target_distribution.extension_modules.values(),
675            &self.target_triple,
676        )? {
677            let resource = (&ext).into();
678            let mut add_context = self
679                .packaging_policy
680                .derive_add_collection_context(&resource);
681
682            if let Some(callback) = &callback {
683                callback(&self.packaging_policy, &resource, &mut add_context)?;
684            }
685
686            if let Some(component) = &ext.license {
687                self.resources_collector
688                    .add_licensed_component(component.clone())?;
689            }
690
691            actions.extend(self.add_python_extension_module(&ext, Some(add_context))?);
692        }
693
694        for resource in self
695            .target_distribution
696            .python_resources()
697            .iter()
698            .filter(|r| match r {
699                PythonResource::ModuleSource(_) => true,
700                PythonResource::PackageResource(_) => true,
701                PythonResource::ModuleBytecode(_) => false,
702                PythonResource::ModuleBytecodeRequest(_) => false,
703                PythonResource::ExtensionModule(_) => false,
704                PythonResource::PackageDistributionResource(_) => false,
705                PythonResource::EggFile(_) => false,
706                PythonResource::PathExtension(_) => false,
707                PythonResource::File(_) => false,
708            })
709        {
710            let mut add_context = self
711                .packaging_policy
712                .derive_add_collection_context(resource);
713
714            if let Some(callback) = &callback {
715                callback(&self.packaging_policy, resource, &mut add_context)?;
716            }
717
718            match resource {
719                PythonResource::ModuleSource(source) => {
720                    self.resources_collector.add_licensed_component(
721                        LicensedComponent::new_spdx(
722                            ComponentFlavor::PythonStandardLibraryModule(source.name.to_string()),
723                            core_license
724                                .spdx_expression()
725                                .ok_or_else(|| anyhow!("should have resolved SPDX expression"))?
726                                .as_ref(),
727                        )?,
728                    )?;
729
730                    actions.extend(self.add_python_module_source(source, Some(add_context))?);
731                }
732                PythonResource::PackageResource(r) => {
733                    actions.extend(self.add_python_package_resource(r, Some(add_context))?);
734                }
735                _ => panic!("should not get here since resources should be filtered above"),
736            }
737        }
738
739        Ok(actions)
740    }
741
742    fn add_python_module_source(
743        &mut self,
744        module: &PythonModuleSource,
745        add_context: Option<PythonResourceAddCollectionContext>,
746    ) -> Result<Vec<AddResourceAction>> {
747        let add_context = add_context.unwrap_or_else(|| {
748            self.packaging_policy
749                .derive_add_collection_context(&module.into())
750        });
751
752        self.resources_collector
753            .add_python_module_source_with_context(module, &add_context)
754    }
755
756    fn add_python_package_resource(
757        &mut self,
758        resource: &PythonPackageResource,
759        add_context: Option<PythonResourceAddCollectionContext>,
760    ) -> Result<Vec<AddResourceAction>> {
761        let add_context = add_context.unwrap_or_else(|| {
762            self.packaging_policy
763                .derive_add_collection_context(&resource.into())
764        });
765
766        self.resources_collector
767            .add_python_package_resource_with_context(resource, &add_context)
768    }
769
770    fn add_python_package_distribution_resource(
771        &mut self,
772        resource: &PythonPackageDistributionResource,
773        add_context: Option<PythonResourceAddCollectionContext>,
774    ) -> Result<Vec<AddResourceAction>> {
775        let add_context = add_context.unwrap_or_else(|| {
776            self.packaging_policy
777                .derive_add_collection_context(&resource.into())
778        });
779
780        self.resources_collector
781            .add_python_package_distribution_resource_with_context(resource, &add_context)
782    }
783
784    fn add_python_extension_module(
785        &mut self,
786        extension_module: &PythonExtensionModule,
787        add_context: Option<PythonResourceAddCollectionContext>,
788    ) -> Result<Vec<AddResourceAction>> {
789        let add_context = add_context.unwrap_or_else(|| {
790            self.packaging_policy
791                .derive_add_collection_context(&extension_module.into())
792        });
793
794        let (actions, build_context) = self
795            .resources_collector
796            .add_python_extension_module_with_context(extension_module, &add_context)?;
797
798        if let Some(mut build_context) = build_context {
799            // Resources collector doesn't doesn't know about ignored libraries. So filter
800            // them here.
801            build_context.static_libraries = build_context
802                .static_libraries
803                .iter()
804                .filter(|x| {
805                    !ignored_libraries_for_target(&self.target_triple).contains(&x.as_str())
806                })
807                .cloned()
808                .collect::<BTreeSet<_>>();
809            build_context.dynamic_libraries = build_context
810                .dynamic_libraries
811                .iter()
812                .filter(|x| {
813                    !ignored_libraries_for_target(&self.target_triple).contains(&x.as_str())
814                })
815                .cloned()
816                .collect::<BTreeSet<_>>();
817
818            self.extension_build_contexts
819                .insert(extension_module.name.clone(), build_context);
820        }
821
822        Ok(actions)
823    }
824
825    fn add_file_data(
826        &mut self,
827        file: &File,
828        add_context: Option<PythonResourceAddCollectionContext>,
829    ) -> Result<Vec<AddResourceAction>> {
830        let add_context = add_context.unwrap_or_else(|| {
831            self.packaging_policy
832                .derive_add_collection_context(&file.into())
833        });
834
835        self.resources_collector
836            .add_file_data_with_context(file, &add_context)
837    }
838
839    fn filter_resources_from_files(
840        &mut self,
841        files: &[&Path],
842        glob_patterns: &[&str],
843    ) -> Result<()> {
844        let resource_names = resolve_resource_names_from_files(files, glob_patterns)?;
845
846        warn!("filtering module entries");
847
848        self.resources_collector.filter_resources_mut(|resource| {
849            if !resource_names.contains(&resource.name) {
850                warn!("removing {}", resource.name);
851                false
852            } else {
853                true
854            }
855        })?;
856
857        warn!("filtering embedded extension modules");
858        filter_btreemap(&mut self.extension_build_contexts, &resource_names);
859
860        Ok(())
861    }
862
863    fn requires_jemalloc(&self) -> bool {
864        self.config.allocator_backend == MemoryAllocatorBackend::Jemalloc
865    }
866
867    fn requires_mimalloc(&self) -> bool {
868        self.config.allocator_backend == MemoryAllocatorBackend::Mimalloc
869    }
870
871    fn requires_snmalloc(&self) -> bool {
872        self.config.allocator_backend == MemoryAllocatorBackend::Snmalloc
873    }
874
875    fn licensed_components(&self) -> Result<LicensedComponents> {
876        Ok(self.resources_collector.normalized_licensed_components())
877    }
878
879    fn add_licensed_component(&mut self, component: LicensedComponent) -> Result<()> {
880        self.resources_collector.add_licensed_component(component)
881    }
882
883    fn to_embedded_python_context(
884        &self,
885        env: &Environment,
886        opt_level: &str,
887    ) -> Result<EmbeddedPythonContext> {
888        let mut file_seen = false;
889        for module in self.resources_collector.find_dunder_file()? {
890            file_seen = true;
891            warn!("warning: {} contains __file__", module);
892        }
893
894        if file_seen {
895            warn!("__file__ was encountered in some embedded modules");
896            warn!("PyOxidizer does not set __file__ and this may create problems at run-time");
897            warn!("See https://github.com/indygreg/PyOxidizer/issues/69 for more");
898        }
899
900        let compiled_resources = {
901            let temp_dir = env.temporary_directory("pyoxidizer-bytecode-compile")?;
902            let mut compiler = BytecodeCompiler::new(self.host_python_exe_path(), temp_dir.path())?;
903            let resources = self.resources_collector.compile_resources(&mut compiler)?;
904
905            temp_dir.close().context("closing temporary directory")?;
906
907            resources
908        };
909
910        let mut pending_resources = vec![];
911
912        let mut extra_files = compiled_resources.extra_files_manifest()?;
913
914        let mut config = self.config.clone();
915
916        match &self.resources_load_mode {
917            PackedResourcesLoadMode::None => {}
918            PackedResourcesLoadMode::EmbeddedInBinary(filename) => {
919                pending_resources.push((compiled_resources, PathBuf::from(filename)));
920                config
921                    .packed_resources
922                    .push(PyembedPackedResourcesSource::MemoryIncludeBytes(
923                        PathBuf::from(filename),
924                    ));
925            }
926            PackedResourcesLoadMode::BinaryRelativePathMemoryMapped(path) => {
927                // We need to materialize the file in extra_files. So compile now.
928                let mut buffer = vec![];
929                compiled_resources
930                    .write_packed_resources(&mut buffer)
931                    .context("serializing packed resources")?;
932                extra_files.add_file_entry(Path::new(path), buffer)?;
933
934                config
935                    .packed_resources
936                    .push(PyembedPackedResourcesSource::MemoryMappedPath(
937                        PathBuf::from("$ORIGIN").join(path),
938                    ));
939            }
940        }
941
942        let link_settings = self.resolve_python_link_settings(env, opt_level)?;
943
944        if self.link_mode == LibpythonLinkMode::Dynamic {
945            if let Some(p) = &self.target_distribution.libpython_shared_library {
946                let manifest_path = Path::new(p.file_name().unwrap());
947                let content = std::fs::read(p)?;
948
949                extra_files.add_file_entry(manifest_path, content)?;
950
951                // Always look for and add the python3.dll variant if it exists. This DLL
952                // exports the stable subset of the Python ABI and it is required by some
953                // extensions.
954                let python3_dll_path = p.with_file_name("python3.dll");
955                let manifest_path = Path::new(python3_dll_path.file_name().unwrap());
956                if python3_dll_path.exists() {
957                    let content = std::fs::read(&python3_dll_path)?;
958
959                    extra_files.add_file_entry(manifest_path, content)?;
960                }
961            }
962        }
963
964        if let Some(tcl_files_path) = self.tcl_files_path() {
965            for (path, location) in self.target_distribution.tcl_files()? {
966                let install_path = PathBuf::from(tcl_files_path).join(path);
967
968                extra_files.add_file_entry(&install_path, location)?;
969            }
970        }
971
972        // Install Windows runtime DLLs if told to do so.
973        extra_files.add_manifest(&self.resolve_windows_runtime_dll_files()?)?;
974
975        let python_implementation = if self
976            .target_distribution
977            .python_implementation
978            .starts_with("cpython")
979        {
980            PythonImplementation::CPython
981        } else if self
982            .target_distribution
983            .python_implementation
984            .starts_with("pypy")
985        {
986            PythonImplementation::PyPy
987        } else {
988            return Err(anyhow!(
989                "unknown Python implementation: {}",
990                self.target_distribution.python_implementation
991            ));
992        };
993
994        let python_version =
995            PythonVersion::from_str(&self.target_distribution.python_major_minor_version())
996                .map_err(|e| anyhow!("unable to determine Python version: {}", e))?;
997
998        // Populate build flags that influence PyO3 configuration.
999        let mut python_build_flags = BuildFlags::new();
1000
1001        if self
1002            .target_distribution
1003            .python_config_vars()
1004            .get("Py_DEBUG")
1005            == Some(&"1".to_string())
1006        {
1007            python_build_flags.0.insert(BuildFlag::Py_DEBUG);
1008        }
1009        if self
1010            .target_distribution
1011            .python_config_vars()
1012            .get("Py_REF_DEBUG")
1013            == Some(&"1".to_string())
1014        {
1015            python_build_flags.0.insert(BuildFlag::Py_REF_DEBUG);
1016        }
1017        if self
1018            .target_distribution
1019            .python_config_vars()
1020            .get("Py_TRACE_REFS")
1021            == Some(&"1".to_string())
1022        {
1023            python_build_flags.0.insert(BuildFlag::Py_TRACE_REFS);
1024        }
1025        if self
1026            .target_distribution
1027            .python_config_vars()
1028            .get("COUNT_ALLOCS")
1029            == Some(&"1".to_string())
1030        {
1031            python_build_flags.0.insert(BuildFlag::COUNT_ALLOCS);
1032        }
1033
1034        let mut context = EmbeddedPythonContext {
1035            config,
1036            link_settings,
1037            pending_resources,
1038            extra_files,
1039            host_triple: self.host_triple.clone(),
1040            target_triple: self.target_triple.clone(),
1041            python_implementation,
1042            python_version,
1043            python_exe_host: self.host_python_exe.clone(),
1044            python_build_flags,
1045            licensing_filename: self.licenses_filename.clone(),
1046            licensing: self.licensed_components()?,
1047        };
1048
1049        context.synchronize_licensing()?;
1050
1051        Ok(context)
1052    }
1053}
1054
1055#[cfg(test)]
1056pub mod tests {
1057    use {
1058        super::*,
1059        crate::{
1060            environment::{default_target_triple, MACOS_TARGET_TRIPLES},
1061            py_packaging::distribution::{BinaryLibpythonLinkMode, DistributionFlavor},
1062            python_distributions::PYTHON_DISTRIBUTIONS,
1063            testutil::*,
1064        },
1065        once_cell::sync::Lazy,
1066        python_packaging::{
1067            licensing::LicensedComponents, location::ConcreteResourceLocation,
1068            policy::ExtensionModuleFilter,
1069        },
1070        std::ops::DerefMut,
1071    };
1072
1073    #[cfg(target_os = "linux")]
1074    use python_packaging::resource::LibraryDependency;
1075
1076    pub static WINDOWS_TARGET_TRIPLES: Lazy<Vec<&'static str>> =
1077        Lazy::new(|| vec!["i686-pc-windows-msvc", "x86_64-pc-windows-msvc"]);
1078
1079    /// An extension module represented by a shared library file.
1080    pub static EXTENSION_MODULE_SHARED_LIBRARY_ONLY: Lazy<PythonExtensionModule> =
1081        Lazy::new(|| PythonExtensionModule {
1082            name: "shared_only".to_string(),
1083            init_fn: Some("PyInit_shared_only".to_string()),
1084            extension_file_suffix: ".so".to_string(),
1085            shared_library: Some(FileData::Memory(vec![42])),
1086            object_file_data: vec![],
1087            is_package: false,
1088            link_libraries: vec![],
1089            is_stdlib: false,
1090            builtin_default: false,
1091            required: false,
1092            variant: None,
1093            license: None,
1094        });
1095
1096    /// An extension module represented by only object files.
1097    pub static EXTENSION_MODULE_OBJECT_FILES_ONLY: Lazy<PythonExtensionModule> =
1098        Lazy::new(|| PythonExtensionModule {
1099            name: "object_files_only".to_string(),
1100            init_fn: Some("PyInit_object_files_only".to_string()),
1101            extension_file_suffix: ".so".to_string(),
1102            shared_library: None,
1103            object_file_data: vec![FileData::Memory(vec![0]), FileData::Memory(vec![1])],
1104            is_package: false,
1105            link_libraries: vec![],
1106            is_stdlib: false,
1107            builtin_default: false,
1108            required: false,
1109            variant: None,
1110            license: None,
1111        });
1112
1113    /// An extension module with both a shared library and object files.
1114    pub static EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES: Lazy<PythonExtensionModule> =
1115        Lazy::new(|| PythonExtensionModule {
1116            name: "shared_and_object_files".to_string(),
1117            init_fn: Some("PyInit_shared_and_object_files".to_string()),
1118            extension_file_suffix: ".so".to_string(),
1119            shared_library: Some(FileData::Memory(b"shared".to_vec())),
1120            object_file_data: vec![FileData::Memory(vec![0]), FileData::Memory(vec![1])],
1121            is_package: false,
1122            link_libraries: vec![],
1123            is_stdlib: false,
1124            builtin_default: false,
1125            required: false,
1126            variant: None,
1127            license: None,
1128        });
1129
1130    /// Defines construction options for a `StandalonePythonExecutableBuilder`.
1131    ///
1132    /// This is mostly intended to be used by tests, to reduce boilerplate for
1133    /// constructing instances.
1134    pub struct StandalonePythonExecutableBuilderOptions {
1135        pub host_triple: String,
1136        pub target_triple: String,
1137        pub distribution_version: Option<String>,
1138        pub distribution_flavor: DistributionFlavor,
1139        pub app_name: String,
1140        pub libpython_link_mode: BinaryLibpythonLinkMode,
1141        pub extension_module_filter: Option<ExtensionModuleFilter>,
1142        pub resources_location: Option<ConcreteResourceLocation>,
1143        pub resources_location_fallback: Option<Option<ConcreteResourceLocation>>,
1144        pub allow_in_memory_shared_library_loading: Option<bool>,
1145        pub config: PyembedPythonInterpreterConfig,
1146    }
1147
1148    impl Default for StandalonePythonExecutableBuilderOptions {
1149        fn default() -> Self {
1150            Self {
1151                host_triple: default_target_triple().to_string(),
1152                target_triple: default_target_triple().to_string(),
1153                distribution_version: None,
1154                distribution_flavor: DistributionFlavor::Standalone,
1155                app_name: "testapp".to_string(),
1156                libpython_link_mode: BinaryLibpythonLinkMode::Default,
1157                extension_module_filter: None,
1158                resources_location: None,
1159                resources_location_fallback: None,
1160                allow_in_memory_shared_library_loading: None,
1161                config: PyembedPythonInterpreterConfig::default(),
1162            }
1163        }
1164    }
1165
1166    impl StandalonePythonExecutableBuilderOptions {
1167        pub fn new_builder(&self) -> Result<Box<StandalonePythonExecutableBuilder>> {
1168            let target_record = PYTHON_DISTRIBUTIONS
1169                .find_distribution(
1170                    &self.target_triple,
1171                    &self.distribution_flavor,
1172                    self.distribution_version.as_deref(),
1173                )
1174                .ok_or_else(|| anyhow!("could not find target Python distribution"))?;
1175
1176            let target_distribution = get_distribution(&target_record.location)?;
1177
1178            let host_distribution = if target_distribution
1179                .compatible_host_triples()
1180                .contains(&self.host_triple)
1181            {
1182                target_distribution.clone_trait()
1183            } else {
1184                let host_record = PYTHON_DISTRIBUTIONS
1185                    .find_distribution(&self.host_triple, &DistributionFlavor::Standalone, None)
1186                    .ok_or_else(|| anyhow!("could not find host Python distribution"))?;
1187
1188                get_distribution(&host_record.location)?.clone_trait()
1189            };
1190
1191            let mut policy = target_distribution.create_packaging_policy()?;
1192            if let Some(filter) = &self.extension_module_filter {
1193                policy.set_extension_module_filter(filter.clone());
1194            }
1195            if let Some(location) = &self.resources_location {
1196                policy.set_resources_location(location.clone());
1197            }
1198            if let Some(location) = &self.resources_location_fallback {
1199                policy.set_resources_location_fallback(location.clone());
1200            }
1201            if let Some(value) = &self.allow_in_memory_shared_library_loading {
1202                policy.set_allow_in_memory_shared_library_loading(*value);
1203            }
1204
1205            let mut builder = StandalonePythonExecutableBuilder::from_distribution(
1206                host_distribution,
1207                target_distribution,
1208                self.host_triple.clone(),
1209                self.target_triple.clone(),
1210                self.app_name.clone(),
1211                self.libpython_link_mode.clone(),
1212                policy,
1213                self.config.clone(),
1214            )?;
1215
1216            builder.add_distribution_resources(None)?;
1217
1218            Ok(builder)
1219        }
1220    }
1221
1222    fn assert_extension_builtin(
1223        builder: &StandalonePythonExecutableBuilder,
1224        extension: &PythonExtensionModule,
1225    ) {
1226        assert_eq!(
1227            builder.iter_resources().find_map(|(name, r)| {
1228                if *name == extension.name {
1229                    Some(r)
1230                } else {
1231                    None
1232                }
1233            }),
1234            Some(&PrePackagedResource {
1235                is_builtin_extension_module: true,
1236                name: extension.name.clone(),
1237                ..PrePackagedResource::default()
1238            }),
1239            "extension module {} is built-in",
1240            extension.name,
1241        );
1242
1243        assert_eq!(
1244            builder.extension_build_contexts.get(&extension.name),
1245            Some(&LibPythonBuildContext {
1246                object_files: extension.object_file_data.clone(),
1247                init_functions: [(
1248                    extension.name.to_string(),
1249                    extension.init_fn.as_ref().unwrap().to_string()
1250                )]
1251                .iter()
1252                .cloned()
1253                .collect(),
1254                ..LibPythonBuildContext::default()
1255            }),
1256            "build context for extension module {} is present",
1257            extension.name
1258        );
1259    }
1260
1261    fn assert_extension_shared_library(
1262        builder: &StandalonePythonExecutableBuilder,
1263        extension: &PythonExtensionModule,
1264        location: ConcreteResourceLocation,
1265    ) {
1266        let mut entry = PrePackagedResource {
1267            is_extension_module: true,
1268            name: extension.name.clone(),
1269            shared_library_dependency_names: Some(vec![]),
1270            ..PrePackagedResource::default()
1271        };
1272
1273        match location {
1274            ConcreteResourceLocation::InMemory => {
1275                assert!(extension.shared_library.is_some());
1276                entry.in_memory_extension_module_shared_library =
1277                    Some(extension.shared_library.as_ref().unwrap().clone());
1278            }
1279            ConcreteResourceLocation::RelativePath(prefix) => {
1280                assert!(extension.shared_library.is_some());
1281                entry.relative_path_extension_module_shared_library = Some((
1282                    PathBuf::from(prefix).join(format!(
1283                        "{}{}",
1284                        extension.name, extension.extension_file_suffix
1285                    )),
1286                    extension.shared_library.as_ref().unwrap().clone(),
1287                ));
1288            }
1289        }
1290
1291        assert_eq!(
1292            builder.iter_resources().find_map(|(name, r)| {
1293                if *name == extension.name {
1294                    Some(r)
1295                } else {
1296                    None
1297                }
1298            }),
1299            Some(&entry)
1300        );
1301
1302        // There is no build context for extensions materialized as shared libraries.
1303        // This could change if we ever link shared library extension modules from
1304        // object files.
1305        assert_eq!(builder.extension_build_contexts.get(&extension.name), None);
1306    }
1307
1308    fn licensed_components_from_extension(ext: &PythonExtensionModule) -> LicensedComponents {
1309        let mut r = LicensedComponents::default();
1310
1311        if let Some(component) = &ext.license {
1312            r.add_component(component.clone());
1313        }
1314
1315        r
1316    }
1317
1318    #[test]
1319    fn test_write_embedded_files() -> Result<()> {
1320        let temp_dir = get_env()?.temporary_directory("pyoxidizer-test")?;
1321
1322        let options = StandalonePythonExecutableBuilderOptions::default();
1323        let exe = options.new_builder()?;
1324        let embedded = exe.to_embedded_python_context(&get_env()?, "0")?;
1325
1326        embedded.write_files(temp_dir.path())?;
1327
1328        let resources_path = temp_dir.path().join("packed-resources");
1329        assert!(resources_path.exists(), "packed-resources file exists");
1330
1331        temp_dir.close()?;
1332
1333        Ok(())
1334    }
1335
1336    #[test]
1337    fn test_memory_mapped_file_resources() -> Result<()> {
1338        let options = StandalonePythonExecutableBuilderOptions::default();
1339        let mut exe = options.new_builder()?;
1340        exe.resources_load_mode =
1341            PackedResourcesLoadMode::BinaryRelativePathMemoryMapped("resources".into());
1342
1343        let embedded = exe.to_embedded_python_context(&get_env()?, "0")?;
1344
1345        assert_eq!(
1346            &embedded.config.packed_resources,
1347            &vec![PyembedPackedResourcesSource::MemoryMappedPath(
1348                "$ORIGIN/resources".into()
1349            )],
1350            "load mode should have mapped to MemoryMappedPath"
1351        );
1352
1353        assert!(
1354            embedded.extra_files.has_path(Path::new("resources")),
1355            "resources file should be present in extra files manifest"
1356        );
1357
1358        Ok(())
1359    }
1360
1361    #[test]
1362    fn test_minimal_extensions_present() -> Result<()> {
1363        let options = StandalonePythonExecutableBuilderOptions::default();
1364        let builder = options.new_builder()?;
1365
1366        let expected = builder
1367            .target_distribution
1368            .extension_modules
1369            .iter()
1370            .filter_map(|(_, extensions)| {
1371                if extensions.default_variant().is_minimally_required() {
1372                    Some(extensions.default_variant().name.clone())
1373                } else {
1374                    None
1375                }
1376            })
1377            .collect::<Vec<_>>();
1378
1379        // Spot check.
1380        assert!(expected.contains(&"_io".to_string()));
1381
1382        for name in &expected {
1383            // All extensions annotated as required in the distribution are marked
1384            // as built-ins.
1385            assert!(builder.extension_build_contexts.keys().any(|x| x == name));
1386            assert!(builder.iter_resources().any(|(x, _)| x == name));
1387        }
1388
1389        Ok(())
1390    }
1391
1392    #[test]
1393    fn test_linux_distribution_extensions() -> Result<()> {
1394        for libpython_link_mode in vec![
1395            BinaryLibpythonLinkMode::Static,
1396            BinaryLibpythonLinkMode::Dynamic,
1397        ] {
1398            let options = StandalonePythonExecutableBuilderOptions {
1399                target_triple: "x86_64-unknown-linux-gnu".to_string(),
1400                extension_module_filter: Some(ExtensionModuleFilter::All),
1401                libpython_link_mode,
1402                ..StandalonePythonExecutableBuilderOptions::default()
1403            };
1404
1405            let builder = options.new_builder()?;
1406
1407            let builtin_names = builder.extension_build_contexts.keys().collect::<Vec<_>>();
1408
1409            // All extensions compiled as built-ins by default.
1410            for (name, _) in builder.target_distribution.extension_modules.iter() {
1411                if builder
1412                    .python_packaging_policy()
1413                    .broken_extensions_for_triple(&builder.target_triple)
1414                    .unwrap_or(&vec![])
1415                    .contains(name)
1416                {
1417                    assert!(!builtin_names.contains(&name))
1418                } else {
1419                    assert!(builtin_names.contains(&name));
1420                }
1421            }
1422        }
1423
1424        Ok(())
1425    }
1426
1427    #[test]
1428    fn test_linux_distribution_extension_static() -> Result<()> {
1429        for libpython_link_mode in vec![
1430            BinaryLibpythonLinkMode::Static,
1431            BinaryLibpythonLinkMode::Dynamic,
1432        ] {
1433            let options = StandalonePythonExecutableBuilderOptions {
1434                target_triple: "x86_64-unknown-linux-gnu".to_string(),
1435                extension_module_filter: Some(ExtensionModuleFilter::Minimal),
1436                libpython_link_mode,
1437                ..StandalonePythonExecutableBuilderOptions::default()
1438            };
1439
1440            let mut builder = options.new_builder()?;
1441
1442            // When adding an extension module in static link mode, it gets
1443            // added as a built-in and linked with libpython.
1444
1445            let sqlite = builder
1446                .target_distribution
1447                .extension_modules
1448                .get("_sqlite3")
1449                .unwrap()
1450                .default_variant()
1451                .clone();
1452
1453            builder.add_python_extension_module(&sqlite, None)?;
1454
1455            assert_eq!(
1456                builder.extension_build_contexts.get("_sqlite3"),
1457                Some(&LibPythonBuildContext {
1458                    object_files: sqlite.object_file_data.clone(),
1459                    static_libraries: ["sqlite3".to_string()].iter().cloned().collect(),
1460                    init_functions: [("_sqlite3".to_string(), "PyInit__sqlite3".to_string())]
1461                        .iter()
1462                        .cloned()
1463                        .collect(),
1464                    licensed_components: licensed_components_from_extension(&sqlite),
1465                    ..LibPythonBuildContext::default()
1466                })
1467            );
1468
1469            assert_eq!(
1470                builder
1471                    .iter_resources()
1472                    .find_map(|(name, r)| if *name == "_sqlite3" { Some(r) } else { None }),
1473                Some(&PrePackagedResource {
1474                    is_builtin_extension_module: true,
1475                    name: "_sqlite3".to_string(),
1476                    ..PrePackagedResource::default()
1477                })
1478            );
1479        }
1480
1481        Ok(())
1482    }
1483
1484    #[test]
1485    fn test_linux_extension_in_memory_only() -> Result<()> {
1486        for libpython_link_mode in vec![
1487            BinaryLibpythonLinkMode::Static,
1488            BinaryLibpythonLinkMode::Dynamic,
1489        ] {
1490            let options = StandalonePythonExecutableBuilderOptions {
1491                target_triple: "x86_64-unknown-linux-gnu".to_string(),
1492                extension_module_filter: Some(ExtensionModuleFilter::Minimal),
1493                libpython_link_mode: libpython_link_mode.clone(),
1494                resources_location: Some(ConcreteResourceLocation::InMemory),
1495                resources_location_fallback: Some(None),
1496                ..StandalonePythonExecutableBuilderOptions::default()
1497            };
1498
1499            let mut builder = options.new_builder()?;
1500
1501            let res =
1502                builder.add_python_extension_module(&EXTENSION_MODULE_SHARED_LIBRARY_ONLY, None);
1503            assert!(res.is_err());
1504            assert_eq!(
1505            res.err().unwrap().to_string(),
1506            "extension module shared_only cannot be loaded from memory but memory loading required"
1507        );
1508
1509            let res =
1510                builder.add_python_extension_module(&EXTENSION_MODULE_OBJECT_FILES_ONLY, None);
1511            match libpython_link_mode {
1512                BinaryLibpythonLinkMode::Static => {
1513                    assert!(res.is_ok());
1514                    assert_extension_builtin(&builder, &EXTENSION_MODULE_OBJECT_FILES_ONLY);
1515                }
1516                BinaryLibpythonLinkMode::Dynamic => {
1517                    assert!(res.is_err());
1518                    assert_eq!(res.err().unwrap().to_string(), "extension module object_files_only cannot be loaded from memory but memory loading required");
1519                }
1520                BinaryLibpythonLinkMode::Default => {
1521                    panic!("should not get here");
1522                }
1523            }
1524
1525            let res = builder.add_python_extension_module(
1526                &EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES,
1527                None,
1528            );
1529            match libpython_link_mode {
1530                BinaryLibpythonLinkMode::Static => {
1531                    assert!(res.is_ok());
1532                    assert_extension_builtin(
1533                        &builder,
1534                        &EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES,
1535                    );
1536                }
1537                BinaryLibpythonLinkMode::Dynamic => {
1538                    assert!(res.is_err());
1539                    assert_eq!(res.err().unwrap().to_string(), "extension module shared_and_object_files cannot be loaded from memory but memory loading required")
1540                }
1541                BinaryLibpythonLinkMode::Default => {
1542                    panic!("should not get here");
1543                }
1544            }
1545        }
1546
1547        Ok(())
1548    }
1549
1550    #[test]
1551    fn test_linux_extension_prefer_in_memory() -> Result<()> {
1552        for libpython_link_mode in vec![
1553            BinaryLibpythonLinkMode::Static,
1554            BinaryLibpythonLinkMode::Dynamic,
1555        ] {
1556            let options = StandalonePythonExecutableBuilderOptions {
1557                target_triple: "x86_64-unknown-linux-gnu".to_string(),
1558                extension_module_filter: Some(ExtensionModuleFilter::Minimal),
1559                libpython_link_mode: libpython_link_mode.clone(),
1560                resources_location: Some(ConcreteResourceLocation::InMemory),
1561                resources_location_fallback: Some(Some(ConcreteResourceLocation::RelativePath(
1562                    "prefix_policy".to_string(),
1563                ))),
1564                ..StandalonePythonExecutableBuilderOptions::default()
1565            };
1566
1567            let mut builder = options.new_builder()?;
1568
1569            builder.add_python_extension_module(&EXTENSION_MODULE_SHARED_LIBRARY_ONLY, None)?;
1570            assert_extension_shared_library(
1571                &builder,
1572                &EXTENSION_MODULE_SHARED_LIBRARY_ONLY,
1573                ConcreteResourceLocation::RelativePath("prefix_policy".to_string()),
1574            );
1575
1576            let res =
1577                builder.add_python_extension_module(&EXTENSION_MODULE_OBJECT_FILES_ONLY, None);
1578            match libpython_link_mode {
1579                BinaryLibpythonLinkMode::Static => {
1580                    assert!(res.is_ok());
1581                    assert_extension_builtin(&builder, &EXTENSION_MODULE_OBJECT_FILES_ONLY);
1582                }
1583                BinaryLibpythonLinkMode::Dynamic => {
1584                    assert!(res.is_err());
1585                    assert_eq!(
1586                        res.err().unwrap().to_string(),
1587                        "no shared library data present"
1588                    );
1589                }
1590                BinaryLibpythonLinkMode::Default => {
1591                    panic!("should not get here");
1592                }
1593            }
1594
1595            builder.add_python_extension_module(
1596                &EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES,
1597                None,
1598            )?;
1599            match libpython_link_mode {
1600                BinaryLibpythonLinkMode::Static => {
1601                    assert_extension_builtin(
1602                        &builder,
1603                        &EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES,
1604                    );
1605                }
1606                BinaryLibpythonLinkMode::Dynamic => {
1607                    assert_extension_shared_library(
1608                        &builder,
1609                        &EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES,
1610                        ConcreteResourceLocation::RelativePath("prefix_policy".to_string()),
1611                    );
1612                }
1613                BinaryLibpythonLinkMode::Default => {
1614                    panic!("should not get here");
1615                }
1616            }
1617        }
1618        Ok(())
1619    }
1620
1621    #[test]
1622    fn test_linux_distribution_extension_filesystem_relative_only() -> Result<()> {
1623        for libpython_link_mode in vec![
1624            BinaryLibpythonLinkMode::Static,
1625            BinaryLibpythonLinkMode::Dynamic,
1626        ] {
1627            let options = StandalonePythonExecutableBuilderOptions {
1628                target_triple: "x86_64-unknown-linux-gnu".to_string(),
1629                extension_module_filter: Some(ExtensionModuleFilter::Minimal),
1630                libpython_link_mode,
1631                resources_location: Some(ConcreteResourceLocation::RelativePath(
1632                    "prefix_policy".to_string(),
1633                )),
1634                resources_location_fallback: Some(None),
1635                ..StandalonePythonExecutableBuilderOptions::default()
1636            };
1637
1638            let mut builder = options.new_builder()?;
1639
1640            let ext = builder
1641                .target_distribution
1642                .extension_modules
1643                .get("_sqlite3")
1644                .unwrap()
1645                .default_variant()
1646                .clone();
1647
1648            // The distribution extension can only be materialized as a built-in.
1649            // So it is added as such.
1650            builder.add_python_extension_module(&ext, None)?;
1651
1652            assert_eq!(
1653                builder.extension_build_contexts.get("_sqlite3"),
1654                Some(&LibPythonBuildContext {
1655                    object_files: ext.object_file_data.clone(),
1656                    static_libraries: ["sqlite3".to_string()].iter().cloned().collect(),
1657                    init_functions: [("_sqlite3".to_string(), "PyInit__sqlite3".to_string())]
1658                        .iter()
1659                        .cloned()
1660                        .collect(),
1661                    licensed_components: licensed_components_from_extension(&ext),
1662                    ..LibPythonBuildContext::default()
1663                })
1664            );
1665
1666            assert_eq!(
1667                builder
1668                    .iter_resources()
1669                    .find_map(|(name, r)| if *name == "_sqlite3" { Some(r) } else { None }),
1670                Some(&PrePackagedResource {
1671                    is_builtin_extension_module: true,
1672                    name: "_sqlite3".to_string(),
1673                    ..PrePackagedResource::default()
1674                })
1675            );
1676        }
1677
1678        Ok(())
1679    }
1680
1681    #[test]
1682    fn test_linux_extension_filesystem_relative_only() -> Result<()> {
1683        for libpython_link_mode in vec![
1684            BinaryLibpythonLinkMode::Static,
1685            BinaryLibpythonLinkMode::Dynamic,
1686        ] {
1687            let options = StandalonePythonExecutableBuilderOptions {
1688                target_triple: "x86_64-unknown-linux-gnu".to_string(),
1689                extension_module_filter: Some(ExtensionModuleFilter::Minimal),
1690                libpython_link_mode: libpython_link_mode.clone(),
1691                resources_location: Some(ConcreteResourceLocation::RelativePath(
1692                    "prefix_policy".to_string(),
1693                )),
1694                resources_location_fallback: Some(None),
1695                ..StandalonePythonExecutableBuilderOptions::default()
1696            };
1697
1698            let mut builder = options.new_builder()?;
1699
1700            builder.add_python_extension_module(&EXTENSION_MODULE_SHARED_LIBRARY_ONLY, None)?;
1701            assert_extension_shared_library(
1702                &builder,
1703                &EXTENSION_MODULE_SHARED_LIBRARY_ONLY,
1704                ConcreteResourceLocation::RelativePath("prefix_policy".to_string()),
1705            );
1706
1707            let res =
1708                builder.add_python_extension_module(&EXTENSION_MODULE_OBJECT_FILES_ONLY, None);
1709            match libpython_link_mode {
1710                BinaryLibpythonLinkMode::Static => {
1711                    assert!(res.is_ok());
1712                    assert_extension_builtin(&builder, &EXTENSION_MODULE_OBJECT_FILES_ONLY);
1713                }
1714                BinaryLibpythonLinkMode::Dynamic => {
1715                    assert!(res.is_err());
1716                    assert_eq!(res.err().unwrap().to_string(), "extension module object_files_only cannot be materialized as a shared library extension but filesystem loading required");
1717                }
1718                BinaryLibpythonLinkMode::Default => {
1719                    panic!("should not get here");
1720                }
1721            }
1722
1723            builder.add_python_extension_module(
1724                &EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES,
1725                None,
1726            )?;
1727            assert_extension_shared_library(
1728                &builder,
1729                &EXTENSION_MODULE_SHARED_LIBRARY_ONLY,
1730                ConcreteResourceLocation::RelativePath("prefix_policy".to_string()),
1731            );
1732        }
1733
1734        Ok(())
1735    }
1736
1737    #[test]
1738    fn test_linux_musl_distribution_dynamic() {
1739        let options = StandalonePythonExecutableBuilderOptions {
1740            target_triple: "x86_64-unknown-linux-musl".to_string(),
1741            extension_module_filter: Some(ExtensionModuleFilter::Minimal),
1742            libpython_link_mode: BinaryLibpythonLinkMode::Dynamic,
1743            ..StandalonePythonExecutableBuilderOptions::default()
1744        };
1745
1746        // Dynamic libpython on musl is not supported.
1747        let err = options.new_builder().err();
1748        assert!(err.is_some());
1749        assert_eq!(
1750            err.unwrap().to_string(),
1751            "Python distribution does not support dynamically linking libpython"
1752        );
1753    }
1754
1755    #[test]
1756    fn test_linux_musl_distribution_extensions() -> Result<()> {
1757        let options = StandalonePythonExecutableBuilderOptions {
1758            target_triple: "x86_64-unknown-linux-musl".to_string(),
1759            extension_module_filter: Some(ExtensionModuleFilter::All),
1760            ..StandalonePythonExecutableBuilderOptions::default()
1761        };
1762
1763        let builder = options.new_builder()?;
1764
1765        // All extensions for musl Linux are built-in because dynamic linking
1766        // not possible.
1767        for name in builder.target_distribution.extension_modules.keys() {
1768            if builder
1769                .python_packaging_policy()
1770                .broken_extensions_for_triple(&builder.target_triple)
1771                .unwrap_or(&vec![])
1772                .contains(name)
1773            {
1774                assert!(!builder.extension_build_contexts.keys().any(|e| name == e));
1775            } else {
1776                assert!(builder.extension_build_contexts.keys().any(|e| name == e));
1777            }
1778        }
1779
1780        Ok(())
1781    }
1782
1783    #[test]
1784    fn test_linux_musl_distribution_extension_static() -> Result<()> {
1785        let options = StandalonePythonExecutableBuilderOptions {
1786            target_triple: "x86_64-unknown-linux-musl".to_string(),
1787            extension_module_filter: Some(ExtensionModuleFilter::Minimal),
1788            libpython_link_mode: BinaryLibpythonLinkMode::Static,
1789            ..StandalonePythonExecutableBuilderOptions::default()
1790        };
1791
1792        let mut builder = options.new_builder()?;
1793
1794        // When adding an extension module in static link mode, it gets
1795        // added as a built-in and linked with libpython.
1796
1797        let sqlite = builder
1798            .target_distribution
1799            .extension_modules
1800            .get("_sqlite3")
1801            .unwrap()
1802            .default_variant()
1803            .clone();
1804
1805        builder.add_python_extension_module(&sqlite, None)?;
1806
1807        assert_eq!(
1808            builder.extension_build_contexts.get("_sqlite3"),
1809            Some(&LibPythonBuildContext {
1810                object_files: sqlite.object_file_data.clone(),
1811                static_libraries: ["sqlite3".to_string()].iter().cloned().collect(),
1812                init_functions: [("_sqlite3".to_string(), "PyInit__sqlite3".to_string())]
1813                    .iter()
1814                    .cloned()
1815                    .collect(),
1816                licensed_components: licensed_components_from_extension(&sqlite),
1817                ..LibPythonBuildContext::default()
1818            })
1819        );
1820
1821        assert_eq!(
1822            builder
1823                .iter_resources()
1824                .find_map(|(name, r)| if *name == "_sqlite3" { Some(r) } else { None }),
1825            Some(&PrePackagedResource {
1826                is_builtin_extension_module: true,
1827                name: "_sqlite3".to_string(),
1828                ..PrePackagedResource::default()
1829            })
1830        );
1831
1832        Ok(())
1833    }
1834
1835    #[test]
1836    fn test_linux_musl_distribution_extension_filesystem_relative_only() -> Result<()> {
1837        let options = StandalonePythonExecutableBuilderOptions {
1838            target_triple: "x86_64-unknown-linux-musl".to_string(),
1839            extension_module_filter: Some(ExtensionModuleFilter::Minimal),
1840            libpython_link_mode: BinaryLibpythonLinkMode::Static,
1841            resources_location: Some(ConcreteResourceLocation::RelativePath(
1842                "prefix_policy".to_string(),
1843            )),
1844            resources_location_fallback: Some(None),
1845            ..StandalonePythonExecutableBuilderOptions::default()
1846        };
1847
1848        let mut builder = options.new_builder()?;
1849
1850        let ext = builder
1851            .target_distribution
1852            .extension_modules
1853            .get("_sqlite3")
1854            .unwrap()
1855            .default_variant()
1856            .clone();
1857
1858        // The distribution extension can only be materialized as a built-in.
1859        // So it is added as such.
1860        builder.add_python_extension_module(&ext, None)?;
1861
1862        assert_eq!(
1863            builder.extension_build_contexts.get("_sqlite3"),
1864            Some(&LibPythonBuildContext {
1865                object_files: ext.object_file_data.clone(),
1866                static_libraries: ["sqlite3".to_string()].iter().cloned().collect(),
1867                init_functions: [("_sqlite3".to_string(), "PyInit__sqlite3".to_string())]
1868                    .iter()
1869                    .cloned()
1870                    .collect(),
1871                licensed_components: licensed_components_from_extension(&ext),
1872                ..LibPythonBuildContext::default()
1873            })
1874        );
1875
1876        assert_eq!(
1877            builder
1878                .iter_resources()
1879                .find_map(|(name, r)| if *name == "_sqlite3" { Some(r) } else { None }),
1880            Some(&PrePackagedResource {
1881                is_builtin_extension_module: true,
1882                name: "_sqlite3".to_string(),
1883                ..PrePackagedResource::default()
1884            })
1885        );
1886
1887        Ok(())
1888    }
1889
1890    #[test]
1891    fn test_linux_musl_extension_in_memory_only() -> Result<()> {
1892        let options = StandalonePythonExecutableBuilderOptions {
1893            target_triple: "x86_64-unknown-linux-musl".to_string(),
1894            extension_module_filter: Some(ExtensionModuleFilter::Minimal),
1895            libpython_link_mode: BinaryLibpythonLinkMode::Static,
1896            resources_location: Some(ConcreteResourceLocation::InMemory),
1897            resources_location_fallback: Some(None),
1898            ..StandalonePythonExecutableBuilderOptions::default()
1899        };
1900
1901        let mut builder = options.new_builder()?;
1902
1903        let res = builder.add_python_extension_module(&EXTENSION_MODULE_SHARED_LIBRARY_ONLY, None);
1904        assert!(res.is_err());
1905        assert_eq!(
1906            res.err().unwrap().to_string(),
1907            "extension module shared_only cannot be loaded from memory but memory loading required"
1908        );
1909
1910        builder.add_python_extension_module(&EXTENSION_MODULE_OBJECT_FILES_ONLY, None)?;
1911        assert_extension_builtin(&builder, &EXTENSION_MODULE_OBJECT_FILES_ONLY);
1912
1913        builder
1914            .add_python_extension_module(&EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES, None)?;
1915        assert_extension_builtin(&builder, &EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES);
1916
1917        Ok(())
1918    }
1919
1920    #[test]
1921    fn test_linux_musl_extension_prefer_in_memory() -> Result<()> {
1922        let options = StandalonePythonExecutableBuilderOptions {
1923            target_triple: "x86_64-unknown-linux-musl".to_string(),
1924            extension_module_filter: Some(ExtensionModuleFilter::Minimal),
1925            libpython_link_mode: BinaryLibpythonLinkMode::Static,
1926            resources_location: Some(ConcreteResourceLocation::InMemory),
1927            resources_location_fallback: Some(Some(ConcreteResourceLocation::RelativePath(
1928                "prefix_policy".to_string(),
1929            ))),
1930            ..StandalonePythonExecutableBuilderOptions::default()
1931        };
1932
1933        let mut builder = options.new_builder()?;
1934
1935        let res = builder.add_python_extension_module(&EXTENSION_MODULE_SHARED_LIBRARY_ONLY, None);
1936        assert!(res.is_err());
1937        assert_eq!(
1938            res.err().unwrap().to_string(),
1939            "extension module shared_only cannot be materialized as a shared library because distribution does not support loading extension module shared libraries"
1940        );
1941
1942        builder.add_python_extension_module(&EXTENSION_MODULE_OBJECT_FILES_ONLY, None)?;
1943        assert_extension_builtin(&builder, &EXTENSION_MODULE_OBJECT_FILES_ONLY);
1944
1945        builder
1946            .add_python_extension_module(&EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES, None)?;
1947        assert_extension_builtin(&builder, &EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES);
1948
1949        Ok(())
1950    }
1951
1952    #[test]
1953    fn test_macos_distribution_extensions() -> Result<()> {
1954        for target_triple in MACOS_TARGET_TRIPLES.iter() {
1955            for libpython_link_mode in vec![
1956                BinaryLibpythonLinkMode::Static,
1957                BinaryLibpythonLinkMode::Dynamic,
1958            ] {
1959                let options = StandalonePythonExecutableBuilderOptions {
1960                    target_triple: target_triple.to_string(),
1961                    libpython_link_mode,
1962                    extension_module_filter: Some(ExtensionModuleFilter::All),
1963                    ..StandalonePythonExecutableBuilderOptions::default()
1964                };
1965
1966                let builder = options.new_builder()?;
1967
1968                let builtin_names = builder.extension_build_contexts.keys().collect::<Vec<_>>();
1969
1970                // All extensions compiled as built-ins by default.
1971                for (name, _) in builder.target_distribution.extension_modules.iter() {
1972                    if builder
1973                        .python_packaging_policy()
1974                        .broken_extensions_for_triple(&builder.target_triple)
1975                        .unwrap_or(&vec![])
1976                        .contains(name)
1977                    {
1978                        assert!(!builtin_names.contains(&name))
1979                    } else {
1980                        assert!(builtin_names.contains(&name));
1981                    }
1982                }
1983            }
1984        }
1985
1986        Ok(())
1987    }
1988
1989    #[test]
1990    fn test_macos_distribution_extension_static() -> Result<()> {
1991        for target_triple in MACOS_TARGET_TRIPLES.iter() {
1992            for libpython_link_mode in vec![
1993                BinaryLibpythonLinkMode::Static,
1994                BinaryLibpythonLinkMode::Dynamic,
1995            ] {
1996                let options = StandalonePythonExecutableBuilderOptions {
1997                    target_triple: target_triple.to_string(),
1998                    extension_module_filter: Some(ExtensionModuleFilter::Minimal),
1999                    libpython_link_mode,
2000                    ..StandalonePythonExecutableBuilderOptions::default()
2001                };
2002
2003                let mut builder = options.new_builder()?;
2004
2005                // When adding an extension module in static link mode, it gets
2006                // added as a built-in and linked with libpython.
2007
2008                let sqlite = builder
2009                    .target_distribution
2010                    .extension_modules
2011                    .get("_sqlite3")
2012                    .unwrap()
2013                    .default_variant()
2014                    .clone();
2015
2016                builder.add_python_extension_module(&sqlite, None)?;
2017
2018                assert_eq!(
2019                    builder.extension_build_contexts.get("_sqlite3"),
2020                    Some(&LibPythonBuildContext {
2021                        object_files: sqlite.object_file_data.clone(),
2022                        static_libraries: ["sqlite3".to_string()].iter().cloned().collect(),
2023                        init_functions: [("_sqlite3".to_string(), "PyInit__sqlite3".to_string())]
2024                            .iter()
2025                            .cloned()
2026                            .collect(),
2027                        licensed_components: licensed_components_from_extension(&sqlite),
2028                        ..LibPythonBuildContext::default()
2029                    })
2030                );
2031
2032                assert_eq!(
2033                    builder
2034                        .iter_resources()
2035                        .find_map(|(name, r)| if *name == "_sqlite3" { Some(r) } else { None }),
2036                    Some(&PrePackagedResource {
2037                        is_builtin_extension_module: true,
2038                        name: "_sqlite3".to_string(),
2039                        ..PrePackagedResource::default()
2040                    })
2041                );
2042            }
2043        }
2044
2045        Ok(())
2046    }
2047
2048    #[test]
2049    fn test_macos_distribution_extension_filesystem_relative_only() -> Result<()> {
2050        for target_triple in MACOS_TARGET_TRIPLES.iter() {
2051            for libpython_link_mode in vec![
2052                BinaryLibpythonLinkMode::Static,
2053                BinaryLibpythonLinkMode::Dynamic,
2054            ] {
2055                let options = StandalonePythonExecutableBuilderOptions {
2056                    target_triple: target_triple.to_string(),
2057                    extension_module_filter: Some(ExtensionModuleFilter::Minimal),
2058                    libpython_link_mode,
2059                    resources_location: Some(ConcreteResourceLocation::RelativePath(
2060                        "prefix_policy".to_string(),
2061                    )),
2062                    resources_location_fallback: Some(None),
2063                    ..StandalonePythonExecutableBuilderOptions::default()
2064                };
2065
2066                let mut builder = options.new_builder()?;
2067
2068                let ext = builder
2069                    .target_distribution
2070                    .extension_modules
2071                    .get("_sqlite3")
2072                    .unwrap()
2073                    .default_variant()
2074                    .clone();
2075
2076                // Distribution extensions can only be materialized as built-ins.
2077                builder.add_python_extension_module(&ext, None)?;
2078
2079                assert_eq!(
2080                    builder.extension_build_contexts.get("_sqlite3"),
2081                    Some(&LibPythonBuildContext {
2082                        object_files: ext.object_file_data.clone(),
2083                        static_libraries: ["sqlite3".to_string()].iter().cloned().collect(),
2084                        init_functions: [("_sqlite3".to_string(), "PyInit__sqlite3".to_string())]
2085                            .iter()
2086                            .cloned()
2087                            .collect(),
2088                        licensed_components: licensed_components_from_extension(&ext),
2089                        ..LibPythonBuildContext::default()
2090                    })
2091                );
2092
2093                assert_eq!(
2094                    builder
2095                        .iter_resources()
2096                        .find_map(|(name, r)| if *name == "_sqlite3" { Some(r) } else { None }),
2097                    Some(&PrePackagedResource {
2098                        is_builtin_extension_module: true,
2099                        name: "_sqlite3".to_string(),
2100                        ..PrePackagedResource::default()
2101                    })
2102                );
2103            }
2104        }
2105
2106        Ok(())
2107    }
2108
2109    #[test]
2110    fn test_macos_extension_in_memory_only() -> Result<()> {
2111        for target_triple in MACOS_TARGET_TRIPLES.iter() {
2112            for libpython_link_mode in vec![
2113                BinaryLibpythonLinkMode::Static,
2114                BinaryLibpythonLinkMode::Dynamic,
2115            ] {
2116                let options = StandalonePythonExecutableBuilderOptions {
2117                    target_triple: target_triple.to_string(),
2118                    extension_module_filter: Some(ExtensionModuleFilter::Minimal),
2119                    libpython_link_mode: libpython_link_mode.clone(),
2120                    resources_location: Some(ConcreteResourceLocation::InMemory),
2121                    resources_location_fallback: Some(None),
2122                    ..StandalonePythonExecutableBuilderOptions::default()
2123                };
2124
2125                let mut builder = options.new_builder()?;
2126
2127                let res = builder
2128                    .add_python_extension_module(&EXTENSION_MODULE_SHARED_LIBRARY_ONLY, None);
2129                assert!(res.is_err());
2130                assert_eq!(
2131                    res.err().unwrap().to_string(),
2132                    "extension module shared_only cannot be loaded from memory but memory loading required"
2133                );
2134
2135                let res =
2136                    builder.add_python_extension_module(&EXTENSION_MODULE_OBJECT_FILES_ONLY, None);
2137                match libpython_link_mode {
2138                    BinaryLibpythonLinkMode::Static => {
2139                        assert!(res.is_ok());
2140                        assert_extension_builtin(&builder, &EXTENSION_MODULE_OBJECT_FILES_ONLY);
2141                    }
2142                    BinaryLibpythonLinkMode::Dynamic => {
2143                        assert!(res.is_err());
2144                        assert_eq!(res.err().unwrap().to_string(), "extension module object_files_only cannot be loaded from memory but memory loading required");
2145                    }
2146                    BinaryLibpythonLinkMode::Default => {
2147                        panic!("should not get here");
2148                    }
2149                }
2150
2151                let res = builder.add_python_extension_module(
2152                    &EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES,
2153                    None,
2154                );
2155                match libpython_link_mode {
2156                    BinaryLibpythonLinkMode::Static => {
2157                        assert!(res.is_ok());
2158                        assert_extension_builtin(
2159                            &builder,
2160                            &EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES,
2161                        );
2162                    }
2163                    BinaryLibpythonLinkMode::Dynamic => {
2164                        assert!(res.is_err());
2165                        assert_eq!(res.err().unwrap().to_string(), "extension module shared_and_object_files cannot be loaded from memory but memory loading required")
2166                    }
2167                    BinaryLibpythonLinkMode::Default => {
2168                        panic!("should not get here");
2169                    }
2170                }
2171            }
2172        }
2173
2174        Ok(())
2175    }
2176
2177    #[test]
2178    fn test_macos_extension_filesystem_relative_only() -> Result<()> {
2179        for target_triple in MACOS_TARGET_TRIPLES.iter() {
2180            for libpython_link_mode in vec![
2181                BinaryLibpythonLinkMode::Static,
2182                BinaryLibpythonLinkMode::Dynamic,
2183            ] {
2184                let options = StandalonePythonExecutableBuilderOptions {
2185                    target_triple: target_triple.to_string(),
2186                    extension_module_filter: Some(ExtensionModuleFilter::Minimal),
2187                    libpython_link_mode: libpython_link_mode.clone(),
2188                    resources_location: Some(ConcreteResourceLocation::RelativePath(
2189                        "prefix_policy".to_string(),
2190                    )),
2191                    resources_location_fallback: Some(None),
2192                    ..StandalonePythonExecutableBuilderOptions::default()
2193                };
2194
2195                let mut builder = options.new_builder()?;
2196
2197                builder.add_python_extension_module(&EXTENSION_MODULE_SHARED_LIBRARY_ONLY, None)?;
2198                assert_extension_shared_library(
2199                    &builder,
2200                    &EXTENSION_MODULE_SHARED_LIBRARY_ONLY,
2201                    ConcreteResourceLocation::RelativePath("prefix_policy".to_string()),
2202                );
2203
2204                let res =
2205                    builder.add_python_extension_module(&EXTENSION_MODULE_OBJECT_FILES_ONLY, None);
2206                match libpython_link_mode {
2207                    BinaryLibpythonLinkMode::Static => {
2208                        assert!(res.is_ok());
2209                        assert_extension_builtin(&builder, &EXTENSION_MODULE_OBJECT_FILES_ONLY);
2210                    }
2211                    BinaryLibpythonLinkMode::Dynamic => {
2212                        assert!(res.is_err());
2213                        assert_eq!(res.err().unwrap().to_string(), "extension module object_files_only cannot be materialized as a shared library extension but filesystem loading required");
2214                    }
2215                    BinaryLibpythonLinkMode::Default => {
2216                        panic!("should not get here");
2217                    }
2218                }
2219
2220                builder.add_python_extension_module(
2221                    &EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES,
2222                    None,
2223                )?;
2224                assert_extension_shared_library(
2225                    &builder,
2226                    &EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES,
2227                    ConcreteResourceLocation::RelativePath("prefix_policy".to_string()),
2228                );
2229            }
2230        }
2231
2232        Ok(())
2233    }
2234
2235    #[test]
2236    fn test_macos_extension_prefer_in_memory() -> Result<()> {
2237        for target_triple in MACOS_TARGET_TRIPLES.iter() {
2238            for libpython_link_mode in vec![
2239                BinaryLibpythonLinkMode::Static,
2240                BinaryLibpythonLinkMode::Dynamic,
2241            ] {
2242                let options = StandalonePythonExecutableBuilderOptions {
2243                    target_triple: target_triple.to_string(),
2244                    extension_module_filter: Some(ExtensionModuleFilter::Minimal),
2245                    libpython_link_mode: libpython_link_mode.clone(),
2246                    resources_location: Some(ConcreteResourceLocation::InMemory),
2247                    resources_location_fallback: Some(Some(
2248                        ConcreteResourceLocation::RelativePath("prefix_policy".to_string()),
2249                    )),
2250                    ..StandalonePythonExecutableBuilderOptions::default()
2251                };
2252
2253                let mut builder = options.new_builder()?;
2254
2255                builder.add_python_extension_module(&EXTENSION_MODULE_SHARED_LIBRARY_ONLY, None)?;
2256                assert_extension_shared_library(
2257                    &builder,
2258                    &EXTENSION_MODULE_SHARED_LIBRARY_ONLY,
2259                    ConcreteResourceLocation::RelativePath("prefix_policy".to_string()),
2260                );
2261
2262                let res =
2263                    builder.add_python_extension_module(&EXTENSION_MODULE_OBJECT_FILES_ONLY, None);
2264                match libpython_link_mode {
2265                    BinaryLibpythonLinkMode::Static => {
2266                        assert!(res.is_ok());
2267                        assert_extension_builtin(&builder, &EXTENSION_MODULE_OBJECT_FILES_ONLY);
2268                    }
2269                    BinaryLibpythonLinkMode::Dynamic => {
2270                        assert!(res.is_err());
2271                        assert_eq!(
2272                            res.err().unwrap().to_string(),
2273                            "no shared library data present"
2274                        );
2275                    }
2276                    BinaryLibpythonLinkMode::Default => {
2277                        panic!("should not get here");
2278                    }
2279                }
2280
2281                builder.add_python_extension_module(
2282                    &EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES,
2283                    None,
2284                )?;
2285                match libpython_link_mode {
2286                    BinaryLibpythonLinkMode::Static => {
2287                        assert_extension_builtin(
2288                            &builder,
2289                            &EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES,
2290                        );
2291                    }
2292                    BinaryLibpythonLinkMode::Dynamic => {
2293                        assert_extension_shared_library(
2294                            &builder,
2295                            &EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES,
2296                            ConcreteResourceLocation::RelativePath("prefix_policy".to_string()),
2297                        );
2298                    }
2299                    BinaryLibpythonLinkMode::Default => {
2300                        panic!("should not get here");
2301                    }
2302                }
2303            }
2304        }
2305
2306        Ok(())
2307    }
2308
2309    #[test]
2310    fn test_windows_dynamic_static_mismatch() {
2311        for target_triple in WINDOWS_TARGET_TRIPLES.iter() {
2312            let options = StandalonePythonExecutableBuilderOptions {
2313                target_triple: target_triple.to_string(),
2314                distribution_flavor: DistributionFlavor::StandaloneDynamic,
2315                extension_module_filter: Some(ExtensionModuleFilter::Minimal),
2316                libpython_link_mode: BinaryLibpythonLinkMode::Static,
2317                ..StandalonePythonExecutableBuilderOptions::default()
2318            };
2319
2320            // We can't request static libpython with a dynamic distribution.
2321            let err = options.new_builder().err();
2322            assert!(err.is_some());
2323            assert_eq!(
2324                err.unwrap().to_string(),
2325                "Python distribution does not support statically linking libpython"
2326            );
2327        }
2328    }
2329
2330    #[test]
2331    fn test_windows_static_dynamic_mismatch() {
2332        for target_triple in WINDOWS_TARGET_TRIPLES.iter() {
2333            let options = StandalonePythonExecutableBuilderOptions {
2334                target_triple: target_triple.to_string(),
2335                distribution_flavor: DistributionFlavor::StandaloneStatic,
2336                extension_module_filter: Some(ExtensionModuleFilter::Minimal),
2337                libpython_link_mode: BinaryLibpythonLinkMode::Dynamic,
2338                ..StandalonePythonExecutableBuilderOptions::default()
2339            };
2340
2341            // We can't request dynamic libpython with a static distribution.
2342            assert!(options.new_builder().is_err());
2343        }
2344    }
2345
2346    #[test]
2347    fn test_windows_dynamic_distribution_extensions() -> Result<()> {
2348        for target in WINDOWS_TARGET_TRIPLES.iter() {
2349            let options = StandalonePythonExecutableBuilderOptions {
2350                target_triple: target.to_string(),
2351                distribution_flavor: DistributionFlavor::StandaloneDynamic,
2352                extension_module_filter: Some(ExtensionModuleFilter::All),
2353                ..StandalonePythonExecutableBuilderOptions::default()
2354            };
2355
2356            let builder = options.new_builder()?;
2357
2358            let builtin_names = builder.extension_build_contexts.keys().collect::<Vec<_>>();
2359            let relative_path_extension_names = builder
2360                .iter_resources()
2361                .filter_map(|(name, r)| {
2362                    if r.relative_path_extension_module_shared_library.is_some() {
2363                        Some(name)
2364                    } else {
2365                        None
2366                    }
2367                })
2368                .collect::<Vec<_>>();
2369            let in_memory_extension_names = builder
2370                .iter_resources()
2371                .filter_map(|(name, r)| {
2372                    if r.in_memory_extension_module_shared_library.is_some() {
2373                        Some(name)
2374                    } else {
2375                        None
2376                    }
2377                })
2378                .collect::<Vec<_>>();
2379
2380            // Required extensions are compiled as built-in.
2381            // This assumes that our extensions annotated as required are built-in.
2382            // But this is an implementation detail. If this fails, it might be OK.
2383            for (name, variants) in builder.target_distribution.extension_modules.iter() {
2384                // !required does not mean it is missing, however!
2385                if variants.iter().any(|e| e.required) {
2386                    assert!(builtin_names.contains(&name));
2387                }
2388            }
2389
2390            // Builtin/default extensions are compiled as built-in.
2391            for (name, variants) in builder.target_distribution.extension_modules.iter() {
2392                if variants.iter().any(|e| e.builtin_default) {
2393                    assert!(builtin_names.contains(&name));
2394                }
2395            }
2396
2397            // Non-builtin/default extensions are compiled as standalone files.
2398            for (name, variants) in builder.target_distribution.extension_modules.iter() {
2399                if variants.iter().all(|e| !e.builtin_default) {
2400                    assert!(!builtin_names.contains(&name));
2401                    assert!(relative_path_extension_names.contains(&name));
2402                    assert!(!in_memory_extension_names.contains(&name));
2403                }
2404            }
2405        }
2406
2407        Ok(())
2408    }
2409
2410    #[test]
2411    fn test_windows_distribution_extension_static() -> Result<()> {
2412        for target_triple in WINDOWS_TARGET_TRIPLES.iter() {
2413            let options = StandalonePythonExecutableBuilderOptions {
2414                target_triple: target_triple.to_string(),
2415                distribution_flavor: DistributionFlavor::StandaloneStatic,
2416                extension_module_filter: Some(ExtensionModuleFilter::Minimal),
2417                libpython_link_mode: BinaryLibpythonLinkMode::Static,
2418                ..StandalonePythonExecutableBuilderOptions::default()
2419            };
2420
2421            let mut builder = options.new_builder()?;
2422
2423            // When adding an extension module in static link mode, it gets
2424            // added as a built-in and linked with libpython.
2425
2426            let sqlite = builder
2427                .target_distribution
2428                .extension_modules
2429                .get("_sqlite3")
2430                .unwrap()
2431                .default_variant()
2432                .clone();
2433
2434            builder.add_python_extension_module(&sqlite, None)?;
2435
2436            assert_eq!(
2437                builder.extension_build_contexts.get("_sqlite3"),
2438                Some(&LibPythonBuildContext {
2439                    object_files: sqlite.object_file_data.clone(),
2440                    static_libraries: ["sqlite3".to_string()].iter().cloned().collect(),
2441                    init_functions: [("_sqlite3".to_string(), "PyInit__sqlite3".to_string())]
2442                        .iter()
2443                        .cloned()
2444                        .collect(),
2445                    licensed_components: licensed_components_from_extension(&sqlite),
2446                    ..LibPythonBuildContext::default()
2447                })
2448            );
2449
2450            assert_eq!(
2451                builder
2452                    .iter_resources()
2453                    .find_map(|(name, r)| if *name == "_sqlite3" { Some(r) } else { None }),
2454                Some(&PrePackagedResource {
2455                    is_builtin_extension_module: true,
2456                    name: "_sqlite3".to_string(),
2457                    ..PrePackagedResource::default()
2458                })
2459            );
2460        }
2461
2462        Ok(())
2463    }
2464
2465    #[test]
2466    fn test_windows_distribution_extension_dynamic() -> Result<()> {
2467        for target_triple in WINDOWS_TARGET_TRIPLES.iter() {
2468            let options = StandalonePythonExecutableBuilderOptions {
2469                target_triple: target_triple.to_string(),
2470                distribution_flavor: DistributionFlavor::StandaloneDynamic,
2471                extension_module_filter: Some(ExtensionModuleFilter::Minimal),
2472                libpython_link_mode: BinaryLibpythonLinkMode::Dynamic,
2473                ..StandalonePythonExecutableBuilderOptions::default()
2474            };
2475
2476            let mut builder = options.new_builder()?;
2477
2478            // When adding an extension module in dynamic link mode and it isn't
2479            // already a built-in, it should be preserved as a standalone extension
2480            // module file.
2481
2482            let sqlite = builder
2483                .target_distribution
2484                .extension_modules
2485                .get("_sqlite3")
2486                .unwrap()
2487                .default_variant()
2488                .clone();
2489
2490            builder.add_python_extension_module(&sqlite, None)?;
2491
2492            assert!(!builder.extension_build_contexts.contains_key("_sqlite3"));
2493
2494            assert_eq!(
2495                builder
2496                    .iter_resources()
2497                    .find_map(|(name, r)| if *name == "_sqlite3" { Some(r) } else { None }),
2498                Some(&PrePackagedResource {
2499                    name: "_sqlite3".to_string(),
2500                    is_extension_module: true,
2501                    relative_path_extension_module_shared_library: Some((
2502                        PathBuf::from("lib/_sqlite3.pyd"),
2503                        sqlite.shared_library.as_ref().unwrap().to_memory()?
2504                    )),
2505                    shared_library_dependency_names: Some(vec!["sqlite3".to_string()]),
2506                    ..PrePackagedResource::default()
2507                })
2508            );
2509
2510            let library = builder
2511                .iter_resources()
2512                .find_map(|(name, r)| if *name == "sqlite3" { Some(r) } else { None })
2513                .unwrap();
2514            assert!(library.is_shared_library);
2515            assert!(library.relative_path_shared_library.is_some());
2516        }
2517
2518        Ok(())
2519    }
2520
2521    #[test]
2522    fn test_windows_dynamic_distribution_dynamic_extension_files() -> Result<()> {
2523        let env = get_env()?;
2524
2525        for target in WINDOWS_TARGET_TRIPLES.iter() {
2526            let options = StandalonePythonExecutableBuilderOptions {
2527                target_triple: target.to_string(),
2528                extension_module_filter: Some(ExtensionModuleFilter::Minimal),
2529                resources_location: Some(ConcreteResourceLocation::RelativePath("lib".to_string())),
2530                resources_location_fallback: Some(None),
2531                ..StandalonePythonExecutableBuilderOptions::default()
2532            };
2533
2534            let mut builder = options.new_builder()?;
2535
2536            // When loading resources from the filesystem, dynamically linked
2537            // extension modules should be manifested as filesystem files and
2538            // library dependencies should be captured.
2539
2540            let ssl_extension = builder
2541                .target_distribution
2542                .extension_modules
2543                .get("_ssl")
2544                .unwrap()
2545                .default_variant()
2546                .clone();
2547            assert_eq!(ssl_extension.extension_file_suffix, ".pyd");
2548            builder.add_python_extension_module(&ssl_extension, None)?;
2549
2550            let extensions = builder
2551                .iter_resources()
2552                .filter_map(|(_, r)| {
2553                    if r.relative_path_extension_module_shared_library.is_some() {
2554                        Some(r)
2555                    } else {
2556                        None
2557                    }
2558                })
2559                .collect::<Vec<_>>();
2560
2561            assert_eq!(
2562                extensions.len(),
2563                1,
2564                "only manually added extension present when using minimal extension mode"
2565            );
2566            let ssl = &extensions[0];
2567            assert_eq!(ssl.name, "_ssl");
2568
2569            let (path, _) = ssl
2570                .relative_path_extension_module_shared_library
2571                .as_ref()
2572                .unwrap();
2573            assert_eq!(path, &PathBuf::from("lib/_ssl.pyd"));
2574
2575            let shared_libraries = builder
2576                .iter_resources()
2577                .filter_map(|(_, r)| {
2578                    if r.relative_path_shared_library.is_some() {
2579                        Some(r)
2580                    } else {
2581                        None
2582                    }
2583                })
2584                .collect::<Vec<_>>();
2585
2586            assert_eq!(
2587                shared_libraries.len(),
2588                2,
2589                "pulled in shared library dependencies for _ssl"
2590            );
2591
2592            let lib_suffix = match *target {
2593                "i686-pc-windows-msvc" => "",
2594                "x86_64-pc-windows-msvc" => "-x64",
2595                _ => panic!("unexpected target: {}", target),
2596            };
2597
2598            assert_eq!(
2599                shared_libraries[0].name,
2600                format!("libcrypto-1_1{}", lib_suffix)
2601            );
2602            assert_eq!(
2603                shared_libraries[0]
2604                    .relative_path_shared_library
2605                    .as_ref()
2606                    .unwrap()
2607                    .0,
2608                "lib"
2609            );
2610
2611            assert_eq!(
2612                shared_libraries[1].name,
2613                format!("libssl-1_1{}", lib_suffix)
2614            );
2615
2616            let mut compiler = builder.host_distribution.create_bytecode_compiler(&env)?;
2617
2618            let resources = shared_libraries
2619                .iter()
2620                .map(|r| r.to_resource(compiler.deref_mut()))
2621                .collect::<Result<Vec<_>>>()?;
2622            assert_eq!(resources.len(), 2);
2623
2624            assert_eq!(
2625                &resources[0].1,
2626                &vec![(
2627                    PathBuf::from(format!("lib/libcrypto-1_1{}.dll", lib_suffix)),
2628                    FileData::Path(
2629                        builder
2630                            .target_distribution
2631                            .base_dir
2632                            .join("python")
2633                            .join("install")
2634                            .join("DLLs")
2635                            .join(format!("libcrypto-1_1{}.dll", lib_suffix))
2636                    ),
2637                    true
2638                )]
2639            );
2640            assert_eq!(
2641                &resources[1].1,
2642                &vec![(
2643                    PathBuf::from(format!("lib/libssl-1_1{}.dll", lib_suffix)),
2644                    FileData::Path(
2645                        builder
2646                            .target_distribution
2647                            .base_dir
2648                            .join("python")
2649                            .join("install")
2650                            .join("DLLs")
2651                            .join(format!("libssl-1_1{}.dll", lib_suffix))
2652                    ),
2653                    true
2654                )]
2655            );
2656        }
2657
2658        Ok(())
2659    }
2660
2661    #[test]
2662    fn test_windows_static_distribution_extensions() -> Result<()> {
2663        for target in WINDOWS_TARGET_TRIPLES.iter() {
2664            let options = StandalonePythonExecutableBuilderOptions {
2665                target_triple: target.to_string(),
2666                distribution_flavor: DistributionFlavor::StandaloneStatic,
2667                extension_module_filter: Some(ExtensionModuleFilter::All),
2668                ..StandalonePythonExecutableBuilderOptions::default()
2669            };
2670
2671            let builder = options.new_builder()?;
2672
2673            // All distribution extensions are built-ins in static Windows
2674            // distributions.
2675            for name in builder.target_distribution.extension_modules.keys() {
2676                assert!(builder.extension_build_contexts.keys().any(|x| x == name));
2677            }
2678        }
2679
2680        Ok(())
2681    }
2682
2683    #[test]
2684    fn test_windows_dynamic_extension_in_memory_only() -> Result<()> {
2685        for target_triple in WINDOWS_TARGET_TRIPLES.iter() {
2686            let options = StandalonePythonExecutableBuilderOptions {
2687                target_triple: target_triple.to_string(),
2688                distribution_flavor: DistributionFlavor::StandaloneDynamic,
2689                extension_module_filter: Some(ExtensionModuleFilter::Minimal),
2690                libpython_link_mode: BinaryLibpythonLinkMode::Dynamic,
2691                resources_location: Some(ConcreteResourceLocation::InMemory),
2692                resources_location_fallback: Some(None),
2693                allow_in_memory_shared_library_loading: Some(true),
2694                ..StandalonePythonExecutableBuilderOptions::default()
2695            };
2696
2697            let mut builder = options.new_builder()?;
2698
2699            builder.add_python_extension_module(&EXTENSION_MODULE_SHARED_LIBRARY_ONLY, None)?;
2700            assert_extension_shared_library(
2701                &builder,
2702                &EXTENSION_MODULE_SHARED_LIBRARY_ONLY,
2703                ConcreteResourceLocation::InMemory,
2704            );
2705
2706            let res =
2707                builder.add_python_extension_module(&EXTENSION_MODULE_OBJECT_FILES_ONLY, None);
2708            assert!(res.is_err());
2709            assert_eq!(
2710                res.err().unwrap().to_string(),
2711                "no shared library data present"
2712            );
2713
2714            builder.add_python_extension_module(
2715                &EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES,
2716                None,
2717            )?;
2718            assert_extension_shared_library(
2719                &builder,
2720                &EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES,
2721                ConcreteResourceLocation::InMemory,
2722            );
2723        }
2724
2725        Ok(())
2726    }
2727
2728    #[test]
2729    fn test_windows_static_extension_in_memory_only() -> Result<()> {
2730        for target_triple in WINDOWS_TARGET_TRIPLES.iter() {
2731            let options = StandalonePythonExecutableBuilderOptions {
2732                target_triple: target_triple.to_string(),
2733                distribution_flavor: DistributionFlavor::StandaloneStatic,
2734                extension_module_filter: Some(ExtensionModuleFilter::Minimal),
2735                libpython_link_mode: BinaryLibpythonLinkMode::Static,
2736                resources_location: Some(ConcreteResourceLocation::InMemory),
2737                resources_location_fallback: Some(None),
2738                ..StandalonePythonExecutableBuilderOptions::default()
2739            };
2740
2741            let mut builder = options.new_builder()?;
2742
2743            let res =
2744                builder.add_python_extension_module(&EXTENSION_MODULE_SHARED_LIBRARY_ONLY, None);
2745            assert!(res.is_err());
2746            assert_eq!(
2747                res.err().unwrap().to_string(),
2748                "extension module shared_only cannot be loaded from memory but memory loading required"
2749            );
2750
2751            builder.add_python_extension_module(&EXTENSION_MODULE_OBJECT_FILES_ONLY, None)?;
2752            assert_extension_builtin(&builder, &EXTENSION_MODULE_OBJECT_FILES_ONLY);
2753
2754            builder.add_python_extension_module(
2755                &EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES,
2756                None,
2757            )?;
2758            assert_extension_builtin(&builder, &EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES);
2759        }
2760
2761        Ok(())
2762    }
2763
2764    #[test]
2765    fn test_windows_dynamic_extension_filesystem_relative_only() -> Result<()> {
2766        for target_triple in WINDOWS_TARGET_TRIPLES.iter() {
2767            let options = StandalonePythonExecutableBuilderOptions {
2768                target_triple: target_triple.to_string(),
2769                distribution_flavor: DistributionFlavor::StandaloneDynamic,
2770                extension_module_filter: Some(ExtensionModuleFilter::Minimal),
2771                libpython_link_mode: BinaryLibpythonLinkMode::Dynamic,
2772                resources_location: Some(ConcreteResourceLocation::RelativePath(
2773                    "prefix_policy".to_string(),
2774                )),
2775                resources_location_fallback: Some(None),
2776                ..StandalonePythonExecutableBuilderOptions::default()
2777            };
2778
2779            let mut builder = options.new_builder()?;
2780
2781            builder.add_python_extension_module(&EXTENSION_MODULE_SHARED_LIBRARY_ONLY, None)?;
2782            assert_extension_shared_library(
2783                &builder,
2784                &EXTENSION_MODULE_SHARED_LIBRARY_ONLY,
2785                ConcreteResourceLocation::RelativePath("prefix_policy".to_string()),
2786            );
2787
2788            let res =
2789                builder.add_python_extension_module(&EXTENSION_MODULE_OBJECT_FILES_ONLY, None);
2790            assert!(res.is_err());
2791            assert_eq!(res.err().unwrap().to_string(), "extension module object_files_only cannot be materialized as a shared library extension but filesystem loading required");
2792
2793            builder.add_python_extension_module(
2794                &EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES,
2795                None,
2796            )?;
2797            assert_extension_shared_library(
2798                &builder,
2799                &EXTENSION_MODULE_SHARED_LIBRARY_ONLY,
2800                ConcreteResourceLocation::RelativePath("prefix_policy".to_string()),
2801            );
2802        }
2803
2804        Ok(())
2805    }
2806
2807    #[test]
2808    fn test_windows_static_extension_filesystem_relative_only() -> Result<()> {
2809        for target_triple in WINDOWS_TARGET_TRIPLES.iter() {
2810            let options = StandalonePythonExecutableBuilderOptions {
2811                target_triple: target_triple.to_string(),
2812                distribution_flavor: DistributionFlavor::StandaloneStatic,
2813                extension_module_filter: Some(ExtensionModuleFilter::Minimal),
2814                libpython_link_mode: BinaryLibpythonLinkMode::Static,
2815                resources_location: Some(ConcreteResourceLocation::RelativePath(
2816                    "prefix_policy".to_string(),
2817                )),
2818                resources_location_fallback: Some(None),
2819                ..StandalonePythonExecutableBuilderOptions::default()
2820            };
2821
2822            let mut builder = options.new_builder()?;
2823
2824            let res =
2825                builder.add_python_extension_module(&EXTENSION_MODULE_SHARED_LIBRARY_ONLY, None);
2826            assert!(res.is_err());
2827            assert_eq!(res.err().unwrap().to_string(),
2828                "extension module shared_only cannot be materialized as a shared library because distribution does not support loading extension module shared libraries"
2829            );
2830
2831            builder.add_python_extension_module(&EXTENSION_MODULE_OBJECT_FILES_ONLY, None)?;
2832            assert_extension_builtin(&builder, &EXTENSION_MODULE_OBJECT_FILES_ONLY);
2833
2834            builder.add_python_extension_module(
2835                &EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES,
2836                None,
2837            )?;
2838            assert_extension_builtin(&builder, &EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES);
2839        }
2840
2841        Ok(())
2842    }
2843
2844    #[test]
2845    fn test_windows_dynamic_extension_prefer_in_memory() -> Result<()> {
2846        for target_triple in WINDOWS_TARGET_TRIPLES.iter() {
2847            let options = StandalonePythonExecutableBuilderOptions {
2848                target_triple: target_triple.to_string(),
2849                distribution_flavor: DistributionFlavor::StandaloneDynamic,
2850                extension_module_filter: Some(ExtensionModuleFilter::Minimal),
2851                libpython_link_mode: BinaryLibpythonLinkMode::Dynamic,
2852                resources_location: Some(ConcreteResourceLocation::InMemory),
2853                resources_location_fallback: Some(Some(ConcreteResourceLocation::RelativePath(
2854                    "prefix_policy".to_string(),
2855                ))),
2856                allow_in_memory_shared_library_loading: Some(true),
2857                ..StandalonePythonExecutableBuilderOptions::default()
2858            };
2859
2860            let mut builder = options.new_builder()?;
2861
2862            builder.add_python_extension_module(&EXTENSION_MODULE_SHARED_LIBRARY_ONLY, None)?;
2863            assert_extension_shared_library(
2864                &builder,
2865                &EXTENSION_MODULE_SHARED_LIBRARY_ONLY,
2866                ConcreteResourceLocation::InMemory,
2867            );
2868
2869            // Cannot link new builtins in dynamic libpython link mode.
2870            let res =
2871                builder.add_python_extension_module(&EXTENSION_MODULE_OBJECT_FILES_ONLY, None);
2872            assert!(res.is_err());
2873            assert_eq!(
2874                res.err().unwrap().to_string(),
2875                "no shared library data present"
2876            );
2877
2878            builder.add_python_extension_module(
2879                &EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES,
2880                None,
2881            )?;
2882            assert_extension_shared_library(
2883                &builder,
2884                &EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES,
2885                ConcreteResourceLocation::InMemory,
2886            );
2887        }
2888
2889        Ok(())
2890    }
2891
2892    #[test]
2893    fn test_windows_static_extension_prefer_in_memory() -> Result<()> {
2894        for target_triple in WINDOWS_TARGET_TRIPLES.iter() {
2895            let options = StandalonePythonExecutableBuilderOptions {
2896                target_triple: target_triple.to_string(),
2897                distribution_flavor: DistributionFlavor::StandaloneStatic,
2898                extension_module_filter: Some(ExtensionModuleFilter::Minimal),
2899                libpython_link_mode: BinaryLibpythonLinkMode::Static,
2900                resources_location: Some(ConcreteResourceLocation::InMemory),
2901                resources_location_fallback: Some(Some(ConcreteResourceLocation::RelativePath(
2902                    "prefix_policy".to_string(),
2903                ))),
2904                ..StandalonePythonExecutableBuilderOptions::default()
2905            };
2906
2907            let mut builder = options.new_builder()?;
2908
2909            let res =
2910                builder.add_python_extension_module(&EXTENSION_MODULE_SHARED_LIBRARY_ONLY, None);
2911            assert!(res.is_err());
2912            assert_eq!(res.err().unwrap().to_string(),
2913                "extension module shared_only cannot be materialized as a shared library because distribution does not support loading extension module shared libraries"
2914            );
2915
2916            builder.add_python_extension_module(&EXTENSION_MODULE_OBJECT_FILES_ONLY, None)?;
2917            assert_extension_builtin(&builder, &EXTENSION_MODULE_OBJECT_FILES_ONLY);
2918
2919            builder.add_python_extension_module(
2920                &EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES,
2921                None,
2922            )?;
2923            assert_extension_builtin(&builder, &EXTENSION_MODULE_SHARED_LIBRARY_AND_OBJECT_FILES);
2924        }
2925
2926        Ok(())
2927    }
2928
2929    #[cfg(target_os = "linux")]
2930    #[test]
2931    fn test_linux_extension_build_with_library() -> Result<()> {
2932        let env = get_env()?;
2933
2934        // The build of 6.0 switched to Cython, which we don't intercept.
2935        // And 5.3 isn't marked as compatible with 3.10. So we pin to older
2936        // Python and a package version.
2937
2938        for libpython_link_mode in vec![
2939            BinaryLibpythonLinkMode::Static,
2940            BinaryLibpythonLinkMode::Dynamic,
2941        ] {
2942            let options = StandalonePythonExecutableBuilderOptions {
2943                target_triple: "x86_64-unknown-linux-gnu".to_string(),
2944                distribution_version: Some("3.9".into()),
2945                extension_module_filter: Some(ExtensionModuleFilter::All),
2946                libpython_link_mode: libpython_link_mode.clone(),
2947                resources_location: Some(ConcreteResourceLocation::InMemory),
2948                ..StandalonePythonExecutableBuilderOptions::default()
2949            };
2950
2951            let mut builder = options.new_builder()?;
2952
2953            let resources = builder.pip_install(
2954                &env,
2955                false,
2956                &["pyyaml==5.3.1".to_string()],
2957                &HashMap::new(),
2958            )?;
2959
2960            let extensions = resources
2961                .iter()
2962                .filter_map(|r| match r {
2963                    PythonResource::ExtensionModule(e) => Some(e),
2964                    _ => None,
2965                })
2966                .collect::<Vec<_>>();
2967
2968            assert_eq!(extensions.len(), 1);
2969
2970            let mut orig = extensions[0].clone();
2971            assert!(orig.shared_library.is_some());
2972
2973            let (objects_len, link_libraries) = match libpython_link_mode {
2974                BinaryLibpythonLinkMode::Dynamic => (0, vec![]),
2975                BinaryLibpythonLinkMode::Static => (
2976                    1,
2977                    vec![LibraryDependency {
2978                        name: "yaml".to_string(),
2979                        static_library: None,
2980                        static_filename: None,
2981                        dynamic_library: None,
2982                        dynamic_filename: None,
2983                        framework: false,
2984                        system: false,
2985                    }],
2986                ),
2987                BinaryLibpythonLinkMode::Default => {
2988                    panic!("should not get here");
2989                }
2990            };
2991
2992            assert_eq!(orig.object_file_data.len(), objects_len);
2993
2994            // Makes compare easier.
2995            let mut e = orig.to_mut();
2996            e.shared_library = None;
2997            e.object_file_data = vec![];
2998
2999            assert_eq!(
3000                e,
3001                &PythonExtensionModule {
3002                    name: "_yaml".to_string(),
3003                    init_fn: Some("PyInit__yaml".to_string()),
3004                    extension_file_suffix: ".cpython-39-x86_64-linux-gnu.so".to_string(),
3005                    shared_library: None,
3006                    object_file_data: vec![],
3007                    is_package: false,
3008                    link_libraries,
3009                    is_stdlib: false,
3010                    builtin_default: false,
3011                    required: false,
3012                    variant: None,
3013                    license: None,
3014                },
3015                "PythonExtensionModule for {:?}",
3016                libpython_link_mode
3017            );
3018        }
3019
3020        Ok(())
3021    }
3022
3023    #[test]
3024    fn test_vcruntime_requirements() -> Result<()> {
3025        for dist in get_all_standalone_distributions()? {
3026            let host_distribution = get_host_distribution_from_target(&dist)?;
3027
3028            let builder = StandalonePythonExecutableBuilder::from_distribution(
3029                host_distribution.clone(),
3030                dist.clone(),
3031                host_distribution.target_triple().to_string(),
3032                dist.target_triple().to_string(),
3033                "myapp".to_string(),
3034                BinaryLibpythonLinkMode::Default,
3035                dist.create_packaging_policy()?,
3036                dist.create_python_interpreter_config()?,
3037            )?;
3038
3039            let reqs = builder.vc_runtime_requirements();
3040
3041            if dist.target_triple().contains("windows") && dist.libpython_shared_library.is_some() {
3042                let platform = match dist.target_triple() {
3043                    "i686-pc-windows-msvc" => VcRedistributablePlatform::X86,
3044                    "x86_64-pc-windows-msvc" => VcRedistributablePlatform::X64,
3045                    triple => {
3046                        return Err(anyhow!("unexpected distribution triple: {}", triple));
3047                    }
3048                };
3049
3050                assert_eq!(reqs, Some(("14".to_string(), platform)));
3051            } else {
3052                assert!(reqs.is_none());
3053            }
3054        }
3055
3056        Ok(())
3057    }
3058
3059    #[test]
3060    fn test_install_windows_runtime_dlls() -> Result<()> {
3061        for dist in get_all_standalone_distributions()? {
3062            let host_distribution = get_host_distribution_from_target(&dist)?;
3063
3064            let mut builder = StandalonePythonExecutableBuilder::from_distribution(
3065                host_distribution.clone(),
3066                dist.clone(),
3067                host_distribution.target_triple().to_string(),
3068                dist.target_triple().to_string(),
3069                "myapp".to_string(),
3070                BinaryLibpythonLinkMode::Default,
3071                dist.create_packaging_policy()?,
3072                dist.create_python_interpreter_config()?,
3073            )?;
3074
3075            // In Never mode, the set of extra files should always be empty.
3076            builder.set_windows_runtime_dlls_mode(WindowsRuntimeDllsMode::Never);
3077            let manifest = builder.resolve_windows_runtime_dll_files()?;
3078            assert!(
3079                manifest.is_empty(),
3080                "target triple: {}",
3081                dist.target_triple()
3082            );
3083
3084            // In WhenPresent mode, we resolve files when the binary requires
3085            // them and when the host machine can locate them.
3086            builder.set_windows_runtime_dlls_mode(WindowsRuntimeDllsMode::WhenPresent);
3087
3088            if let Some((version, platform)) = builder.vc_runtime_requirements() {
3089                let can_locate_runtime =
3090                    find_visual_cpp_redistributable(&version, platform).is_ok();
3091
3092                let manifest = builder.resolve_windows_runtime_dll_files()?;
3093
3094                if can_locate_runtime {
3095                    assert!(
3096                        !manifest.is_empty(),
3097                        "target triple: {}",
3098                        dist.target_triple()
3099                    );
3100                } else {
3101                    assert!(
3102                        manifest.is_empty(),
3103                        "target triple: {}",
3104                        dist.target_triple()
3105                    );
3106                }
3107            } else {
3108                assert!(
3109                    builder.resolve_windows_runtime_dll_files()?.is_empty(),
3110                    "target triple: {}",
3111                    dist.target_triple()
3112                );
3113            }
3114
3115            // In Always mode, we error if we can't locate the runtime files.
3116            builder.set_windows_runtime_dlls_mode(WindowsRuntimeDllsMode::Always);
3117
3118            if let Some((version, platform)) = builder.vc_runtime_requirements() {
3119                let can_locate_runtime =
3120                    find_visual_cpp_redistributable(&version, platform).is_ok();
3121
3122                let res = builder.resolve_windows_runtime_dll_files();
3123
3124                if can_locate_runtime {
3125                    assert!(!res?.is_empty(), "target triple: {}", dist.target_triple());
3126                } else {
3127                    assert!(res.is_err());
3128                }
3129            } else {
3130                assert!(
3131                    builder.resolve_windows_runtime_dll_files()?.is_empty(),
3132                    "target triple: {}",
3133                    dist.target_triple()
3134                );
3135            }
3136        }
3137
3138        Ok(())
3139    }
3140
3141    // This test is expensive so shard for parallelism.
3142    fn test_stdlib(dist: Arc<StandaloneDistribution>) -> Result<()> {
3143        let host_distribution = get_host_distribution_from_target(&dist)?;
3144
3145        let mut policy = dist.create_packaging_policy()?;
3146        policy.set_include_test(true);
3147
3148        let mut builder = StandalonePythonExecutableBuilder::from_distribution(
3149            host_distribution.clone(),
3150            dist.clone(),
3151            host_distribution.target_triple().to_string(),
3152            dist.target_triple().to_string(),
3153            "myapp".to_string(),
3154            BinaryLibpythonLinkMode::Default,
3155            policy,
3156            dist.create_python_interpreter_config()?,
3157        )?;
3158
3159        builder.add_distribution_resources(None)?;
3160
3161        let temp_dir = get_env()?.temporary_directory("pyoxidizer-test")?;
3162
3163        let mut compiler =
3164            BytecodeCompiler::new(host_distribution.python_exe_path(), temp_dir.path())?;
3165
3166        // Some stdlib test modules are malformed and cause resource compiling to fail.
3167        builder
3168            .resources_collector
3169            .compile_resources(&mut compiler)?;
3170
3171        temp_dir.close()?;
3172
3173        Ok(())
3174    }
3175
3176    #[test]
3177    fn test_stdlib_tests_0() -> Result<()> {
3178        for dist in get_all_standalone_distributions_chunk(0, 5)? {
3179            test_stdlib(dist)?;
3180        }
3181
3182        Ok(())
3183    }
3184
3185    #[test]
3186    fn test_stdlib_tests_1() -> Result<()> {
3187        for dist in get_all_standalone_distributions_chunk(1, 5)? {
3188            test_stdlib(dist)?;
3189        }
3190
3191        Ok(())
3192    }
3193
3194    #[test]
3195    fn test_stdlib_tests_2() -> Result<()> {
3196        for dist in get_all_standalone_distributions_chunk(2, 5)? {
3197            test_stdlib(dist)?;
3198        }
3199
3200        Ok(())
3201    }
3202
3203    #[test]
3204    fn test_stdlib_tests_3() -> Result<()> {
3205        for dist in get_all_standalone_distributions_chunk(3, 5)? {
3206            test_stdlib(dist)?;
3207        }
3208
3209        Ok(())
3210    }
3211
3212    #[test]
3213    fn test_stdlib_tests_4() -> Result<()> {
3214        for dist in get_all_standalone_distributions_chunk(4, 5)? {
3215            test_stdlib(dist)?;
3216        }
3217
3218        Ok(())
3219    }
3220}