1#[cfg(test)]
2use std::cell::RefCell;
3use std::{
4 collections::{HashMap, HashSet},
5 env,
6 ffi::{OsStr, OsString},
7 fmt::Display,
8 fs::{self, DirEntry},
9 io::{BufRead, BufReader, Read, Write},
10 path::{Path, PathBuf},
11 process::{Command, Stdio},
12 str::{self, FromStr},
13};
14
15pub use target_lexicon::Triple;
16
17use target_lexicon::{Architecture, Environment, OperatingSystem, Vendor};
18
19use crate::{
20 bail, ensure,
21 errors::{Context, Error, Result},
22 warn,
23};
24
25pub(crate) const MINIMUM_SUPPORTED_VERSION: PythonVersion = PythonVersion { major: 3, minor: 8 };
27
28pub(crate) const MINIMUM_SUPPORTED_VERSION_ABI3T: PythonVersion = PythonVersion {
29 major: 3,
30 minor: 15,
31};
32
33const MINIMUM_SUPPORTED_VERSION_GRAALPY: PythonVersion = PythonVersion {
35 major: 25,
36 minor: 0,
37};
38
39pub(crate) const STABLE_ABI_MAX_MINOR: u8 = 15;
41
42#[cfg(test)]
43thread_local! {
44 static READ_ENV_VARS: RefCell<Vec<String>> = const { RefCell::new(Vec::new()) };
45}
46
47pub fn cargo_env_var(var: &str) -> Option<String> {
51 env::var_os(var).map(|os_string| os_string.to_str().unwrap().into())
52}
53
54pub fn env_var(var: &str) -> Option<OsString> {
57 println!("cargo:rerun-if-env-changed={var}");
58 #[cfg(test)]
59 {
60 READ_ENV_VARS.with(|env_vars| {
61 env_vars.borrow_mut().push(var.to_owned());
62 });
63 }
64 env::var_os(var)
65}
66
67pub fn target_triple_from_env() -> Triple {
71 env::var("TARGET")
72 .expect("target_triple_from_env() must be called from a build script")
73 .parse()
74 .expect("Unrecognized TARGET environment variable value")
75}
76
77fn sanitize_stable_abi_version(
78 stable_abi_version: Option<PythonVersion>,
79 version: PythonVersion,
80) -> Result<PythonVersion> {
81 if let Some(min_version) = stable_abi_version {
82 ensure!(
83 min_version <= version,
84 "cannot set a minimum Python version {} higher than the interpreter version {} \
85 (the minimum Python version is implied by the abi3-py3{} feature)",
86 min_version,
87 version,
88 min_version.minor
89 );
90 Ok(min_version)
91 } else {
92 Ok(version)
93 }
94}
95
96fn applicable_stable_abi(
103 implementation: PythonImplementation,
104 version: PythonVersion,
105 gil_disabled: bool,
106 abi3_version: Option<StableAbiVersion>,
107 abi3t_version: Option<StableAbiVersion>,
108) -> Option<(StableAbi, PythonVersion)> {
109 let exact = |requested: StableAbiVersion| match requested {
110 StableAbiVersion::Current => version,
111 StableAbiVersion::Target(target) => target,
112 };
113 let abi3 = abi3_version.map(|v| (StableAbi::Abi3, exact(v)));
114 let abi3t = abi3t_version.map(|v| (StableAbi::Abi3t, exact(v)));
115 let selected = if version >= MINIMUM_SUPPORTED_VERSION_ABI3T {
116 match gil_disabled {
117 false => abi3t.or(abi3),
118 true => abi3t,
119 }
120 } else {
121 match gil_disabled {
122 false => abi3,
123 true => None,
124 }
125 };
126 match implementation {
127 PythonImplementation::PyPy | PythonImplementation::GraalPy => {
128 selected.map(|(kind, _)| (kind, version))
129 }
130 _ => selected,
131 }
132}
133
134fn applicable_stable_abi_at_interpreter_version(
139 implementation: PythonImplementation,
140 version: PythonVersion,
141 gil_disabled: bool,
142) -> Option<(StableAbi, PythonVersion)> {
143 applicable_stable_abi(
144 implementation,
145 version,
146 gil_disabled,
147 get_abi3_version(),
148 get_abi3t_version(),
149 )
150 .map(|(kind, _)| (kind, version))
151}
152
153#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
165pub struct InterpreterConfig {
166 #[deprecated(
170 since = "0.29.0",
171 note = "please use `.implementation()` getter or `InterpreterConfigBuilder` instead"
172 )]
173 pub implementation: PythonImplementation,
174
175 #[deprecated(
179 since = "0.29.0",
180 note = "please use `.version()` getter or `InterpreterConfigBuilder` instead"
181 )]
182 pub version: PythonVersion,
183
184 #[deprecated(
188 since = "0.29.0",
189 note = "please use `.shared()` getter or `InterpreterConfigBuilder` instead"
190 )]
191 pub shared: bool,
192
193 target_abi: PythonAbi,
194
195 #[deprecated(since = "0.29.0", note = "please match against target_abi instead")]
197 pub abi3: bool,
198
199 #[deprecated(
207 since = "0.29.0",
208 note = "please use `.lib_name()` getter or `InterpreterConfigBuilder` instead"
209 )]
210 pub lib_name: Option<String>,
211
212 #[deprecated(
219 since = "0.29.0",
220 note = "please use `.lib_dir()` getter or `InterpreterConfigBuilder` instead"
221 )]
222 pub lib_dir: Option<String>,
223
224 #[deprecated(
232 since = "0.29.0",
233 note = "please use `.executable()` getter or `InterpreterConfigBuilder` instead"
234 )]
235 pub executable: Option<String>,
236
237 #[deprecated(
241 since = "0.29.0",
242 note = "please use `.pointer_width()` getter or `InterpreterConfigBuilder` instead"
243 )]
244 pub pointer_width: Option<u32>,
245
246 #[deprecated(
250 since = "0.29.0",
251 note = "please use `.build_flags()` getter or `InterpreterConfigBuilder` instead"
252 )]
253 pub build_flags: BuildFlags,
254
255 #[deprecated(
266 since = "0.29.0",
267 note = "please use `.suppress_build_script_link_lines()` getter or `InterpreterConfigBuilder` instead"
268 )]
269 pub suppress_build_script_link_lines: bool,
270
271 #[deprecated(
282 since = "0.29.0",
283 note = "please use `.extra_build_script_lines()` getter or `InterpreterConfigBuilder` instead"
284 )]
285 pub extra_build_script_lines: Vec<String>,
286 #[deprecated(
288 since = "0.29.0",
289 note = "please use `.python_framework_prefix()` getter or `InterpreterConfigBuilder` instead"
290 )]
291 pub python_framework_prefix: Option<String>,
292}
293
294#[expect(deprecated, reason = "this impl block touches the internal fields")]
296impl InterpreterConfig {
297 pub fn implementation(&self) -> PythonImplementation {
301 self.implementation
302 }
303
304 pub fn version(&self) -> PythonVersion {
308 self.version
309 }
310
311 pub fn shared(&self) -> bool {
315 self.shared
316 }
317
318 pub fn target_abi(&self) -> PythonAbi {
323 self.target_abi
324 }
325
326 #[deprecated(since = "0.29.0", note = "please use `target_abi()` instead")]
329 pub fn abi3(&self) -> bool {
330 matches!(self.target_abi.kind, PythonAbiKind::Stable(StableAbi::Abi3))
331 }
332
333 pub fn lib_name(&self) -> Option<&str> {
341 self.lib_name.as_deref()
342 }
343
344 pub fn lib_dir(&self) -> Option<&str> {
351 self.lib_dir.as_deref()
352 }
353
354 pub fn executable(&self) -> Option<&str> {
362 self.executable.as_deref()
363 }
364
365 pub fn pointer_width(&self) -> Option<u32> {
369 self.pointer_width
370 }
371
372 pub fn build_flags(&self) -> &BuildFlags {
376 &self.build_flags
377 }
378
379 pub fn suppress_build_script_link_lines(&self) -> bool {
381 self.suppress_build_script_link_lines
382 }
383
384 pub fn extra_build_script_lines(&self) -> &[String] {
388 &self.extra_build_script_lines
389 }
390
391 pub fn python_framework_prefix(&self) -> Option<&str> {
393 self.python_framework_prefix.as_deref()
394 }
395
396 #[doc(hidden)]
397 pub fn build_script_outputs(&self) -> Vec<String> {
398 assert!(self.target_abi.version() >= MINIMUM_SUPPORTED_VERSION);
400
401 let mut out = vec![];
402
403 for i in MINIMUM_SUPPORTED_VERSION.minor..=self.target_abi.version().minor {
404 out.push(format!("cargo:rustc-cfg=Py_3_{i}"));
405 }
406
407 match self.target_abi.implementation() {
408 PythonImplementation::CPython => {}
409 PythonImplementation::PyPy => out.push("cargo:rustc-cfg=PyPy".to_owned()),
410 PythonImplementation::GraalPy => out.push("cargo:rustc-cfg=GraalPy".to_owned()),
411 PythonImplementation::RustPython => out.push("cargo:rustc-cfg=RustPython".to_owned()),
412 }
413
414 match self.target_abi.kind() {
415 PythonAbiKind::Stable(kind) => {
416 out.push("cargo:rustc-cfg=Py_LIMITED_API".to_owned());
417 if kind == StableAbi::Abi3t {
418 out.push("cargo:rustc-cfg=Py_GIL_DISABLED".to_owned());
419 }
420 }
421 PythonAbiKind::VersionSpecific(kind) => match kind {
422 GilUsed::FreeThreaded => {
423 out.push("cargo:rustc-cfg=Py_GIL_DISABLED".to_owned());
424 }
425 GilUsed::GilEnabled => {}
426 },
427 }
428 for flag in &self.build_flags.0 {
429 match flag {
430 BuildFlag::Py_GIL_DISABLED => continue,
432 flag => out.push(format!("cargo:rustc-cfg=py_sys_config=\"{flag}\"")),
433 }
434 }
435 out
436 }
437
438 fn from_interpreter(
439 interpreter: impl AsRef<Path>,
440 abi3_version: Option<StableAbiVersion>,
441 abi3t_version: Option<StableAbiVersion>,
442 ) -> Result<Self> {
443 const SCRIPT: &str = r#"
444# Allow the script to run on Python 2, so that nicer error can be printed later.
445from __future__ import print_function
446
447import os.path
448import platform
449import struct
450import sys
451from sysconfig import get_config_var, get_platform
452
453PYPY = platform.python_implementation() == "PyPy"
454GRAALPY = platform.python_implementation() == "GraalVM"
455
456if GRAALPY:
457 graalpy_ver = map(int, __graalpython__.get_graalvm_version().split('.'));
458 print("graalpy_major", next(graalpy_ver))
459 print("graalpy_minor", next(graalpy_ver))
460
461# sys.base_prefix is missing on Python versions older than 3.3; this allows the script to continue
462# so that the version mismatch can be reported in a nicer way later.
463base_prefix = getattr(sys, "base_prefix", None)
464
465if base_prefix:
466 # Anaconda based python distributions have a static python executable, but include
467 # the shared library. Use the shared library for embedding to avoid rust trying to
468 # LTO the static library (and failing with newer gcc's, because it is old).
469 ANACONDA = os.path.exists(os.path.join(base_prefix, "conda-meta"))
470else:
471 ANACONDA = False
472
473def print_if_set(varname, value):
474 if value is not None:
475 print(varname, value)
476
477# Windows always uses shared linking
478WINDOWS = platform.system() == "Windows"
479
480# macOS framework packages use shared linking
481FRAMEWORK = bool(get_config_var("PYTHONFRAMEWORK"))
482FRAMEWORK_PREFIX = get_config_var("PYTHONFRAMEWORKPREFIX")
483
484# unix-style shared library enabled
485SHARED = bool(get_config_var("Py_ENABLE_SHARED"))
486
487print("implementation", platform.python_implementation())
488print("version_major", sys.version_info[0])
489print("version_minor", sys.version_info[1])
490print("shared", PYPY or GRAALPY or ANACONDA or WINDOWS or FRAMEWORK or SHARED)
491print("python_framework_prefix", FRAMEWORK_PREFIX)
492print_if_set("ld_version", get_config_var("LDVERSION"))
493print_if_set("libdir", get_config_var("LIBDIR"))
494print_if_set("base_prefix", base_prefix)
495print("executable", sys.executable)
496print("calcsize_pointer", struct.calcsize("P"))
497print("mingw", get_platform().startswith("mingw"))
498print("cygwin", get_platform().startswith("cygwin"))
499print("ext_suffix", get_config_var("EXT_SUFFIX"))
500print("gil_disabled", get_config_var("Py_GIL_DISABLED"))
501"#;
502 let output = run_python_script(interpreter.as_ref(), SCRIPT)?;
503 let map: HashMap<String, String> = parse_script_output(&output);
504
505 ensure!(
506 !map.is_empty(),
507 "broken Python interpreter: {}",
508 interpreter.as_ref().display()
509 );
510
511 if let Some(value) = map.get("graalpy_major") {
512 let graalpy_version = PythonVersion {
513 major: value
514 .parse()
515 .context("failed to parse GraalPy major version")?,
516 minor: map["graalpy_minor"]
517 .parse()
518 .context("failed to parse GraalPy minor version")?,
519 };
520 ensure!(
521 graalpy_version >= MINIMUM_SUPPORTED_VERSION_GRAALPY,
522 "At least GraalPy version {} needed, got {}",
523 MINIMUM_SUPPORTED_VERSION_GRAALPY,
524 graalpy_version
525 );
526 };
527
528 let shared = map["shared"].as_str() == "True";
529 let python_framework_prefix = map.get("python_framework_prefix").cloned();
530
531 let version = PythonVersion {
532 major: map["version_major"]
533 .parse()
534 .context("failed to parse major version")?,
535 minor: map["version_minor"]
536 .parse()
537 .context("failed to parse minor version")?,
538 };
539
540 let implementation = map["implementation"].parse()?;
541
542 let gil_disabled = match map["gil_disabled"].as_str() {
543 "1" => true,
544 "0" => false,
545 "None" => false,
546 _ => panic!("Unknown Py_GIL_DISABLED value"),
547 };
548
549 let stable_abi = applicable_stable_abi(
550 implementation,
551 version,
552 gil_disabled,
553 abi3_version,
554 abi3t_version,
555 );
556
557 let target_abi =
558 PythonAbi::from_stable_abi(implementation, version, stable_abi, gil_disabled)?;
559
560 let cygwin = map["cygwin"].as_str() == "True";
561
562 let lib_name = if cfg!(windows) {
563 default_lib_name_windows(
564 target_abi,
565 map["mingw"].as_str() == "True",
566 map["ext_suffix"].starts_with("_d."),
570 )?
571 } else {
572 default_lib_name_unix(
573 target_abi,
574 cygwin,
575 map.get("ld_version").map(String::as_str),
576 )?
577 };
578
579 let lib_dir = if cfg!(windows) {
580 map.get("base_prefix")
581 .map(|base_prefix| format!("{base_prefix}\\libs"))
582 } else {
583 map.get("libdir").cloned()
584 };
585
586 let calcsize_pointer: u32 = map["calcsize_pointer"]
592 .parse()
593 .context("failed to parse calcsize_pointer")?;
594
595 InterpreterConfigBuilder::new(implementation, version)
596 .target_abi(target_abi)
597 .shared(shared)
598 .lib_name(lib_name)
599 .lib_dir(lib_dir)
600 .executable(map["executable"].clone())
601 .pointer_width(calcsize_pointer * 8)
602 .build_flags(BuildFlags::from_interpreter(interpreter)?)
603 .python_framework_prefix(python_framework_prefix)
604 .finalize()
605 }
606
607 pub fn from_sysconfigdata(sysconfigdata: &Sysconfigdata) -> Result<Self> {
612 macro_rules! get_key {
613 ($sysconfigdata:expr, $key:literal) => {
614 $sysconfigdata
615 .get_value($key)
616 .ok_or(concat!($key, " not found in sysconfigdata file"))
617 };
618 }
619
620 macro_rules! parse_key {
621 ($sysconfigdata:expr, $key:literal) => {
622 get_key!($sysconfigdata, $key)?
623 .parse()
624 .context(concat!("could not parse value of ", $key))
625 };
626 }
627
628 let soabi = get_key!(sysconfigdata, "SOABI")?;
629 let implementation = PythonImplementation::from_soabi(soabi)?;
630 let version = parse_key!(sysconfigdata, "VERSION")?;
631 let shared = match sysconfigdata.get_value("Py_ENABLE_SHARED") {
632 Some("1") | Some("true") | Some("True") => true,
633 Some("0") | Some("false") | Some("False") => false,
634 _ => bail!("expected a bool (1/true/True or 0/false/False) for Py_ENABLE_SHARED"),
635 };
636 let framework = match sysconfigdata.get_value("PYTHONFRAMEWORK") {
638 Some(s) => !s.is_empty(),
639 _ => false,
640 };
641 let python_framework_prefix = sysconfigdata
642 .get_value("PYTHONFRAMEWORKPREFIX")
643 .map(str::to_string);
644 let lib_dir = get_key!(sysconfigdata, "LIBDIR").ok().map(str::to_string);
645 let gil_disabled = match sysconfigdata.get_value("Py_GIL_DISABLED") {
646 Some(value) => value == "1",
647 None => false,
648 };
649 let cygwin = soabi.ends_with("cygwin");
650 let stable_abi =
651 applicable_stable_abi_at_interpreter_version(implementation, version, gil_disabled);
652 let target_abi =
653 PythonAbi::from_stable_abi(implementation, version, stable_abi, gil_disabled)?;
654 let lib_name =
655 default_lib_name_unix(target_abi, cygwin, sysconfigdata.get_value("LDVERSION"))?;
656 let pointer_width = parse_key!(sysconfigdata, "SIZEOF_VOID_P")
657 .map(|bytes_width: u32| bytes_width * 8)
658 .ok();
659 let build_flags = BuildFlags::from_sysconfigdata(sysconfigdata);
660
661 InterpreterConfigBuilder::new(implementation, version)
662 .target_abi(target_abi)
663 .shared(shared || framework)
664 .pointer_width(pointer_width)
665 .lib_name(lib_name)
666 .lib_dir(lib_dir)
667 .python_framework_prefix(python_framework_prefix)
668 .build_flags(build_flags)
669 .finalize()
670 }
671
672 pub(super) fn from_pyo3_config_file_env(target: &Triple) -> Option<Result<Self>> {
676 env_var("PYO3_CONFIG_FILE").map(|path| {
677 let path = Path::new(&path);
678 println!("cargo:rerun-if-changed={}", path.display());
679 ensure!(
682 path.is_absolute(),
683 "PYO3_CONFIG_FILE must be an absolute path"
684 );
685
686 let mut config = InterpreterConfig::from_path(path)
687 .context("failed to parse contents of PYO3_CONFIG_FILE")?
688 .apply_build_env()?;
689
690 if config.lib_name.is_none() {
693 config.lib_name = Some(default_lib_name_for_target(config.target_abi, target));
694 }
695
696 Ok(config)
697 })
698 }
699
700 fn from_path(path: impl AsRef<Path>) -> Result<Self> {
701 let path = path.as_ref();
702 let config_file = std::fs::File::open(path)
703 .with_context(|| format!("failed to open PyO3 config file at {}", path.display()))?;
704 let reader = std::io::BufReader::new(config_file);
705 InterpreterConfig::from_reader(reader)
706 }
707
708 pub(crate) const PYO3_FFI_CONFIG_ENV_VAR: &str = "DEP_PYTHON_PYO3_CONFIG";
710 pub(crate) const PYO3_CONFIG_ENV_VAR: &str = "DEP_PYO3_PYTHON_PYO3_CONFIG";
712
713 pub(crate) fn from_cargo_dep_env() -> Option<Result<Self>> {
714 cargo_env_var(Self::PYO3_FFI_CONFIG_ENV_VAR)
715 .or_else(|| cargo_env_var(Self::PYO3_CONFIG_ENV_VAR))
716 .map(|buf| InterpreterConfig::from_reader(&*unescape(&buf)))
717 }
718
719 fn from_reader(reader: impl Read) -> Result<Self> {
720 let reader = BufReader::new(reader);
721 let lines = reader.lines();
722
723 macro_rules! parse_value {
724 ($variable:ident, $value:ident) => {
725 $variable = Some($value.trim().parse().context(format!(
726 concat!(
727 "failed to parse ",
728 stringify!($variable),
729 " from config value '{}'"
730 ),
731 $value
732 ))?)
733 };
734 }
735
736 let mut implementation = None;
737 let mut version = None;
738 let mut shared = None;
739 let mut target_abi = None;
740 let mut abi3 = None;
742 let mut lib_name = None;
743 let mut lib_dir = None;
744 let mut executable = None;
745 let mut pointer_width = None;
746 let mut build_flags: Option<BuildFlags> = None;
747 let mut suppress_build_script_link_lines: Option<bool> = None;
748 let mut extra_build_script_lines = vec![];
749 let mut python_framework_prefix = None;
750
751 for (i, line) in lines.enumerate() {
752 let line = line.context("failed to read line from config")?;
753 let mut split = line.splitn(2, '=');
754 let (key, value) = (
755 split
756 .next()
757 .expect("first splitn value should always be present"),
758 split
759 .next()
760 .ok_or_else(|| format!("expected key=value pair on line {}", i + 1))?,
761 );
762 match key {
763 "implementation" => parse_value!(implementation, value),
764 "version" => parse_value!(version, value),
765 "shared" => parse_value!(shared, value),
766 "target_abi" => parse_value!(target_abi, value),
767 "abi3" => parse_value!(abi3, value),
768 "lib_name" => parse_value!(lib_name, value),
769 "lib_dir" => parse_value!(lib_dir, value),
770 "executable" => parse_value!(executable, value),
771 "pointer_width" => parse_value!(pointer_width, value),
772 "build_flags" => parse_value!(build_flags, value),
773 "suppress_build_script_link_lines" => {
774 parse_value!(suppress_build_script_link_lines, value)
775 }
776 "extra_build_script_line" => {
777 extra_build_script_lines.push(value.to_string());
778 }
779 "python_framework_prefix" => parse_value!(python_framework_prefix, value),
780 unknown => warn!("unknown config key `{}`", unknown),
781 }
782 }
783
784 let version = version.ok_or("missing value for version")?;
785 let implementation = implementation.unwrap_or(PythonImplementation::CPython);
786 let flags_contains_free_threaded = if let Some(ref flags) = build_flags {
787 flags.0.contains(&BuildFlag::Py_GIL_DISABLED)
788 } else {
789 false
790 };
791 let target_abi = if let Some(target_abi) = target_abi {
792 ensure!(
793 abi3.is_none(),
794 "Invalid config that sets both target_abi and abi3."
795 );
796 target_abi
797 } else if flags_contains_free_threaded {
798 PythonAbiBuilder::new(implementation, version)
800 .free_threaded()
801 .finalize()?
802 } else if abi3 == Some(true) {
803 warn!("abi3 configuration file option is deprecated since pyo3 0.29, set target_abi instead");
804 PythonAbiBuilder::new(implementation, version)
805 .stable_abi(StableAbi::Abi3)
806 .finalize()?
807 } else {
808 PythonAbiBuilder::new(implementation, version).finalize()?
809 };
810
811 let builder = InterpreterConfigBuilder::new(implementation, version)
812 .target_abi(target_abi)
813 .shared(shared.unwrap_or(true))
814 .lib_name(lib_name)
815 .lib_dir(lib_dir)
816 .executable(executable)
817 .pointer_width(pointer_width)
818 .build_flags(build_flags.unwrap_or_default())
819 .suppress_build_script_link_lines(suppress_build_script_link_lines.unwrap_or(false))
820 .extra_build_script_lines(extra_build_script_lines)
821 .python_framework_prefix(python_framework_prefix);
822
823 builder.finalize()
824 }
825
826 #[doc(hidden)]
827 pub fn to_cargo_dep_env(&self) -> Result<()> {
838 let mut buf = Vec::new();
839 self.to_writer(&mut buf)?;
840 println!("cargo:PYO3_CONFIG={}", escape(&buf));
842 Ok(())
843 }
844
845 #[doc(hidden)]
846 pub fn to_writer(&self, mut writer: impl Write) -> Result<()> {
847 macro_rules! write_line {
848 ($value:ident) => {
849 writeln!(writer, "{}={}", stringify!($value), self.$value).context(concat!(
850 "failed to write ",
851 stringify!($value),
852 " to config"
853 ))
854 };
855 }
856
857 macro_rules! write_option_line {
858 ($value:ident) => {
859 if let Some(value) = &self.$value {
860 writeln!(writer, "{}={}", stringify!($value), value).context(concat!(
861 "failed to write ",
862 stringify!($value),
863 " to config"
864 ))
865 } else {
866 Ok(())
867 }
868 };
869 }
870
871 write_line!(implementation)?;
872 write_line!(version)?;
873 write_line!(shared)?;
874 write_line!(target_abi)?;
875 write_option_line!(lib_name)?;
876 write_option_line!(lib_dir)?;
877 write_option_line!(executable)?;
878 write_option_line!(pointer_width)?;
879 write_line!(build_flags)?;
880 write_option_line!(python_framework_prefix)?;
881 write_line!(suppress_build_script_link_lines)?;
882 for line in &self.extra_build_script_lines {
883 writeln!(writer, "extra_build_script_line={line}")
884 .context("failed to write extra_build_script_line")?;
885 }
886 Ok(())
887 }
888
889 pub fn run_python_script(&self, script: &str) -> Result<String> {
895 run_python_script_with_envs(
896 Path::new(self.executable.as_ref().expect("no interpreter executable")),
897 script,
898 std::iter::empty::<(&str, &str)>(),
899 )
900 }
901
902 pub fn run_python_script_with_envs<I, K, V>(&self, script: &str, envs: I) -> Result<String>
909 where
910 I: IntoIterator<Item = (K, V)>,
911 K: AsRef<OsStr>,
912 V: AsRef<OsStr>,
913 {
914 run_python_script_with_envs(
915 Path::new(self.executable.as_ref().expect("no interpreter executable")),
916 script,
917 envs,
918 )
919 }
920
921 pub fn is_free_threaded(&self) -> bool {
922 self.target_abi.kind().is_free_threaded()
923 }
924
925 fn apply_build_env(mut self) -> Result<InterpreterConfig> {
926 let implementation = self.target_abi.implementation;
929 let gil_disabled = self.target_abi.kind().is_free_threaded();
930 let stable_abi = applicable_stable_abi(
931 implementation,
932 self.version,
933 gil_disabled,
934 get_abi3_version(),
935 get_abi3t_version(),
936 );
937 self.target_abi =
938 PythonAbi::from_stable_abi(implementation, self.version, stable_abi, gil_disabled)?;
939 Ok(self)
940 }
941}
942
943#[cfg_attr(test, derive(Debug))]
944pub struct PythonAbiBuilder {
945 implementation: PythonImplementation,
946 version: PythonVersion,
947 kind: Option<PythonAbiKind>,
948}
949
950impl PythonAbiBuilder {
951 pub fn new(implementation: PythonImplementation, version: PythonVersion) -> PythonAbiBuilder {
952 PythonAbiBuilder {
953 implementation,
954 version,
955 kind: None,
956 }
957 }
958
959 pub fn stable_abi(self, kind: StableAbi) -> PythonAbiBuilder {
960 let mut build_version = self.version;
961 if self.version.minor > STABLE_ABI_MAX_MINOR {
962 warn!("Automatically falling back to {kind}-py3{STABLE_ABI_MAX_MINOR} because current Python is higher than the maximum supported");
963 build_version.minor = STABLE_ABI_MAX_MINOR;
964 }
965
966 PythonAbiBuilder {
967 kind: Some(PythonAbiKind::Stable(kind)),
968 version: build_version,
969 ..self
970 }
971 }
972
973 pub fn free_threaded(self) -> PythonAbiBuilder {
974 PythonAbiBuilder {
975 kind: Some(PythonAbiKind::VersionSpecific(GilUsed::FreeThreaded)),
976 ..self
977 }
978 }
979
980 pub fn finalize(self) -> Result<PythonAbi> {
981 let kind = self.kind.unwrap_or(match self.implementation {
983 PythonImplementation::RustPython => PythonAbiKind::Stable(StableAbi::Abi3t),
984 _ => PythonAbiKind::VersionSpecific(GilUsed::GilEnabled),
985 });
986 if matches!(self.implementation, PythonImplementation::RustPython) {
987 ensure!(matches!(kind, PythonAbiKind::Stable(StableAbi::Abi3t)),
988 "RustPython only supports targeting abi3t, it does not allow targeting other Python ABIs. Currently targeting '{kind}'")
989 }
990 if matches!(kind, PythonAbiKind::VersionSpecific(GilUsed::FreeThreaded))
991 && self.version
992 < (PythonVersion {
993 major: 3,
994 minor: 13,
995 })
996 {
997 bail!(
998 "Cannot target free-threaded builds for Python versions before 3.13, tried to build for {}", self.version
999 )
1000 }
1001 Ok(PythonAbi {
1002 implementation: self.implementation,
1003 kind,
1004 version: self.version,
1005 })
1006 }
1007}
1008
1009#[non_exhaustive]
1010#[derive(Copy, Clone, PartialEq, Eq)]
1011#[cfg_attr(test, derive(Debug))]
1012pub struct PythonAbi {
1013 implementation: PythonImplementation,
1014 kind: PythonAbiKind,
1015 version: PythonVersion,
1016}
1017
1018impl Display for PythonAbi {
1019 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1020 write!(f, "{}-{}-{}", self.implementation, self.kind, self.version)
1021 }
1022}
1023
1024impl FromStr for PythonAbi {
1025 type Err = crate::errors::Error;
1026
1027 fn from_str(value: &str) -> Result<Self, Self::Err> {
1028 let mut parts = value.splitn(3, '-');
1029 Ok(PythonAbi {
1030 implementation: parts
1031 .next()
1032 .ok_or_else(|| format!("Invalid ABI string representation: {value}"))?
1033 .parse()?,
1034 kind: parts
1035 .next()
1036 .ok_or_else(|| format!("Invalid ABI string representation: {value}"))?
1037 .parse()?,
1038 version: parts
1039 .next()
1040 .ok_or_else(|| format!("Invalid ABI string representation: {value}"))?
1041 .parse()?,
1042 })
1043 }
1044}
1045
1046impl PythonAbi {
1047 fn from_stable_abi(
1055 implementation: PythonImplementation,
1056 version: PythonVersion,
1057 stable_abi: Option<(StableAbi, PythonVersion)>,
1058 gil_disabled: bool,
1059 ) -> Result<PythonAbi> {
1060 let builder = match stable_abi {
1061 Some((kind, min_version)) => {
1062 ensure!(
1063 min_version <= version,
1064 "cannot set a minimum Python version {} higher than the interpreter version {} \
1065 (the minimum Python version is implied by the {}-py3{} feature)",
1066 min_version,
1067 version,
1068 kind,
1069 min_version.minor
1070 );
1071 PythonAbiBuilder::new(implementation, min_version).stable_abi(kind)
1072 }
1073 None if gil_disabled => PythonAbiBuilder::new(implementation, version).free_threaded(),
1074 None => PythonAbiBuilder::new(implementation, version),
1075 };
1076 builder.finalize()
1077 }
1078
1079 pub fn from_build_env(
1080 implementation: PythonImplementation,
1081 version: PythonVersion,
1082 stable_abi_version: Option<PythonVersion>,
1083 gil_disabled: bool,
1084 ) -> Result<PythonAbi> {
1085 let builder = PythonAbiBuilder {
1086 implementation,
1087 version: sanitize_stable_abi_version(stable_abi_version, version)?,
1088 kind: None,
1089 };
1090 let builder = if get_abi3t_version().is_some() && version >= MINIMUM_SUPPORTED_VERSION_ABI3T
1091 {
1092 builder.stable_abi(StableAbi::Abi3t)
1093 } else if get_abi3_version().is_some() && !gil_disabled {
1094 builder.stable_abi(StableAbi::Abi3)
1095 } else if gil_disabled {
1096 builder.free_threaded()
1097 } else {
1098 builder
1099 };
1100 builder.finalize()
1101 }
1102
1103 pub fn implementation(&self) -> PythonImplementation {
1107 self.implementation
1108 }
1109
1110 pub fn kind(&self) -> PythonAbiKind {
1114 self.kind
1115 }
1116
1117 pub fn version(&self) -> PythonVersion {
1121 self.version
1122 }
1123}
1124
1125#[derive(Clone, Copy, PartialEq, Eq)]
1130#[cfg_attr(test, derive(Debug))]
1131pub enum PythonAbiKind {
1132 Stable(StableAbi),
1134 VersionSpecific(GilUsed),
1136}
1137
1138impl Display for PythonAbiKind {
1139 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1140 match self {
1141 PythonAbiKind::Stable(stable_abi) => write!(f, "{stable_abi}"),
1142 PythonAbiKind::VersionSpecific(gil_used) => {
1143 write!(f, "{gil_used}")
1144 }
1145 }
1146 }
1147}
1148
1149impl FromStr for PythonAbiKind {
1150 type Err = crate::errors::Error;
1151
1152 fn from_str(value: &str) -> Result<Self, Self::Err> {
1153 match value {
1154 "abi3" => Ok(PythonAbiKind::Stable(StableAbi::Abi3)),
1155 "abi3t" => Ok(PythonAbiKind::Stable(StableAbi::Abi3t)),
1156 "free_threaded" => Ok(PythonAbiKind::VersionSpecific(GilUsed::FreeThreaded)),
1157 "gil_enabled" => Ok(PythonAbiKind::VersionSpecific(GilUsed::GilEnabled)),
1158 _ => Err(format!("Unrecognized ABI name: {value}").into()),
1159 }
1160 }
1161}
1162
1163impl PythonAbiKind {
1164 pub fn is_free_threaded(self) -> bool {
1165 match self {
1166 PythonAbiKind::VersionSpecific(gil_disabled) => gil_disabled == GilUsed::FreeThreaded,
1167 PythonAbiKind::Stable(StableAbi::Abi3) => false,
1168 PythonAbiKind::Stable(StableAbi::Abi3t) => true,
1169 }
1170 }
1171}
1172
1173#[derive(Clone, Copy, PartialEq, Eq)]
1175#[cfg_attr(test, derive(Debug))]
1176pub enum StableAbi {
1177 Abi3,
1179 Abi3t,
1181}
1182
1183impl Display for StableAbi {
1184 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1185 match self {
1186 StableAbi::Abi3 => write!(f, "abi3"),
1187 StableAbi::Abi3t => write!(f, "abi3t"),
1188 }
1189 }
1190}
1191
1192#[derive(Clone, Copy, PartialEq, Eq)]
1194#[cfg_attr(test, derive(Debug))]
1195pub enum GilUsed {
1196 GilEnabled,
1198 FreeThreaded,
1200}
1201
1202impl Display for GilUsed {
1203 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1204 match self {
1205 GilUsed::GilEnabled => write!(f, "gil_enabled"),
1206 GilUsed::FreeThreaded => write!(f, "free_threaded"),
1207 }
1208 }
1209}
1210
1211#[cfg_attr(test, derive(Debug))]
1212pub struct InterpreterConfigBuilder {
1213 implementation: PythonImplementation,
1214 version: PythonVersion,
1215 shared: bool,
1216 target_abi: Option<PythonAbi>,
1217 lib_name: Option<String>,
1218 lib_dir: Option<String>,
1219 executable: Option<String>,
1220 pointer_width: Option<u32>,
1221 build_flags: BuildFlags,
1222 suppress_build_script_link_lines: bool,
1223 extra_build_script_lines: Vec<String>,
1224 python_framework_prefix: Option<String>,
1225}
1226
1227impl InterpreterConfigBuilder {
1228 pub fn new(
1229 implementation: PythonImplementation,
1230 version: PythonVersion,
1231 ) -> InterpreterConfigBuilder {
1232 InterpreterConfigBuilder {
1233 implementation,
1234 version,
1235 shared: true,
1236 target_abi: None,
1237 lib_name: None,
1238 lib_dir: None,
1239 executable: None,
1240 pointer_width: None,
1241 build_flags: BuildFlags::default(),
1242 suppress_build_script_link_lines: false,
1243 extra_build_script_lines: vec![],
1244 python_framework_prefix: None,
1245 }
1246 }
1247
1248 pub fn target_abi(self, target_abi: PythonAbi) -> InterpreterConfigBuilder {
1249 InterpreterConfigBuilder {
1250 target_abi: Some(target_abi),
1251 ..self
1252 }
1253 }
1254
1255 pub fn stable_abi(self, kind: StableAbi) -> InterpreterConfigBuilder {
1256 let implementation = self.implementation;
1257 let version = self.version;
1258 self.target_abi(
1259 PythonAbiBuilder::new(implementation, version)
1260 .stable_abi(kind)
1261 .finalize()
1262 .unwrap(),
1264 )
1265 }
1266
1267 pub fn free_threaded(self) -> Result<InterpreterConfigBuilder> {
1268 let implementation = self.implementation;
1269 let version = self.version;
1270 Ok(self.target_abi(
1271 PythonAbiBuilder::new(implementation, version)
1272 .free_threaded()
1273 .finalize()?,
1274 ))
1275 }
1276
1277 pub fn lib_name(mut self, lib_name: impl Into<Option<String>>) -> InterpreterConfigBuilder {
1278 self.lib_name = lib_name.into();
1279 self
1280 }
1281
1282 pub fn pointer_width(
1283 mut self,
1284 pointer_width: impl Into<Option<u32>>,
1285 ) -> InterpreterConfigBuilder {
1286 self.pointer_width = pointer_width.into();
1287 self
1288 }
1289
1290 pub fn executable(mut self, executable: impl Into<Option<String>>) -> InterpreterConfigBuilder {
1291 self.executable = executable.into();
1292 self
1293 }
1294
1295 pub fn suppress_build_script_link_lines(
1296 mut self,
1297 suppress_build_script_link_lines: bool,
1298 ) -> InterpreterConfigBuilder {
1299 self.suppress_build_script_link_lines = suppress_build_script_link_lines;
1300 self
1301 }
1302
1303 pub fn extra_build_script_lines(
1304 mut self,
1305 extra_build_script_lines: Vec<String>,
1306 ) -> InterpreterConfigBuilder {
1307 self.extra_build_script_lines = extra_build_script_lines;
1308 self
1309 }
1310
1311 pub fn lib_dir(mut self, lib_dir: impl Into<Option<String>>) -> InterpreterConfigBuilder {
1312 self.lib_dir = lib_dir.into();
1313 self
1314 }
1315
1316 pub fn shared(mut self, shared: bool) -> InterpreterConfigBuilder {
1317 self.shared = shared;
1318 self
1319 }
1320
1321 pub fn build_flags(mut self, build_flags: BuildFlags) -> InterpreterConfigBuilder {
1322 self.build_flags = build_flags;
1323 self
1324 }
1325
1326 pub fn python_framework_prefix(
1327 mut self,
1328 python_framework_prefix: impl Into<Option<String>>,
1329 ) -> InterpreterConfigBuilder {
1330 self.python_framework_prefix = python_framework_prefix.into();
1331 self
1332 }
1333
1334 pub fn finalize(self) -> Result<InterpreterConfig> {
1335 let mut build_flags = self.build_flags.clone();
1336 let py_gil_disabled = build_flags.0.contains(&BuildFlag::Py_GIL_DISABLED);
1337 let target_abi = match (self.target_abi, py_gil_disabled) {
1338 (None, false) => PythonAbiBuilder::new(self.implementation, self.version).finalize()?,
1340 (None, true) => PythonAbiBuilder::new(self.implementation, self.version)
1342 .free_threaded()
1343 .finalize()?,
1344 (Some(target_abi), false) => target_abi,
1346 (Some(target_abi), true) => match target_abi.kind() {
1348 PythonAbiKind::Stable(StableAbi::Abi3) => {
1352 let new_abi =
1353 PythonAbiBuilder::new(target_abi.implementation(), target_abi.version())
1354 .free_threaded()
1355 .finalize()?;
1356 warn!(
1357 "Targeting an abi3 build but build_flags contains Py_GIL_DISABLED, \
1358 falling back to a version-specific free-threaded build"
1359 );
1360 new_abi
1361 }
1362 PythonAbiKind::VersionSpecific(GilUsed::GilEnabled) => bail!(
1364 "build_flags contains Py_GIL_DISABLED but target_abi \
1365 '{target_abi}' is not free-threaded"
1366 ),
1367 _ => target_abi,
1369 },
1370 };
1371 if target_abi.kind().is_free_threaded() {
1372 build_flags.0.insert(BuildFlag::Py_GIL_DISABLED);
1373 }
1374 #[expect(
1375 deprecated,
1376 reason = "constructing an InterpreterConfig directly, need to write to fields"
1377 )]
1378 Ok(InterpreterConfig {
1379 implementation: self.implementation,
1380 version: self.version,
1381 shared: self.shared,
1382 target_abi,
1383 abi3: matches!(target_abi.kind(), PythonAbiKind::Stable(StableAbi::Abi3)),
1384 lib_name: self.lib_name,
1385 lib_dir: self.lib_dir,
1386 executable: self.executable,
1387 pointer_width: self.pointer_width,
1388 build_flags,
1389 suppress_build_script_link_lines: self.suppress_build_script_link_lines,
1390 extra_build_script_lines: self.extra_build_script_lines,
1391 python_framework_prefix: self.python_framework_prefix,
1392 })
1393 }
1394}
1395
1396#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
1397pub struct PythonVersion {
1398 pub major: u8,
1399 pub minor: u8,
1400}
1401
1402impl PythonVersion {
1403 #[cfg(test)]
1404 pub(crate) const PY315: Self = PythonVersion {
1405 major: 3,
1406 minor: 15,
1407 };
1408 #[cfg(test)]
1409 pub(crate) const PY314: Self = PythonVersion {
1410 major: 3,
1411 minor: 14,
1412 };
1413 #[deprecated(
1414 since = "0.29.0",
1415 note = "please construct `PythonVersion` directly rather than use these constants"
1416 )]
1417 pub const PY313: Self = PythonVersion {
1418 major: 3,
1419 minor: 13,
1420 };
1421 #[deprecated(
1422 since = "0.29.0",
1423 note = "please construct `PythonVersion` directly rather than use these constants"
1424 )]
1425 pub const PY312: Self = PythonVersion {
1426 major: 3,
1427 minor: 12,
1428 };
1429 #[cfg(test)]
1430 const PY311: Self = PythonVersion {
1431 major: 3,
1432 minor: 11,
1433 };
1434 const PY310: Self = PythonVersion {
1435 major: 3,
1436 minor: 10,
1437 };
1438 #[cfg(test)]
1439 const PY39: Self = PythonVersion { major: 3, minor: 9 };
1440 #[cfg(test)]
1441 const PY38: Self = PythonVersion { major: 3, minor: 8 };
1442}
1443
1444impl Display for PythonVersion {
1445 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1446 write!(f, "{}.{}", self.major, self.minor)
1447 }
1448}
1449
1450impl FromStr for PythonVersion {
1451 type Err = crate::errors::Error;
1452
1453 fn from_str(value: &str) -> Result<Self, Self::Err> {
1454 let mut split = value.splitn(2, '.');
1455 let (major, minor) = (
1456 split
1457 .next()
1458 .expect("first splitn value should always be present"),
1459 split.next().ok_or("expected major.minor version")?,
1460 );
1461 Ok(Self {
1462 major: major.parse().context("failed to parse major version")?,
1463 minor: minor.parse().context("failed to parse minor version")?,
1464 })
1465 }
1466}
1467
1468#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1469pub enum PythonImplementation {
1470 CPython,
1471 PyPy,
1472 GraalPy,
1473 RustPython,
1474}
1475
1476impl PythonImplementation {
1477 fn is_pypy(self) -> bool {
1478 self == PythonImplementation::PyPy
1479 }
1480
1481 fn from_soabi(soabi: &str) -> Result<Self> {
1482 if soabi.starts_with("pypy") {
1483 Ok(PythonImplementation::PyPy)
1484 } else if soabi.starts_with("cpython") {
1485 Ok(PythonImplementation::CPython)
1486 } else if soabi.starts_with("graalpy") {
1487 Ok(PythonImplementation::GraalPy)
1488 } else {
1489 bail!("unsupported Python interpreter");
1490 }
1491 }
1492}
1493
1494impl Display for PythonImplementation {
1495 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1496 match self {
1497 PythonImplementation::CPython => write!(f, "CPython"),
1498 PythonImplementation::PyPy => write!(f, "PyPy"),
1499 PythonImplementation::GraalPy => write!(f, "GraalVM"),
1500 PythonImplementation::RustPython => write!(f, "RustPython"),
1501 }
1502 }
1503}
1504
1505impl FromStr for PythonImplementation {
1506 type Err = Error;
1507 fn from_str(s: &str) -> Result<Self> {
1508 match s {
1509 "CPython" => Ok(PythonImplementation::CPython),
1510 "PyPy" => Ok(PythonImplementation::PyPy),
1511 "GraalVM" => Ok(PythonImplementation::GraalPy),
1512 "RustPython" => Ok(PythonImplementation::RustPython),
1513 _ => bail!("unknown interpreter: {}", s),
1514 }
1515 }
1516}
1517
1518fn have_python_interpreter() -> bool {
1523 env_var("PYO3_NO_PYTHON").is_none()
1524}
1525
1526#[derive(Debug, Copy, Clone)]
1531pub enum StableAbiVersion {
1532 Current,
1533 Target(PythonVersion),
1534}
1535
1536pub fn get_abi3_version() -> Option<StableAbiVersion> {
1542 let minor_version = (MINIMUM_SUPPORTED_VERSION.minor..=STABLE_ABI_MAX_MINOR)
1543 .find(|i| cargo_env_var(&format!("CARGO_FEATURE_ABI3_PY3{i}")).is_some());
1544 minor_version.map_or(
1545 if cargo_env_var("CARGO_FEATURE_ABI3").is_some() {
1546 Some(StableAbiVersion::Current)
1547 } else {
1548 None
1549 },
1550 |minor| Some(StableAbiVersion::Target(PythonVersion { major: 3, minor })),
1551 )
1552}
1553
1554pub fn get_abi3t_version() -> Option<StableAbiVersion> {
1560 let minor_version = (MINIMUM_SUPPORTED_VERSION_ABI3T.minor..=STABLE_ABI_MAX_MINOR)
1561 .find(|i| cargo_env_var(&format!("CARGO_FEATURE_ABI3T_PY3{i}")).is_some());
1562 minor_version.map_or(
1563 if cargo_env_var("CARGO_FEATURE_ABI3T").is_some() {
1564 Some(StableAbiVersion::Current)
1565 } else {
1566 None
1567 },
1568 |minor| Some(StableAbiVersion::Target(PythonVersion { major: 3, minor })),
1569 )
1570}
1571
1572pub fn is_extension_module() -> bool {
1580 cargo_env_var("CARGO_FEATURE_EXTENSION_MODULE").is_some()
1581 || env_var("PYO3_BUILD_EXTENSION_MODULE").is_some()
1582}
1583
1584pub fn is_linking_libpython_for_target(target: &Triple) -> bool {
1588 target.operating_system == OperatingSystem::Windows
1589 || target.operating_system == OperatingSystem::Aix
1591 || target.environment == Environment::Android
1592 || target.environment == Environment::Androideabi
1593 || target.operating_system == OperatingSystem::Cygwin
1594 || matches!(target.operating_system, OperatingSystem::IOS(_))
1595 || !is_extension_module()
1596}
1597
1598fn require_libdir_for_target(target: &Triple) -> bool {
1603 if target.operating_system == OperatingSystem::Windows {
1606 return false;
1607 }
1608
1609 is_linking_libpython_for_target(target)
1610}
1611
1612#[derive(Debug, PartialEq, Eq)]
1617pub struct CrossCompileConfig {
1618 pub lib_dir: Option<PathBuf>,
1620
1621 version: Option<PythonVersion>,
1623
1624 implementation: Option<PythonImplementation>,
1626
1627 target: Triple,
1629
1630 abiflags: Option<String>,
1632}
1633
1634impl CrossCompileConfig {
1635 fn try_from_env_vars_host_target(
1640 env_vars: CrossCompileEnvVars,
1641 host: &Triple,
1642 target: &Triple,
1643 ) -> Result<Option<Self>> {
1644 if env_vars.any() || Self::is_cross_compiling_from_to(host, target) {
1645 let lib_dir = env_vars.lib_dir_path()?;
1646 let (version, abiflags) = env_vars.parse_version()?;
1647 let implementation = env_vars.parse_implementation()?;
1648 let target = target.clone();
1649
1650 Ok(Some(CrossCompileConfig {
1651 lib_dir,
1652 version,
1653 implementation,
1654 target,
1655 abiflags,
1656 }))
1657 } else {
1658 Ok(None)
1659 }
1660 }
1661
1662 fn is_cross_compiling_from_to(host: &Triple, target: &Triple) -> bool {
1666 let mut compatible = host.architecture == target.architecture
1670 && (host.vendor == target.vendor
1671 || (host.vendor == Vendor::Pc && target.vendor.as_str() == "win7"))
1673 && host.operating_system == target.operating_system;
1674
1675 compatible |= target.operating_system == OperatingSystem::Windows
1677 && host.operating_system == OperatingSystem::Windows
1678 && matches!(target.architecture, Architecture::X86_32(_))
1679 && host.architecture == Architecture::X86_64;
1680
1681 compatible |= matches!(target.operating_system, OperatingSystem::Darwin(_))
1683 && matches!(host.operating_system, OperatingSystem::Darwin(_));
1684
1685 compatible |= matches!(target.operating_system, OperatingSystem::IOS(_));
1686
1687 !compatible
1688 }
1689
1690 fn lib_dir_string(&self) -> Option<String> {
1695 self.lib_dir
1696 .as_ref()
1697 .map(|s| s.to_str().unwrap().to_owned())
1698 }
1699}
1700
1701struct CrossCompileEnvVars {
1703 pyo3_cross: Option<OsString>,
1705 pyo3_cross_lib_dir: Option<OsString>,
1707 pyo3_cross_python_version: Option<OsString>,
1709 pyo3_cross_python_implementation: Option<OsString>,
1711}
1712
1713impl CrossCompileEnvVars {
1714 fn from_env() -> Self {
1718 CrossCompileEnvVars {
1719 pyo3_cross: env_var("PYO3_CROSS"),
1720 pyo3_cross_lib_dir: env_var("PYO3_CROSS_LIB_DIR"),
1721 pyo3_cross_python_version: env_var("PYO3_CROSS_PYTHON_VERSION"),
1722 pyo3_cross_python_implementation: env_var("PYO3_CROSS_PYTHON_IMPLEMENTATION"),
1723 }
1724 }
1725
1726 fn any(&self) -> bool {
1728 self.pyo3_cross.is_some()
1729 || self.pyo3_cross_lib_dir.is_some()
1730 || self.pyo3_cross_python_version.is_some()
1731 || self.pyo3_cross_python_implementation.is_some()
1732 }
1733
1734 fn parse_version(&self) -> Result<(Option<PythonVersion>, Option<String>)> {
1737 match self.pyo3_cross_python_version.as_ref() {
1738 Some(os_string) => {
1739 let utf8_str = os_string
1740 .to_str()
1741 .ok_or("PYO3_CROSS_PYTHON_VERSION is not valid a UTF-8 string")?;
1742 let (utf8_str, abiflags) = if let Some(version) = utf8_str.strip_suffix('t') {
1743 (version, Some("t".to_string()))
1744 } else {
1745 (utf8_str, None)
1746 };
1747 let version = utf8_str
1748 .parse()
1749 .context("failed to parse PYO3_CROSS_PYTHON_VERSION")?;
1750 Ok((Some(version), abiflags))
1751 }
1752 None => Ok((None, None)),
1753 }
1754 }
1755
1756 fn parse_implementation(&self) -> Result<Option<PythonImplementation>> {
1759 let implementation = self
1760 .pyo3_cross_python_implementation
1761 .as_ref()
1762 .map(|os_string| {
1763 let utf8_str = os_string
1764 .to_str()
1765 .ok_or("PYO3_CROSS_PYTHON_IMPLEMENTATION is not valid a UTF-8 string")?;
1766 utf8_str
1767 .parse()
1768 .context("failed to parse PYO3_CROSS_PYTHON_IMPLEMENTATION")
1769 })
1770 .transpose()?;
1771
1772 Ok(implementation)
1773 }
1774
1775 fn lib_dir_path(&self) -> Result<Option<PathBuf>> {
1780 let lib_dir = self.pyo3_cross_lib_dir.as_ref().map(PathBuf::from);
1781
1782 if let Some(dir) = lib_dir.as_ref() {
1783 ensure!(
1784 dir.to_str().is_some(),
1785 "PYO3_CROSS_LIB_DIR variable value is not a valid UTF-8 string"
1786 );
1787 }
1788
1789 Ok(lib_dir)
1790 }
1791}
1792
1793pub fn cross_compiling_from_to(
1808 host: &Triple,
1809 target: &Triple,
1810) -> Result<Option<CrossCompileConfig>> {
1811 let env_vars = CrossCompileEnvVars::from_env();
1812 CrossCompileConfig::try_from_env_vars_host_target(env_vars, host, target)
1813}
1814
1815#[allow(non_camel_case_types)]
1816#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1817pub enum BuildFlag {
1818 Py_DEBUG,
1819 Py_REF_DEBUG,
1820 #[deprecated(since = "0.29.0", note = "no longer supported by PyO3")]
1821 Py_TRACE_REFS,
1822 Py_GIL_DISABLED,
1823 COUNT_ALLOCS,
1824 Other(String),
1825}
1826
1827impl Display for BuildFlag {
1828 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1829 match self {
1830 BuildFlag::Other(flag) => write!(f, "{flag}"),
1831 _ => write!(f, "{self:?}"),
1832 }
1833 }
1834}
1835
1836impl FromStr for BuildFlag {
1837 type Err = std::convert::Infallible;
1838 fn from_str(s: &str) -> Result<Self, Self::Err> {
1839 match s {
1840 "Py_DEBUG" => Ok(BuildFlag::Py_DEBUG),
1841 "Py_REF_DEBUG" => Ok(BuildFlag::Py_REF_DEBUG),
1842 "Py_GIL_DISABLED" => Ok(BuildFlag::Py_GIL_DISABLED),
1843 "COUNT_ALLOCS" => Ok(BuildFlag::COUNT_ALLOCS),
1844 other => Ok(BuildFlag::Other(other.to_owned())),
1845 }
1846 }
1847}
1848
1849#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
1863#[derive(Clone, Default)]
1864pub struct BuildFlags(pub HashSet<BuildFlag>);
1865
1866impl BuildFlags {
1867 const ALL: [BuildFlag; 4] = [
1868 BuildFlag::Py_DEBUG,
1869 BuildFlag::Py_REF_DEBUG,
1870 BuildFlag::Py_GIL_DISABLED,
1871 BuildFlag::COUNT_ALLOCS,
1872 ];
1873
1874 pub fn new() -> Self {
1875 BuildFlags(HashSet::new())
1876 }
1877
1878 fn from_sysconfigdata(config_map: &Sysconfigdata) -> Self {
1879 Self(
1880 BuildFlags::ALL
1881 .iter()
1882 .filter(|flag| config_map.get_value(flag.to_string()) == Some("1"))
1883 .cloned()
1884 .collect(),
1885 )
1886 .fixup()
1887 }
1888
1889 fn from_interpreter(interpreter: impl AsRef<Path>) -> Result<Self> {
1893 if cfg!(windows) {
1897 let script = String::from("import sys;print(sys.version_info < (3, 13))");
1898 let stdout = run_python_script(interpreter.as_ref(), &script)?;
1899 if stdout.trim_end() == "True" {
1900 return Ok(Self::new());
1901 }
1902 }
1903
1904 let mut script = String::from("import sysconfig\n");
1905 script.push_str("config = sysconfig.get_config_vars()\n");
1906
1907 for k in &BuildFlags::ALL {
1908 use std::fmt::Write;
1909 writeln!(&mut script, "print(config.get('{k}', '0'))").unwrap();
1910 }
1911
1912 let stdout = run_python_script(interpreter.as_ref(), &script)?;
1913 let split_stdout: Vec<&str> = stdout.trim_end().lines().collect();
1914 ensure!(
1915 split_stdout.len() == BuildFlags::ALL.len(),
1916 "Python stdout len didn't return expected number of lines: {}",
1917 split_stdout.len()
1918 );
1919 let flags = BuildFlags::ALL
1920 .iter()
1921 .zip(split_stdout)
1922 .filter(|(_, flag_value)| *flag_value == "1")
1923 .map(|(flag, _)| flag.clone())
1924 .collect();
1925
1926 Ok(Self(flags).fixup())
1927 }
1928
1929 fn fixup(mut self) -> Self {
1930 if self.0.contains(&BuildFlag::Py_DEBUG) {
1931 self.0.insert(BuildFlag::Py_REF_DEBUG);
1932 }
1933
1934 self
1935 }
1936}
1937
1938impl Display for BuildFlags {
1939 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1940 let mut first = true;
1941 for flag in &self.0 {
1942 if first {
1943 first = false;
1944 } else {
1945 write!(f, ",")?;
1946 }
1947 write!(f, "{flag}")?;
1948 }
1949 Ok(())
1950 }
1951}
1952
1953impl FromStr for BuildFlags {
1954 type Err = std::convert::Infallible;
1955
1956 fn from_str(value: &str) -> Result<Self, Self::Err> {
1957 let mut flags = HashSet::new();
1958 for flag in value.split_terminator(',') {
1959 flags.insert(flag.parse().unwrap());
1960 }
1961 Ok(BuildFlags(flags))
1962 }
1963}
1964
1965fn parse_script_output(output: &str) -> HashMap<String, String> {
1966 output
1967 .lines()
1968 .filter_map(|line| {
1969 let mut i = line.splitn(2, ' ');
1970 Some((i.next()?.into(), i.next()?.into()))
1971 })
1972 .collect()
1973}
1974
1975pub struct Sysconfigdata(HashMap<String, String>);
1979
1980impl Sysconfigdata {
1981 pub fn get_value<S: AsRef<str>>(&self, k: S) -> Option<&str> {
1982 self.0.get(k.as_ref()).map(String::as_str)
1983 }
1984
1985 #[cfg(test)]
1986 fn new() -> Self {
1987 Sysconfigdata(HashMap::new())
1988 }
1989
1990 #[cfg(test)]
1991 fn insert<S: Into<String>>(&mut self, k: S, v: S) {
1992 self.0.insert(k.into(), v.into());
1993 }
1994}
1995
1996pub fn parse_sysconfigdata(sysconfigdata_path: impl AsRef<Path>) -> Result<Sysconfigdata> {
2004 let sysconfigdata_path = sysconfigdata_path.as_ref();
2005 let mut script = fs::read_to_string(sysconfigdata_path).with_context(|| {
2006 format!(
2007 "failed to read config from {}",
2008 sysconfigdata_path.display()
2009 )
2010 })?;
2011 script += r#"
2012for key, val in build_time_vars.items():
2013 # (ana)conda(-forge) built Pythons are statically linked but ship the shared library with them.
2014 # We detect them based on the magic prefix directory they have encoded in their builds.
2015 if key == "Py_ENABLE_SHARED" and "_h_env_placehold" in build_time_vars.get("prefix"):
2016 val = 1
2017 print(key, val)
2018"#;
2019
2020 let output = run_python_script(&find_interpreter()?, &script)?;
2021
2022 Ok(Sysconfigdata(parse_script_output(&output)))
2023}
2024
2025fn starts_with(entry: &DirEntry, pat: &str) -> bool {
2026 let name = entry.file_name();
2027 name.to_string_lossy().starts_with(pat)
2028}
2029fn ends_with(entry: &DirEntry, pat: &str) -> bool {
2030 let name = entry.file_name();
2031 name.to_string_lossy().ends_with(pat)
2032}
2033
2034fn find_sysconfigdata(cross: &CrossCompileConfig) -> Result<Option<PathBuf>> {
2039 let mut sysconfig_paths = find_all_sysconfigdata(cross)?;
2040 if sysconfig_paths.is_empty() {
2041 if let Some(lib_dir) = cross.lib_dir.as_ref() {
2042 bail!("Could not find _sysconfigdata*.py in {}", lib_dir.display());
2043 } else {
2044 return Ok(None);
2046 }
2047 } else if sysconfig_paths.len() > 1 {
2048 let mut error_msg = String::from(
2049 "Detected multiple possible Python versions. Please set either the \
2050 PYO3_CROSS_PYTHON_VERSION variable to the wanted version or the \
2051 _PYTHON_SYSCONFIGDATA_NAME variable to the wanted sysconfigdata file name.\n\n\
2052 sysconfigdata files found:",
2053 );
2054 for path in sysconfig_paths {
2055 use std::fmt::Write;
2056 write!(&mut error_msg, "\n\t{}", path.display()).unwrap();
2057 }
2058 bail!("{}\n", error_msg);
2059 }
2060
2061 Ok(Some(sysconfig_paths.remove(0)))
2062}
2063
2064pub fn find_all_sysconfigdata(cross: &CrossCompileConfig) -> Result<Vec<PathBuf>> {
2103 let sysconfig_paths = if let Some(lib_dir) = cross.lib_dir.as_ref() {
2104 search_lib_dir(lib_dir, cross).with_context(|| {
2105 format!(
2106 "failed to search the lib dir at 'PYO3_CROSS_LIB_DIR={}'",
2107 lib_dir.display()
2108 )
2109 })?
2110 } else {
2111 return Ok(Vec::new());
2112 };
2113
2114 let sysconfig_name = env_var("_PYTHON_SYSCONFIGDATA_NAME");
2115 let mut sysconfig_paths = sysconfig_paths
2116 .iter()
2117 .filter_map(|p| {
2118 let canonical = fs::canonicalize(p).ok();
2119 match &sysconfig_name {
2120 Some(_) => canonical.filter(|p| p.file_stem() == sysconfig_name.as_deref()),
2121 None => canonical,
2122 }
2123 })
2124 .collect::<Vec<PathBuf>>();
2125
2126 sysconfig_paths.sort();
2127 sysconfig_paths.dedup();
2128
2129 Ok(sysconfig_paths)
2130}
2131
2132fn is_pypy_lib_dir(path: &str, v: &Option<PythonVersion>) -> bool {
2133 let pypy_version_pat = if let Some(v) = v {
2134 format!("pypy{v}")
2135 } else {
2136 "pypy3.".into()
2137 };
2138 path == "lib_pypy" || path.starts_with(&pypy_version_pat)
2139}
2140
2141fn is_graalpy_lib_dir(path: &str, v: &Option<PythonVersion>) -> bool {
2142 let graalpy_version_pat = if let Some(v) = v {
2143 format!("graalpy{v}")
2144 } else {
2145 "graalpy2".into()
2146 };
2147 path == "lib_graalpython" || path.starts_with(&graalpy_version_pat)
2148}
2149
2150fn is_cpython_lib_dir(path: &str, v: &Option<PythonVersion>) -> bool {
2151 let cpython_version_pat = if let Some(v) = v {
2152 format!("python{v}")
2153 } else {
2154 "python3.".into()
2155 };
2156 path.starts_with(&cpython_version_pat)
2157}
2158
2159fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Result<Vec<PathBuf>> {
2161 let mut sysconfig_paths = vec![];
2162 for f in fs::read_dir(path.as_ref()).with_context(|| {
2163 format!(
2164 "failed to list the entries in '{}'",
2165 path.as_ref().display()
2166 )
2167 })? {
2168 sysconfig_paths.extend(match &f {
2169 Ok(f) if starts_with(f, "_sysconfigdata_") && ends_with(f, "py") => vec![f.path()],
2171 Ok(f) if f.metadata().is_ok_and(|metadata| metadata.is_dir()) => {
2172 let file_name = f.file_name();
2173 let file_name = file_name.to_string_lossy();
2174 if file_name == "build" || file_name == "lib" {
2175 search_lib_dir(f.path(), cross)?
2176 } else if file_name.starts_with("lib.") {
2177 if !file_name.contains(&cross.target.operating_system.to_string()) {
2179 continue;
2180 }
2181 if !file_name.contains(&cross.target.architecture.to_string()) {
2183 continue;
2184 }
2185 search_lib_dir(f.path(), cross)?
2186 } else if is_cpython_lib_dir(&file_name, &cross.version)
2187 || is_pypy_lib_dir(&file_name, &cross.version)
2188 || is_graalpy_lib_dir(&file_name, &cross.version)
2189 {
2190 search_lib_dir(f.path(), cross)?
2191 } else {
2192 continue;
2193 }
2194 }
2195 _ => continue,
2196 });
2197 }
2198 if sysconfig_paths.len() > 1 {
2206 let temp = sysconfig_paths
2207 .iter()
2208 .filter(|p| {
2209 p.to_string_lossy()
2210 .contains(&cross.target.architecture.to_string())
2211 })
2212 .cloned()
2213 .collect::<Vec<PathBuf>>();
2214 if !temp.is_empty() {
2215 sysconfig_paths = temp;
2216 }
2217 }
2218
2219 Ok(sysconfig_paths)
2220}
2221
2222fn cross_compile_from_sysconfigdata(
2230 cross_compile_config: &CrossCompileConfig,
2231) -> Result<Option<InterpreterConfig>> {
2232 if let Some(path) = find_sysconfigdata(cross_compile_config)? {
2233 let data = parse_sysconfigdata(path)?;
2234 let mut config = InterpreterConfig::from_sysconfigdata(&data)?;
2235 #[expect(deprecated, reason = "modifying config inline")]
2236 if let Some(cross_lib_dir) = cross_compile_config.lib_dir_string() {
2237 config.lib_dir = Some(cross_lib_dir)
2238 }
2239
2240 Ok(Some(config))
2241 } else {
2242 Ok(None)
2243 }
2244}
2245
2246fn exact_stable_abi_version(version: Option<StableAbiVersion>) -> Option<PythonVersion> {
2247 version.and_then(|v| match v {
2248 StableAbiVersion::Current => None,
2249 StableAbiVersion::Target(inner) => Some(inner),
2250 })
2251}
2252
2253fn default_cross_compile(cross_compile_config: &CrossCompileConfig) -> Result<InterpreterConfig> {
2260 let version = cross_compile_config
2261 .version
2262 .or_else(|| exact_stable_abi_version(get_abi3_version()))
2263 .or_else(|| exact_stable_abi_version(get_abi3t_version()))
2264 .ok_or_else(||
2265 format!(
2266 "PYO3_CROSS_PYTHON_VERSION or either an abi3-py3* or abi3t-py3* feature must be specified \
2267 when cross-compiling and PYO3_CROSS_LIB_DIR is not set.\n\
2268 = help: see the PyO3 user guide for more information: https://pyo3.rs/v{}/building-and-distribution.html#cross-compiling",
2269 env!("CARGO_PKG_VERSION")
2270 )
2271 )?;
2272 let gil_disabled = cross_compile_config.abiflags.as_deref() == Some("t");
2273
2274 let implementation = cross_compile_config
2275 .implementation
2276 .unwrap_or(PythonImplementation::CPython);
2277
2278 let stable_abi =
2279 applicable_stable_abi_at_interpreter_version(implementation, version, gil_disabled);
2280
2281 let target_abi = PythonAbi::from_stable_abi(implementation, version, stable_abi, gil_disabled)?;
2282
2283 let lib_name = default_lib_name_for_target(target_abi, &cross_compile_config.target);
2284
2285 let lib_dir = cross_compile_config.lib_dir_string();
2286
2287 InterpreterConfigBuilder::new(implementation, version)
2288 .target_abi(target_abi)
2289 .lib_name(lib_name)
2290 .lib_dir(lib_dir)
2291 .finalize()
2292}
2293
2294fn default_stable_abi_config(
2305 host: &Triple,
2306 abi3_version: Option<PythonVersion>,
2307 abi3t_version: Option<PythonVersion>,
2308) -> Result<InterpreterConfig> {
2309 if abi3_version.is_none() && abi3t_version.is_none() {
2310 bail!("Neither abi3 or abi3t features are enabled")
2311 }
2312 let (stable_abi, version) = if let Some(version) = abi3_version {
2313 (StableAbi::Abi3, version)
2314 } else if let Some(version) = abi3t_version {
2315 (StableAbi::Abi3t, version)
2316 } else {
2317 unreachable!();
2318 };
2319
2320 if stable_abi == StableAbi::Abi3t && version < MINIMUM_SUPPORTED_VERSION_ABI3T {
2321 bail!("Cannot target an abi3t version below {MINIMUM_SUPPORTED_VERSION_ABI3T}")
2322 }
2323
2324 let target_abi = PythonAbiBuilder::new(PythonImplementation::CPython, version)
2326 .stable_abi(stable_abi)
2327 .finalize()?;
2328 let builder = InterpreterConfigBuilder::new(PythonImplementation::CPython, version)
2329 .target_abi(target_abi);
2330 if host.operating_system == OperatingSystem::Windows {
2331 builder.lib_name(default_lib_name_windows(target_abi, false, false)?)
2332 } else {
2333 builder
2334 }
2335 .finalize()
2336}
2337
2338fn load_cross_compile_config(
2346 cross_compile_config: CrossCompileConfig,
2347) -> Result<InterpreterConfig> {
2348 let windows = cross_compile_config.target.operating_system == OperatingSystem::Windows;
2349
2350 let config = if windows || !have_python_interpreter() {
2351 default_cross_compile(&cross_compile_config)?
2355 } else if let Some(config) = cross_compile_from_sysconfigdata(&cross_compile_config)? {
2356 config
2358 } else {
2359 default_cross_compile(&cross_compile_config)?
2361 };
2362
2363 Ok(config)
2364}
2365
2366const WINDOWS_STABLE_ABI_LIB_NAME: &str = "python3";
2368const WINDOWS_STABLE_ABI_DEBUG_LIB_NAME: &str = "python3_d";
2369
2370#[allow(dead_code)]
2372fn default_lib_name_for_target(abi: PythonAbi, target: &Triple) -> String {
2373 if target.operating_system == OperatingSystem::Windows {
2374 default_lib_name_windows(abi, false, false).unwrap()
2375 } else {
2376 default_lib_name_unix(
2377 abi,
2378 target.operating_system == OperatingSystem::Cygwin,
2379 None,
2380 )
2381 .unwrap()
2382 }
2383}
2384
2385fn default_lib_name_windows(abi: PythonAbi, mingw: bool, debug: bool) -> Result<String> {
2386 if mingw {
2390 let mut lib_name = default_lib_name_unix(abi, true, None)?;
2391 lib_name.insert_str(0, "lib");
2392 return Ok(lib_name);
2393 }
2394
2395 if abi.implementation.is_pypy() {
2396 Ok(format!(
2400 "libpypy{}.{}-c",
2401 abi.version.major, abi.version.minor
2402 ))
2403 } else if debug && abi.version < PythonVersion::PY310 {
2404 Ok(format!(
2407 "python{}{}_d",
2408 abi.version.major, abi.version.minor
2409 ))
2410 } else if abi.kind == PythonAbiKind::Stable(StableAbi::Abi3)
2411 || abi.kind == PythonAbiKind::Stable(StableAbi::Abi3t)
2412 {
2413 let mut lib_name = if debug {
2414 WINDOWS_STABLE_ABI_DEBUG_LIB_NAME.to_owned()
2415 } else {
2416 WINDOWS_STABLE_ABI_LIB_NAME.to_owned()
2417 };
2418 if abi.kind == PythonAbiKind::Stable(StableAbi::Abi3t) {
2419 lib_name = lib_name.replace("python3", "python3t");
2420 }
2421 Ok(lib_name)
2422 } else if abi.kind().is_free_threaded() {
2423 #[expect(deprecated, reason = "using constant internally")]
2424 {
2425 ensure!(abi.version() >= PythonVersion::PY313, "Cannot compile extensions for the free-threaded build on Python versions earlier than 3.13, found {}.{}", abi.version.major, abi.version.minor);
2426 }
2427 if debug {
2428 Ok(format!(
2429 "python{}{}t_d",
2430 abi.version.major, abi.version.minor
2431 ))
2432 } else {
2433 Ok(format!("python{}{}t", abi.version.major, abi.version.minor))
2434 }
2435 } else if debug {
2436 Ok(format!(
2437 "python{}{}_d",
2438 abi.version.major, abi.version.minor
2439 ))
2440 } else {
2441 Ok(format!("python{}{}", abi.version.major, abi.version.minor))
2442 }
2443}
2444
2445fn default_lib_name_unix(
2446 abi: PythonAbi,
2447 use_stable_abi_lib: bool,
2448 ld_version: Option<&str>,
2449) -> Result<String> {
2450 match abi.implementation {
2451 PythonImplementation::CPython => match ld_version {
2452 Some(ld_version) => Ok(format!("python{ld_version}")),
2453 None => match abi.kind {
2454 PythonAbiKind::Stable(StableAbi::Abi3) if use_stable_abi_lib => {
2455 Ok("python3".to_string())
2456 }
2457 PythonAbiKind::Stable(StableAbi::Abi3t) if use_stable_abi_lib => {
2458 Ok("python3t".to_string())
2459 }
2460 _ => {
2461 if abi.kind.is_free_threaded() {
2462 #[expect(deprecated, reason = "using constant internally")]
2463 {
2464 ensure!(abi.version >= PythonVersion::PY313, "Cannot compile extensions for the free-threaded build on Python versions earlier than 3.13, found {}.{}", abi.version.major, abi.version.minor);
2465 }
2466 Ok(format!(
2467 "python{}.{}t",
2468 abi.version.major, abi.version.minor
2469 ))
2470 } else {
2471 Ok(format!("python{}.{}", abi.version.major, abi.version.minor))
2472 }
2473 }
2474 },
2475 },
2476 PythonImplementation::PyPy => match ld_version {
2477 Some(ld_version) => Ok(format!("pypy{ld_version}-c")),
2478 None => Ok(format!("pypy{}.{}-c", abi.version.major, abi.version.minor)),
2479 },
2480
2481 PythonImplementation::GraalPy => Ok("python-native".to_string()),
2482 PythonImplementation::RustPython => Ok("rustpython_capi".to_string()),
2483 }
2484}
2485
2486fn run_python_script(interpreter: &Path, script: &str) -> Result<String> {
2488 run_python_script_with_envs(interpreter, script, std::iter::empty::<(&str, &str)>())
2489}
2490
2491fn run_python_script_with_envs<I, K, V>(interpreter: &Path, script: &str, envs: I) -> Result<String>
2494where
2495 I: IntoIterator<Item = (K, V)>,
2496 K: AsRef<OsStr>,
2497 V: AsRef<OsStr>,
2498{
2499 let out = Command::new(interpreter)
2500 .env("PYTHONIOENCODING", "utf-8")
2501 .envs(envs)
2502 .stdin(Stdio::piped())
2503 .stdout(Stdio::piped())
2504 .stderr(Stdio::inherit())
2505 .spawn()
2506 .and_then(|mut child| {
2507 child
2508 .stdin
2509 .as_mut()
2510 .expect("piped stdin")
2511 .write_all(script.as_bytes())?;
2512 child.wait_with_output()
2513 });
2514
2515 match out {
2516 Err(err) => bail!(
2517 "failed to run the Python interpreter at {}: {}",
2518 interpreter.display(),
2519 err
2520 ),
2521 Ok(ok) if !ok.status.success() => bail!("Python script failed"),
2522 Ok(ok) => Ok(String::from_utf8(ok.stdout)
2523 .context("failed to parse Python script output as utf-8")?),
2524 }
2525}
2526
2527fn venv_interpreter(virtual_env: &OsStr, windows: bool) -> PathBuf {
2528 let venv = Path::new(virtual_env);
2529 println!(
2531 "cargo:rerun-if-changed={}",
2532 venv.join("pyvenv.cfg").display()
2533 );
2534 if windows {
2535 venv.join("Scripts").join("python.exe")
2536 } else {
2537 venv.join("bin").join("python")
2538 }
2539}
2540
2541fn conda_env_interpreter(conda_prefix: &OsStr, windows: bool) -> PathBuf {
2542 if windows {
2543 Path::new(conda_prefix).join("python.exe")
2544 } else {
2545 Path::new(conda_prefix).join("bin").join("python")
2546 }
2547}
2548
2549fn get_env_interpreter() -> Option<PathBuf> {
2550 match (env_var("VIRTUAL_ENV"), env_var("CONDA_PREFIX")) {
2551 (Some(dir), None) => Some(venv_interpreter(&dir, cfg!(windows))),
2554 (None, Some(dir)) => Some(conda_env_interpreter(&dir, cfg!(windows))),
2555 (Some(_), Some(_)) => {
2556 warn!(
2557 "Both VIRTUAL_ENV and CONDA_PREFIX are set. PyO3 will ignore both of these for \
2558 locating the Python interpreter until you unset one of them."
2559 );
2560 None
2561 }
2562 (None, None) => None,
2563 }
2564}
2565
2566pub fn find_interpreter() -> Result<PathBuf> {
2574 println!("cargo:rerun-if-env-changed=PYO3_ENVIRONMENT_SIGNATURE");
2577
2578 if let Some(exe) = env_var("PYO3_PYTHON") {
2579 Ok(exe.into())
2580 } else if let Some(env_interpreter) = get_env_interpreter() {
2581 Ok(env_interpreter)
2582 } else {
2583 println!("cargo:rerun-if-env-changed=PATH");
2584 ["python", "python3"]
2585 .iter()
2586 .find(|bin| {
2587 if let Ok(out) = Command::new(bin).arg("--version").output() {
2588 out.stdout.starts_with(b"Python 3")
2590 || out.stderr.starts_with(b"Python 3")
2591 || out.stdout.starts_with(b"GraalPy 3")
2592 } else {
2593 false
2594 }
2595 })
2596 .map(PathBuf::from)
2597 .ok_or_else(|| "no Python 3.x interpreter found".into())
2598 }
2599}
2600
2601fn get_host_interpreter(
2605 abi3_version: Option<StableAbiVersion>,
2606 abi3t_version: Option<StableAbiVersion>,
2607) -> Result<InterpreterConfig> {
2608 let interpreter_path = find_interpreter()?;
2609
2610 let interpreter_config =
2611 InterpreterConfig::from_interpreter(interpreter_path, abi3_version, abi3t_version)?;
2612
2613 Ok(interpreter_config)
2614}
2615
2616pub fn make_cross_compile_config(target: &Triple) -> Result<Option<InterpreterConfig>> {
2621 let interpreter_config =
2622 if let Some(cross_config) = cross_compiling_from_to(&Triple::host(), target)? {
2623 Some(load_cross_compile_config(cross_config)?.apply_build_env()?)
2624 } else {
2625 None
2626 };
2627
2628 Ok(interpreter_config)
2629}
2630
2631pub fn make_interpreter_config() -> Result<InterpreterConfig> {
2633 let host = Triple::host();
2634 let abi3_version = get_abi3_version();
2635 let abi3t_version = get_abi3t_version();
2636
2637 let need_interpreter =
2640 (abi3_version.is_none() && abi3t_version.is_none()) || require_libdir_for_target(&host);
2641
2642 if have_python_interpreter() {
2643 match get_host_interpreter(abi3_version, abi3t_version) {
2644 Ok(interpreter_config) => return Ok(interpreter_config),
2645 Err(e) if need_interpreter => return Err(e),
2647 _ => {
2648 warn!("Compiling without a working Python interpreter.");
2651 }
2652 }
2653 }
2654
2655 let interpreter_config = default_stable_abi_config(
2656 &host,
2657 exact_stable_abi_version(abi3_version),
2658 exact_stable_abi_version(abi3t_version),
2659 )?;
2660
2661 Ok(interpreter_config)
2662}
2663
2664pub(crate) fn escape(bytes: &[u8]) -> String {
2665 let mut escaped = String::with_capacity(2 * bytes.len());
2666
2667 for byte in bytes {
2668 const LUT: &[u8; 16] = b"0123456789abcdef";
2669
2670 escaped.push(LUT[(byte >> 4) as usize] as char);
2671 escaped.push(LUT[(byte & 0x0F) as usize] as char);
2672 }
2673
2674 escaped
2675}
2676
2677fn unescape(escaped: &str) -> Vec<u8> {
2678 assert_eq!(escaped.len() % 2, 0, "invalid hex encoding");
2679
2680 let mut bytes = Vec::with_capacity(escaped.len() / 2);
2681
2682 for chunk in escaped.as_bytes().chunks_exact(2) {
2683 fn unhex(hex: u8) -> u8 {
2684 match hex {
2685 b'a'..=b'f' => hex - b'a' + 10,
2686 b'0'..=b'9' => hex - b'0',
2687 _ => panic!("invalid hex encoding"),
2688 }
2689 }
2690
2691 bytes.push((unhex(chunk[0]) << 4) | unhex(chunk[1]));
2692 }
2693
2694 bytes
2695}
2696
2697#[cfg(test)]
2698#[expect(deprecated, reason = "accessing config fields directly for testing")]
2700mod tests {
2701 use target_lexicon::triple;
2702
2703 use super::*;
2704
2705 #[test]
2706 fn test_config_file_roundtrip() {
2707 let implementation = PythonImplementation::CPython;
2708 let version = MINIMUM_SUPPORTED_VERSION;
2709 let config = InterpreterConfigBuilder::new(implementation, version)
2710 .stable_abi(StableAbi::Abi3)
2711 .pointer_width(32)
2712 .executable("executable".to_string())
2713 .lib_dir("lib_name".to_string())
2714 .lib_name("lib_name".to_string())
2715 .extra_build_script_lines(vec!["cargo:test1".to_string(), "cargo:test2".to_string()])
2716 .finalize()
2717 .unwrap();
2718 let mut buf: Vec<u8> = Vec::new();
2719 config.to_writer(&mut buf).unwrap();
2720
2721 assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap());
2722
2723 let version = PythonVersion::PY310;
2725 let implementation = PythonImplementation::PyPy;
2726 let build_flags = {
2727 let mut flags = HashSet::new();
2728 flags.insert(BuildFlag::Py_DEBUG);
2729 flags.insert(BuildFlag::Other(String::from("Py_SOME_FLAG")));
2730 BuildFlags(flags)
2731 };
2732 let config = InterpreterConfigBuilder::new(implementation, version)
2733 .build_flags(build_flags)
2734 .finalize()
2735 .unwrap();
2736
2737 let mut buf: Vec<u8> = Vec::new();
2738 config.to_writer(&mut buf).unwrap();
2739
2740 assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap());
2741 }
2742
2743 #[test]
2744 fn test_config_file_roundtrip_with_escaping() {
2745 let implementation = PythonImplementation::CPython;
2746 let version = MINIMUM_SUPPORTED_VERSION;
2747 let config = InterpreterConfigBuilder::new(implementation, version)
2748 .stable_abi(StableAbi::Abi3)
2749 .pointer_width(32)
2750 .executable("executable".to_string())
2751 .lib_name("lib_name".to_string())
2752 .lib_dir("lib_dir\\n".to_string())
2753 .extra_build_script_lines(vec!["cargo:test1".to_string(), "cargo:test2".to_string()])
2754 .finalize()
2755 .unwrap();
2756 let mut buf: Vec<u8> = Vec::new();
2757 config.to_writer(&mut buf).unwrap();
2758
2759 let buf = unescape(&escape(&buf));
2760
2761 assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap());
2762 }
2763
2764 #[test]
2765 fn test_config_file_defaults() {
2766 let implementation = PythonImplementation::CPython;
2768 let version = PythonVersion::PY38;
2769 assert_eq!(
2770 InterpreterConfig::from_reader("version=3.8".as_bytes()).unwrap(),
2771 InterpreterConfigBuilder::new(implementation, version,)
2772 .finalize()
2773 .unwrap()
2774 )
2775 }
2776
2777 #[test]
2778 fn test_config_file_unknown_keys() {
2779 let implementation = PythonImplementation::CPython;
2781 let version = PythonVersion::PY38;
2782 assert_eq!(
2783 InterpreterConfig::from_reader("version=3.8\next_suffix=.python38.so".as_bytes())
2784 .unwrap(),
2785 InterpreterConfigBuilder::new(implementation, version,)
2786 .finalize()
2787 .unwrap()
2788 )
2789 }
2790
2791 #[test]
2792 fn test_config_file_invalid_keys() {
2793 assert!(
2794 InterpreterConfig::from_reader("version=3.14\ntarget_abi=foo-bar-baz".as_bytes())
2795 .is_err()
2796 );
2797 assert!(InterpreterConfig::from_reader(
2798 "version=3.14\ntarget_abi=CPython-bar-baz".as_bytes()
2799 )
2800 .is_err());
2801 assert!(InterpreterConfig::from_reader(
2802 "version=3.14\ntarget_abi=CPython-abi3-baz".as_bytes()
2803 )
2804 .is_err());
2805 }
2806
2807 #[test]
2808 fn gil_disabled_config_file_corner_cases() {
2809 let implementation = PythonImplementation::CPython;
2810 let version = PythonVersion::PY313;
2811 assert_eq!(
2813 InterpreterConfig::from_reader("version=3.13\nbuild_flags=Py_GIL_DISABLED".as_bytes())
2814 .unwrap(),
2815 InterpreterConfigBuilder::new(implementation, version)
2816 .free_threaded()
2817 .unwrap()
2818 .finalize()
2819 .unwrap()
2820 );
2821 assert_eq!(
2823 InterpreterConfig::from_reader(
2824 "version=3.13\ntarget_abi=CPython-free_threaded-3.13".as_bytes()
2825 )
2826 .unwrap(),
2827 InterpreterConfigBuilder::new(implementation, version)
2828 .free_threaded()
2829 .unwrap()
2830 .finalize()
2831 .unwrap()
2832 );
2833 assert!(InterpreterConfig::from_reader(
2835 "version=3.13\ntarget_abi=CPython-gil_enabled-3.13\nbuild_flags=Py_GIL_DISABLED"
2836 .as_bytes()
2837 )
2838 .is_err());
2839 let mut flags = BuildFlags::default();
2841 flags.0.insert(BuildFlag::Py_GIL_DISABLED);
2842 assert!(InterpreterConfigBuilder::new(implementation, version)
2843 .build_flags(flags)
2844 .finalize()
2845 .unwrap()
2846 .target_abi
2847 .kind
2848 .is_free_threaded());
2849
2850 let mut flags = BuildFlags::default();
2851 flags.0.insert(BuildFlag::Py_GIL_DISABLED);
2852 assert!(
2853 InterpreterConfigBuilder::new(implementation, PythonVersion::PY312)
2854 .build_flags(flags)
2855 .finalize()
2856 .is_err()
2857 );
2858
2859 let mut flags = BuildFlags::default();
2860 flags.0.insert(BuildFlag::Py_GIL_DISABLED);
2861 assert!(
2862 InterpreterConfigBuilder::new(implementation, PythonVersion::PY312)
2863 .stable_abi(StableAbi::Abi3)
2864 .build_flags(flags)
2865 .finalize()
2866 .is_err()
2867 );
2868
2869 assert!(
2870 InterpreterConfigBuilder::new(implementation, PythonVersion::PY38)
2871 .free_threaded()
2872 .is_err()
2873 );
2874 }
2875
2876 #[test]
2877 fn abi3_from_old_config_file() {
2878 let implementation = PythonImplementation::CPython;
2879 let version = PythonVersion::PY313;
2880 assert_eq!(
2881 InterpreterConfig::from_reader("version=3.13\nabi3=true".as_bytes()).unwrap(),
2882 InterpreterConfigBuilder::new(implementation, version)
2883 .stable_abi(StableAbi::Abi3)
2884 .finalize()
2885 .unwrap()
2886 );
2887 }
2888
2889 #[test]
2890 fn test_target_abi_and_abi3() {
2891 assert!(InterpreterConfig::from_reader(
2892 "version=3.13\nabi3=true\ntarget_abi=CPython-abi3-3.13".as_bytes()
2893 )
2894 .unwrap_err()
2895 .to_string()
2896 .contains("Invalid config"),);
2897 }
2898
2899 #[test]
2900 fn build_flags_default() {
2901 assert_eq!(BuildFlags::default(), BuildFlags::new());
2902 }
2903
2904 #[test]
2905 fn build_flags_from_sysconfigdata() {
2906 let mut sysconfigdata = Sysconfigdata::new();
2907
2908 assert_eq!(
2909 BuildFlags::from_sysconfigdata(&sysconfigdata).0,
2910 HashSet::new()
2911 );
2912
2913 for flag in &BuildFlags::ALL {
2914 sysconfigdata.insert(flag.to_string(), "0".into());
2915 }
2916
2917 assert_eq!(
2918 BuildFlags::from_sysconfigdata(&sysconfigdata).0,
2919 HashSet::new()
2920 );
2921
2922 let mut expected_flags = HashSet::new();
2923 for flag in &BuildFlags::ALL {
2924 sysconfigdata.insert(flag.to_string(), "1".into());
2925 expected_flags.insert(flag.clone());
2926 }
2927
2928 assert_eq!(
2929 BuildFlags::from_sysconfigdata(&sysconfigdata).0,
2930 expected_flags
2931 );
2932 }
2933
2934 #[test]
2935 fn build_flags_fixup() {
2936 let mut build_flags = BuildFlags::new();
2937
2938 build_flags = build_flags.fixup();
2939 assert!(build_flags.0.is_empty());
2940
2941 build_flags.0.insert(BuildFlag::Py_DEBUG);
2942
2943 build_flags = build_flags.fixup();
2944
2945 assert!(build_flags.0.contains(&BuildFlag::Py_REF_DEBUG));
2947 }
2948
2949 #[test]
2950 fn parse_script_output() {
2951 let output = "foo bar\nbar foobar\n\n";
2952 let map = super::parse_script_output(output);
2953 assert_eq!(map.len(), 2);
2954 assert_eq!(map["foo"], "bar");
2955 assert_eq!(map["bar"], "foobar");
2956 }
2957
2958 #[test]
2959 fn config_from_interpreter() {
2960 assert!(make_interpreter_config().is_ok())
2964 }
2965
2966 #[test]
2967 fn config_from_empty_sysconfigdata() {
2968 let sysconfigdata = Sysconfigdata::new();
2969 assert!(InterpreterConfig::from_sysconfigdata(&sysconfigdata).is_err());
2970 }
2971
2972 #[test]
2973 fn config_from_sysconfigdata() {
2974 let mut sysconfigdata = Sysconfigdata::new();
2975 sysconfigdata.insert("SOABI", "cpython-38-x86_64-linux-gnu");
2978 sysconfigdata.insert("VERSION", "3.8");
2979 sysconfigdata.insert("Py_ENABLE_SHARED", "1");
2980 sysconfigdata.insert("LIBDIR", "/usr/lib");
2981 sysconfigdata.insert("LDVERSION", "3.8");
2982 sysconfigdata.insert("SIZEOF_VOID_P", "8");
2983 let implementation = PythonImplementation::CPython;
2984 let version = PythonVersion::PY38;
2985 assert_eq!(
2986 InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(),
2987 InterpreterConfigBuilder::new(implementation, version,)
2988 .build_flags(BuildFlags::from_sysconfigdata(&sysconfigdata))
2989 .lib_dir("/usr/lib".to_string())
2990 .lib_name("python3.8".to_string())
2991 .pointer_width(64)
2992 .finalize()
2993 .unwrap()
2994 );
2995 }
2996
2997 #[test]
2998 fn config_from_sysconfigdata_framework() {
2999 let mut sysconfigdata = Sysconfigdata::new();
3000 sysconfigdata.insert("SOABI", "cpython-38-x86_64-linux-gnu");
3001 sysconfigdata.insert("VERSION", "3.8");
3002 sysconfigdata.insert("Py_ENABLE_SHARED", "0");
3004 sysconfigdata.insert("PYTHONFRAMEWORK", "Python");
3005 sysconfigdata.insert("LIBDIR", "/usr/lib");
3006 sysconfigdata.insert("LDVERSION", "3.8");
3007 sysconfigdata.insert("SIZEOF_VOID_P", "8");
3008 let implementation = PythonImplementation::CPython;
3009 let version = PythonVersion::PY38;
3010 assert_eq!(
3011 InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(),
3012 InterpreterConfigBuilder::new(implementation, version,)
3013 .build_flags(BuildFlags::from_sysconfigdata(&sysconfigdata))
3014 .lib_dir("/usr/lib".to_string())
3015 .lib_name("python3.8".to_string())
3016 .pointer_width(64)
3017 .finalize()
3018 .unwrap()
3019 );
3020
3021 sysconfigdata = Sysconfigdata::new();
3022 sysconfigdata.insert("SOABI", "cpython-38-x86_64-linux-gnu");
3023 sysconfigdata.insert("VERSION", "3.8");
3024 sysconfigdata.insert("Py_ENABLE_SHARED", "0");
3026 sysconfigdata.insert("PYTHONFRAMEWORK", "");
3027 sysconfigdata.insert("LIBDIR", "/usr/lib");
3028 sysconfigdata.insert("LDVERSION", "3.8");
3029 sysconfigdata.insert("SIZEOF_VOID_P", "8");
3030 let implementation = PythonImplementation::CPython;
3031 let version = PythonVersion::PY38;
3032 assert_eq!(
3033 InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(),
3034 InterpreterConfigBuilder::new(implementation, version,)
3035 .build_flags(BuildFlags::from_sysconfigdata(&sysconfigdata))
3036 .lib_dir("/usr/lib".to_string())
3037 .lib_name("python3.8".to_string())
3038 .pointer_width(64)
3039 .shared(false)
3040 .finalize()
3041 .unwrap()
3042 );
3043 }
3044
3045 #[test]
3046 fn windows_hardcoded_abi3_compile() {
3047 let host = triple!("x86_64-pc-windows-msvc");
3048 let implementation = PythonImplementation::CPython;
3049 let version = PythonVersion::PY38;
3050 let config = InterpreterConfigBuilder::new(implementation, version)
3051 .stable_abi(StableAbi::Abi3)
3052 .lib_name("python3".to_string())
3053 .finalize()
3054 .unwrap();
3055 assert_eq!(
3056 default_stable_abi_config(&host, Some(version), None).unwrap(),
3057 config
3058 );
3059 }
3060
3061 #[test]
3062 fn windows_hardcoded_abi3t_compile() {
3063 let host = triple!("x86_64-pc-windows-msvc");
3064 let implementation = PythonImplementation::CPython;
3065 let version = PythonVersion::PY315;
3066 let config = InterpreterConfigBuilder::new(implementation, version)
3067 .stable_abi(StableAbi::Abi3t)
3068 .lib_name("python3t".to_string())
3069 .finalize()
3070 .unwrap();
3071 assert_eq!(
3072 default_stable_abi_config(&host, None, Some(version)).unwrap(),
3073 config
3074 );
3075 }
3076
3077 #[test]
3078 fn unix_hardcoded_abi3_compile() {
3079 let host = triple!("x86_64-unknown-linux-gnu");
3080 let implementation = PythonImplementation::CPython;
3081 let version = PythonVersion::PY39;
3082 let config = InterpreterConfigBuilder::new(implementation, version)
3083 .stable_abi(StableAbi::Abi3)
3084 .finalize()
3085 .unwrap();
3086 assert_eq!(
3087 default_stable_abi_config(&host, Some(version), None).unwrap(),
3088 config
3089 );
3090 }
3091
3092 #[test]
3093 fn unix_hardcoded_abi3t_compile() {
3094 let host = triple!("x86_64-unknown-linux-gnu");
3095 let implementation = PythonImplementation::CPython;
3096 let version = PythonVersion::PY315;
3097 let config = InterpreterConfigBuilder::new(implementation, version)
3098 .stable_abi(StableAbi::Abi3t)
3099 .finalize()
3100 .unwrap();
3101 assert_eq!(
3102 default_stable_abi_config(&host, None, Some(version)).unwrap(),
3103 config
3104 );
3105 }
3106
3107 #[test]
3108 fn default_stable_abi_config_corner_cases() {
3109 let host = triple!("x86_64-unknown-linux-gnu");
3110 let py315 = Some("3.15".parse().unwrap());
3111 let py39 = Some("3.9".parse().unwrap());
3112 let implementation = PythonImplementation::CPython;
3113 let version = PythonVersion::PY39;
3114 let config = InterpreterConfigBuilder::new(implementation, version)
3115 .stable_abi(StableAbi::Abi3)
3116 .finalize()
3117 .unwrap();
3118 assert_eq!(
3119 default_stable_abi_config(&host, py39, py315).unwrap(),
3120 config
3121 );
3122 assert!(default_stable_abi_config(&host, None, py39).is_err());
3123 }
3124
3125 #[test]
3126 fn windows_hardcoded_cross_compile() {
3127 let env_vars = CrossCompileEnvVars {
3128 pyo3_cross: None,
3129 pyo3_cross_lib_dir: Some("C:\\some\\path".into()),
3130 pyo3_cross_python_implementation: None,
3131 pyo3_cross_python_version: Some("3.8".into()),
3132 };
3133
3134 let host = triple!("x86_64-unknown-linux-gnu");
3135 let target = triple!("i686-pc-windows-msvc");
3136 let cross_config =
3137 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
3138 .unwrap()
3139 .unwrap();
3140
3141 let implementation = PythonImplementation::CPython;
3142 let version = PythonVersion::PY38;
3143 let config = InterpreterConfigBuilder::new(implementation, version)
3144 .lib_name("python38".to_string())
3145 .lib_dir("C:\\some\\path".to_string())
3146 .finalize()
3147 .unwrap();
3148 assert_eq!(default_cross_compile(&cross_config).unwrap(), config);
3149 }
3150
3151 #[test]
3152 fn mingw_hardcoded_cross_compile() {
3153 let env_vars = CrossCompileEnvVars {
3154 pyo3_cross: None,
3155 pyo3_cross_lib_dir: Some("/usr/lib/mingw".into()),
3156 pyo3_cross_python_implementation: None,
3157 pyo3_cross_python_version: Some("3.8".into()),
3158 };
3159
3160 let host = triple!("x86_64-unknown-linux-gnu");
3161 let target = triple!("i686-pc-windows-gnu");
3162 let cross_config =
3163 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
3164 .unwrap()
3165 .unwrap();
3166
3167 let implementation = PythonImplementation::CPython;
3168 let version = PythonVersion::PY38;
3169 let config = InterpreterConfigBuilder::new(implementation, version)
3170 .lib_name("python38".to_string())
3171 .lib_dir("/usr/lib/mingw".to_string())
3172 .finalize()
3173 .unwrap();
3174 assert_eq!(default_cross_compile(&cross_config).unwrap(), config);
3175 }
3176
3177 #[test]
3178 fn unix_hardcoded_cross_compile() {
3179 let env_vars = CrossCompileEnvVars {
3180 pyo3_cross: None,
3181 pyo3_cross_lib_dir: Some("/usr/arm64/lib".into()),
3182 pyo3_cross_python_implementation: None,
3183 pyo3_cross_python_version: Some("3.9".into()),
3184 };
3185
3186 let host = triple!("x86_64-unknown-linux-gnu");
3187 let target = triple!("aarch64-unknown-linux-gnu");
3188 let cross_config =
3189 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
3190 .unwrap()
3191 .unwrap();
3192
3193 let implementation = PythonImplementation::CPython;
3194 let version = PythonVersion::PY39;
3195 let config = InterpreterConfigBuilder::new(implementation, version)
3196 .lib_name("python3.9".to_string())
3197 .lib_dir("/usr/arm64/lib".to_string())
3198 .finalize()
3199 .unwrap();
3200 assert_eq!(default_cross_compile(&cross_config).unwrap(), config);
3201 }
3202
3203 #[test]
3204 fn pypy_hardcoded_cross_compile() {
3205 let env_vars = CrossCompileEnvVars {
3206 pyo3_cross: None,
3207 pyo3_cross_lib_dir: None,
3208 pyo3_cross_python_implementation: Some("PyPy".into()),
3209 pyo3_cross_python_version: Some("3.11".into()),
3210 };
3211
3212 let triple = triple!("x86_64-unknown-linux-gnu");
3213 let cross_config =
3214 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &triple, &triple)
3215 .unwrap()
3216 .unwrap();
3217
3218 let implementation = PythonImplementation::PyPy;
3219 let version = PythonVersion::PY311;
3220 let config = InterpreterConfigBuilder::new(implementation, version)
3221 .lib_name("pypy3.11-c".to_string())
3222 .finalize()
3223 .unwrap();
3224 assert_eq!(default_cross_compile(&cross_config).unwrap(), config);
3225 }
3226
3227 #[test]
3231 fn unix_free_threaded_pre_315_cross_compile() {
3232 let env_vars = CrossCompileEnvVars {
3233 pyo3_cross: None,
3234 pyo3_cross_lib_dir: None,
3235 pyo3_cross_python_implementation: None,
3236 pyo3_cross_python_version: Some("3.14t".into()),
3237 };
3238
3239 let host = triple!("x86_64-unknown-linux-gnu");
3240 let target = triple!("aarch64-unknown-linux-gnu");
3241 let cross_config =
3242 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
3243 .unwrap()
3244 .unwrap();
3245
3246 let implementation = PythonImplementation::CPython;
3247 let version = PythonVersion::PY314;
3248 let config = InterpreterConfigBuilder::new(implementation, version)
3249 .free_threaded()
3250 .unwrap()
3251 .lib_name("python3.14t".to_string())
3252 .finalize()
3253 .unwrap();
3254 let result = default_cross_compile(&cross_config).unwrap();
3255 assert_eq!(result, config);
3256 assert_eq!(
3257 result.target_abi.kind(),
3258 PythonAbiKind::VersionSpecific(GilUsed::FreeThreaded)
3259 );
3260 }
3261
3262 #[test]
3263 fn windows_free_threaded_pre_315_cross_compile() {
3264 let env_vars = CrossCompileEnvVars {
3265 pyo3_cross: None,
3266 pyo3_cross_lib_dir: None,
3267 pyo3_cross_python_implementation: None,
3268 pyo3_cross_python_version: Some("3.14t".into()),
3269 };
3270
3271 let host = triple!("x86_64-unknown-linux-gnu");
3272 let target = triple!("x86_64-pc-windows-msvc");
3273 let cross_config =
3274 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
3275 .unwrap()
3276 .unwrap();
3277
3278 let implementation = PythonImplementation::CPython;
3279 let version = PythonVersion::PY314;
3280 let config = InterpreterConfigBuilder::new(implementation, version)
3281 .free_threaded()
3282 .unwrap()
3283 .lib_name("python314t".to_string())
3284 .finalize()
3285 .unwrap();
3286 let result = default_cross_compile(&cross_config).unwrap();
3287 assert_eq!(result, config);
3288 assert_eq!(
3289 result.target_abi.kind(),
3290 PythonAbiKind::VersionSpecific(GilUsed::FreeThreaded)
3291 );
3292 }
3293
3294 #[test]
3297 fn unix_free_threaded_315_cross_compile() {
3298 let env_vars = CrossCompileEnvVars {
3299 pyo3_cross: None,
3300 pyo3_cross_lib_dir: None,
3301 pyo3_cross_python_implementation: None,
3302 pyo3_cross_python_version: Some("3.15t".into()),
3303 };
3304
3305 let host = triple!("x86_64-unknown-linux-gnu");
3306 let target = triple!("aarch64-unknown-linux-gnu");
3307 let cross_config =
3308 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
3309 .unwrap()
3310 .unwrap();
3311
3312 let implementation = PythonImplementation::CPython;
3313 let version = PythonVersion::PY315;
3314 let config = InterpreterConfigBuilder::new(implementation, version)
3315 .free_threaded()
3316 .unwrap()
3317 .lib_name("python3.15t".to_string())
3318 .finalize()
3319 .unwrap();
3320 let result = default_cross_compile(&cross_config).unwrap();
3321 assert_eq!(result, config);
3322 assert_eq!(
3323 result.target_abi.kind(),
3324 PythonAbiKind::VersionSpecific(GilUsed::FreeThreaded)
3325 );
3326 }
3327
3328 #[test]
3329 fn default_lib_name_windows() {
3330 assert_eq!(
3331 super::default_lib_name_windows(
3332 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY39)
3333 .finalize()
3334 .unwrap(),
3335 false,
3336 false,
3337 )
3338 .unwrap(),
3339 "python39",
3340 );
3341 assert!(
3343 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY39)
3344 .free_threaded()
3345 .finalize()
3346 .is_err()
3347 );
3348 assert_eq!(
3349 super::default_lib_name_windows(
3350 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY39)
3351 .stable_abi(StableAbi::Abi3)
3352 .finalize()
3353 .unwrap(),
3354 false,
3355 false,
3356 )
3357 .unwrap(),
3358 "python3",
3359 );
3360 assert_eq!(
3361 super::default_lib_name_windows(
3362 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY39)
3363 .finalize()
3364 .unwrap(),
3365 true,
3366 false,
3367 )
3368 .unwrap(),
3369 "libpython3.9",
3370 );
3371 assert_eq!(
3372 super::default_lib_name_windows(
3373 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY39)
3374 .stable_abi(StableAbi::Abi3)
3375 .finalize()
3376 .unwrap(),
3377 true,
3378 false,
3379 )
3380 .unwrap(),
3381 "libpython3",
3382 );
3383 assert_eq!(
3384 super::default_lib_name_windows(
3385 PythonAbiBuilder::new(PythonImplementation::PyPy, PythonVersion::PY39)
3386 .stable_abi(StableAbi::Abi3)
3387 .finalize()
3388 .unwrap(),
3389 false,
3390 false,
3391 )
3392 .unwrap(),
3393 "libpypy3.9-c",
3394 );
3395 assert_eq!(
3396 super::default_lib_name_windows(
3397 PythonAbiBuilder::new(PythonImplementation::PyPy, PythonVersion::PY311)
3398 .stable_abi(StableAbi::Abi3)
3399 .finalize()
3400 .unwrap(),
3401 false,
3402 false,
3403 )
3404 .unwrap(),
3405 "libpypy3.11-c",
3406 );
3407 assert_eq!(
3408 super::default_lib_name_windows(
3409 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY310)
3410 .stable_abi(StableAbi::Abi3)
3411 .finalize()
3412 .unwrap(),
3413 false,
3414 true,
3415 )
3416 .unwrap(),
3417 "python3_d",
3418 );
3419 assert_eq!(
3422 super::default_lib_name_windows(
3423 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY39)
3424 .stable_abi(StableAbi::Abi3)
3425 .finalize()
3426 .unwrap(),
3427 false,
3428 true,
3429 )
3430 .unwrap(),
3431 "python39_d",
3432 );
3433 assert_eq!(
3434 super::default_lib_name_windows(
3435 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY310)
3436 .stable_abi(StableAbi::Abi3)
3437 .finalize()
3438 .unwrap(),
3439 false,
3440 true,
3441 )
3442 .unwrap(),
3443 "python3_d",
3444 );
3445 assert_eq!(
3446 super::default_lib_name_windows(
3447 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY313)
3448 .free_threaded()
3449 .finalize()
3450 .unwrap(),
3451 false,
3452 false,
3453 )
3454 .unwrap(),
3455 "python313t",
3456 );
3457 assert_eq!(
3458 super::default_lib_name_windows(
3459 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY313)
3460 .free_threaded()
3461 .finalize()
3462 .unwrap(),
3463 false,
3464 true,
3465 )
3466 .unwrap(),
3467 "python313t_d",
3468 );
3469 assert_eq!(
3470 super::default_lib_name_windows(
3471 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY315)
3472 .stable_abi(StableAbi::Abi3t)
3473 .finalize()
3474 .unwrap(),
3475 false,
3476 false,
3477 )
3478 .unwrap(),
3479 "python3t",
3480 );
3481 assert_eq!(
3482 super::default_lib_name_windows(
3483 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY315)
3484 .stable_abi(StableAbi::Abi3t)
3485 .finalize()
3486 .unwrap(),
3487 false,
3488 true,
3489 )
3490 .unwrap(),
3491 "python3t_d",
3492 );
3493 }
3494
3495 #[test]
3496 fn default_lib_name_unix() {
3497 assert_eq!(
3499 super::default_lib_name_unix(
3500 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY38)
3501 .finalize()
3502 .unwrap(),
3503 false,
3504 None,
3505 )
3506 .unwrap(),
3507 "python3.8",
3508 );
3509 assert_eq!(
3510 super::default_lib_name_unix(
3511 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY39)
3512 .finalize()
3513 .unwrap(),
3514 false,
3515 None,
3516 )
3517 .unwrap(),
3518 "python3.9",
3519 );
3520 assert_eq!(
3522 super::default_lib_name_unix(
3523 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY39)
3524 .finalize()
3525 .unwrap(),
3526 false,
3527 Some("3.8d"),
3528 )
3529 .unwrap(),
3530 "python3.8d",
3531 );
3532
3533 assert_eq!(
3535 super::default_lib_name_unix(
3536 PythonAbiBuilder::new(PythonImplementation::PyPy, PythonVersion::PY311)
3537 .finalize()
3538 .unwrap(),
3539 false,
3540 None,
3541 )
3542 .unwrap(),
3543 "pypy3.11-c",
3544 );
3545
3546 assert_eq!(
3547 super::default_lib_name_unix(
3548 PythonAbiBuilder::new(PythonImplementation::PyPy, PythonVersion::PY39)
3549 .finalize()
3550 .unwrap(),
3551 false,
3552 Some("3.11d"),
3553 )
3554 .unwrap(),
3555 "pypy3.11d-c",
3556 );
3557
3558 assert_eq!(
3560 super::default_lib_name_unix(
3561 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY313)
3562 .free_threaded()
3563 .finalize()
3564 .unwrap(),
3565 false,
3566 None,
3567 )
3568 .unwrap(),
3569 "python3.13t",
3570 );
3571 assert_eq!(
3573 super::default_lib_name_unix(
3574 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY313)
3575 .stable_abi(StableAbi::Abi3)
3576 .finalize()
3577 .unwrap(),
3578 true,
3579 None,
3580 )
3581 .unwrap(),
3582 "python3",
3583 );
3584 }
3585
3586 #[test]
3587 fn abi_builder_error_paths() {
3588 let builder = PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY39)
3589 .free_threaded()
3590 .finalize();
3591
3592 assert!(builder.is_err());
3593 assert!(builder.unwrap_err().to_string().contains("Cannot target"));
3594
3595 assert_eq!(
3596 PythonAbiBuilder::new(
3597 PythonImplementation::CPython,
3598 PythonVersion {
3599 major: 3,
3600 minor: 16,
3601 },
3602 )
3603 .stable_abi(StableAbi::Abi3)
3604 .finalize()
3605 .unwrap()
3606 .version
3607 .minor,
3608 STABLE_ABI_MAX_MINOR
3609 );
3610
3611 assert!("invalid".parse::<PythonAbi>().is_err());
3612 assert!("CPython-invalid".parse::<PythonAbi>().is_err());
3613 assert!("CPython-free_threaded-invalid"
3614 .parse::<PythonAbi>()
3615 .is_err());
3616
3617 let builder = PythonAbiBuilder::new(PythonImplementation::RustPython, PythonVersion::PY315)
3618 .free_threaded();
3619 let res = builder.finalize();
3620
3621 assert!(res.is_err());
3622 assert!(res
3623 .unwrap_err()
3624 .to_string()
3625 .contains("RustPython only supports targeting abi3t"));
3626 }
3627
3628 #[test]
3629 fn parse_cross_python_version() {
3630 let env_vars = CrossCompileEnvVars {
3631 pyo3_cross: None,
3632 pyo3_cross_lib_dir: None,
3633 pyo3_cross_python_version: Some("3.9".into()),
3634 pyo3_cross_python_implementation: None,
3635 };
3636
3637 assert_eq!(
3638 env_vars.parse_version().unwrap(),
3639 (Some(PythonVersion { major: 3, minor: 9 }), None),
3640 );
3641
3642 let env_vars = CrossCompileEnvVars {
3643 pyo3_cross: None,
3644 pyo3_cross_lib_dir: None,
3645 pyo3_cross_python_version: None,
3646 pyo3_cross_python_implementation: None,
3647 };
3648
3649 assert_eq!(env_vars.parse_version().unwrap(), (None, None));
3650
3651 let env_vars = CrossCompileEnvVars {
3652 pyo3_cross: None,
3653 pyo3_cross_lib_dir: None,
3654 pyo3_cross_python_version: Some("3.13t".into()),
3655 pyo3_cross_python_implementation: None,
3656 };
3657
3658 assert_eq!(
3659 env_vars.parse_version().unwrap(),
3660 (
3661 Some(PythonVersion {
3662 major: 3,
3663 minor: 13
3664 }),
3665 Some("t".into())
3666 ),
3667 );
3668
3669 let env_vars = CrossCompileEnvVars {
3670 pyo3_cross: None,
3671 pyo3_cross_lib_dir: None,
3672 pyo3_cross_python_version: Some("100".into()),
3673 pyo3_cross_python_implementation: None,
3674 };
3675
3676 assert!(env_vars.parse_version().is_err());
3677 }
3678
3679 #[test]
3680 fn target_abi3_version_different_from_host() {
3681 let implementation = PythonImplementation::CPython;
3682 let host_version = PythonVersion::PY39;
3683 let target_version = PythonVersion::PY38;
3684 let config = InterpreterConfigBuilder::new(implementation, host_version)
3685 .target_abi(
3686 PythonAbiBuilder::new(implementation, target_version)
3687 .stable_abi(StableAbi::Abi3)
3688 .finalize()
3689 .unwrap(),
3690 )
3691 .finalize()
3692 .unwrap();
3693 assert_eq!(config.target_abi.version(), target_version);
3694 assert_eq!(config.version, host_version);
3695 }
3696
3697 #[test]
3698 fn stable_abi_applicability() {
3699 use PythonImplementation::*;
3700 let abi3 = Some(StableAbiVersion::Target(PythonVersion::PY310));
3701 let abi3t = Some(StableAbiVersion::Target(PythonVersion::PY315));
3702
3703 assert_eq!(
3706 applicable_stable_abi(CPython, PythonVersion::PY314, true, abi3, abi3t),
3707 None
3708 );
3709 assert_eq!(
3711 applicable_stable_abi(CPython, PythonVersion::PY314, false, abi3, abi3t),
3712 Some((StableAbi::Abi3, PythonVersion::PY310))
3713 );
3714 assert_eq!(
3715 applicable_stable_abi(CPython, PythonVersion::PY314, false, None, abi3t),
3716 None
3717 );
3718 assert_eq!(
3720 applicable_stable_abi(CPython, PythonVersion::PY315, false, abi3, abi3t),
3721 Some((StableAbi::Abi3t, PythonVersion::PY315))
3722 );
3723 assert_eq!(
3724 applicable_stable_abi(CPython, PythonVersion::PY315, false, abi3, None),
3725 Some((StableAbi::Abi3, PythonVersion::PY310))
3726 );
3727 assert_eq!(
3729 applicable_stable_abi(CPython, PythonVersion::PY315, true, abi3, abi3t),
3730 Some((StableAbi::Abi3t, PythonVersion::PY315))
3731 );
3732 assert_eq!(
3733 applicable_stable_abi(CPython, PythonVersion::PY315, true, abi3, None),
3734 None
3735 );
3736 assert_eq!(
3738 applicable_stable_abi(
3739 CPython,
3740 PythonVersion::PY314,
3741 false,
3742 Some(StableAbiVersion::Current),
3743 None
3744 ),
3745 Some((StableAbi::Abi3, PythonVersion::PY314))
3746 );
3747 assert_eq!(
3748 applicable_stable_abi(
3749 CPython,
3750 PythonVersion::PY315,
3751 true,
3752 None,
3753 Some(StableAbiVersion::Current)
3754 ),
3755 Some((StableAbi::Abi3t, PythonVersion::PY315))
3756 );
3757 assert_eq!(
3759 applicable_stable_abi(PyPy, PythonVersion::PY311, false, abi3, abi3t),
3760 Some((StableAbi::Abi3, PythonVersion::PY311))
3761 );
3762 assert_eq!(
3763 applicable_stable_abi(GraalPy, PythonVersion::PY311, false, abi3, abi3t),
3764 Some((StableAbi::Abi3, PythonVersion::PY311))
3765 );
3766 assert_eq!(
3767 applicable_stable_abi(PyPy, PythonVersion::PY311, false, None, abi3t),
3768 None
3769 );
3770 }
3771
3772 #[test]
3773 fn apply_build_env_preserves_target_implementation() {
3774 let config = InterpreterConfig::from_reader(
3778 "implementation=CPython\nversion=3.11\ntarget_abi=PyPy-gil_enabled-3.11".as_bytes(),
3779 )
3780 .unwrap()
3781 .apply_build_env()
3782 .unwrap();
3783 assert_eq!(
3784 config.target_abi.implementation(),
3785 PythonImplementation::PyPy
3786 );
3787 assert_eq!(
3788 config.target_abi.kind(),
3789 PythonAbiKind::VersionSpecific(GilUsed::GilEnabled)
3790 );
3791 assert_eq!(config.target_abi.version(), PythonVersion::PY311);
3792 }
3793
3794 #[test]
3795 fn python_abi_from_stable_abi() {
3796 let implementation = PythonImplementation::CPython;
3797
3798 let abi =
3800 PythonAbi::from_stable_abi(implementation, PythonVersion::PY314, None, true).unwrap();
3801 assert_eq!(
3802 abi.kind(),
3803 PythonAbiKind::VersionSpecific(GilUsed::FreeThreaded)
3804 );
3805 assert_eq!(abi.version(), PythonVersion::PY314);
3806
3807 let abi =
3808 PythonAbi::from_stable_abi(implementation, PythonVersion::PY314, None, false).unwrap();
3809 assert_eq!(
3810 abi.kind(),
3811 PythonAbiKind::VersionSpecific(GilUsed::GilEnabled)
3812 );
3813
3814 let abi = PythonAbi::from_stable_abi(
3816 implementation,
3817 PythonVersion::PY314,
3818 Some((StableAbi::Abi3, PythonVersion::PY310)),
3819 false,
3820 )
3821 .unwrap();
3822 assert_eq!(abi.kind(), PythonAbiKind::Stable(StableAbi::Abi3));
3823 assert_eq!(abi.version(), PythonVersion::PY310);
3824
3825 let error = PythonAbi::from_stable_abi(
3827 implementation,
3828 PythonVersion::PY314,
3829 Some((StableAbi::Abi3t, PythonVersion::PY315)),
3830 true,
3831 )
3832 .unwrap_err();
3833 assert!(error.to_string().contains(
3834 "cannot set a minimum Python version 3.15 higher than the interpreter version 3.14 \
3835 (the minimum Python version is implied by the abi3t-py315 feature)"
3836 ));
3837 }
3838
3839 #[test]
3840 fn config_file_applies_build_env() {
3841 let config = InterpreterConfig::from_reader(
3844 "version=3.14\ntarget_abi=CPython-free_threaded-3.14\nbuild_flags=Py_GIL_DISABLED"
3845 .as_bytes(),
3846 )
3847 .unwrap()
3848 .apply_build_env()
3849 .unwrap();
3850 assert_eq!(
3851 config.target_abi.kind(),
3852 PythonAbiKind::VersionSpecific(GilUsed::FreeThreaded)
3853 );
3854 assert_eq!(config.target_abi.version(), PythonVersion::PY314);
3855
3856 let config =
3859 InterpreterConfig::from_reader("version=3.12\ntarget_abi=CPython-abi3-3.10".as_bytes())
3860 .unwrap()
3861 .apply_build_env()
3862 .unwrap();
3863 assert_eq!(
3864 config.target_abi.kind(),
3865 PythonAbiKind::VersionSpecific(GilUsed::GilEnabled)
3866 );
3867 assert_eq!(config.target_abi.version(), PythonVersion::PY312);
3868 }
3869
3870 #[test]
3871 fn abi3_version_cannot_be_higher_than_interpreter() {
3872 if !have_python_interpreter() {
3873 return;
3874 }
3875
3876 let host_interpreter = get_host_interpreter(None, None).unwrap();
3877 let host_version = host_interpreter.version;
3878 let host_free_threaded = host_interpreter.target_abi.kind.is_free_threaded();
3879
3880 if matches!(
3882 host_interpreter.implementation,
3883 PythonImplementation::PyPy | PythonImplementation::GraalPy
3884 ) || ((host_version == PythonVersion::PY314) && host_free_threaded)
3885 {
3886 return;
3887 }
3888
3889 let interpreter = get_host_interpreter(
3890 Some(StableAbiVersion::Target(PythonVersion {
3891 major: 3,
3892 minor: 45,
3893 })),
3894 None,
3895 );
3896 if !host_free_threaded {
3897 assert!(interpreter.unwrap_err().to_string().contains(
3898 "cannot set a minimum Python version 3.45 higher than the interpreter version"
3899 ));
3900 if host_version >= PythonVersion::PY313 {
3901 let interpreter = get_host_interpreter(
3902 Some(StableAbiVersion::Target(PythonVersion::PY313)),
3903 None,
3904 );
3905 assert_eq!(
3906 interpreter.unwrap().target_abi.version(),
3907 PythonVersion::PY313
3908 );
3909 }
3910 }
3911
3912 if host_version >= PythonVersion::PY313 {
3914 let interpreter = get_host_interpreter(
3915 Some(StableAbiVersion::Target(PythonVersion::PY313)),
3916 Some(StableAbiVersion::Target(PythonVersion::PY315)),
3917 )
3918 .unwrap();
3919 assert_eq!(
3920 interpreter.target_abi.version(),
3921 if host_version >= PythonVersion::PY315 {
3922 PythonVersion::PY315
3923 } else {
3924 PythonVersion::PY313
3925 }
3926 );
3927 }
3928 }
3929
3930 #[test]
3931 #[cfg(all(target_os = "linux", target_arch = "x86_64",))]
3932 fn parse_sysconfigdata() {
3933 let Ok(interpreter_config) = make_interpreter_config() else {
3938 return;
3941 };
3942
3943 let lib_dir = match &interpreter_config.lib_dir {
3944 Some(lib_dir) => Path::new(lib_dir),
3945 None => return,
3947 };
3948
3949 let cross = CrossCompileConfig {
3950 lib_dir: Some(lib_dir.into()),
3951 version: Some(interpreter_config.version),
3952 implementation: Some(interpreter_config.implementation),
3953 target: triple!("x86_64-unknown-linux-gnu"),
3954 abiflags: if interpreter_config.target_abi.kind().is_free_threaded() {
3955 Some("t".into())
3956 } else {
3957 None
3958 },
3959 };
3960
3961 let sysconfigdata_path = match find_sysconfigdata(&cross) {
3962 Ok(Some(path)) => path,
3963 _ => return,
3965 };
3966 let sysconfigdata = super::parse_sysconfigdata(sysconfigdata_path).unwrap();
3967 let mut parsed_config = InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap();
3968
3969 if parsed_config.python_framework_prefix.as_deref() == Some("") {
3975 parsed_config.python_framework_prefix = None;
3976 }
3977
3978 assert_eq!(parsed_config.implementation, PythonImplementation::CPython);
3979 assert_eq!(
3980 parsed_config,
3981 InterpreterConfigBuilder::new(
3982 interpreter_config.implementation,
3983 interpreter_config.version,
3984 )
3985 .build_flags(interpreter_config.build_flags().clone())
3986 .pointer_width(64)
3987 .lib_dir(interpreter_config.lib_dir().map(str::to_owned))
3988 .lib_name(interpreter_config.lib_name().map(str::to_owned))
3989 .finalize()
3990 .unwrap()
3991 )
3992 }
3993
3994 #[test]
3995 fn test_venv_interpreter() {
3996 let base = OsStr::new("base");
3997 assert_eq!(
3998 venv_interpreter(base, true),
3999 PathBuf::from_iter(&["base", "Scripts", "python.exe"])
4000 );
4001 assert_eq!(
4002 venv_interpreter(base, false),
4003 PathBuf::from_iter(&["base", "bin", "python"])
4004 );
4005 }
4006
4007 #[test]
4008 fn test_conda_env_interpreter() {
4009 let base = OsStr::new("base");
4010 assert_eq!(
4011 conda_env_interpreter(base, true),
4012 PathBuf::from_iter(&["base", "python.exe"])
4013 );
4014 assert_eq!(
4015 conda_env_interpreter(base, false),
4016 PathBuf::from_iter(&["base", "bin", "python"])
4017 );
4018 }
4019
4020 #[test]
4021 fn test_not_cross_compiling_from_to() {
4022 assert!(cross_compiling_from_to(
4023 &triple!("x86_64-unknown-linux-gnu"),
4024 &triple!("x86_64-unknown-linux-gnu"),
4025 )
4026 .unwrap()
4027 .is_none());
4028
4029 assert!(cross_compiling_from_to(
4030 &triple!("x86_64-apple-darwin"),
4031 &triple!("x86_64-apple-darwin")
4032 )
4033 .unwrap()
4034 .is_none());
4035
4036 assert!(cross_compiling_from_to(
4037 &triple!("aarch64-apple-darwin"),
4038 &triple!("x86_64-apple-darwin")
4039 )
4040 .unwrap()
4041 .is_none());
4042
4043 assert!(cross_compiling_from_to(
4044 &triple!("x86_64-apple-darwin"),
4045 &triple!("aarch64-apple-darwin")
4046 )
4047 .unwrap()
4048 .is_none());
4049
4050 assert!(cross_compiling_from_to(
4051 &triple!("x86_64-pc-windows-msvc"),
4052 &triple!("i686-pc-windows-msvc")
4053 )
4054 .unwrap()
4055 .is_none());
4056
4057 assert!(cross_compiling_from_to(
4058 &triple!("x86_64-unknown-linux-gnu"),
4059 &triple!("x86_64-unknown-linux-musl")
4060 )
4061 .unwrap()
4062 .is_none());
4063
4064 assert!(cross_compiling_from_to(
4065 &triple!("x86_64-pc-windows-msvc"),
4066 &triple!("x86_64-win7-windows-msvc"),
4067 )
4068 .unwrap()
4069 .is_none());
4070 }
4071
4072 #[test]
4073 fn test_is_cross_compiling_from_to() {
4074 assert!(cross_compiling_from_to(
4075 &triple!("x86_64-pc-windows-msvc"),
4076 &triple!("aarch64-pc-windows-msvc")
4077 )
4078 .unwrap()
4079 .is_some());
4080 }
4081
4082 #[test]
4083 fn test_run_python_script() {
4084 let interpreter = make_interpreter_config()
4086 .expect("could not get InterpreterConfig from installed interpreter");
4087 let out = interpreter
4088 .run_python_script("print(2 + 2)")
4089 .expect("failed to run Python script");
4090 assert_eq!(out.trim_end(), "4");
4091 }
4092
4093 #[test]
4094 fn test_run_python_script_with_envs() {
4095 let interpreter = make_interpreter_config()
4097 .expect("could not get InterpreterConfig from installed interpreter");
4098 let out = interpreter
4099 .run_python_script_with_envs(
4100 "import os; print(os.getenv('PYO3_TEST'))",
4101 vec![("PYO3_TEST", "42")],
4102 )
4103 .expect("failed to run Python script");
4104 assert_eq!(out.trim_end(), "42");
4105 }
4106
4107 #[test]
4108 fn test_build_script_outputs_base() {
4109 let implementation = PythonImplementation::CPython;
4110 let version = PythonVersion::PY311;
4111 let interpreter_config = InterpreterConfigBuilder::new(implementation, version)
4112 .finalize()
4113 .unwrap();
4114 assert_eq!(
4115 interpreter_config.build_script_outputs(),
4116 [
4117 "cargo:rustc-cfg=Py_3_8".to_owned(),
4118 "cargo:rustc-cfg=Py_3_9".to_owned(),
4119 "cargo:rustc-cfg=Py_3_10".to_owned(),
4120 "cargo:rustc-cfg=Py_3_11".to_owned(),
4121 ]
4122 );
4123
4124 let interpreter_config = InterpreterConfigBuilder::new(PythonImplementation::PyPy, version)
4125 .finalize()
4126 .unwrap();
4127 assert_eq!(
4128 interpreter_config.build_script_outputs(),
4129 [
4130 "cargo:rustc-cfg=Py_3_8".to_owned(),
4131 "cargo:rustc-cfg=Py_3_9".to_owned(),
4132 "cargo:rustc-cfg=Py_3_10".to_owned(),
4133 "cargo:rustc-cfg=Py_3_11".to_owned(),
4134 "cargo:rustc-cfg=PyPy".to_owned(),
4135 ]
4136 );
4137
4138 let interpreter_config =
4139 InterpreterConfigBuilder::new(PythonImplementation::RustPython, version)
4140 .finalize()
4141 .unwrap();
4142 assert_eq!(
4143 interpreter_config.build_script_outputs(),
4144 [
4145 "cargo:rustc-cfg=Py_3_8".to_owned(),
4146 "cargo:rustc-cfg=Py_3_9".to_owned(),
4147 "cargo:rustc-cfg=Py_3_10".to_owned(),
4148 "cargo:rustc-cfg=Py_3_11".to_owned(),
4149 "cargo:rustc-cfg=RustPython".to_owned(),
4150 "cargo:rustc-cfg=Py_LIMITED_API".to_owned(),
4151 "cargo:rustc-cfg=Py_GIL_DISABLED".to_owned(),
4152 ]
4153 );
4154 }
4155
4156 #[test]
4157 fn test_build_script_outputs_abi3() {
4158 let implementation = PythonImplementation::CPython;
4159 let version = PythonVersion::PY39;
4160 let interpreter_config = InterpreterConfigBuilder::new(implementation, version)
4161 .stable_abi(StableAbi::Abi3)
4162 .finalize()
4163 .unwrap();
4164
4165 assert_eq!(
4166 interpreter_config.build_script_outputs(),
4167 [
4168 "cargo:rustc-cfg=Py_3_8".to_owned(),
4169 "cargo:rustc-cfg=Py_3_9".to_owned(),
4170 "cargo:rustc-cfg=Py_LIMITED_API".to_owned(),
4171 ]
4172 );
4173
4174 let interpreter_config = InterpreterConfigBuilder::new(PythonImplementation::PyPy, version)
4175 .stable_abi(StableAbi::Abi3)
4176 .finalize()
4177 .unwrap();
4178 assert_eq!(
4179 interpreter_config.build_script_outputs(),
4180 [
4181 "cargo:rustc-cfg=Py_3_8".to_owned(),
4182 "cargo:rustc-cfg=Py_3_9".to_owned(),
4183 "cargo:rustc-cfg=PyPy".to_owned(),
4184 "cargo:rustc-cfg=Py_LIMITED_API".to_owned(),
4185 ]
4186 );
4187
4188 let interpreter_config =
4189 InterpreterConfigBuilder::new(PythonImplementation::CPython, PythonVersion::PY315)
4190 .stable_abi(StableAbi::Abi3)
4191 .finalize()
4192 .unwrap();
4193 assert_eq!(
4194 interpreter_config.build_script_outputs(),
4195 [
4196 "cargo:rustc-cfg=Py_3_8".to_owned(),
4197 "cargo:rustc-cfg=Py_3_9".to_owned(),
4198 "cargo:rustc-cfg=Py_3_10".to_owned(),
4199 "cargo:rustc-cfg=Py_3_11".to_owned(),
4200 "cargo:rustc-cfg=Py_3_12".to_owned(),
4201 "cargo:rustc-cfg=Py_3_13".to_owned(),
4202 "cargo:rustc-cfg=Py_3_14".to_owned(),
4203 "cargo:rustc-cfg=Py_3_15".to_owned(),
4204 "cargo:rustc-cfg=Py_LIMITED_API".to_owned(),
4205 ]
4206 );
4207 }
4208
4209 #[test]
4210 fn test_build_script_outputs_gil_disabled() {
4211 let interpreter_config =
4212 InterpreterConfigBuilder::new(PythonImplementation::CPython, PythonVersion::PY313)
4213 .free_threaded()
4214 .unwrap()
4215 .finalize()
4216 .unwrap();
4217 assert_eq!(
4218 interpreter_config.build_script_outputs(),
4219 [
4220 "cargo:rustc-cfg=Py_3_8".to_owned(),
4221 "cargo:rustc-cfg=Py_3_9".to_owned(),
4222 "cargo:rustc-cfg=Py_3_10".to_owned(),
4223 "cargo:rustc-cfg=Py_3_11".to_owned(),
4224 "cargo:rustc-cfg=Py_3_12".to_owned(),
4225 "cargo:rustc-cfg=Py_3_13".to_owned(),
4226 "cargo:rustc-cfg=Py_GIL_DISABLED".to_owned(),
4227 ]
4228 );
4229 }
4230
4231 #[test]
4232 fn test_interpreter_config_builder_gil_disabled_flag() {
4233 let builder = InterpreterConfigBuilder::new(
4234 PythonImplementation::CPython,
4235 PythonVersion {
4236 major: 3,
4237 minor: 14,
4238 },
4239 );
4240 let mut flags = BuildFlags::new();
4241 flags.0.insert(BuildFlag::Py_GIL_DISABLED);
4242 let config = builder
4243 .stable_abi(StableAbi::Abi3)
4244 .build_flags(flags)
4245 .finalize()
4246 .unwrap();
4247 assert!(config.target_abi.kind() == PythonAbiKind::VersionSpecific(GilUsed::FreeThreaded));
4249
4250 let builder = InterpreterConfigBuilder::new(
4253 PythonImplementation::CPython,
4254 PythonVersion {
4255 major: 3,
4256 minor: 14,
4257 },
4258 );
4259 let mut flags = BuildFlags::new();
4260 flags.0.insert(BuildFlag::Py_GIL_DISABLED);
4261 let config = builder
4262 .build_flags(flags)
4263 .stable_abi(StableAbi::Abi3)
4264 .finalize()
4265 .unwrap();
4266 assert!(config.target_abi.kind() == PythonAbiKind::VersionSpecific(GilUsed::FreeThreaded));
4267
4268 let builder = InterpreterConfigBuilder::new(
4271 PythonImplementation::CPython,
4272 PythonVersion {
4273 major: 3,
4274 minor: 14,
4275 },
4276 );
4277 let target_abi = PythonAbiBuilder::new(
4278 PythonImplementation::CPython,
4279 PythonVersion {
4280 major: 3,
4281 minor: 14,
4282 },
4283 )
4284 .finalize()
4285 .unwrap();
4286 let mut flags = BuildFlags::new();
4287 flags.0.insert(BuildFlag::Py_GIL_DISABLED);
4288 assert!(builder
4289 .target_abi(target_abi)
4290 .build_flags(flags)
4291 .finalize()
4292 .is_err());
4293
4294 let builder = InterpreterConfigBuilder::new(
4295 PythonImplementation::CPython,
4296 PythonVersion {
4297 major: 3,
4298 minor: 14,
4299 },
4300 );
4301 let config = builder.free_threaded().unwrap().finalize().unwrap();
4302 assert!(config.target_abi.kind().is_free_threaded());
4303 assert!(config.build_flags.0.contains(&BuildFlag::Py_GIL_DISABLED));
4304 }
4305
4306 #[test]
4307 fn test_build_script_outputs_debug() {
4308 let mut build_flags = BuildFlags::default();
4309 build_flags.0.insert(BuildFlag::Py_DEBUG);
4310 let implementation = PythonImplementation::CPython;
4311 let version = PythonVersion::PY38;
4312 let interpreter_config = InterpreterConfigBuilder::new(implementation, version)
4313 .build_flags(build_flags)
4314 .finalize()
4315 .unwrap();
4316 assert_eq!(
4317 interpreter_config.build_script_outputs(),
4318 [
4319 "cargo:rustc-cfg=Py_3_8".to_owned(),
4320 "cargo:rustc-cfg=py_sys_config=\"Py_DEBUG\"".to_owned(),
4321 ]
4322 );
4323 }
4324
4325 #[test]
4326 fn test_find_sysconfigdata_in_invalid_lib_dir() {
4327 let e = find_all_sysconfigdata(&CrossCompileConfig {
4328 lib_dir: Some(PathBuf::from("/abc/123/not/a/real/path")),
4329 version: None,
4330 implementation: None,
4331 target: triple!("x86_64-unknown-linux-gnu"),
4332 abiflags: None,
4333 })
4334 .unwrap_err();
4335
4336 assert!(e.report().to_string().starts_with(
4338 "failed to search the lib dir at 'PYO3_CROSS_LIB_DIR=/abc/123/not/a/real/path'\n\
4339 caused by:\n \
4340 - 0: failed to list the entries in '/abc/123/not/a/real/path'\n \
4341 - 1: \
4342 "
4343 ));
4344 }
4345
4346 #[test]
4347 fn test_from_pyo3_config_file_env_rebuild() {
4348 READ_ENV_VARS.with(|vars| vars.borrow_mut().clear());
4349 let _ = InterpreterConfig::from_pyo3_config_file_env(&Triple::host());
4350 READ_ENV_VARS.with(|vars| assert!(vars.borrow().contains(&"PYO3_CONFIG_FILE".to_string())));
4352 }
4353
4354 #[test]
4355 fn test_default_lib_name_for_target() {
4356 let cpython = PythonImplementation::CPython;
4357 let pypy = PythonImplementation::PyPy;
4358 let py39 = PythonVersion::PY39;
4359 let py311 = PythonVersion {
4360 major: 3,
4361 minor: 11,
4362 };
4363 let py313 = PythonVersion {
4364 major: 3,
4365 minor: 13,
4366 };
4367 let cpy39 = PythonAbiBuilder::new(cpython, py39).finalize().unwrap();
4368 let pypy311 = PythonAbiBuilder::new(pypy, py311).finalize().unwrap();
4369 let cpy313t = PythonAbiBuilder::new(cpython, py313)
4370 .free_threaded()
4371 .finalize()
4372 .unwrap();
4373 let cpy313_abi3 = PythonAbiBuilder::new(cpython, py313)
4374 .stable_abi(StableAbi::Abi3)
4375 .finalize()
4376 .unwrap();
4377
4378 let unix = Triple::from_str("x86_64-unknown-linux-gnu").unwrap();
4379 let win_x64 = Triple::from_str("x86_64-pc-windows-msvc").unwrap();
4380 let win_arm64 = Triple::from_str("aarch64-pc-windows-msvc").unwrap();
4381
4382 let lib_name = default_lib_name_for_target(cpy39, &unix);
4383 assert_eq!(lib_name, "python3.9");
4384
4385 let lib_name = default_lib_name_for_target(cpy39, &win_x64);
4386 assert_eq!(lib_name, "python39");
4387
4388 let lib_name = default_lib_name_for_target(cpy39, &win_arm64);
4389 assert_eq!(lib_name, "python39");
4390
4391 let lib_name = default_lib_name_for_target(pypy311, &unix);
4393 assert_eq!(lib_name, "pypy3.11-c");
4394
4395 let lib_name = default_lib_name_for_target(pypy311, &win_x64);
4396 assert_eq!(lib_name, "libpypy3.11-c");
4397
4398 let lib_name = default_lib_name_for_target(cpy313t, &unix);
4400 assert_eq!(lib_name, "python3.13t");
4401
4402 let lib_name = default_lib_name_for_target(cpy313t, &win_x64);
4403 assert_eq!(lib_name, "python313t");
4404
4405 let lib_name = default_lib_name_for_target(cpy313t, &win_arm64);
4406 assert_eq!(lib_name, "python313t");
4407
4408 let lib_name = default_lib_name_for_target(cpy313_abi3, &unix);
4410 assert_eq!(lib_name, "python3.13");
4411
4412 let lib_name = default_lib_name_for_target(cpy313_abi3, &win_x64);
4413 assert_eq!(lib_name, "python3");
4414
4415 let lib_name = default_lib_name_for_target(cpy313_abi3, &win_arm64);
4416 assert_eq!(lib_name, "python3");
4417 }
4418}