pyoxidizerlib/py_packaging/
config.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5/*!
6Configuring a Python interpreter.
7*/
8
9use {
10    anyhow::Result,
11    itertools::Itertools,
12    python_packaging::{
13        interpreter::{
14            Allocator, BytesWarning, CheckHashPycsMode, CoerceCLocale, MemoryAllocatorBackend,
15            MultiprocessingStartMethod, PythonInterpreterConfig, PythonInterpreterProfile,
16            TerminfoResolution,
17        },
18        resource::BytecodeOptimizationLevel,
19    },
20    std::{
21        io::Write,
22        path::{Path, PathBuf},
23    },
24};
25
26/// Determine the default memory allocator for a target triple.
27pub fn default_memory_allocator(target_triple: &str) -> MemoryAllocatorBackend {
28    // Jemalloc doesn't work on Windows.
29    //
30    // We don't use Jemalloc by default in the test environment because it slows down
31    // builds of test projects.
32    if target_triple.ends_with("-pc-windows-msvc") || cfg!(test) {
33        MemoryAllocatorBackend::Default
34    } else {
35        MemoryAllocatorBackend::Jemalloc
36    }
37}
38
39fn optional_bool_to_string(value: &Option<bool>) -> String {
40    match value {
41        Some(value) => format!("Some({})", value),
42        None => "None".to_string(),
43    }
44}
45
46fn optional_string_to_string(value: &Option<String>) -> String {
47    match value {
48        Some(value) => format!("Some(\"{}\".to_string())", value.escape_default()),
49        None => "None".to_string(),
50    }
51}
52
53fn path_to_string(value: &Path) -> String {
54    format!(
55        "std::path::PathBuf::from(\"{}\")",
56        value.display().to_string().escape_default()
57    )
58}
59
60fn optional_pathbuf_to_string(value: &Option<PathBuf>) -> String {
61    match value {
62        Some(value) => format!("Some({})", path_to_string(value)),
63        None => "None".to_string(),
64    }
65}
66
67fn optional_vec_string_to_string(value: &Option<Vec<String>>) -> String {
68    match value {
69        Some(value) => format!(
70            "Some(vec![{}])",
71            value
72                .iter()
73                .map(|x| format!("\"{}\".to_string()", x.escape_default()))
74                .collect::<Vec<_>>()
75                .join(", ")
76        ),
77        None => "None".to_string(),
78    }
79}
80
81/// Represents sources for loading packed resources data.
82#[derive(Clone, Debug, PartialEq, Eq)]
83pub enum PyembedPackedResourcesSource {
84    /// Load from memory via an `include_bytes!` directive.
85    MemoryIncludeBytes(PathBuf),
86    /// Load from a file using memory mapped I/O.
87    ///
88    /// The string `$ORIGIN` is expanded at runtime.
89    MemoryMappedPath(PathBuf),
90}
91
92impl ToString for PyembedPackedResourcesSource {
93    fn to_string(&self) -> String {
94        match self {
95            Self::MemoryIncludeBytes(path) => {
96                format!(
97                    "pyembed::PackedResourcesSource::Memory(include_bytes!(r#\"{}\"#))",
98                    path.display()
99                )
100            }
101            Self::MemoryMappedPath(path) => {
102                format!(
103                    "pyembed::PackedResourcesSource::MemoryMappedPath({})",
104                    path_to_string(path)
105                )
106            }
107        }
108    }
109}
110
111/// Represents the run-time configuration of a Python interpreter.
112///
113/// This type mirrors `pyembed::OxidizedPythonInterpreterConfig`. We can't
114/// use that type verbatim because of lifetime issues. It might be possible.
115/// But that type holds a reference to resources data and this type needs to
116/// be embedded in Starlark values, which have a `static lifetime.
117#[derive(Clone, Debug, PartialEq, Eq)]
118pub struct PyembedPythonInterpreterConfig {
119    pub config: PythonInterpreterConfig,
120    pub allocator_backend: MemoryAllocatorBackend,
121    pub allocator_raw: bool,
122    pub allocator_mem: bool,
123    pub allocator_obj: bool,
124    pub allocator_pymalloc_arena: bool,
125    pub allocator_debug: bool,
126    pub set_missing_path_configuration: bool,
127    pub oxidized_importer: bool,
128    pub filesystem_importer: bool,
129    pub packed_resources: Vec<PyembedPackedResourcesSource>,
130    pub argvb: bool,
131    pub multiprocessing_auto_dispatch: bool,
132    pub multiprocessing_start_method: MultiprocessingStartMethod,
133    pub sys_frozen: bool,
134    pub sys_meipass: bool,
135    pub terminfo_resolution: TerminfoResolution,
136    pub tcl_library: Option<PathBuf>,
137    pub write_modules_directory_env: Option<String>,
138}
139
140impl Default for PyembedPythonInterpreterConfig {
141    fn default() -> Self {
142        PyembedPythonInterpreterConfig {
143            config: PythonInterpreterConfig {
144                profile: PythonInterpreterProfile::Isolated,
145                // Isolated mode disables configure_locale by default. But this
146                // setting is essential for properly initializing encoding at
147                // run-time. Without this, UTF-8 arguments are mangled, for
148                // example. See
149                // https://github.com/indygreg/PyOxidizer/issues/294 for more.
150                configure_locale: Some(true),
151                ..PythonInterpreterConfig::default()
152            },
153            allocator_backend: MemoryAllocatorBackend::Default,
154            // This setting has no effect by itself. But the default of true
155            // makes it so a custom backend is used automatically.
156            allocator_raw: true,
157            allocator_mem: false,
158            allocator_obj: false,
159            allocator_pymalloc_arena: false,
160            allocator_debug: false,
161            set_missing_path_configuration: true,
162            oxidized_importer: true,
163            filesystem_importer: false,
164            packed_resources: vec![],
165            argvb: false,
166            multiprocessing_auto_dispatch: true,
167            multiprocessing_start_method: MultiprocessingStartMethod::Auto,
168            sys_frozen: true,
169            sys_meipass: false,
170            terminfo_resolution: TerminfoResolution::None,
171            tcl_library: None,
172            write_modules_directory_env: None,
173        }
174    }
175}
176
177impl PyembedPythonInterpreterConfig {
178    /// Convert the instance to Rust code that constructs a `pyembed::OxidizedPythonInterpreterConfig`.
179    pub fn to_oxidized_python_interpreter_config_rs(&self) -> Result<String> {
180        // This code is complicated enough. Let's not worry about format! in format!.
181        #[allow(unknown_lints, clippy::format_in_format_args)]
182        let code = format!(
183            "pyembed::OxidizedPythonInterpreterConfig {{\n    \
184            exe: None,\n    \
185            origin: None,\n    \
186            interpreter_config: pyembed::PythonInterpreterConfig {{\n        \
187            profile: {},\n        \
188            allocator: {},\n        \
189            configure_locale: {},\n        \
190            coerce_c_locale: {},\n        \
191            coerce_c_locale_warn: {},\n        \
192            development_mode: {},\n        \
193            isolated: {},\n        \
194            legacy_windows_fs_encoding: {},\n        \
195            parse_argv: {},\n        \
196            use_environment: {},\n        \
197            utf8_mode: {},\n        \
198            argv: None,\n        \
199            base_exec_prefix: {},\n        \
200            base_executable: {},\n        \
201            base_prefix: {},\n        \
202            buffered_stdio: {},\n        \
203            bytes_warning: {},\n        \
204            check_hash_pycs_mode: {},\n        \
205            configure_c_stdio: {},\n        \
206            dump_refs: {},\n        \
207            exec_prefix: {},\n        \
208            executable: {},\n        \
209            fault_handler: {},\n        \
210            filesystem_encoding: {},\n        \
211            filesystem_errors: {},\n        \
212            hash_seed: {},\n        \
213            home: {},\n        \
214            import_time: {},\n        \
215            inspect: {},\n        \
216            install_signal_handlers: {},\n        \
217            interactive: {},\n        \
218            legacy_windows_stdio: {},\n        \
219            malloc_stats: {},\n        \
220            module_search_paths: {},\n        \
221            optimization_level: {},\n        \
222            parser_debug: {},\n        \
223            pathconfig_warnings: {},\n        \
224            prefix: {},\n        \
225            program_name: {},\n        \
226            pycache_prefix: {},\n        \
227            python_path_env: {},\n        \
228            quiet: {},\n        \
229            run_command: {},\n        \
230            run_filename: {},\n        \
231            run_module: {},\n        \
232            show_ref_count: {},\n        \
233            site_import: {},\n        \
234            skip_first_source_line: {},\n        \
235            stdio_encoding: {},\n        \
236            stdio_errors: {},\n        \
237            tracemalloc: {},\n        \
238            user_site_directory: {},\n        \
239            verbose: {},\n        \
240            warn_options: {},\n        \
241            write_bytecode: {},\n        \
242            x_options: {},\n        \
243            }},\n    \
244            allocator_backend: {},\n    \
245            allocator_raw: {},\n    \
246            allocator_mem: {},\n    \
247            allocator_obj: {},\n    \
248            allocator_pymalloc_arena: {},\n    \
249            allocator_debug: {},\n    \
250            set_missing_path_configuration: {},\n    \
251            oxidized_importer: {},\n    \
252            filesystem_importer: {},\n    \
253            packed_resources: {},\n    \
254            extra_extension_modules: None,\n    \
255            argv: None,\n    \
256            argvb: {},\n    \
257            multiprocessing_auto_dispatch: {},\n    \
258            multiprocessing_start_method: {},\n    \
259            sys_frozen: {},\n    \
260            sys_meipass: {},\n    \
261            terminfo_resolution: {},\n    \
262            tcl_library: {},\n    \
263            write_modules_directory_env: {},\n    \
264            }}\n\
265            ",
266            match self.config.profile {
267                PythonInterpreterProfile::Isolated => "pyembed::PythonInterpreterProfile::Isolated",
268                PythonInterpreterProfile::Python => "pyembed::PythonInterpreterProfile::Python",
269            },
270            match self.config.allocator {
271                Some(Allocator::Debug) => "Some(pyembed::Allocator::Debug)",
272                Some(Allocator::Default) => "Some(pyembed::Allocator::Default)",
273                Some(Allocator::Malloc) => "Some(pyembed::Allocator::Malloc)",
274                Some(Allocator::MallocDebug) => "Some(pyembed::Allocator::MallocDebug)",
275                Some(Allocator::NotSet) => "Some(pyembed::Allocator::NotSet)",
276                Some(Allocator::PyMalloc) => "Some(pyembed::Allocator::PyMalloc)",
277                Some(Allocator::PyMallocDebug) => "Some(pyembed::Allocator::PyMallocDebug)",
278                None => "None",
279            },
280            optional_bool_to_string(&self.config.configure_locale),
281            match &self.config.coerce_c_locale {
282                Some(CoerceCLocale::C) => "Some(pyembed::CoerceCLocale::C)",
283                Some(CoerceCLocale::LCCtype) => "Some(pyembed::CoerceCLocale::LCCtype)",
284                None => "None",
285            },
286            optional_bool_to_string(&self.config.coerce_c_locale_warn),
287            optional_bool_to_string(&self.config.development_mode),
288            optional_bool_to_string(&self.config.isolated),
289            optional_bool_to_string(&self.config.legacy_windows_fs_encoding),
290            optional_bool_to_string(&self.config.parse_argv),
291            optional_bool_to_string(&self.config.use_environment),
292            optional_bool_to_string(&self.config.utf8_mode),
293            optional_pathbuf_to_string(&self.config.base_exec_prefix),
294            optional_pathbuf_to_string(&self.config.base_executable),
295            optional_pathbuf_to_string(&self.config.base_prefix),
296            optional_bool_to_string(&self.config.buffered_stdio),
297            match self.config.bytes_warning {
298                Some(BytesWarning::None) => "Some(pyembed::BytesWarning::None)",
299                Some(BytesWarning::Warn) => "Some(pyembed::BytesWarning::Warn)",
300                Some(BytesWarning::Raise) => "Some(pyembed::BytesWarning::Raise)",
301                None => "None",
302            },
303            match self.config.check_hash_pycs_mode {
304                Some(CheckHashPycsMode::Always) => "Some(pyembed::CheckHashPycsMode::Always)",
305                Some(CheckHashPycsMode::Default) => "Some(pyembed::CheckHashPycsMode::Default)",
306                Some(CheckHashPycsMode::Never) => "Some(pyembed::CheckHashPycsMode::Never)",
307                None => "None",
308            },
309            optional_bool_to_string(&self.config.configure_c_stdio),
310            optional_bool_to_string(&self.config.dump_refs),
311            optional_pathbuf_to_string(&self.config.exec_prefix),
312            optional_pathbuf_to_string(&self.config.executable),
313            optional_bool_to_string(&self.config.fault_handler),
314            optional_string_to_string(&self.config.filesystem_encoding),
315            optional_string_to_string(&self.config.filesystem_errors),
316            match &self.config.hash_seed {
317                Some(value) => format!("Some({})", value),
318                None => "None".to_string(),
319            },
320            optional_pathbuf_to_string(&self.config.home),
321            optional_bool_to_string(&self.config.import_time),
322            optional_bool_to_string(&self.config.inspect),
323            optional_bool_to_string(&self.config.install_signal_handlers),
324            optional_bool_to_string(&self.config.interactive),
325            optional_bool_to_string(&self.config.legacy_windows_stdio),
326            optional_bool_to_string(&self.config.malloc_stats),
327            match &self.config.module_search_paths {
328                Some(paths) => {
329                    format!(
330                        "Some(vec![{}])",
331                        paths
332                            .iter()
333                            .map(|p| path_to_string(p.as_path()))
334                            .collect::<Vec<String>>()
335                            .join(", ")
336                    )
337                }
338                None => "None".to_string(),
339            },
340            match self.config.optimization_level {
341                Some(BytecodeOptimizationLevel::Zero) =>
342                    "Some(pyembed::BytecodeOptimizationLevel::Zero)",
343                Some(BytecodeOptimizationLevel::One) =>
344                    "Some(pyembed::BytecodeOptimizationLevel::One)",
345                Some(BytecodeOptimizationLevel::Two) =>
346                    "Some(pyembed::BytecodeOptimizationLevel::Two)",
347                None => "None",
348            },
349            optional_bool_to_string(&self.config.parser_debug),
350            optional_bool_to_string(&self.config.pathconfig_warnings),
351            optional_pathbuf_to_string(&self.config.prefix),
352            optional_pathbuf_to_string(&self.config.program_name),
353            optional_pathbuf_to_string(&self.config.pycache_prefix),
354            optional_string_to_string(&self.config.python_path_env),
355            optional_bool_to_string(&self.config.quiet),
356            optional_string_to_string(&self.config.run_command),
357            optional_pathbuf_to_string(&self.config.run_filename),
358            optional_string_to_string(&self.config.run_module),
359            optional_bool_to_string(&self.config.show_ref_count),
360            optional_bool_to_string(&self.config.site_import),
361            optional_bool_to_string(&self.config.skip_first_source_line),
362            optional_string_to_string(&self.config.stdio_encoding),
363            optional_string_to_string(&self.config.stdio_errors),
364            optional_bool_to_string(&self.config.tracemalloc),
365            optional_bool_to_string(&self.config.user_site_directory),
366            optional_bool_to_string(&self.config.verbose),
367            optional_vec_string_to_string(&self.config.warn_options),
368            optional_bool_to_string(&self.config.write_bytecode),
369            optional_vec_string_to_string(&self.config.x_options),
370            match self.allocator_backend {
371                MemoryAllocatorBackend::Jemalloc => "pyembed::MemoryAllocatorBackend::Jemalloc",
372                MemoryAllocatorBackend::Mimalloc => "pyembed::MemoryAllocatorBackend::Mimalloc",
373                MemoryAllocatorBackend::Snmalloc => "pyembed::MemoryAllocatorBackend::Snmalloc",
374                MemoryAllocatorBackend::Rust => "pyembed::MemoryAllocatorBackend::Rust",
375                MemoryAllocatorBackend::Default => "pyembed::MemoryAllocatorBackend::Default",
376            },
377            self.allocator_raw,
378            self.allocator_mem,
379            self.allocator_obj,
380            self.allocator_pymalloc_arena,
381            self.allocator_debug,
382            self.set_missing_path_configuration,
383            self.oxidized_importer,
384            self.filesystem_importer,
385            format!(
386                "vec![{}]",
387                self.packed_resources
388                    .iter()
389                    .map(|e| e.to_string())
390                    .join(", ")
391            ),
392            self.argvb,
393            self.multiprocessing_auto_dispatch,
394            match self.multiprocessing_start_method {
395                MultiprocessingStartMethod::None =>
396                    "pyembed::MultiprocessingStartMethod::None".to_string(),
397                MultiprocessingStartMethod::Fork =>
398                    "pyembed::MultiprocessingStartMethod::Fork".to_string(),
399                MultiprocessingStartMethod::ForkServer =>
400                    "pyembed::MultiprocessingStartMethod::ForkServer".to_string(),
401                MultiprocessingStartMethod::Spawn =>
402                    "pyembed::MultiprocessingStartMethod::Spawn".to_string(),
403                MultiprocessingStartMethod::Auto =>
404                    "pyembed::MultiprocessingStartMethod::Auto".to_string(),
405            },
406            self.sys_frozen,
407            self.sys_meipass,
408            match self.terminfo_resolution {
409                TerminfoResolution::Dynamic => "pyembed::TerminfoResolution::Dynamic".to_string(),
410                TerminfoResolution::None => "pyembed::TerminfoResolution::None".to_string(),
411                TerminfoResolution::Static(ref v) => {
412                    format!("pyembed::TerminfoResolution::Static(r###\"{}\"###", v)
413                }
414            },
415            optional_pathbuf_to_string(&self.tcl_library),
416            optional_string_to_string(&self.write_modules_directory_env),
417        );
418
419        Ok(code)
420    }
421
422    /// Write a Rust file containing a function for obtaining the default `OxidizedPythonInterpreterConfig`.
423    pub fn write_default_python_config_rs(&self, path: impl AsRef<Path>) -> Result<()> {
424        let mut f = std::fs::File::create(path.as_ref())?;
425
426        let indented = self
427            .to_oxidized_python_interpreter_config_rs()?
428            .split('\n')
429            .map(|line| "    ".to_string() + line)
430            .join("\n");
431
432        f.write_fmt(format_args!(
433            "/// Obtain the default Python configuration\n\
434             ///\n\
435             /// The crate is compiled with a default Python configuration embedded\n\
436             /// in the crate. This function will return an instance of that\n\
437             /// configuration.\n\
438             pub fn default_python_config<'a>() -> pyembed::OxidizedPythonInterpreterConfig<'a> {{\n{}\n}}\n",
439            indented
440        ))?;
441
442        Ok(())
443    }
444}
445
446#[cfg(test)]
447mod tests {
448    use crate::{
449        environment::default_target_triple,
450        py_packaging::distribution::{BinaryLibpythonLinkMode, PythonDistribution},
451    };
452    use {super::*, crate::testutil::*};
453
454    fn assert_contains(haystack: &str, needle: &str) -> Result<()> {
455        assert!(
456            haystack.contains(needle),
457            "expected to find {} in {}",
458            needle,
459            haystack
460        );
461
462        Ok(())
463    }
464
465    fn assert_serialize_module_search_paths(paths: &[&str], expected_contents: &str) -> Result<()> {
466        let mut config = PyembedPythonInterpreterConfig::default();
467        config.config.module_search_paths = Some(paths.iter().map(PathBuf::from).collect());
468
469        let code = config.to_oxidized_python_interpreter_config_rs()?;
470        assert_contains(&code, expected_contents)
471    }
472
473    #[test]
474    fn test_serialize_module_search_paths() -> Result<()> {
475        assert_serialize_module_search_paths(
476            &["$ORIGIN/lib", "lib"],
477            "module_search_paths: Some(vec![std::path::PathBuf::from(\"$ORIGIN/lib\"), std::path::PathBuf::from(\"lib\")]),"
478        )
479    }
480
481    #[test]
482    fn test_serialize_module_search_paths_backslash() -> Result<()> {
483        assert_serialize_module_search_paths(
484            &["$ORIGIN\\lib", "lib"],
485            "module_search_paths: Some(vec![std::path::PathBuf::from(\"$ORIGIN\\\\lib\"), std::path::PathBuf::from(\"lib\")]),"
486        )
487    }
488
489    #[test]
490    fn test_serialize_filesystem_fields() -> Result<()> {
491        let mut config = PyembedPythonInterpreterConfig::default();
492        config.config.filesystem_encoding = Some("ascii".to_string());
493        config.config.filesystem_errors = Some("strict".to_string());
494
495        let code = config.to_oxidized_python_interpreter_config_rs()?;
496
497        assert!(code.contains("filesystem_encoding: Some(\"ascii\".to_string()),"));
498        assert!(code.contains("filesystem_errors: Some(\"strict\".to_string()),"));
499
500        Ok(())
501    }
502
503    #[test]
504    fn test_backslash_in_path() -> Result<()> {
505        let config = PyembedPythonInterpreterConfig {
506            tcl_library: Some(PathBuf::from("c:\\windows")),
507            ..Default::default()
508        };
509
510        let code = config.to_oxidized_python_interpreter_config_rs()?;
511
512        assert_contains(
513            &code,
514            "tcl_library: Some(std::path::PathBuf::from(\"c:\\\\windows\")),",
515        )
516    }
517
518    // TODO enable once CI has a linkable Python.
519    #[test]
520    #[ignore]
521    fn test_build_all_fields() -> Result<()> {
522        let env = get_env()?;
523        let dist = get_default_distribution(None)?;
524        let policy = dist.create_packaging_policy()?;
525
526        let config = PyembedPythonInterpreterConfig {
527            config: PythonInterpreterConfig {
528                profile: Default::default(),
529                allocator: Some(Allocator::MallocDebug),
530                configure_locale: Some(true),
531                coerce_c_locale: Some(CoerceCLocale::C),
532                coerce_c_locale_warn: Some(true),
533                development_mode: Some(true),
534                isolated: Some(false),
535                legacy_windows_fs_encoding: Some(false),
536                parse_argv: Some(true),
537                use_environment: Some(true),
538                utf8_mode: Some(true),
539                argv: Some(vec!["foo".into(), "bar".into()]),
540                base_exec_prefix: Some("path".into()),
541                base_executable: Some("path".into()),
542                base_prefix: Some("path".into()),
543                buffered_stdio: Some(false),
544                bytes_warning: Some(BytesWarning::Raise),
545                check_hash_pycs_mode: Some(CheckHashPycsMode::Always),
546                configure_c_stdio: Some(true),
547                dump_refs: Some(true),
548                exec_prefix: Some("path".into()),
549                executable: Some("path".into()),
550                fault_handler: Some(false),
551                filesystem_encoding: Some("encoding".into()),
552                filesystem_errors: Some("errors".into()),
553                hash_seed: Some(42),
554                home: Some("home".into()),
555                import_time: Some(true),
556                inspect: Some(false),
557                install_signal_handlers: Some(true),
558                interactive: Some(true),
559                legacy_windows_stdio: Some(false),
560                malloc_stats: Some(false),
561                module_search_paths: Some(vec!["lib".into()]),
562                optimization_level: Some(BytecodeOptimizationLevel::One),
563                parser_debug: Some(true),
564                pathconfig_warnings: Some(false),
565                prefix: Some("prefix".into()),
566                program_name: Some("program_name".into()),
567                pycache_prefix: Some("prefix".into()),
568                python_path_env: Some("env".into()),
569                quiet: Some(true),
570                run_command: Some("command".into()),
571                run_filename: Some("filename".into()),
572                run_module: Some("module".into()),
573                show_ref_count: Some(false),
574                site_import: Some(true),
575                skip_first_source_line: Some(false),
576                stdio_encoding: Some("encoding".into()),
577                stdio_errors: Some("errors".into()),
578                tracemalloc: Some(false),
579                user_site_directory: Some(false),
580                verbose: Some(true),
581                warn_options: Some(vec!["option0".into(), "option1".into()]),
582                write_bytecode: Some(true),
583                x_options: Some(vec!["x0".into(), "x1".into()]),
584            },
585            allocator_backend: MemoryAllocatorBackend::Default,
586            allocator_raw: true,
587            allocator_mem: true,
588            allocator_obj: true,
589            allocator_pymalloc_arena: true,
590            allocator_debug: true,
591            set_missing_path_configuration: false,
592            oxidized_importer: true,
593            filesystem_importer: true,
594            packed_resources: vec![
595                PyembedPackedResourcesSource::MemoryIncludeBytes(PathBuf::from("packed-resources")),
596                PyembedPackedResourcesSource::MemoryMappedPath(PathBuf::from(
597                    "$ORIGIN/packed-resources",
598                )),
599            ],
600            argvb: true,
601            sys_frozen: false,
602            sys_meipass: true,
603            terminfo_resolution: TerminfoResolution::Dynamic,
604            tcl_library: Some("path".into()),
605            write_modules_directory_env: Some("env".into()),
606            multiprocessing_auto_dispatch: false,
607            multiprocessing_start_method: MultiprocessingStartMethod::Spawn,
608        };
609
610        let builder = dist.as_python_executable_builder(
611            default_target_triple(),
612            default_target_triple(),
613            "all_config_fields",
614            BinaryLibpythonLinkMode::Dynamic,
615            &policy,
616            &config,
617            None,
618        )?;
619
620        crate::project_building::build_python_executable(
621            &env,
622            "all_config_fields",
623            builder.as_ref(),
624            default_target_triple(),
625            "0",
626            false,
627        )?;
628
629        Ok(())
630    }
631}