1use {
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
26pub fn default_memory_allocator(target_triple: &str) -> MemoryAllocatorBackend {
28 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#[derive(Clone, Debug, PartialEq, Eq)]
83pub enum PyembedPackedResourcesSource {
84 MemoryIncludeBytes(PathBuf),
86 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#[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 configure_locale: Some(true),
151 ..PythonInterpreterConfig::default()
152 },
153 allocator_backend: MemoryAllocatorBackend::Default,
154 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 pub fn to_oxidized_python_interpreter_config_rs(&self) -> Result<String> {
180 #[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 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 #[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}