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_PYPY: PythonVersion = PythonVersion {
29 major: 3,
30 minor: 11,
31};
32pub(crate) const MAXIMUM_SUPPORTED_VERSION_PYPY: PythonVersion = PythonVersion {
33 major: 3,
34 minor: 11,
35};
36
37pub(crate) const MINIMUM_SUPPORTED_VERSION_ABI3T: PythonVersion = PythonVersion {
38 major: 3,
39 minor: 15,
40};
41
42const MINIMUM_SUPPORTED_VERSION_GRAALPY: PythonVersion = PythonVersion {
44 major: 25,
45 minor: 0,
46};
47
48pub(crate) const STABLE_ABI_MAX_MINOR: u8 = 15;
50
51#[cfg(test)]
52thread_local! {
53 static READ_ENV_VARS: RefCell<Vec<String>> = const { RefCell::new(Vec::new()) };
54}
55
56pub fn cargo_env_var(var: &str) -> Option<String> {
60 env::var_os(var).map(|os_string| os_string.to_str().unwrap().into())
61}
62
63pub fn env_var(var: &str) -> Option<OsString> {
66 println!("cargo:rerun-if-env-changed={var}");
67 #[cfg(test)]
68 {
69 READ_ENV_VARS.with(|env_vars| {
70 env_vars.borrow_mut().push(var.to_owned());
71 });
72 }
73 env::var_os(var)
74}
75
76pub fn target_triple_from_env() -> Triple {
80 env::var("TARGET")
81 .expect("target_triple_from_env() must be called from a build script")
82 .parse()
83 .expect("Unrecognized TARGET environment variable value")
84}
85
86fn sanitize_stable_abi_version(
87 stable_abi_version: Option<PythonVersion>,
88 version: PythonVersion,
89) -> Result<PythonVersion> {
90 if let Some(min_version) = stable_abi_version {
91 ensure!(
92 min_version <= version,
93 "cannot set a minimum Python version {} higher than the interpreter version {} \
94 (the minimum Python version is implied by the abi3-py3{} feature)",
95 min_version,
96 version,
97 min_version.minor
98 );
99 Ok(min_version)
100 } else {
101 Ok(version)
102 }
103}
104
105#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
117pub struct InterpreterConfig {
118 #[deprecated(
122 since = "0.29.0",
123 note = "please use `.implementation()` getter or `InterpreterConfigBuilder` instead"
124 )]
125 pub implementation: PythonImplementation,
126
127 #[deprecated(
131 since = "0.29.0",
132 note = "please use `.version()` getter or `InterpreterConfigBuilder` instead"
133 )]
134 pub version: PythonVersion,
135
136 #[deprecated(
140 since = "0.29.0",
141 note = "please use `.shared()` getter or `InterpreterConfigBuilder` instead"
142 )]
143 pub shared: bool,
144
145 target_abi: PythonAbi,
146
147 #[deprecated(since = "0.29.0", note = "please match against target_abi instead")]
149 pub abi3: bool,
150
151 #[deprecated(
159 since = "0.29.0",
160 note = "please use `.lib_name()` getter or `InterpreterConfigBuilder` instead"
161 )]
162 pub lib_name: Option<String>,
163
164 #[deprecated(
171 since = "0.29.0",
172 note = "please use `.lib_dir()` getter or `InterpreterConfigBuilder` instead"
173 )]
174 pub lib_dir: Option<String>,
175
176 #[deprecated(
184 since = "0.29.0",
185 note = "please use `.executable()` getter or `InterpreterConfigBuilder` instead"
186 )]
187 pub executable: Option<String>,
188
189 #[deprecated(
193 since = "0.29.0",
194 note = "please use `.pointer_width()` getter or `InterpreterConfigBuilder` instead"
195 )]
196 pub pointer_width: Option<u32>,
197
198 #[deprecated(
202 since = "0.29.0",
203 note = "please use `.build_flags()` getter or `InterpreterConfigBuilder` instead"
204 )]
205 pub build_flags: BuildFlags,
206
207 #[deprecated(
218 since = "0.29.0",
219 note = "please use `.suppress_build_script_link_lines()` getter or `InterpreterConfigBuilder` instead"
220 )]
221 pub suppress_build_script_link_lines: bool,
222
223 #[deprecated(
234 since = "0.29.0",
235 note = "please use `.extra_build_script_lines()` getter or `InterpreterConfigBuilder` instead"
236 )]
237 pub extra_build_script_lines: Vec<String>,
238 #[deprecated(
240 since = "0.29.0",
241 note = "please use `.python_framework_prefix()` getter or `InterpreterConfigBuilder` instead"
242 )]
243 pub python_framework_prefix: Option<String>,
244}
245
246#[expect(deprecated, reason = "this impl block touches the internal fields")]
248impl InterpreterConfig {
249 pub fn implementation(&self) -> PythonImplementation {
253 self.implementation
254 }
255
256 pub fn version(&self) -> PythonVersion {
260 self.version
261 }
262
263 pub fn shared(&self) -> bool {
267 self.shared
268 }
269
270 pub fn target_abi(&self) -> PythonAbi {
275 self.target_abi
276 }
277
278 #[deprecated(since = "0.29.0", note = "please use `target_abi()` instead")]
281 pub fn abi3(&self) -> bool {
282 matches!(self.target_abi.kind, PythonAbiKind::Stable(StableAbi::Abi3))
283 }
284
285 pub fn lib_name(&self) -> Option<&str> {
293 self.lib_name.as_deref()
294 }
295
296 pub fn lib_dir(&self) -> Option<&str> {
303 self.lib_dir.as_deref()
304 }
305
306 pub fn executable(&self) -> Option<&str> {
314 self.executable.as_deref()
315 }
316
317 pub fn pointer_width(&self) -> Option<u32> {
321 self.pointer_width
322 }
323
324 pub fn build_flags(&self) -> &BuildFlags {
328 &self.build_flags
329 }
330
331 pub fn suppress_build_script_link_lines(&self) -> bool {
333 self.suppress_build_script_link_lines
334 }
335
336 pub fn extra_build_script_lines(&self) -> &[String] {
340 &self.extra_build_script_lines
341 }
342
343 pub fn python_framework_prefix(&self) -> Option<&str> {
345 self.python_framework_prefix.as_deref()
346 }
347
348 #[doc(hidden)]
349 pub fn build_script_outputs(&self) -> Vec<String> {
350 assert!(self.target_abi.version() >= MINIMUM_SUPPORTED_VERSION);
352
353 let mut out = vec![];
354
355 for i in MINIMUM_SUPPORTED_VERSION.minor..=self.target_abi.version().minor {
356 out.push(format!("cargo:rustc-cfg=Py_3_{i}"));
357 }
358
359 match self.target_abi.implementation() {
360 PythonImplementation::CPython => {}
361 PythonImplementation::PyPy => out.push("cargo:rustc-cfg=PyPy".to_owned()),
362 PythonImplementation::GraalPy => out.push("cargo:rustc-cfg=GraalPy".to_owned()),
363 PythonImplementation::RustPython => out.push("cargo:rustc-cfg=RustPython".to_owned()),
364 }
365
366 match self.target_abi.kind() {
367 PythonAbiKind::Stable(kind) => {
368 out.push("cargo:rustc-cfg=Py_LIMITED_API".to_owned());
369 if kind == StableAbi::Abi3t {
370 out.push("cargo:rustc-cfg=Py_GIL_DISABLED".to_owned());
371 }
372 }
373 PythonAbiKind::VersionSpecific(kind) => match kind {
374 GilUsed::FreeThreaded => {
375 out.push("cargo:rustc-cfg=Py_GIL_DISABLED".to_owned());
376 }
377 GilUsed::GilEnabled => {}
378 },
379 }
380 for flag in &self.build_flags.0 {
381 match flag {
382 BuildFlag::Py_GIL_DISABLED => continue,
384 flag => out.push(format!("cargo:rustc-cfg=py_sys_config=\"{flag}\"")),
385 }
386 }
387 out
388 }
389
390 fn from_interpreter(
391 interpreter: impl AsRef<Path>,
392 abi3_version: Option<PythonVersion>,
393 abi3t_version: Option<PythonVersion>,
394 ) -> Result<Self> {
395 const SCRIPT: &str = r#"
396# Allow the script to run on Python 2, so that nicer error can be printed later.
397from __future__ import print_function
398
399import os.path
400import platform
401import struct
402import sys
403from sysconfig import get_config_var, get_platform
404
405PYPY = platform.python_implementation() == "PyPy"
406GRAALPY = platform.python_implementation() == "GraalVM"
407
408if GRAALPY:
409 graalpy_ver = map(int, __graalpython__.get_graalvm_version().split('.'));
410 print("graalpy_major", next(graalpy_ver))
411 print("graalpy_minor", next(graalpy_ver))
412
413# sys.base_prefix is missing on Python versions older than 3.3; this allows the script to continue
414# so that the version mismatch can be reported in a nicer way later.
415base_prefix = getattr(sys, "base_prefix", None)
416
417if base_prefix:
418 # Anaconda based python distributions have a static python executable, but include
419 # the shared library. Use the shared library for embedding to avoid rust trying to
420 # LTO the static library (and failing with newer gcc's, because it is old).
421 ANACONDA = os.path.exists(os.path.join(base_prefix, "conda-meta"))
422else:
423 ANACONDA = False
424
425def print_if_set(varname, value):
426 if value is not None:
427 print(varname, value)
428
429# Windows always uses shared linking
430WINDOWS = platform.system() == "Windows"
431
432# macOS framework packages use shared linking
433FRAMEWORK = bool(get_config_var("PYTHONFRAMEWORK"))
434FRAMEWORK_PREFIX = get_config_var("PYTHONFRAMEWORKPREFIX")
435
436# unix-style shared library enabled
437SHARED = bool(get_config_var("Py_ENABLE_SHARED"))
438
439print("implementation", platform.python_implementation())
440print("version_major", sys.version_info[0])
441print("version_minor", sys.version_info[1])
442print("shared", PYPY or GRAALPY or ANACONDA or WINDOWS or FRAMEWORK or SHARED)
443print("python_framework_prefix", FRAMEWORK_PREFIX)
444print_if_set("ld_version", get_config_var("LDVERSION"))
445print_if_set("libdir", get_config_var("LIBDIR"))
446print_if_set("base_prefix", base_prefix)
447print("executable", sys.executable)
448print("calcsize_pointer", struct.calcsize("P"))
449print("mingw", get_platform().startswith("mingw"))
450print("cygwin", get_platform().startswith("cygwin"))
451print("ext_suffix", get_config_var("EXT_SUFFIX"))
452print("gil_disabled", get_config_var("Py_GIL_DISABLED"))
453"#;
454 let output = run_python_script(interpreter.as_ref(), SCRIPT)?;
455 let map: HashMap<String, String> = parse_script_output(&output);
456
457 ensure!(
458 !map.is_empty(),
459 "broken Python interpreter: {}",
460 interpreter.as_ref().display()
461 );
462
463 if let Some(value) = map.get("graalpy_major") {
464 let graalpy_version = PythonVersion {
465 major: value
466 .parse()
467 .context("failed to parse GraalPy major version")?,
468 minor: map["graalpy_minor"]
469 .parse()
470 .context("failed to parse GraalPy minor version")?,
471 };
472 ensure!(
473 graalpy_version >= MINIMUM_SUPPORTED_VERSION_GRAALPY,
474 "At least GraalPy version {} needed, got {}",
475 MINIMUM_SUPPORTED_VERSION_GRAALPY,
476 graalpy_version
477 );
478 };
479
480 let shared = map["shared"].as_str() == "True";
481 let python_framework_prefix = map.get("python_framework_prefix").cloned();
482
483 let version = PythonVersion {
484 major: map["version_major"]
485 .parse()
486 .context("failed to parse major version")?,
487 minor: map["version_minor"]
488 .parse()
489 .context("failed to parse minor version")?,
490 };
491
492 let implementation = map["implementation"].parse()?;
493
494 let gil_disabled = match map["gil_disabled"].as_str() {
495 "1" => true,
496 "0" => false,
497 "None" => false,
498 _ => panic!("Unknown Py_GIL_DISABLED value"),
499 };
500
501 let stable_abi_version = if !matches!(
502 implementation,
503 PythonImplementation::PyPy | PythonImplementation::GraalPy
504 ) {
505 if version >= PythonVersion::PY315 {
506 match gil_disabled {
507 false => abi3t_version.or(abi3_version),
508 true => abi3t_version,
509 }
510 } else {
511 match gil_disabled {
512 false => abi3_version,
513 true => None,
514 }
515 }
516 } else {
517 None
518 };
519
520 let target_abi =
521 PythonAbi::from_build_env(implementation, version, stable_abi_version, gil_disabled)?;
522
523 let cygwin = map["cygwin"].as_str() == "True";
524
525 let lib_name = if cfg!(windows) {
526 default_lib_name_windows(
527 target_abi,
528 map["mingw"].as_str() == "True",
529 map["ext_suffix"].starts_with("_d."),
533 )?
534 } else {
535 default_lib_name_unix(
536 target_abi,
537 cygwin,
538 map.get("ld_version").map(String::as_str),
539 )?
540 };
541
542 let lib_dir = if cfg!(windows) {
543 map.get("base_prefix")
544 .map(|base_prefix| format!("{base_prefix}\\libs"))
545 } else {
546 map.get("libdir").cloned()
547 };
548
549 let calcsize_pointer: u32 = map["calcsize_pointer"]
555 .parse()
556 .context("failed to parse calcsize_pointer")?;
557
558 InterpreterConfigBuilder::new(implementation, version)
559 .target_abi(target_abi)
560 .shared(shared)
561 .lib_name(lib_name)
562 .lib_dir(lib_dir)
563 .executable(map["executable"].clone())
564 .pointer_width(calcsize_pointer * 8)
565 .build_flags(BuildFlags::from_interpreter(interpreter)?)
566 .python_framework_prefix(python_framework_prefix)
567 .finalize()
568 }
569
570 pub fn from_sysconfigdata(sysconfigdata: &Sysconfigdata) -> Result<Self> {
575 macro_rules! get_key {
576 ($sysconfigdata:expr, $key:literal) => {
577 $sysconfigdata
578 .get_value($key)
579 .ok_or(concat!($key, " not found in sysconfigdata file"))
580 };
581 }
582
583 macro_rules! parse_key {
584 ($sysconfigdata:expr, $key:literal) => {
585 get_key!($sysconfigdata, $key)?
586 .parse()
587 .context(concat!("could not parse value of ", $key))
588 };
589 }
590
591 let soabi = get_key!(sysconfigdata, "SOABI")?;
592 let implementation = PythonImplementation::from_soabi(soabi)?;
593 let version = parse_key!(sysconfigdata, "VERSION")?;
594 let shared = match sysconfigdata.get_value("Py_ENABLE_SHARED") {
595 Some("1") | Some("true") | Some("True") => true,
596 Some("0") | Some("false") | Some("False") => false,
597 _ => bail!("expected a bool (1/true/True or 0/false/False) for Py_ENABLE_SHARED"),
598 };
599 let framework = match sysconfigdata.get_value("PYTHONFRAMEWORK") {
601 Some(s) => !s.is_empty(),
602 _ => false,
603 };
604 let python_framework_prefix = sysconfigdata
605 .get_value("PYTHONFRAMEWORKPREFIX")
606 .map(str::to_string);
607 let lib_dir = get_key!(sysconfigdata, "LIBDIR").ok().map(str::to_string);
608 let gil_disabled = match sysconfigdata.get_value("Py_GIL_DISABLED") {
609 Some(value) => value == "1",
610 None => false,
611 };
612 let cygwin = soabi.ends_with("cygwin");
613 let target_abi = PythonAbi::from_build_env(implementation, version, None, gil_disabled)?;
614 let lib_name =
615 default_lib_name_unix(target_abi, cygwin, sysconfigdata.get_value("LDVERSION"))?;
616 let pointer_width = parse_key!(sysconfigdata, "SIZEOF_VOID_P")
617 .map(|bytes_width: u32| bytes_width * 8)
618 .ok();
619 let build_flags = BuildFlags::from_sysconfigdata(sysconfigdata);
620
621 InterpreterConfigBuilder::new(implementation, version)
622 .target_abi(target_abi)
623 .shared(shared || framework)
624 .pointer_width(pointer_width)
625 .lib_name(lib_name)
626 .lib_dir(lib_dir)
627 .python_framework_prefix(python_framework_prefix)
628 .build_flags(build_flags)
629 .finalize()
630 }
631
632 pub(super) fn from_pyo3_config_file_env(target: &Triple) -> Option<Result<Self>> {
636 env_var("PYO3_CONFIG_FILE").map(|path| {
637 let path = Path::new(&path);
638 println!("cargo:rerun-if-changed={}", path.display());
639 ensure!(
642 path.is_absolute(),
643 "PYO3_CONFIG_FILE must be an absolute path"
644 );
645
646 let mut config = InterpreterConfig::from_path(path)
647 .context("failed to parse contents of PYO3_CONFIG_FILE")?
648 .apply_build_env()?;
649
650 if config.lib_name.is_none() {
653 config.lib_name = Some(default_lib_name_for_target(config.target_abi, target));
654 }
655
656 Ok(config)
657 })
658 }
659
660 fn from_path(path: impl AsRef<Path>) -> Result<Self> {
661 let path = path.as_ref();
662 let config_file = std::fs::File::open(path)
663 .with_context(|| format!("failed to open PyO3 config file at {}", path.display()))?;
664 let reader = std::io::BufReader::new(config_file);
665 InterpreterConfig::from_reader(reader)
666 }
667
668 pub(crate) const PYO3_FFI_CONFIG_ENV_VAR: &str = "DEP_PYTHON_PYO3_CONFIG";
670 pub(crate) const PYO3_CONFIG_ENV_VAR: &str = "DEP_PYO3_PYTHON_PYO3_CONFIG";
672
673 pub(crate) fn from_cargo_dep_env() -> Option<Result<Self>> {
674 cargo_env_var(Self::PYO3_FFI_CONFIG_ENV_VAR)
675 .or_else(|| cargo_env_var(Self::PYO3_CONFIG_ENV_VAR))
676 .map(|buf| InterpreterConfig::from_reader(&*unescape(&buf)))
677 }
678
679 fn from_reader(reader: impl Read) -> Result<Self> {
680 let reader = BufReader::new(reader);
681 let lines = reader.lines();
682
683 macro_rules! parse_value {
684 ($variable:ident, $value:ident) => {
685 $variable = Some($value.trim().parse().context(format!(
686 concat!(
687 "failed to parse ",
688 stringify!($variable),
689 " from config value '{}'"
690 ),
691 $value
692 ))?)
693 };
694 }
695
696 let mut implementation = None;
697 let mut version = None;
698 let mut shared = None;
699 let mut target_abi = None;
700 let mut abi3 = None;
702 let mut lib_name = None;
703 let mut lib_dir = None;
704 let mut executable = None;
705 let mut pointer_width = None;
706 let mut build_flags: Option<BuildFlags> = None;
707 let mut suppress_build_script_link_lines: Option<bool> = None;
708 let mut extra_build_script_lines = vec![];
709 let mut python_framework_prefix = None;
710
711 for (i, line) in lines.enumerate() {
712 let line = line.context("failed to read line from config")?;
713 let mut split = line.splitn(2, '=');
714 let (key, value) = (
715 split
716 .next()
717 .expect("first splitn value should always be present"),
718 split
719 .next()
720 .ok_or_else(|| format!("expected key=value pair on line {}", i + 1))?,
721 );
722 match key {
723 "implementation" => parse_value!(implementation, value),
724 "version" => parse_value!(version, value),
725 "shared" => parse_value!(shared, value),
726 "target_abi" => parse_value!(target_abi, value),
727 "abi3" => parse_value!(abi3, value),
728 "lib_name" => parse_value!(lib_name, value),
729 "lib_dir" => parse_value!(lib_dir, value),
730 "executable" => parse_value!(executable, value),
731 "pointer_width" => parse_value!(pointer_width, value),
732 "build_flags" => parse_value!(build_flags, value),
733 "suppress_build_script_link_lines" => {
734 parse_value!(suppress_build_script_link_lines, value)
735 }
736 "extra_build_script_line" => {
737 extra_build_script_lines.push(value.to_string());
738 }
739 "python_framework_prefix" => parse_value!(python_framework_prefix, value),
740 unknown => warn!("unknown config key `{}`", unknown),
741 }
742 }
743
744 let version = version.ok_or("missing value for version")?;
745 let implementation = implementation.unwrap_or(PythonImplementation::CPython);
746 let flags_contains_free_threaded = if let Some(ref flags) = build_flags {
747 flags.0.contains(&BuildFlag::Py_GIL_DISABLED)
748 } else {
749 false
750 };
751 let target_abi = if let Some(target_abi) = target_abi {
752 ensure!(
753 abi3.is_none(),
754 "Invalid config that sets both target_abi and abi3."
755 );
756 target_abi
757 } else if flags_contains_free_threaded {
758 PythonAbiBuilder::new(implementation, version)
760 .free_threaded()
761 .finalize()?
762 } else if abi3 == Some(true) {
763 warn!("abi3 configuration file option is deprecated since pyo3 0.29, set target_abi instead");
764 PythonAbiBuilder::new(implementation, version)
765 .stable_abi(StableAbi::Abi3)
766 .finalize()?
767 } else {
768 PythonAbiBuilder::new(implementation, version).finalize()?
769 };
770
771 let builder = InterpreterConfigBuilder::new(implementation, version)
772 .target_abi(target_abi)
773 .shared(shared.unwrap_or(true))
774 .lib_name(lib_name)
775 .lib_dir(lib_dir)
776 .executable(executable)
777 .pointer_width(pointer_width)
778 .build_flags(build_flags.unwrap_or_default())
779 .suppress_build_script_link_lines(suppress_build_script_link_lines.unwrap_or(false))
780 .extra_build_script_lines(extra_build_script_lines)
781 .python_framework_prefix(python_framework_prefix);
782
783 builder.finalize()
784 }
785
786 #[doc(hidden)]
787 pub fn to_cargo_dep_env(&self) -> Result<()> {
798 let mut buf = Vec::new();
799 self.to_writer(&mut buf)?;
800 println!("cargo:PYO3_CONFIG={}", escape(&buf));
802 Ok(())
803 }
804
805 #[doc(hidden)]
806 pub fn to_writer(&self, mut writer: impl Write) -> Result<()> {
807 macro_rules! write_line {
808 ($value:ident) => {
809 writeln!(writer, "{}={}", stringify!($value), self.$value).context(concat!(
810 "failed to write ",
811 stringify!($value),
812 " to config"
813 ))
814 };
815 }
816
817 macro_rules! write_option_line {
818 ($value:ident) => {
819 if let Some(value) = &self.$value {
820 writeln!(writer, "{}={}", stringify!($value), value).context(concat!(
821 "failed to write ",
822 stringify!($value),
823 " to config"
824 ))
825 } else {
826 Ok(())
827 }
828 };
829 }
830
831 write_line!(implementation)?;
832 write_line!(version)?;
833 write_line!(shared)?;
834 write_line!(target_abi)?;
835 write_option_line!(lib_name)?;
836 write_option_line!(lib_dir)?;
837 write_option_line!(executable)?;
838 write_option_line!(pointer_width)?;
839 write_line!(build_flags)?;
840 write_option_line!(python_framework_prefix)?;
841 write_line!(suppress_build_script_link_lines)?;
842 for line in &self.extra_build_script_lines {
843 writeln!(writer, "extra_build_script_line={line}")
844 .context("failed to write extra_build_script_line")?;
845 }
846 Ok(())
847 }
848
849 pub fn run_python_script(&self, script: &str) -> Result<String> {
855 run_python_script_with_envs(
856 Path::new(self.executable.as_ref().expect("no interpreter executable")),
857 script,
858 std::iter::empty::<(&str, &str)>(),
859 )
860 }
861
862 pub fn run_python_script_with_envs<I, K, V>(&self, script: &str, envs: I) -> Result<String>
869 where
870 I: IntoIterator<Item = (K, V)>,
871 K: AsRef<OsStr>,
872 V: AsRef<OsStr>,
873 {
874 run_python_script_with_envs(
875 Path::new(self.executable.as_ref().expect("no interpreter executable")),
876 script,
877 envs,
878 )
879 }
880
881 pub fn is_free_threaded(&self) -> bool {
882 self.target_abi.kind().is_free_threaded()
883 }
884
885 fn apply_build_env(mut self) -> Result<InterpreterConfig> {
886 let abi3_version = if self.target_abi.kind.is_free_threaded()
887 || matches!(
888 self.target_abi.implementation,
889 PythonImplementation::PyPy | PythonImplementation::GraalPy
890 ) {
891 None
892 } else {
893 get_abi3_version()
894 };
895 self.target_abi = PythonAbi::from_build_env(
896 self.implementation,
897 self.version,
898 exact_stable_abi_version(abi3_version.or(get_abi3t_version())),
899 self.target_abi.kind().is_free_threaded(),
900 )?;
901 Ok(self)
902 }
903}
904
905#[cfg_attr(test, derive(Debug))]
906pub struct PythonAbiBuilder {
907 implementation: PythonImplementation,
908 version: PythonVersion,
909 kind: Option<PythonAbiKind>,
910}
911
912impl PythonAbiBuilder {
913 pub fn new(implementation: PythonImplementation, version: PythonVersion) -> PythonAbiBuilder {
914 PythonAbiBuilder {
915 implementation,
916 version,
917 kind: None,
918 }
919 }
920
921 pub fn stable_abi(self, kind: StableAbi) -> PythonAbiBuilder {
922 let mut build_version = self.version;
923 if self.version.minor > STABLE_ABI_MAX_MINOR {
924 warn!("Automatically falling back to {kind}-py3{STABLE_ABI_MAX_MINOR} because current Python is higher than the maximum supported");
925 build_version.minor = STABLE_ABI_MAX_MINOR;
926 }
927
928 PythonAbiBuilder {
929 kind: Some(PythonAbiKind::Stable(kind)),
930 version: build_version,
931 ..self
932 }
933 }
934
935 pub fn free_threaded(self) -> PythonAbiBuilder {
936 PythonAbiBuilder {
937 kind: Some(PythonAbiKind::VersionSpecific(GilUsed::FreeThreaded)),
938 ..self
939 }
940 }
941
942 pub fn finalize(self) -> Result<PythonAbi> {
943 let kind = self.kind.unwrap_or(match self.implementation {
945 PythonImplementation::RustPython => PythonAbiKind::Stable(StableAbi::Abi3t),
946 _ => PythonAbiKind::VersionSpecific(GilUsed::GilEnabled),
947 });
948 if matches!(self.implementation, PythonImplementation::RustPython) {
949 ensure!(matches!(kind, PythonAbiKind::Stable(StableAbi::Abi3t)),
950 "RustPython only supports targeting abi3t, it does not allow targeting other Python ABIs. Currently targeting '{kind}'")
951 }
952 if matches!(kind, PythonAbiKind::VersionSpecific(GilUsed::FreeThreaded))
953 && self.version
954 < (PythonVersion {
955 major: 3,
956 minor: 13,
957 })
958 {
959 bail!(
960 "Cannot target free-threaded builds for Python versions before 3.13, tried to build for {}", self.version
961 )
962 }
963 Ok(PythonAbi {
964 implementation: self.implementation,
965 kind,
966 version: self.version,
967 })
968 }
969}
970
971#[non_exhaustive]
972#[derive(Copy, Clone, PartialEq, Eq)]
973#[cfg_attr(test, derive(Debug))]
974pub struct PythonAbi {
975 implementation: PythonImplementation,
976 kind: PythonAbiKind,
977 version: PythonVersion,
978}
979
980impl Display for PythonAbi {
981 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
982 write!(f, "{}-{}-{}", self.implementation, self.kind, self.version)
983 }
984}
985
986impl FromStr for PythonAbi {
987 type Err = crate::errors::Error;
988
989 fn from_str(value: &str) -> Result<Self, Self::Err> {
990 let mut parts = value.splitn(3, '-');
991 Ok(PythonAbi {
992 implementation: parts
993 .next()
994 .ok_or_else(|| format!("Invalid ABI string representation: {value}"))?
995 .parse()?,
996 kind: parts
997 .next()
998 .ok_or_else(|| format!("Invalid ABI string representation: {value}"))?
999 .parse()?,
1000 version: parts
1001 .next()
1002 .ok_or_else(|| format!("Invalid ABI string representation: {value}"))?
1003 .parse()?,
1004 })
1005 }
1006}
1007
1008impl PythonAbi {
1009 pub fn from_build_env(
1010 implementation: PythonImplementation,
1011 version: PythonVersion,
1012 stable_abi_version: Option<PythonVersion>,
1013 gil_disabled: bool,
1014 ) -> Result<PythonAbi> {
1015 let builder = PythonAbiBuilder {
1016 implementation,
1017 version: sanitize_stable_abi_version(stable_abi_version, version)?,
1018 kind: None,
1019 };
1020 let builder = if get_abi3t_version().is_some() && version >= MINIMUM_SUPPORTED_VERSION_ABI3T
1021 {
1022 builder.stable_abi(StableAbi::Abi3t)
1023 } else if get_abi3_version().is_some() && !gil_disabled {
1024 builder.stable_abi(StableAbi::Abi3)
1025 } else if gil_disabled {
1026 builder.free_threaded()
1027 } else {
1028 builder
1029 };
1030 builder.finalize()
1031 }
1032
1033 pub fn implementation(&self) -> PythonImplementation {
1037 self.implementation
1038 }
1039
1040 pub fn kind(&self) -> PythonAbiKind {
1044 self.kind
1045 }
1046
1047 pub fn version(&self) -> PythonVersion {
1051 self.version
1052 }
1053}
1054
1055#[derive(Clone, Copy, PartialEq, Eq)]
1060#[cfg_attr(test, derive(Debug))]
1061pub enum PythonAbiKind {
1062 Stable(StableAbi),
1064 VersionSpecific(GilUsed),
1066}
1067
1068impl Display for PythonAbiKind {
1069 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1070 match self {
1071 PythonAbiKind::Stable(stable_abi) => write!(f, "{stable_abi}"),
1072 PythonAbiKind::VersionSpecific(gil_used) => {
1073 write!(f, "{gil_used}")
1074 }
1075 }
1076 }
1077}
1078
1079impl FromStr for PythonAbiKind {
1080 type Err = crate::errors::Error;
1081
1082 fn from_str(value: &str) -> Result<Self, Self::Err> {
1083 match value {
1084 "abi3" => Ok(PythonAbiKind::Stable(StableAbi::Abi3)),
1085 "abi3t" => Ok(PythonAbiKind::Stable(StableAbi::Abi3t)),
1086 "free_threaded" => Ok(PythonAbiKind::VersionSpecific(GilUsed::FreeThreaded)),
1087 "gil_enabled" => Ok(PythonAbiKind::VersionSpecific(GilUsed::GilEnabled)),
1088 _ => Err(format!("Unrecognized ABI name: {value}").into()),
1089 }
1090 }
1091}
1092
1093impl PythonAbiKind {
1094 pub fn is_free_threaded(self) -> bool {
1095 match self {
1096 PythonAbiKind::VersionSpecific(gil_disabled) => gil_disabled == GilUsed::FreeThreaded,
1097 PythonAbiKind::Stable(StableAbi::Abi3) => false,
1098 PythonAbiKind::Stable(StableAbi::Abi3t) => true,
1099 }
1100 }
1101}
1102
1103#[derive(Clone, Copy, PartialEq, Eq)]
1105#[cfg_attr(test, derive(Debug))]
1106pub enum StableAbi {
1107 Abi3,
1109 Abi3t,
1111}
1112
1113impl Display for StableAbi {
1114 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1115 match self {
1116 StableAbi::Abi3 => write!(f, "abi3"),
1117 StableAbi::Abi3t => write!(f, "abi3t"),
1118 }
1119 }
1120}
1121
1122#[derive(Clone, Copy, PartialEq, Eq)]
1124#[cfg_attr(test, derive(Debug))]
1125pub enum GilUsed {
1126 GilEnabled,
1128 FreeThreaded,
1130}
1131
1132impl Display for GilUsed {
1133 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1134 match self {
1135 GilUsed::GilEnabled => write!(f, "gil_enabled"),
1136 GilUsed::FreeThreaded => write!(f, "free_threaded"),
1137 }
1138 }
1139}
1140
1141#[cfg_attr(test, derive(Debug))]
1142pub struct InterpreterConfigBuilder {
1143 implementation: PythonImplementation,
1144 version: PythonVersion,
1145 shared: bool,
1146 target_abi: Option<PythonAbi>,
1147 lib_name: Option<String>,
1148 lib_dir: Option<String>,
1149 executable: Option<String>,
1150 pointer_width: Option<u32>,
1151 build_flags: BuildFlags,
1152 suppress_build_script_link_lines: bool,
1153 extra_build_script_lines: Vec<String>,
1154 python_framework_prefix: Option<String>,
1155}
1156
1157impl InterpreterConfigBuilder {
1158 pub fn new(
1159 implementation: PythonImplementation,
1160 version: PythonVersion,
1161 ) -> InterpreterConfigBuilder {
1162 InterpreterConfigBuilder {
1163 implementation,
1164 version,
1165 shared: true,
1166 target_abi: None,
1167 lib_name: None,
1168 lib_dir: None,
1169 executable: None,
1170 pointer_width: None,
1171 build_flags: BuildFlags::default(),
1172 suppress_build_script_link_lines: false,
1173 extra_build_script_lines: vec![],
1174 python_framework_prefix: None,
1175 }
1176 }
1177
1178 pub fn target_abi(self, target_abi: PythonAbi) -> InterpreterConfigBuilder {
1179 InterpreterConfigBuilder {
1180 target_abi: Some(target_abi),
1181 ..self
1182 }
1183 }
1184
1185 pub fn stable_abi(self, kind: StableAbi) -> InterpreterConfigBuilder {
1186 let implementation = self.implementation;
1187 let version = self.version;
1188 self.target_abi(
1189 PythonAbiBuilder::new(implementation, version)
1190 .stable_abi(kind)
1191 .finalize()
1192 .unwrap(),
1194 )
1195 }
1196
1197 pub fn free_threaded(self) -> Result<InterpreterConfigBuilder> {
1198 let implementation = self.implementation;
1199 let version = self.version;
1200 Ok(self.target_abi(
1201 PythonAbiBuilder::new(implementation, version)
1202 .free_threaded()
1203 .finalize()?,
1204 ))
1205 }
1206
1207 pub fn lib_name(mut self, lib_name: impl Into<Option<String>>) -> InterpreterConfigBuilder {
1208 self.lib_name = lib_name.into();
1209 self
1210 }
1211
1212 pub fn pointer_width(
1213 mut self,
1214 pointer_width: impl Into<Option<u32>>,
1215 ) -> InterpreterConfigBuilder {
1216 self.pointer_width = pointer_width.into();
1217 self
1218 }
1219
1220 pub fn executable(mut self, executable: impl Into<Option<String>>) -> InterpreterConfigBuilder {
1221 self.executable = executable.into();
1222 self
1223 }
1224
1225 pub fn suppress_build_script_link_lines(
1226 mut self,
1227 suppress_build_script_link_lines: bool,
1228 ) -> InterpreterConfigBuilder {
1229 self.suppress_build_script_link_lines = suppress_build_script_link_lines;
1230 self
1231 }
1232
1233 pub fn extra_build_script_lines(
1234 mut self,
1235 extra_build_script_lines: Vec<String>,
1236 ) -> InterpreterConfigBuilder {
1237 self.extra_build_script_lines = extra_build_script_lines;
1238 self
1239 }
1240
1241 pub fn lib_dir(mut self, lib_dir: impl Into<Option<String>>) -> InterpreterConfigBuilder {
1242 self.lib_dir = lib_dir.into();
1243 self
1244 }
1245
1246 pub fn shared(mut self, shared: bool) -> InterpreterConfigBuilder {
1247 self.shared = shared;
1248 self
1249 }
1250
1251 pub fn build_flags(mut self, build_flags: BuildFlags) -> InterpreterConfigBuilder {
1252 self.build_flags = build_flags;
1253 self
1254 }
1255
1256 pub fn python_framework_prefix(
1257 mut self,
1258 python_framework_prefix: impl Into<Option<String>>,
1259 ) -> InterpreterConfigBuilder {
1260 self.python_framework_prefix = python_framework_prefix.into();
1261 self
1262 }
1263
1264 pub fn finalize(self) -> Result<InterpreterConfig> {
1265 let mut build_flags = self.build_flags.clone();
1266 let py_gil_disabled = build_flags.0.contains(&BuildFlag::Py_GIL_DISABLED);
1267 let target_abi = match (self.target_abi, py_gil_disabled) {
1268 (None, false) => PythonAbiBuilder::new(self.implementation, self.version).finalize()?,
1270 (None, true) => PythonAbiBuilder::new(self.implementation, self.version)
1272 .free_threaded()
1273 .finalize()?,
1274 (Some(target_abi), false) => target_abi,
1276 (Some(target_abi), true) => match target_abi.kind() {
1278 PythonAbiKind::Stable(StableAbi::Abi3) => {
1282 let new_abi =
1283 PythonAbiBuilder::new(target_abi.implementation(), target_abi.version())
1284 .free_threaded()
1285 .finalize()?;
1286 warn!(
1287 "Targeting an abi3 build but build_flags contains Py_GIL_DISABLED, \
1288 falling back to a version-specific free-threaded build"
1289 );
1290 new_abi
1291 }
1292 PythonAbiKind::VersionSpecific(GilUsed::GilEnabled) => bail!(
1294 "build_flags contains Py_GIL_DISABLED but target_abi \
1295 '{target_abi}' is not free-threaded"
1296 ),
1297 _ => target_abi,
1299 },
1300 };
1301 if target_abi.kind().is_free_threaded() {
1302 build_flags.0.insert(BuildFlag::Py_GIL_DISABLED);
1303 }
1304 #[expect(
1305 deprecated,
1306 reason = "constructing an InterpreterConfig directly, need to write to fields"
1307 )]
1308 Ok(InterpreterConfig {
1309 implementation: self.implementation,
1310 version: self.version,
1311 shared: self.shared,
1312 target_abi,
1313 abi3: matches!(target_abi.kind(), PythonAbiKind::Stable(StableAbi::Abi3)),
1314 lib_name: self.lib_name,
1315 lib_dir: self.lib_dir,
1316 executable: self.executable,
1317 pointer_width: self.pointer_width,
1318 build_flags,
1319 suppress_build_script_link_lines: self.suppress_build_script_link_lines,
1320 extra_build_script_lines: self.extra_build_script_lines,
1321 python_framework_prefix: self.python_framework_prefix,
1322 })
1323 }
1324}
1325
1326#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
1327pub struct PythonVersion {
1328 pub major: u8,
1329 pub minor: u8,
1330}
1331
1332impl PythonVersion {
1333 pub(crate) const PY315: Self = PythonVersion {
1334 major: 3,
1335 minor: 15,
1336 };
1337 #[cfg(test)]
1338 pub(crate) const PY314: Self = PythonVersion {
1339 major: 3,
1340 minor: 14,
1341 };
1342 #[deprecated(
1343 since = "0.29.0",
1344 note = "please construct `PythonVersion` directly rather than use these constants"
1345 )]
1346 pub const PY313: Self = PythonVersion {
1347 major: 3,
1348 minor: 13,
1349 };
1350 #[deprecated(
1351 since = "0.29.0",
1352 note = "please construct `PythonVersion` directly rather than use these constants"
1353 )]
1354 pub const PY312: Self = PythonVersion {
1355 major: 3,
1356 minor: 12,
1357 };
1358 #[cfg(test)]
1359 const PY311: Self = PythonVersion {
1360 major: 3,
1361 minor: 11,
1362 };
1363 const PY310: Self = PythonVersion {
1364 major: 3,
1365 minor: 10,
1366 };
1367 #[cfg(test)]
1368 const PY39: Self = PythonVersion { major: 3, minor: 9 };
1369 #[cfg(test)]
1370 const PY38: Self = PythonVersion { major: 3, minor: 8 };
1371}
1372
1373impl Display for PythonVersion {
1374 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1375 write!(f, "{}.{}", self.major, self.minor)
1376 }
1377}
1378
1379impl FromStr for PythonVersion {
1380 type Err = crate::errors::Error;
1381
1382 fn from_str(value: &str) -> Result<Self, Self::Err> {
1383 let mut split = value.splitn(2, '.');
1384 let (major, minor) = (
1385 split
1386 .next()
1387 .expect("first splitn value should always be present"),
1388 split.next().ok_or("expected major.minor version")?,
1389 );
1390 Ok(Self {
1391 major: major.parse().context("failed to parse major version")?,
1392 minor: minor.parse().context("failed to parse minor version")?,
1393 })
1394 }
1395}
1396
1397#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1398pub enum PythonImplementation {
1399 CPython,
1400 PyPy,
1401 GraalPy,
1402 RustPython,
1403}
1404
1405impl PythonImplementation {
1406 fn is_pypy(self) -> bool {
1407 self == PythonImplementation::PyPy
1408 }
1409
1410 fn from_soabi(soabi: &str) -> Result<Self> {
1411 if soabi.starts_with("pypy") {
1412 Ok(PythonImplementation::PyPy)
1413 } else if soabi.starts_with("cpython") {
1414 Ok(PythonImplementation::CPython)
1415 } else if soabi.starts_with("graalpy") {
1416 Ok(PythonImplementation::GraalPy)
1417 } else {
1418 bail!("unsupported Python interpreter");
1419 }
1420 }
1421}
1422
1423impl Display for PythonImplementation {
1424 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1425 match self {
1426 PythonImplementation::CPython => write!(f, "CPython"),
1427 PythonImplementation::PyPy => write!(f, "PyPy"),
1428 PythonImplementation::GraalPy => write!(f, "GraalVM"),
1429 PythonImplementation::RustPython => write!(f, "RustPython"),
1430 }
1431 }
1432}
1433
1434impl FromStr for PythonImplementation {
1435 type Err = Error;
1436 fn from_str(s: &str) -> Result<Self> {
1437 match s {
1438 "CPython" => Ok(PythonImplementation::CPython),
1439 "PyPy" => Ok(PythonImplementation::PyPy),
1440 "GraalVM" => Ok(PythonImplementation::GraalPy),
1441 "RustPython" => Ok(PythonImplementation::RustPython),
1442 _ => bail!("unknown interpreter: {}", s),
1443 }
1444 }
1445}
1446
1447fn have_python_interpreter() -> bool {
1452 env_var("PYO3_NO_PYTHON").is_none()
1453}
1454
1455#[derive(Debug, Copy, Clone)]
1460pub enum StableAbiVersion {
1461 Current,
1462 Target(PythonVersion),
1463}
1464
1465pub fn get_abi3_version() -> Option<StableAbiVersion> {
1471 let minor_version = (MINIMUM_SUPPORTED_VERSION.minor..=STABLE_ABI_MAX_MINOR)
1472 .find(|i| cargo_env_var(&format!("CARGO_FEATURE_ABI3_PY3{i}")).is_some());
1473 minor_version.map_or(
1474 if cargo_env_var("CARGO_FEATURE_ABI3").is_some() {
1475 Some(StableAbiVersion::Current)
1476 } else {
1477 None
1478 },
1479 |minor| Some(StableAbiVersion::Target(PythonVersion { major: 3, minor })),
1480 )
1481}
1482
1483pub fn get_abi3t_version() -> Option<StableAbiVersion> {
1489 let minor_version = (MINIMUM_SUPPORTED_VERSION_ABI3T.minor..=STABLE_ABI_MAX_MINOR)
1490 .find(|i| cargo_env_var(&format!("CARGO_FEATURE_ABI3T_PY3{i}")).is_some());
1491 minor_version.map_or(
1492 if cargo_env_var("CARGO_FEATURE_ABI3T").is_some() {
1493 Some(StableAbiVersion::Current)
1494 } else {
1495 None
1496 },
1497 |minor| Some(StableAbiVersion::Target(PythonVersion { major: 3, minor })),
1498 )
1499}
1500
1501pub fn is_extension_module() -> bool {
1509 cargo_env_var("CARGO_FEATURE_EXTENSION_MODULE").is_some()
1510 || env_var("PYO3_BUILD_EXTENSION_MODULE").is_some()
1511}
1512
1513pub fn is_linking_libpython_for_target(target: &Triple) -> bool {
1517 target.operating_system == OperatingSystem::Windows
1518 || target.operating_system == OperatingSystem::Aix
1520 || target.environment == Environment::Android
1521 || target.environment == Environment::Androideabi
1522 || target.operating_system == OperatingSystem::Cygwin
1523 || matches!(target.operating_system, OperatingSystem::IOS(_))
1524 || !is_extension_module()
1525}
1526
1527fn require_libdir_for_target(target: &Triple) -> bool {
1532 if target.operating_system == OperatingSystem::Windows {
1535 return false;
1536 }
1537
1538 is_linking_libpython_for_target(target)
1539}
1540
1541#[derive(Debug, PartialEq, Eq)]
1546pub struct CrossCompileConfig {
1547 pub lib_dir: Option<PathBuf>,
1549
1550 version: Option<PythonVersion>,
1552
1553 implementation: Option<PythonImplementation>,
1555
1556 target: Triple,
1558
1559 abiflags: Option<String>,
1561}
1562
1563impl CrossCompileConfig {
1564 fn try_from_env_vars_host_target(
1569 env_vars: CrossCompileEnvVars,
1570 host: &Triple,
1571 target: &Triple,
1572 ) -> Result<Option<Self>> {
1573 if env_vars.any() || Self::is_cross_compiling_from_to(host, target) {
1574 let lib_dir = env_vars.lib_dir_path()?;
1575 let (version, abiflags) = env_vars.parse_version()?;
1576 let implementation = env_vars.parse_implementation()?;
1577 let target = target.clone();
1578
1579 Ok(Some(CrossCompileConfig {
1580 lib_dir,
1581 version,
1582 implementation,
1583 target,
1584 abiflags,
1585 }))
1586 } else {
1587 Ok(None)
1588 }
1589 }
1590
1591 fn is_cross_compiling_from_to(host: &Triple, target: &Triple) -> bool {
1595 let mut compatible = host.architecture == target.architecture
1599 && (host.vendor == target.vendor
1600 || (host.vendor == Vendor::Pc && target.vendor.as_str() == "win7"))
1602 && host.operating_system == target.operating_system;
1603
1604 compatible |= target.operating_system == OperatingSystem::Windows
1606 && host.operating_system == OperatingSystem::Windows
1607 && matches!(target.architecture, Architecture::X86_32(_))
1608 && host.architecture == Architecture::X86_64;
1609
1610 compatible |= matches!(target.operating_system, OperatingSystem::Darwin(_))
1612 && matches!(host.operating_system, OperatingSystem::Darwin(_));
1613
1614 compatible |= matches!(target.operating_system, OperatingSystem::IOS(_));
1615
1616 !compatible
1617 }
1618
1619 fn lib_dir_string(&self) -> Option<String> {
1624 self.lib_dir
1625 .as_ref()
1626 .map(|s| s.to_str().unwrap().to_owned())
1627 }
1628}
1629
1630struct CrossCompileEnvVars {
1632 pyo3_cross: Option<OsString>,
1634 pyo3_cross_lib_dir: Option<OsString>,
1636 pyo3_cross_python_version: Option<OsString>,
1638 pyo3_cross_python_implementation: Option<OsString>,
1640}
1641
1642impl CrossCompileEnvVars {
1643 fn from_env() -> Self {
1647 CrossCompileEnvVars {
1648 pyo3_cross: env_var("PYO3_CROSS"),
1649 pyo3_cross_lib_dir: env_var("PYO3_CROSS_LIB_DIR"),
1650 pyo3_cross_python_version: env_var("PYO3_CROSS_PYTHON_VERSION"),
1651 pyo3_cross_python_implementation: env_var("PYO3_CROSS_PYTHON_IMPLEMENTATION"),
1652 }
1653 }
1654
1655 fn any(&self) -> bool {
1657 self.pyo3_cross.is_some()
1658 || self.pyo3_cross_lib_dir.is_some()
1659 || self.pyo3_cross_python_version.is_some()
1660 || self.pyo3_cross_python_implementation.is_some()
1661 }
1662
1663 fn parse_version(&self) -> Result<(Option<PythonVersion>, Option<String>)> {
1666 match self.pyo3_cross_python_version.as_ref() {
1667 Some(os_string) => {
1668 let utf8_str = os_string
1669 .to_str()
1670 .ok_or("PYO3_CROSS_PYTHON_VERSION is not valid a UTF-8 string")?;
1671 let (utf8_str, abiflags) = if let Some(version) = utf8_str.strip_suffix('t') {
1672 (version, Some("t".to_string()))
1673 } else {
1674 (utf8_str, None)
1675 };
1676 let version = utf8_str
1677 .parse()
1678 .context("failed to parse PYO3_CROSS_PYTHON_VERSION")?;
1679 Ok((Some(version), abiflags))
1680 }
1681 None => Ok((None, None)),
1682 }
1683 }
1684
1685 fn parse_implementation(&self) -> Result<Option<PythonImplementation>> {
1688 let implementation = self
1689 .pyo3_cross_python_implementation
1690 .as_ref()
1691 .map(|os_string| {
1692 let utf8_str = os_string
1693 .to_str()
1694 .ok_or("PYO3_CROSS_PYTHON_IMPLEMENTATION is not valid a UTF-8 string")?;
1695 utf8_str
1696 .parse()
1697 .context("failed to parse PYO3_CROSS_PYTHON_IMPLEMENTATION")
1698 })
1699 .transpose()?;
1700
1701 Ok(implementation)
1702 }
1703
1704 fn lib_dir_path(&self) -> Result<Option<PathBuf>> {
1709 let lib_dir = self.pyo3_cross_lib_dir.as_ref().map(PathBuf::from);
1710
1711 if let Some(dir) = lib_dir.as_ref() {
1712 ensure!(
1713 dir.to_str().is_some(),
1714 "PYO3_CROSS_LIB_DIR variable value is not a valid UTF-8 string"
1715 );
1716 }
1717
1718 Ok(lib_dir)
1719 }
1720}
1721
1722pub fn cross_compiling_from_to(
1737 host: &Triple,
1738 target: &Triple,
1739) -> Result<Option<CrossCompileConfig>> {
1740 let env_vars = CrossCompileEnvVars::from_env();
1741 CrossCompileConfig::try_from_env_vars_host_target(env_vars, host, target)
1742}
1743
1744#[allow(non_camel_case_types)]
1745#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1746pub enum BuildFlag {
1747 Py_DEBUG,
1748 Py_REF_DEBUG,
1749 #[deprecated(since = "0.29.0", note = "no longer supported by PyO3")]
1750 Py_TRACE_REFS,
1751 Py_GIL_DISABLED,
1752 COUNT_ALLOCS,
1753 Other(String),
1754}
1755
1756impl Display for BuildFlag {
1757 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1758 match self {
1759 BuildFlag::Other(flag) => write!(f, "{flag}"),
1760 _ => write!(f, "{self:?}"),
1761 }
1762 }
1763}
1764
1765impl FromStr for BuildFlag {
1766 type Err = std::convert::Infallible;
1767 fn from_str(s: &str) -> Result<Self, Self::Err> {
1768 match s {
1769 "Py_DEBUG" => Ok(BuildFlag::Py_DEBUG),
1770 "Py_REF_DEBUG" => Ok(BuildFlag::Py_REF_DEBUG),
1771 "Py_GIL_DISABLED" => Ok(BuildFlag::Py_GIL_DISABLED),
1772 "COUNT_ALLOCS" => Ok(BuildFlag::COUNT_ALLOCS),
1773 other => Ok(BuildFlag::Other(other.to_owned())),
1774 }
1775 }
1776}
1777
1778#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
1792#[derive(Clone, Default)]
1793pub struct BuildFlags(pub HashSet<BuildFlag>);
1794
1795impl BuildFlags {
1796 const ALL: [BuildFlag; 4] = [
1797 BuildFlag::Py_DEBUG,
1798 BuildFlag::Py_REF_DEBUG,
1799 BuildFlag::Py_GIL_DISABLED,
1800 BuildFlag::COUNT_ALLOCS,
1801 ];
1802
1803 pub fn new() -> Self {
1804 BuildFlags(HashSet::new())
1805 }
1806
1807 fn from_sysconfigdata(config_map: &Sysconfigdata) -> Self {
1808 Self(
1809 BuildFlags::ALL
1810 .iter()
1811 .filter(|flag| config_map.get_value(flag.to_string()) == Some("1"))
1812 .cloned()
1813 .collect(),
1814 )
1815 .fixup()
1816 }
1817
1818 fn from_interpreter(interpreter: impl AsRef<Path>) -> Result<Self> {
1822 if cfg!(windows) {
1826 let script = String::from("import sys;print(sys.version_info < (3, 13))");
1827 let stdout = run_python_script(interpreter.as_ref(), &script)?;
1828 if stdout.trim_end() == "True" {
1829 return Ok(Self::new());
1830 }
1831 }
1832
1833 let mut script = String::from("import sysconfig\n");
1834 script.push_str("config = sysconfig.get_config_vars()\n");
1835
1836 for k in &BuildFlags::ALL {
1837 use std::fmt::Write;
1838 writeln!(&mut script, "print(config.get('{k}', '0'))").unwrap();
1839 }
1840
1841 let stdout = run_python_script(interpreter.as_ref(), &script)?;
1842 let split_stdout: Vec<&str> = stdout.trim_end().lines().collect();
1843 ensure!(
1844 split_stdout.len() == BuildFlags::ALL.len(),
1845 "Python stdout len didn't return expected number of lines: {}",
1846 split_stdout.len()
1847 );
1848 let flags = BuildFlags::ALL
1849 .iter()
1850 .zip(split_stdout)
1851 .filter(|(_, flag_value)| *flag_value == "1")
1852 .map(|(flag, _)| flag.clone())
1853 .collect();
1854
1855 Ok(Self(flags).fixup())
1856 }
1857
1858 fn fixup(mut self) -> Self {
1859 if self.0.contains(&BuildFlag::Py_DEBUG) {
1860 self.0.insert(BuildFlag::Py_REF_DEBUG);
1861 }
1862
1863 self
1864 }
1865}
1866
1867impl Display for BuildFlags {
1868 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1869 let mut first = true;
1870 for flag in &self.0 {
1871 if first {
1872 first = false;
1873 } else {
1874 write!(f, ",")?;
1875 }
1876 write!(f, "{flag}")?;
1877 }
1878 Ok(())
1879 }
1880}
1881
1882impl FromStr for BuildFlags {
1883 type Err = std::convert::Infallible;
1884
1885 fn from_str(value: &str) -> Result<Self, Self::Err> {
1886 let mut flags = HashSet::new();
1887 for flag in value.split_terminator(',') {
1888 flags.insert(flag.parse().unwrap());
1889 }
1890 Ok(BuildFlags(flags))
1891 }
1892}
1893
1894fn parse_script_output(output: &str) -> HashMap<String, String> {
1895 output
1896 .lines()
1897 .filter_map(|line| {
1898 let mut i = line.splitn(2, ' ');
1899 Some((i.next()?.into(), i.next()?.into()))
1900 })
1901 .collect()
1902}
1903
1904pub struct Sysconfigdata(HashMap<String, String>);
1908
1909impl Sysconfigdata {
1910 pub fn get_value<S: AsRef<str>>(&self, k: S) -> Option<&str> {
1911 self.0.get(k.as_ref()).map(String::as_str)
1912 }
1913
1914 #[cfg(test)]
1915 fn new() -> Self {
1916 Sysconfigdata(HashMap::new())
1917 }
1918
1919 #[cfg(test)]
1920 fn insert<S: Into<String>>(&mut self, k: S, v: S) {
1921 self.0.insert(k.into(), v.into());
1922 }
1923}
1924
1925pub fn parse_sysconfigdata(sysconfigdata_path: impl AsRef<Path>) -> Result<Sysconfigdata> {
1933 let sysconfigdata_path = sysconfigdata_path.as_ref();
1934 let mut script = fs::read_to_string(sysconfigdata_path).with_context(|| {
1935 format!(
1936 "failed to read config from {}",
1937 sysconfigdata_path.display()
1938 )
1939 })?;
1940 script += r#"
1941for key, val in build_time_vars.items():
1942 # (ana)conda(-forge) built Pythons are statically linked but ship the shared library with them.
1943 # We detect them based on the magic prefix directory they have encoded in their builds.
1944 if key == "Py_ENABLE_SHARED" and "_h_env_placehold" in build_time_vars.get("prefix"):
1945 val = 1
1946 print(key, val)
1947"#;
1948
1949 let output = run_python_script(&find_interpreter()?, &script)?;
1950
1951 Ok(Sysconfigdata(parse_script_output(&output)))
1952}
1953
1954fn starts_with(entry: &DirEntry, pat: &str) -> bool {
1955 let name = entry.file_name();
1956 name.to_string_lossy().starts_with(pat)
1957}
1958fn ends_with(entry: &DirEntry, pat: &str) -> bool {
1959 let name = entry.file_name();
1960 name.to_string_lossy().ends_with(pat)
1961}
1962
1963fn find_sysconfigdata(cross: &CrossCompileConfig) -> Result<Option<PathBuf>> {
1968 let mut sysconfig_paths = find_all_sysconfigdata(cross)?;
1969 if sysconfig_paths.is_empty() {
1970 if let Some(lib_dir) = cross.lib_dir.as_ref() {
1971 bail!("Could not find _sysconfigdata*.py in {}", lib_dir.display());
1972 } else {
1973 return Ok(None);
1975 }
1976 } else if sysconfig_paths.len() > 1 {
1977 let mut error_msg = String::from(
1978 "Detected multiple possible Python versions. Please set either the \
1979 PYO3_CROSS_PYTHON_VERSION variable to the wanted version or the \
1980 _PYTHON_SYSCONFIGDATA_NAME variable to the wanted sysconfigdata file name.\n\n\
1981 sysconfigdata files found:",
1982 );
1983 for path in sysconfig_paths {
1984 use std::fmt::Write;
1985 write!(&mut error_msg, "\n\t{}", path.display()).unwrap();
1986 }
1987 bail!("{}\n", error_msg);
1988 }
1989
1990 Ok(Some(sysconfig_paths.remove(0)))
1991}
1992
1993pub fn find_all_sysconfigdata(cross: &CrossCompileConfig) -> Result<Vec<PathBuf>> {
2032 let sysconfig_paths = if let Some(lib_dir) = cross.lib_dir.as_ref() {
2033 search_lib_dir(lib_dir, cross).with_context(|| {
2034 format!(
2035 "failed to search the lib dir at 'PYO3_CROSS_LIB_DIR={}'",
2036 lib_dir.display()
2037 )
2038 })?
2039 } else {
2040 return Ok(Vec::new());
2041 };
2042
2043 let sysconfig_name = env_var("_PYTHON_SYSCONFIGDATA_NAME");
2044 let mut sysconfig_paths = sysconfig_paths
2045 .iter()
2046 .filter_map(|p| {
2047 let canonical = fs::canonicalize(p).ok();
2048 match &sysconfig_name {
2049 Some(_) => canonical.filter(|p| p.file_stem() == sysconfig_name.as_deref()),
2050 None => canonical,
2051 }
2052 })
2053 .collect::<Vec<PathBuf>>();
2054
2055 sysconfig_paths.sort();
2056 sysconfig_paths.dedup();
2057
2058 Ok(sysconfig_paths)
2059}
2060
2061fn is_pypy_lib_dir(path: &str, v: &Option<PythonVersion>) -> bool {
2062 let pypy_version_pat = if let Some(v) = v {
2063 format!("pypy{v}")
2064 } else {
2065 "pypy3.".into()
2066 };
2067 path == "lib_pypy" || path.starts_with(&pypy_version_pat)
2068}
2069
2070fn is_graalpy_lib_dir(path: &str, v: &Option<PythonVersion>) -> bool {
2071 let graalpy_version_pat = if let Some(v) = v {
2072 format!("graalpy{v}")
2073 } else {
2074 "graalpy2".into()
2075 };
2076 path == "lib_graalpython" || path.starts_with(&graalpy_version_pat)
2077}
2078
2079fn is_cpython_lib_dir(path: &str, v: &Option<PythonVersion>) -> bool {
2080 let cpython_version_pat = if let Some(v) = v {
2081 format!("python{v}")
2082 } else {
2083 "python3.".into()
2084 };
2085 path.starts_with(&cpython_version_pat)
2086}
2087
2088fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Result<Vec<PathBuf>> {
2090 let mut sysconfig_paths = vec![];
2091 for f in fs::read_dir(path.as_ref()).with_context(|| {
2092 format!(
2093 "failed to list the entries in '{}'",
2094 path.as_ref().display()
2095 )
2096 })? {
2097 sysconfig_paths.extend(match &f {
2098 Ok(f) if starts_with(f, "_sysconfigdata_") && ends_with(f, "py") => vec![f.path()],
2100 Ok(f) if f.metadata().is_ok_and(|metadata| metadata.is_dir()) => {
2101 let file_name = f.file_name();
2102 let file_name = file_name.to_string_lossy();
2103 if file_name == "build" || file_name == "lib" {
2104 search_lib_dir(f.path(), cross)?
2105 } else if file_name.starts_with("lib.") {
2106 if !file_name.contains(&cross.target.operating_system.to_string()) {
2108 continue;
2109 }
2110 if !file_name.contains(&cross.target.architecture.to_string()) {
2112 continue;
2113 }
2114 search_lib_dir(f.path(), cross)?
2115 } else if is_cpython_lib_dir(&file_name, &cross.version)
2116 || is_pypy_lib_dir(&file_name, &cross.version)
2117 || is_graalpy_lib_dir(&file_name, &cross.version)
2118 {
2119 search_lib_dir(f.path(), cross)?
2120 } else {
2121 continue;
2122 }
2123 }
2124 _ => continue,
2125 });
2126 }
2127 if sysconfig_paths.len() > 1 {
2135 let temp = sysconfig_paths
2136 .iter()
2137 .filter(|p| {
2138 p.to_string_lossy()
2139 .contains(&cross.target.architecture.to_string())
2140 })
2141 .cloned()
2142 .collect::<Vec<PathBuf>>();
2143 if !temp.is_empty() {
2144 sysconfig_paths = temp;
2145 }
2146 }
2147
2148 Ok(sysconfig_paths)
2149}
2150
2151fn cross_compile_from_sysconfigdata(
2159 cross_compile_config: &CrossCompileConfig,
2160) -> Result<Option<InterpreterConfig>> {
2161 if let Some(path) = find_sysconfigdata(cross_compile_config)? {
2162 let data = parse_sysconfigdata(path)?;
2163 let mut config = InterpreterConfig::from_sysconfigdata(&data)?;
2164 #[expect(deprecated, reason = "modifying config inline")]
2165 if let Some(cross_lib_dir) = cross_compile_config.lib_dir_string() {
2166 config.lib_dir = Some(cross_lib_dir)
2167 }
2168
2169 Ok(Some(config))
2170 } else {
2171 Ok(None)
2172 }
2173}
2174
2175fn exact_stable_abi_version(version: Option<StableAbiVersion>) -> Option<PythonVersion> {
2176 version.and_then(|v| match v {
2177 StableAbiVersion::Current => None,
2178 StableAbiVersion::Target(inner) => Some(inner),
2179 })
2180}
2181
2182fn default_cross_compile(cross_compile_config: &CrossCompileConfig) -> Result<InterpreterConfig> {
2189 let version = cross_compile_config
2190 .version
2191 .or_else(|| exact_stable_abi_version(get_abi3_version()))
2192 .or_else(|| exact_stable_abi_version(get_abi3t_version()))
2193 .ok_or_else(||
2194 format!(
2195 "PYO3_CROSS_PYTHON_VERSION or either an abi3-py3* or abi3t-py3* feature must be specified \
2196 when cross-compiling and PYO3_CROSS_LIB_DIR is not set.\n\
2197 = help: see the PyO3 user guide for more information: https://pyo3.rs/v{}/building-and-distribution.html#cross-compiling",
2198 env!("CARGO_PKG_VERSION")
2199 )
2200 )?;
2201 let gil_disabled = cross_compile_config.abiflags.as_deref() == Some("t");
2202
2203 let stable_abi_version = if gil_disabled && version < PythonVersion::PY315 {
2204 None
2205 } else {
2206 Some(version)
2207 };
2208
2209 let implementation = cross_compile_config
2210 .implementation
2211 .unwrap_or(PythonImplementation::CPython);
2212
2213 let target_abi =
2214 PythonAbi::from_build_env(implementation, version, stable_abi_version, gil_disabled)?;
2215
2216 let lib_name = default_lib_name_for_target(target_abi, &cross_compile_config.target);
2217
2218 let lib_dir = cross_compile_config.lib_dir_string();
2219
2220 InterpreterConfigBuilder::new(implementation, version)
2221 .target_abi(target_abi)
2222 .lib_name(lib_name)
2223 .lib_dir(lib_dir)
2224 .finalize()
2225}
2226
2227fn default_stable_abi_config(
2238 host: &Triple,
2239 abi3_version: Option<PythonVersion>,
2240 abi3t_version: Option<PythonVersion>,
2241) -> Result<InterpreterConfig> {
2242 if abi3_version.is_none() && abi3t_version.is_none() {
2243 bail!("Neither abi3 or abi3t features are enabled")
2244 }
2245 let (stable_abi, version) = if let Some(version) = abi3_version {
2246 (StableAbi::Abi3, version)
2247 } else if let Some(version) = abi3t_version {
2248 (StableAbi::Abi3t, version)
2249 } else {
2250 unreachable!();
2251 };
2252
2253 if stable_abi == StableAbi::Abi3t && version < MINIMUM_SUPPORTED_VERSION_ABI3T {
2254 bail!("Cannot target an abi3t version below {MINIMUM_SUPPORTED_VERSION_ABI3T}")
2255 }
2256
2257 let target_abi = PythonAbiBuilder::new(PythonImplementation::CPython, version)
2259 .stable_abi(stable_abi)
2260 .finalize()?;
2261 let builder = InterpreterConfigBuilder::new(PythonImplementation::CPython, version)
2262 .target_abi(target_abi);
2263 if host.operating_system == OperatingSystem::Windows {
2264 builder.lib_name(default_lib_name_windows(target_abi, false, false)?)
2265 } else {
2266 builder
2267 }
2268 .finalize()
2269}
2270
2271fn load_cross_compile_config(
2279 cross_compile_config: CrossCompileConfig,
2280) -> Result<InterpreterConfig> {
2281 let windows = cross_compile_config.target.operating_system == OperatingSystem::Windows;
2282
2283 let config = if windows || !have_python_interpreter() {
2284 default_cross_compile(&cross_compile_config)?
2288 } else if let Some(config) = cross_compile_from_sysconfigdata(&cross_compile_config)? {
2289 config
2291 } else {
2292 default_cross_compile(&cross_compile_config)?
2294 };
2295
2296 Ok(config)
2297}
2298
2299const WINDOWS_STABLE_ABI_LIB_NAME: &str = "python3";
2301const WINDOWS_STABLE_ABI_DEBUG_LIB_NAME: &str = "python3_d";
2302
2303#[allow(dead_code)]
2305fn default_lib_name_for_target(abi: PythonAbi, target: &Triple) -> String {
2306 if target.operating_system == OperatingSystem::Windows {
2307 default_lib_name_windows(abi, false, false).unwrap()
2308 } else {
2309 default_lib_name_unix(
2310 abi,
2311 target.operating_system == OperatingSystem::Cygwin,
2312 None,
2313 )
2314 .unwrap()
2315 }
2316}
2317
2318fn default_lib_name_windows(abi: PythonAbi, mingw: bool, debug: bool) -> Result<String> {
2319 if abi.implementation.is_pypy() {
2320 Ok(format!(
2324 "libpypy{}.{}-c",
2325 abi.version.major, abi.version.minor
2326 ))
2327 } else if debug && abi.version < PythonVersion::PY310 {
2328 Ok(format!(
2331 "python{}{}_d",
2332 abi.version.major, abi.version.minor
2333 ))
2334 } else if abi.kind == PythonAbiKind::Stable(StableAbi::Abi3)
2335 || abi.kind == PythonAbiKind::Stable(StableAbi::Abi3t)
2336 {
2337 let mut lib_name = if debug {
2338 WINDOWS_STABLE_ABI_DEBUG_LIB_NAME.to_owned()
2339 } else {
2340 WINDOWS_STABLE_ABI_LIB_NAME.to_owned()
2341 };
2342 if abi.kind == PythonAbiKind::Stable(StableAbi::Abi3t) {
2343 lib_name = lib_name.replace("python3", "python3t");
2344 }
2345 Ok(lib_name)
2346 } else if mingw {
2347 ensure!(
2348 !abi.kind.is_free_threaded(),
2349 "MinGW free-threaded builds are not currently tested or supported"
2350 );
2351 Ok(format!("python{}.{}", abi.version.major, abi.version.minor))
2353 } else if abi.kind().is_free_threaded() {
2354 #[expect(deprecated, reason = "using constant internally")]
2355 {
2356 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);
2357 }
2358 if debug {
2359 Ok(format!(
2360 "python{}{}t_d",
2361 abi.version.major, abi.version.minor
2362 ))
2363 } else {
2364 Ok(format!("python{}{}t", abi.version.major, abi.version.minor))
2365 }
2366 } else if debug {
2367 Ok(format!(
2368 "python{}{}_d",
2369 abi.version.major, abi.version.minor
2370 ))
2371 } else {
2372 Ok(format!("python{}{}", abi.version.major, abi.version.minor))
2373 }
2374}
2375
2376fn default_lib_name_unix(abi: PythonAbi, cygwin: bool, ld_version: Option<&str>) -> Result<String> {
2377 match abi.implementation {
2378 PythonImplementation::CPython => match ld_version {
2379 Some(ld_version) => Ok(format!("python{ld_version}")),
2380 None => {
2381 if cygwin && matches!(abi.kind, PythonAbiKind::Stable(StableAbi::Abi3)) {
2382 Ok("python3".to_string())
2383 } else if cygwin && matches!(abi.kind, PythonAbiKind::Stable(StableAbi::Abi3t)) {
2384 Ok("python3t".to_string())
2385 } else if abi.kind.is_free_threaded() {
2386 #[expect(deprecated, reason = "using constant internally")]
2387 {
2388 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);
2389 }
2390 Ok(format!(
2391 "python{}.{}t",
2392 abi.version.major, abi.version.minor
2393 ))
2394 } else {
2395 Ok(format!("python{}.{}", abi.version.major, abi.version.minor))
2396 }
2397 }
2398 },
2399 PythonImplementation::PyPy => match ld_version {
2400 Some(ld_version) => Ok(format!("pypy{ld_version}-c")),
2401 None => Ok(format!("pypy{}.{}-c", abi.version.major, abi.version.minor)),
2402 },
2403
2404 PythonImplementation::GraalPy => Ok("python-native".to_string()),
2405 PythonImplementation::RustPython => Ok("rustpython_capi".to_string()),
2406 }
2407}
2408
2409fn run_python_script(interpreter: &Path, script: &str) -> Result<String> {
2411 run_python_script_with_envs(interpreter, script, std::iter::empty::<(&str, &str)>())
2412}
2413
2414fn run_python_script_with_envs<I, K, V>(interpreter: &Path, script: &str, envs: I) -> Result<String>
2417where
2418 I: IntoIterator<Item = (K, V)>,
2419 K: AsRef<OsStr>,
2420 V: AsRef<OsStr>,
2421{
2422 let out = Command::new(interpreter)
2423 .env("PYTHONIOENCODING", "utf-8")
2424 .envs(envs)
2425 .stdin(Stdio::piped())
2426 .stdout(Stdio::piped())
2427 .stderr(Stdio::inherit())
2428 .spawn()
2429 .and_then(|mut child| {
2430 child
2431 .stdin
2432 .as_mut()
2433 .expect("piped stdin")
2434 .write_all(script.as_bytes())?;
2435 child.wait_with_output()
2436 });
2437
2438 match out {
2439 Err(err) => bail!(
2440 "failed to run the Python interpreter at {}: {}",
2441 interpreter.display(),
2442 err
2443 ),
2444 Ok(ok) if !ok.status.success() => bail!("Python script failed"),
2445 Ok(ok) => Ok(String::from_utf8(ok.stdout)
2446 .context("failed to parse Python script output as utf-8")?),
2447 }
2448}
2449
2450fn venv_interpreter(virtual_env: &OsStr, windows: bool) -> PathBuf {
2451 let venv = Path::new(virtual_env);
2452 println!(
2454 "cargo:rerun-if-changed={}",
2455 venv.join("pyvenv.cfg").display()
2456 );
2457 if windows {
2458 venv.join("Scripts").join("python.exe")
2459 } else {
2460 venv.join("bin").join("python")
2461 }
2462}
2463
2464fn conda_env_interpreter(conda_prefix: &OsStr, windows: bool) -> PathBuf {
2465 if windows {
2466 Path::new(conda_prefix).join("python.exe")
2467 } else {
2468 Path::new(conda_prefix).join("bin").join("python")
2469 }
2470}
2471
2472fn get_env_interpreter() -> Option<PathBuf> {
2473 match (env_var("VIRTUAL_ENV"), env_var("CONDA_PREFIX")) {
2474 (Some(dir), None) => Some(venv_interpreter(&dir, cfg!(windows))),
2477 (None, Some(dir)) => Some(conda_env_interpreter(&dir, cfg!(windows))),
2478 (Some(_), Some(_)) => {
2479 warn!(
2480 "Both VIRTUAL_ENV and CONDA_PREFIX are set. PyO3 will ignore both of these for \
2481 locating the Python interpreter until you unset one of them."
2482 );
2483 None
2484 }
2485 (None, None) => None,
2486 }
2487}
2488
2489pub fn find_interpreter() -> Result<PathBuf> {
2497 println!("cargo:rerun-if-env-changed=PYO3_ENVIRONMENT_SIGNATURE");
2500
2501 if let Some(exe) = env_var("PYO3_PYTHON") {
2502 Ok(exe.into())
2503 } else if let Some(env_interpreter) = get_env_interpreter() {
2504 Ok(env_interpreter)
2505 } else {
2506 println!("cargo:rerun-if-env-changed=PATH");
2507 ["python", "python3"]
2508 .iter()
2509 .find(|bin| {
2510 if let Ok(out) = Command::new(bin).arg("--version").output() {
2511 out.stdout.starts_with(b"Python 3")
2513 || out.stderr.starts_with(b"Python 3")
2514 || out.stdout.starts_with(b"GraalPy 3")
2515 } else {
2516 false
2517 }
2518 })
2519 .map(PathBuf::from)
2520 .ok_or_else(|| "no Python 3.x interpreter found".into())
2521 }
2522}
2523
2524fn get_host_interpreter(
2528 abi3_version: Option<PythonVersion>,
2529 abi3t_version: Option<PythonVersion>,
2530) -> Result<InterpreterConfig> {
2531 let interpreter_path = find_interpreter()?;
2532
2533 let interpreter_config =
2534 InterpreterConfig::from_interpreter(interpreter_path, abi3_version, abi3t_version)?;
2535
2536 Ok(interpreter_config)
2537}
2538
2539pub fn make_cross_compile_config(target: &Triple) -> Result<Option<InterpreterConfig>> {
2544 let interpreter_config =
2545 if let Some(cross_config) = cross_compiling_from_to(&Triple::host(), target)? {
2546 Some(load_cross_compile_config(cross_config)?.apply_build_env()?)
2547 } else {
2548 None
2549 };
2550
2551 Ok(interpreter_config)
2552}
2553
2554pub fn make_interpreter_config() -> Result<InterpreterConfig> {
2556 let host = Triple::host();
2557 let abi3_version = get_abi3_version();
2558 let abi3t_version = get_abi3t_version();
2559
2560 let need_interpreter =
2563 (abi3_version.is_none() && abi3t_version.is_none()) || require_libdir_for_target(&host);
2564
2565 if have_python_interpreter() {
2566 match get_host_interpreter(
2567 exact_stable_abi_version(abi3_version),
2568 exact_stable_abi_version(abi3t_version),
2569 ) {
2570 Ok(interpreter_config) => return Ok(interpreter_config),
2571 Err(e) if need_interpreter => return Err(e),
2573 _ => {
2574 warn!("Compiling without a working Python interpreter.");
2577 }
2578 }
2579 }
2580
2581 let interpreter_config = default_stable_abi_config(
2582 &host,
2583 exact_stable_abi_version(abi3_version),
2584 exact_stable_abi_version(abi3t_version),
2585 )?;
2586
2587 Ok(interpreter_config)
2588}
2589
2590pub(crate) fn escape(bytes: &[u8]) -> String {
2591 let mut escaped = String::with_capacity(2 * bytes.len());
2592
2593 for byte in bytes {
2594 const LUT: &[u8; 16] = b"0123456789abcdef";
2595
2596 escaped.push(LUT[(byte >> 4) as usize] as char);
2597 escaped.push(LUT[(byte & 0x0F) as usize] as char);
2598 }
2599
2600 escaped
2601}
2602
2603fn unescape(escaped: &str) -> Vec<u8> {
2604 assert_eq!(escaped.len() % 2, 0, "invalid hex encoding");
2605
2606 let mut bytes = Vec::with_capacity(escaped.len() / 2);
2607
2608 for chunk in escaped.as_bytes().chunks_exact(2) {
2609 fn unhex(hex: u8) -> u8 {
2610 match hex {
2611 b'a'..=b'f' => hex - b'a' + 10,
2612 b'0'..=b'9' => hex - b'0',
2613 _ => panic!("invalid hex encoding"),
2614 }
2615 }
2616
2617 bytes.push((unhex(chunk[0]) << 4) | unhex(chunk[1]));
2618 }
2619
2620 bytes
2621}
2622
2623#[cfg(test)]
2624#[expect(deprecated, reason = "accessing config fields directly for testing")]
2626mod tests {
2627 use target_lexicon::triple;
2628
2629 use super::*;
2630
2631 #[test]
2632 fn test_config_file_roundtrip() {
2633 let implementation = PythonImplementation::CPython;
2634 let version = MINIMUM_SUPPORTED_VERSION;
2635 let config = InterpreterConfigBuilder::new(implementation, version)
2636 .stable_abi(StableAbi::Abi3)
2637 .pointer_width(32)
2638 .executable("executable".to_string())
2639 .lib_dir("lib_name".to_string())
2640 .lib_name("lib_name".to_string())
2641 .extra_build_script_lines(vec!["cargo:test1".to_string(), "cargo:test2".to_string()])
2642 .finalize()
2643 .unwrap();
2644 let mut buf: Vec<u8> = Vec::new();
2645 config.to_writer(&mut buf).unwrap();
2646
2647 assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap());
2648
2649 let version = PythonVersion::PY310;
2651 let implementation = PythonImplementation::PyPy;
2652 let build_flags = {
2653 let mut flags = HashSet::new();
2654 flags.insert(BuildFlag::Py_DEBUG);
2655 flags.insert(BuildFlag::Other(String::from("Py_SOME_FLAG")));
2656 BuildFlags(flags)
2657 };
2658 let config = InterpreterConfigBuilder::new(implementation, version)
2659 .build_flags(build_flags)
2660 .finalize()
2661 .unwrap();
2662
2663 let mut buf: Vec<u8> = Vec::new();
2664 config.to_writer(&mut buf).unwrap();
2665
2666 assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap());
2667 }
2668
2669 #[test]
2670 fn test_config_file_roundtrip_with_escaping() {
2671 let implementation = PythonImplementation::CPython;
2672 let version = MINIMUM_SUPPORTED_VERSION;
2673 let config = InterpreterConfigBuilder::new(implementation, version)
2674 .stable_abi(StableAbi::Abi3)
2675 .pointer_width(32)
2676 .executable("executable".to_string())
2677 .lib_name("lib_name".to_string())
2678 .lib_dir("lib_dir\\n".to_string())
2679 .extra_build_script_lines(vec!["cargo:test1".to_string(), "cargo:test2".to_string()])
2680 .finalize()
2681 .unwrap();
2682 let mut buf: Vec<u8> = Vec::new();
2683 config.to_writer(&mut buf).unwrap();
2684
2685 let buf = unescape(&escape(&buf));
2686
2687 assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap());
2688 }
2689
2690 #[test]
2691 fn test_config_file_defaults() {
2692 let implementation = PythonImplementation::CPython;
2694 let version = PythonVersion::PY38;
2695 assert_eq!(
2696 InterpreterConfig::from_reader("version=3.8".as_bytes()).unwrap(),
2697 InterpreterConfigBuilder::new(implementation, version,)
2698 .finalize()
2699 .unwrap()
2700 )
2701 }
2702
2703 #[test]
2704 fn test_config_file_unknown_keys() {
2705 let implementation = PythonImplementation::CPython;
2707 let version = PythonVersion::PY38;
2708 assert_eq!(
2709 InterpreterConfig::from_reader("version=3.8\next_suffix=.python38.so".as_bytes())
2710 .unwrap(),
2711 InterpreterConfigBuilder::new(implementation, version,)
2712 .finalize()
2713 .unwrap()
2714 )
2715 }
2716
2717 #[test]
2718 fn test_config_file_invalid_keys() {
2719 assert!(
2720 InterpreterConfig::from_reader("version=3.14\ntarget_abi=foo-bar-baz".as_bytes())
2721 .is_err()
2722 );
2723 assert!(InterpreterConfig::from_reader(
2724 "version=3.14\ntarget_abi=CPython-bar-baz".as_bytes()
2725 )
2726 .is_err());
2727 assert!(InterpreterConfig::from_reader(
2728 "version=3.14\ntarget_abi=CPython-abi3-baz".as_bytes()
2729 )
2730 .is_err());
2731 }
2732
2733 #[test]
2734 fn gil_disabled_config_file_corner_cases() {
2735 let implementation = PythonImplementation::CPython;
2736 let version = PythonVersion::PY313;
2737 assert_eq!(
2739 InterpreterConfig::from_reader("version=3.13\nbuild_flags=Py_GIL_DISABLED".as_bytes())
2740 .unwrap(),
2741 InterpreterConfigBuilder::new(implementation, version)
2742 .free_threaded()
2743 .unwrap()
2744 .finalize()
2745 .unwrap()
2746 );
2747 assert_eq!(
2749 InterpreterConfig::from_reader(
2750 "version=3.13\ntarget_abi=CPython-free_threaded-3.13".as_bytes()
2751 )
2752 .unwrap(),
2753 InterpreterConfigBuilder::new(implementation, version)
2754 .free_threaded()
2755 .unwrap()
2756 .finalize()
2757 .unwrap()
2758 );
2759 assert!(InterpreterConfig::from_reader(
2761 "version=3.13\ntarget_abi=CPython-gil_enabled-3.13\nbuild_flags=Py_GIL_DISABLED"
2762 .as_bytes()
2763 )
2764 .is_err());
2765 let mut flags = BuildFlags::default();
2767 flags.0.insert(BuildFlag::Py_GIL_DISABLED);
2768 assert!(InterpreterConfigBuilder::new(implementation, version)
2769 .build_flags(flags)
2770 .finalize()
2771 .unwrap()
2772 .target_abi
2773 .kind
2774 .is_free_threaded());
2775
2776 let mut flags = BuildFlags::default();
2777 flags.0.insert(BuildFlag::Py_GIL_DISABLED);
2778 assert!(
2779 InterpreterConfigBuilder::new(implementation, PythonVersion::PY312)
2780 .build_flags(flags)
2781 .finalize()
2782 .is_err()
2783 );
2784
2785 let mut flags = BuildFlags::default();
2786 flags.0.insert(BuildFlag::Py_GIL_DISABLED);
2787 assert!(
2788 InterpreterConfigBuilder::new(implementation, PythonVersion::PY312)
2789 .stable_abi(StableAbi::Abi3)
2790 .build_flags(flags)
2791 .finalize()
2792 .is_err()
2793 );
2794
2795 assert!(
2796 InterpreterConfigBuilder::new(implementation, PythonVersion::PY38)
2797 .free_threaded()
2798 .is_err()
2799 );
2800 }
2801
2802 #[test]
2803 fn abi3_from_old_config_file() {
2804 let implementation = PythonImplementation::CPython;
2805 let version = PythonVersion::PY313;
2806 assert_eq!(
2807 InterpreterConfig::from_reader("version=3.13\nabi3=true".as_bytes()).unwrap(),
2808 InterpreterConfigBuilder::new(implementation, version)
2809 .stable_abi(StableAbi::Abi3)
2810 .finalize()
2811 .unwrap()
2812 );
2813 }
2814
2815 #[test]
2816 fn test_target_abi_and_abi3() {
2817 assert!(InterpreterConfig::from_reader(
2818 "version=3.13\nabi3=true\ntarget_abi=CPython-abi3-3.13".as_bytes()
2819 )
2820 .unwrap_err()
2821 .to_string()
2822 .contains("Invalid config"),);
2823 }
2824
2825 #[test]
2826 fn build_flags_default() {
2827 assert_eq!(BuildFlags::default(), BuildFlags::new());
2828 }
2829
2830 #[test]
2831 fn build_flags_from_sysconfigdata() {
2832 let mut sysconfigdata = Sysconfigdata::new();
2833
2834 assert_eq!(
2835 BuildFlags::from_sysconfigdata(&sysconfigdata).0,
2836 HashSet::new()
2837 );
2838
2839 for flag in &BuildFlags::ALL {
2840 sysconfigdata.insert(flag.to_string(), "0".into());
2841 }
2842
2843 assert_eq!(
2844 BuildFlags::from_sysconfigdata(&sysconfigdata).0,
2845 HashSet::new()
2846 );
2847
2848 let mut expected_flags = HashSet::new();
2849 for flag in &BuildFlags::ALL {
2850 sysconfigdata.insert(flag.to_string(), "1".into());
2851 expected_flags.insert(flag.clone());
2852 }
2853
2854 assert_eq!(
2855 BuildFlags::from_sysconfigdata(&sysconfigdata).0,
2856 expected_flags
2857 );
2858 }
2859
2860 #[test]
2861 fn build_flags_fixup() {
2862 let mut build_flags = BuildFlags::new();
2863
2864 build_flags = build_flags.fixup();
2865 assert!(build_flags.0.is_empty());
2866
2867 build_flags.0.insert(BuildFlag::Py_DEBUG);
2868
2869 build_flags = build_flags.fixup();
2870
2871 assert!(build_flags.0.contains(&BuildFlag::Py_REF_DEBUG));
2873 }
2874
2875 #[test]
2876 fn parse_script_output() {
2877 let output = "foo bar\nbar foobar\n\n";
2878 let map = super::parse_script_output(output);
2879 assert_eq!(map.len(), 2);
2880 assert_eq!(map["foo"], "bar");
2881 assert_eq!(map["bar"], "foobar");
2882 }
2883
2884 #[test]
2885 fn config_from_interpreter() {
2886 assert!(make_interpreter_config().is_ok())
2890 }
2891
2892 #[test]
2893 fn config_from_empty_sysconfigdata() {
2894 let sysconfigdata = Sysconfigdata::new();
2895 assert!(InterpreterConfig::from_sysconfigdata(&sysconfigdata).is_err());
2896 }
2897
2898 #[test]
2899 fn config_from_sysconfigdata() {
2900 let mut sysconfigdata = Sysconfigdata::new();
2901 sysconfigdata.insert("SOABI", "cpython-38-x86_64-linux-gnu");
2904 sysconfigdata.insert("VERSION", "3.8");
2905 sysconfigdata.insert("Py_ENABLE_SHARED", "1");
2906 sysconfigdata.insert("LIBDIR", "/usr/lib");
2907 sysconfigdata.insert("LDVERSION", "3.8");
2908 sysconfigdata.insert("SIZEOF_VOID_P", "8");
2909 let implementation = PythonImplementation::CPython;
2910 let version = PythonVersion::PY38;
2911 assert_eq!(
2912 InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(),
2913 InterpreterConfigBuilder::new(implementation, version,)
2914 .build_flags(BuildFlags::from_sysconfigdata(&sysconfigdata))
2915 .lib_dir("/usr/lib".to_string())
2916 .lib_name("python3.8".to_string())
2917 .pointer_width(64)
2918 .finalize()
2919 .unwrap()
2920 );
2921 }
2922
2923 #[test]
2924 fn config_from_sysconfigdata_framework() {
2925 let mut sysconfigdata = Sysconfigdata::new();
2926 sysconfigdata.insert("SOABI", "cpython-38-x86_64-linux-gnu");
2927 sysconfigdata.insert("VERSION", "3.8");
2928 sysconfigdata.insert("Py_ENABLE_SHARED", "0");
2930 sysconfigdata.insert("PYTHONFRAMEWORK", "Python");
2931 sysconfigdata.insert("LIBDIR", "/usr/lib");
2932 sysconfigdata.insert("LDVERSION", "3.8");
2933 sysconfigdata.insert("SIZEOF_VOID_P", "8");
2934 let implementation = PythonImplementation::CPython;
2935 let version = PythonVersion::PY38;
2936 assert_eq!(
2937 InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(),
2938 InterpreterConfigBuilder::new(implementation, version,)
2939 .build_flags(BuildFlags::from_sysconfigdata(&sysconfigdata))
2940 .lib_dir("/usr/lib".to_string())
2941 .lib_name("python3.8".to_string())
2942 .pointer_width(64)
2943 .finalize()
2944 .unwrap()
2945 );
2946
2947 sysconfigdata = Sysconfigdata::new();
2948 sysconfigdata.insert("SOABI", "cpython-38-x86_64-linux-gnu");
2949 sysconfigdata.insert("VERSION", "3.8");
2950 sysconfigdata.insert("Py_ENABLE_SHARED", "0");
2952 sysconfigdata.insert("PYTHONFRAMEWORK", "");
2953 sysconfigdata.insert("LIBDIR", "/usr/lib");
2954 sysconfigdata.insert("LDVERSION", "3.8");
2955 sysconfigdata.insert("SIZEOF_VOID_P", "8");
2956 let implementation = PythonImplementation::CPython;
2957 let version = PythonVersion::PY38;
2958 assert_eq!(
2959 InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(),
2960 InterpreterConfigBuilder::new(implementation, version,)
2961 .build_flags(BuildFlags::from_sysconfigdata(&sysconfigdata))
2962 .lib_dir("/usr/lib".to_string())
2963 .lib_name("python3.8".to_string())
2964 .pointer_width(64)
2965 .shared(false)
2966 .finalize()
2967 .unwrap()
2968 );
2969 }
2970
2971 #[test]
2972 fn windows_hardcoded_abi3_compile() {
2973 let host = triple!("x86_64-pc-windows-msvc");
2974 let implementation = PythonImplementation::CPython;
2975 let version = PythonVersion::PY38;
2976 let config = InterpreterConfigBuilder::new(implementation, version)
2977 .stable_abi(StableAbi::Abi3)
2978 .lib_name("python3".to_string())
2979 .finalize()
2980 .unwrap();
2981 assert_eq!(
2982 default_stable_abi_config(&host, Some(version), None).unwrap(),
2983 config
2984 );
2985 }
2986
2987 #[test]
2988 fn windows_hardcoded_abi3t_compile() {
2989 let host = triple!("x86_64-pc-windows-msvc");
2990 let implementation = PythonImplementation::CPython;
2991 let version = PythonVersion::PY315;
2992 let config = InterpreterConfigBuilder::new(implementation, version)
2993 .stable_abi(StableAbi::Abi3t)
2994 .lib_name("python3t".to_string())
2995 .finalize()
2996 .unwrap();
2997 assert_eq!(
2998 default_stable_abi_config(&host, None, Some(version)).unwrap(),
2999 config
3000 );
3001 }
3002
3003 #[test]
3004 fn unix_hardcoded_abi3_compile() {
3005 let host = triple!("x86_64-unknown-linux-gnu");
3006 let implementation = PythonImplementation::CPython;
3007 let version = PythonVersion::PY39;
3008 let config = InterpreterConfigBuilder::new(implementation, version)
3009 .stable_abi(StableAbi::Abi3)
3010 .finalize()
3011 .unwrap();
3012 assert_eq!(
3013 default_stable_abi_config(&host, Some(version), None).unwrap(),
3014 config
3015 );
3016 }
3017
3018 #[test]
3019 fn unix_hardcoded_abi3t_compile() {
3020 let host = triple!("x86_64-unknown-linux-gnu");
3021 let implementation = PythonImplementation::CPython;
3022 let version = PythonVersion::PY315;
3023 let config = InterpreterConfigBuilder::new(implementation, version)
3024 .stable_abi(StableAbi::Abi3t)
3025 .finalize()
3026 .unwrap();
3027 assert_eq!(
3028 default_stable_abi_config(&host, None, Some(version)).unwrap(),
3029 config
3030 );
3031 }
3032
3033 #[test]
3034 fn default_stable_abi_config_corner_cases() {
3035 let host = triple!("x86_64-unknown-linux-gnu");
3036 let py315 = Some("3.15".parse().unwrap());
3037 let py39 = Some("3.9".parse().unwrap());
3038 let implementation = PythonImplementation::CPython;
3039 let version = PythonVersion::PY39;
3040 let config = InterpreterConfigBuilder::new(implementation, version)
3041 .stable_abi(StableAbi::Abi3)
3042 .finalize()
3043 .unwrap();
3044 assert_eq!(
3045 default_stable_abi_config(&host, py39, py315).unwrap(),
3046 config
3047 );
3048 assert!(default_stable_abi_config(&host, None, py39).is_err());
3049 }
3050
3051 #[test]
3052 fn windows_hardcoded_cross_compile() {
3053 let env_vars = CrossCompileEnvVars {
3054 pyo3_cross: None,
3055 pyo3_cross_lib_dir: Some("C:\\some\\path".into()),
3056 pyo3_cross_python_implementation: None,
3057 pyo3_cross_python_version: Some("3.8".into()),
3058 };
3059
3060 let host = triple!("x86_64-unknown-linux-gnu");
3061 let target = triple!("i686-pc-windows-msvc");
3062 let cross_config =
3063 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
3064 .unwrap()
3065 .unwrap();
3066
3067 let implementation = PythonImplementation::CPython;
3068 let version = PythonVersion::PY38;
3069 let config = InterpreterConfigBuilder::new(implementation, version)
3070 .lib_name("python38".to_string())
3071 .lib_dir("C:\\some\\path".to_string())
3072 .finalize()
3073 .unwrap();
3074 assert_eq!(default_cross_compile(&cross_config).unwrap(), config);
3075 }
3076
3077 #[test]
3078 fn mingw_hardcoded_cross_compile() {
3079 let env_vars = CrossCompileEnvVars {
3080 pyo3_cross: None,
3081 pyo3_cross_lib_dir: Some("/usr/lib/mingw".into()),
3082 pyo3_cross_python_implementation: None,
3083 pyo3_cross_python_version: Some("3.8".into()),
3084 };
3085
3086 let host = triple!("x86_64-unknown-linux-gnu");
3087 let target = triple!("i686-pc-windows-gnu");
3088 let cross_config =
3089 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
3090 .unwrap()
3091 .unwrap();
3092
3093 let implementation = PythonImplementation::CPython;
3094 let version = PythonVersion::PY38;
3095 let config = InterpreterConfigBuilder::new(implementation, version)
3096 .lib_name("python38".to_string())
3097 .lib_dir("/usr/lib/mingw".to_string())
3098 .finalize()
3099 .unwrap();
3100 assert_eq!(default_cross_compile(&cross_config).unwrap(), config);
3101 }
3102
3103 #[test]
3104 fn unix_hardcoded_cross_compile() {
3105 let env_vars = CrossCompileEnvVars {
3106 pyo3_cross: None,
3107 pyo3_cross_lib_dir: Some("/usr/arm64/lib".into()),
3108 pyo3_cross_python_implementation: None,
3109 pyo3_cross_python_version: Some("3.9".into()),
3110 };
3111
3112 let host = triple!("x86_64-unknown-linux-gnu");
3113 let target = triple!("aarch64-unknown-linux-gnu");
3114 let cross_config =
3115 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
3116 .unwrap()
3117 .unwrap();
3118
3119 let implementation = PythonImplementation::CPython;
3120 let version = PythonVersion::PY39;
3121 let config = InterpreterConfigBuilder::new(implementation, version)
3122 .lib_name("python3.9".to_string())
3123 .lib_dir("/usr/arm64/lib".to_string())
3124 .finalize()
3125 .unwrap();
3126 assert_eq!(default_cross_compile(&cross_config).unwrap(), config);
3127 }
3128
3129 #[test]
3130 fn pypy_hardcoded_cross_compile() {
3131 let env_vars = CrossCompileEnvVars {
3132 pyo3_cross: None,
3133 pyo3_cross_lib_dir: None,
3134 pyo3_cross_python_implementation: Some("PyPy".into()),
3135 pyo3_cross_python_version: Some("3.11".into()),
3136 };
3137
3138 let triple = triple!("x86_64-unknown-linux-gnu");
3139 let cross_config =
3140 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &triple, &triple)
3141 .unwrap()
3142 .unwrap();
3143
3144 let implementation = PythonImplementation::PyPy;
3145 let version = PythonVersion::PY311;
3146 let config = InterpreterConfigBuilder::new(implementation, version)
3147 .lib_name("pypy3.11-c".to_string())
3148 .finalize()
3149 .unwrap();
3150 assert_eq!(default_cross_compile(&cross_config).unwrap(), config);
3151 }
3152
3153 #[test]
3157 fn unix_free_threaded_pre_315_cross_compile() {
3158 let env_vars = CrossCompileEnvVars {
3159 pyo3_cross: None,
3160 pyo3_cross_lib_dir: None,
3161 pyo3_cross_python_implementation: None,
3162 pyo3_cross_python_version: Some("3.14t".into()),
3163 };
3164
3165 let host = triple!("x86_64-unknown-linux-gnu");
3166 let target = triple!("aarch64-unknown-linux-gnu");
3167 let cross_config =
3168 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
3169 .unwrap()
3170 .unwrap();
3171
3172 let implementation = PythonImplementation::CPython;
3173 let version = PythonVersion::PY314;
3174 let config = InterpreterConfigBuilder::new(implementation, version)
3175 .free_threaded()
3176 .unwrap()
3177 .lib_name("python3.14t".to_string())
3178 .finalize()
3179 .unwrap();
3180 let result = default_cross_compile(&cross_config).unwrap();
3181 assert_eq!(result, config);
3182 assert_eq!(
3183 result.target_abi.kind(),
3184 PythonAbiKind::VersionSpecific(GilUsed::FreeThreaded)
3185 );
3186 }
3187
3188 #[test]
3189 fn windows_free_threaded_pre_315_cross_compile() {
3190 let env_vars = CrossCompileEnvVars {
3191 pyo3_cross: None,
3192 pyo3_cross_lib_dir: None,
3193 pyo3_cross_python_implementation: None,
3194 pyo3_cross_python_version: Some("3.14t".into()),
3195 };
3196
3197 let host = triple!("x86_64-unknown-linux-gnu");
3198 let target = triple!("x86_64-pc-windows-msvc");
3199 let cross_config =
3200 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
3201 .unwrap()
3202 .unwrap();
3203
3204 let implementation = PythonImplementation::CPython;
3205 let version = PythonVersion::PY314;
3206 let config = InterpreterConfigBuilder::new(implementation, version)
3207 .free_threaded()
3208 .unwrap()
3209 .lib_name("python314t".to_string())
3210 .finalize()
3211 .unwrap();
3212 let result = default_cross_compile(&cross_config).unwrap();
3213 assert_eq!(result, config);
3214 assert_eq!(
3215 result.target_abi.kind(),
3216 PythonAbiKind::VersionSpecific(GilUsed::FreeThreaded)
3217 );
3218 }
3219
3220 #[test]
3223 fn unix_free_threaded_315_cross_compile() {
3224 let env_vars = CrossCompileEnvVars {
3225 pyo3_cross: None,
3226 pyo3_cross_lib_dir: None,
3227 pyo3_cross_python_implementation: None,
3228 pyo3_cross_python_version: Some("3.15t".into()),
3229 };
3230
3231 let host = triple!("x86_64-unknown-linux-gnu");
3232 let target = triple!("aarch64-unknown-linux-gnu");
3233 let cross_config =
3234 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
3235 .unwrap()
3236 .unwrap();
3237
3238 let implementation = PythonImplementation::CPython;
3239 let version = PythonVersion::PY315;
3240 let config = InterpreterConfigBuilder::new(implementation, version)
3241 .free_threaded()
3242 .unwrap()
3243 .lib_name("python3.15t".to_string())
3244 .finalize()
3245 .unwrap();
3246 let result = default_cross_compile(&cross_config).unwrap();
3247 assert_eq!(result, config);
3248 assert_eq!(
3249 result.target_abi.kind(),
3250 PythonAbiKind::VersionSpecific(GilUsed::FreeThreaded)
3251 );
3252 }
3253
3254 #[test]
3255 fn default_lib_name_windows() {
3256 assert_eq!(
3257 super::default_lib_name_windows(
3258 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY39)
3259 .finalize()
3260 .unwrap(),
3261 false,
3262 false,
3263 )
3264 .unwrap(),
3265 "python39",
3266 );
3267 assert!(
3269 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY39)
3270 .free_threaded()
3271 .finalize()
3272 .is_err()
3273 );
3274 assert_eq!(
3275 super::default_lib_name_windows(
3276 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY39)
3277 .stable_abi(StableAbi::Abi3)
3278 .finalize()
3279 .unwrap(),
3280 false,
3281 false,
3282 )
3283 .unwrap(),
3284 "python3",
3285 );
3286 assert_eq!(
3287 super::default_lib_name_windows(
3288 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY39)
3289 .finalize()
3290 .unwrap(),
3291 true,
3292 false,
3293 )
3294 .unwrap(),
3295 "python3.9",
3296 );
3297 assert_eq!(
3298 super::default_lib_name_windows(
3299 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY39)
3300 .stable_abi(StableAbi::Abi3)
3301 .finalize()
3302 .unwrap(),
3303 true,
3304 false,
3305 )
3306 .unwrap(),
3307 "python3",
3308 );
3309 assert_eq!(
3310 super::default_lib_name_windows(
3311 PythonAbiBuilder::new(PythonImplementation::PyPy, PythonVersion::PY39)
3312 .stable_abi(StableAbi::Abi3)
3313 .finalize()
3314 .unwrap(),
3315 false,
3316 false,
3317 )
3318 .unwrap(),
3319 "libpypy3.9-c",
3320 );
3321 assert_eq!(
3322 super::default_lib_name_windows(
3323 PythonAbiBuilder::new(PythonImplementation::PyPy, PythonVersion::PY311)
3324 .stable_abi(StableAbi::Abi3)
3325 .finalize()
3326 .unwrap(),
3327 false,
3328 false,
3329 )
3330 .unwrap(),
3331 "libpypy3.11-c",
3332 );
3333 assert_eq!(
3334 super::default_lib_name_windows(
3335 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY310)
3336 .stable_abi(StableAbi::Abi3)
3337 .finalize()
3338 .unwrap(),
3339 false,
3340 true,
3341 )
3342 .unwrap(),
3343 "python3_d",
3344 );
3345 assert_eq!(
3348 super::default_lib_name_windows(
3349 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY39)
3350 .stable_abi(StableAbi::Abi3)
3351 .finalize()
3352 .unwrap(),
3353 false,
3354 true,
3355 )
3356 .unwrap(),
3357 "python39_d",
3358 );
3359 assert_eq!(
3360 super::default_lib_name_windows(
3361 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY310)
3362 .stable_abi(StableAbi::Abi3)
3363 .finalize()
3364 .unwrap(),
3365 false,
3366 true,
3367 )
3368 .unwrap(),
3369 "python3_d",
3370 );
3371 assert!(super::default_lib_name_windows(
3373 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY313)
3374 .free_threaded()
3375 .finalize()
3376 .unwrap(),
3377 true,
3378 false,
3379 )
3380 .is_err());
3381 assert_eq!(
3382 super::default_lib_name_windows(
3383 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY313)
3384 .free_threaded()
3385 .finalize()
3386 .unwrap(),
3387 false,
3388 false,
3389 )
3390 .unwrap(),
3391 "python313t",
3392 );
3393 assert_eq!(
3394 super::default_lib_name_windows(
3395 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY313)
3396 .free_threaded()
3397 .finalize()
3398 .unwrap(),
3399 false,
3400 true,
3401 )
3402 .unwrap(),
3403 "python313t_d",
3404 );
3405 assert_eq!(
3406 super::default_lib_name_windows(
3407 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY315)
3408 .stable_abi(StableAbi::Abi3t)
3409 .finalize()
3410 .unwrap(),
3411 false,
3412 false,
3413 )
3414 .unwrap(),
3415 "python3t",
3416 );
3417 assert_eq!(
3418 super::default_lib_name_windows(
3419 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY315)
3420 .stable_abi(StableAbi::Abi3t)
3421 .finalize()
3422 .unwrap(),
3423 false,
3424 true,
3425 )
3426 .unwrap(),
3427 "python3t_d",
3428 );
3429 }
3430
3431 #[test]
3432 fn default_lib_name_unix() {
3433 assert_eq!(
3435 super::default_lib_name_unix(
3436 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY38)
3437 .finalize()
3438 .unwrap(),
3439 false,
3440 None,
3441 )
3442 .unwrap(),
3443 "python3.8",
3444 );
3445 assert_eq!(
3446 super::default_lib_name_unix(
3447 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY39)
3448 .finalize()
3449 .unwrap(),
3450 false,
3451 None,
3452 )
3453 .unwrap(),
3454 "python3.9",
3455 );
3456 assert_eq!(
3458 super::default_lib_name_unix(
3459 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY39)
3460 .finalize()
3461 .unwrap(),
3462 false,
3463 Some("3.8d"),
3464 )
3465 .unwrap(),
3466 "python3.8d",
3467 );
3468
3469 assert_eq!(
3471 super::default_lib_name_unix(
3472 PythonAbiBuilder::new(PythonImplementation::PyPy, PythonVersion::PY311)
3473 .finalize()
3474 .unwrap(),
3475 false,
3476 None,
3477 )
3478 .unwrap(),
3479 "pypy3.11-c",
3480 );
3481
3482 assert_eq!(
3483 super::default_lib_name_unix(
3484 PythonAbiBuilder::new(PythonImplementation::PyPy, PythonVersion::PY39)
3485 .finalize()
3486 .unwrap(),
3487 false,
3488 Some("3.11d"),
3489 )
3490 .unwrap(),
3491 "pypy3.11d-c",
3492 );
3493
3494 assert_eq!(
3496 super::default_lib_name_unix(
3497 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY313)
3498 .free_threaded()
3499 .finalize()
3500 .unwrap(),
3501 false,
3502 None,
3503 )
3504 .unwrap(),
3505 "python3.13t",
3506 );
3507 assert_eq!(
3509 super::default_lib_name_unix(
3510 PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY313)
3511 .stable_abi(StableAbi::Abi3)
3512 .finalize()
3513 .unwrap(),
3514 true,
3515 None,
3516 )
3517 .unwrap(),
3518 "python3",
3519 );
3520 }
3521
3522 #[test]
3523 fn abi_builder_error_paths() {
3524 let builder = PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY39)
3525 .free_threaded()
3526 .finalize();
3527
3528 assert!(builder.is_err());
3529 assert!(builder.unwrap_err().to_string().contains("Cannot target"));
3530
3531 assert_eq!(
3532 PythonAbiBuilder::new(
3533 PythonImplementation::CPython,
3534 PythonVersion {
3535 major: 3,
3536 minor: 16,
3537 },
3538 )
3539 .stable_abi(StableAbi::Abi3)
3540 .finalize()
3541 .unwrap()
3542 .version
3543 .minor,
3544 STABLE_ABI_MAX_MINOR
3545 );
3546
3547 assert!("invalid".parse::<PythonAbi>().is_err());
3548 assert!("CPython-invalid".parse::<PythonAbi>().is_err());
3549 assert!("CPython-free_threaded-invalid"
3550 .parse::<PythonAbi>()
3551 .is_err());
3552
3553 let builder = PythonAbiBuilder::new(PythonImplementation::RustPython, PythonVersion::PY315)
3554 .free_threaded();
3555 let res = builder.finalize();
3556
3557 assert!(res.is_err());
3558 assert!(res
3559 .unwrap_err()
3560 .to_string()
3561 .contains("RustPython only supports targeting abi3t"));
3562 }
3563
3564 #[test]
3565 fn parse_cross_python_version() {
3566 let env_vars = CrossCompileEnvVars {
3567 pyo3_cross: None,
3568 pyo3_cross_lib_dir: None,
3569 pyo3_cross_python_version: Some("3.9".into()),
3570 pyo3_cross_python_implementation: None,
3571 };
3572
3573 assert_eq!(
3574 env_vars.parse_version().unwrap(),
3575 (Some(PythonVersion { major: 3, minor: 9 }), None),
3576 );
3577
3578 let env_vars = CrossCompileEnvVars {
3579 pyo3_cross: None,
3580 pyo3_cross_lib_dir: None,
3581 pyo3_cross_python_version: None,
3582 pyo3_cross_python_implementation: None,
3583 };
3584
3585 assert_eq!(env_vars.parse_version().unwrap(), (None, None));
3586
3587 let env_vars = CrossCompileEnvVars {
3588 pyo3_cross: None,
3589 pyo3_cross_lib_dir: None,
3590 pyo3_cross_python_version: Some("3.13t".into()),
3591 pyo3_cross_python_implementation: None,
3592 };
3593
3594 assert_eq!(
3595 env_vars.parse_version().unwrap(),
3596 (
3597 Some(PythonVersion {
3598 major: 3,
3599 minor: 13
3600 }),
3601 Some("t".into())
3602 ),
3603 );
3604
3605 let env_vars = CrossCompileEnvVars {
3606 pyo3_cross: None,
3607 pyo3_cross_lib_dir: None,
3608 pyo3_cross_python_version: Some("100".into()),
3609 pyo3_cross_python_implementation: None,
3610 };
3611
3612 assert!(env_vars.parse_version().is_err());
3613 }
3614
3615 #[test]
3616 fn target_abi3_version_different_from_host() {
3617 let implementation = PythonImplementation::CPython;
3618 let host_version = PythonVersion::PY39;
3619 let target_version = PythonVersion::PY38;
3620 let config = InterpreterConfigBuilder::new(implementation, host_version)
3621 .target_abi(
3622 PythonAbiBuilder::new(implementation, target_version)
3623 .stable_abi(StableAbi::Abi3)
3624 .finalize()
3625 .unwrap(),
3626 )
3627 .finalize()
3628 .unwrap();
3629 assert_eq!(config.target_abi.version(), target_version);
3630 assert_eq!(config.version, host_version);
3631 }
3632
3633 #[test]
3634 fn abi3_version_cannot_be_higher_than_interpreter() {
3635 if !have_python_interpreter() {
3636 return;
3637 }
3638
3639 let host_interpreter = get_host_interpreter(None, None).unwrap();
3640 let host_version = host_interpreter.version;
3641 let host_free_threaded = host_interpreter.target_abi.kind.is_free_threaded();
3642
3643 if matches!(
3645 host_interpreter.implementation,
3646 PythonImplementation::PyPy | PythonImplementation::GraalPy
3647 ) || ((host_version == PythonVersion::PY314) && host_free_threaded)
3648 {
3649 return;
3650 }
3651
3652 let interpreter = get_host_interpreter(
3653 Some(PythonVersion {
3654 major: 3,
3655 minor: 45,
3656 }),
3657 None,
3658 );
3659 if !host_free_threaded {
3660 assert!(interpreter.unwrap_err().to_string().contains(
3661 "cannot set a minimum Python version 3.45 higher than the interpreter version"
3662 ));
3663 if host_version >= PythonVersion::PY313 {
3664 let interpreter = get_host_interpreter(Some(PythonVersion::PY313), None);
3665 assert_eq!(
3666 interpreter.unwrap().target_abi.version(),
3667 PythonVersion::PY313
3668 );
3669 }
3670 }
3671
3672 if host_version >= PythonVersion::PY313 {
3674 let interpreter =
3675 get_host_interpreter(Some(PythonVersion::PY313), Some(PythonVersion::PY315))
3676 .unwrap();
3677 assert_eq!(
3678 interpreter.target_abi.version(),
3679 if host_version >= PythonVersion::PY315 {
3680 PythonVersion::PY315
3681 } else {
3682 PythonVersion::PY313
3683 }
3684 );
3685 }
3686 }
3687
3688 #[test]
3689 #[cfg(all(target_os = "linux", target_arch = "x86_64",))]
3690 fn parse_sysconfigdata() {
3691 let Ok(interpreter_config) = make_interpreter_config() else {
3696 return;
3699 };
3700
3701 let lib_dir = match &interpreter_config.lib_dir {
3702 Some(lib_dir) => Path::new(lib_dir),
3703 None => return,
3705 };
3706
3707 let cross = CrossCompileConfig {
3708 lib_dir: Some(lib_dir.into()),
3709 version: Some(interpreter_config.version),
3710 implementation: Some(interpreter_config.implementation),
3711 target: triple!("x86_64-unknown-linux-gnu"),
3712 abiflags: if interpreter_config.target_abi.kind().is_free_threaded() {
3713 Some("t".into())
3714 } else {
3715 None
3716 },
3717 };
3718
3719 let sysconfigdata_path = match find_sysconfigdata(&cross) {
3720 Ok(Some(path)) => path,
3721 _ => return,
3723 };
3724 let sysconfigdata = super::parse_sysconfigdata(sysconfigdata_path).unwrap();
3725 let mut parsed_config = InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap();
3726
3727 if parsed_config.python_framework_prefix.as_deref() == Some("") {
3733 parsed_config.python_framework_prefix = None;
3734 }
3735
3736 assert_eq!(parsed_config.implementation, PythonImplementation::CPython);
3737 assert_eq!(
3738 parsed_config,
3739 InterpreterConfigBuilder::new(
3740 interpreter_config.implementation,
3741 interpreter_config.version,
3742 )
3743 .build_flags(interpreter_config.build_flags().clone())
3744 .pointer_width(64)
3745 .lib_dir(interpreter_config.lib_dir().map(str::to_owned))
3746 .lib_name(interpreter_config.lib_name().map(str::to_owned))
3747 .finalize()
3748 .unwrap()
3749 )
3750 }
3751
3752 #[test]
3753 fn test_venv_interpreter() {
3754 let base = OsStr::new("base");
3755 assert_eq!(
3756 venv_interpreter(base, true),
3757 PathBuf::from_iter(&["base", "Scripts", "python.exe"])
3758 );
3759 assert_eq!(
3760 venv_interpreter(base, false),
3761 PathBuf::from_iter(&["base", "bin", "python"])
3762 );
3763 }
3764
3765 #[test]
3766 fn test_conda_env_interpreter() {
3767 let base = OsStr::new("base");
3768 assert_eq!(
3769 conda_env_interpreter(base, true),
3770 PathBuf::from_iter(&["base", "python.exe"])
3771 );
3772 assert_eq!(
3773 conda_env_interpreter(base, false),
3774 PathBuf::from_iter(&["base", "bin", "python"])
3775 );
3776 }
3777
3778 #[test]
3779 fn test_not_cross_compiling_from_to() {
3780 assert!(cross_compiling_from_to(
3781 &triple!("x86_64-unknown-linux-gnu"),
3782 &triple!("x86_64-unknown-linux-gnu"),
3783 )
3784 .unwrap()
3785 .is_none());
3786
3787 assert!(cross_compiling_from_to(
3788 &triple!("x86_64-apple-darwin"),
3789 &triple!("x86_64-apple-darwin")
3790 )
3791 .unwrap()
3792 .is_none());
3793
3794 assert!(cross_compiling_from_to(
3795 &triple!("aarch64-apple-darwin"),
3796 &triple!("x86_64-apple-darwin")
3797 )
3798 .unwrap()
3799 .is_none());
3800
3801 assert!(cross_compiling_from_to(
3802 &triple!("x86_64-apple-darwin"),
3803 &triple!("aarch64-apple-darwin")
3804 )
3805 .unwrap()
3806 .is_none());
3807
3808 assert!(cross_compiling_from_to(
3809 &triple!("x86_64-pc-windows-msvc"),
3810 &triple!("i686-pc-windows-msvc")
3811 )
3812 .unwrap()
3813 .is_none());
3814
3815 assert!(cross_compiling_from_to(
3816 &triple!("x86_64-unknown-linux-gnu"),
3817 &triple!("x86_64-unknown-linux-musl")
3818 )
3819 .unwrap()
3820 .is_none());
3821
3822 assert!(cross_compiling_from_to(
3823 &triple!("x86_64-pc-windows-msvc"),
3824 &triple!("x86_64-win7-windows-msvc"),
3825 )
3826 .unwrap()
3827 .is_none());
3828 }
3829
3830 #[test]
3831 fn test_is_cross_compiling_from_to() {
3832 assert!(cross_compiling_from_to(
3833 &triple!("x86_64-pc-windows-msvc"),
3834 &triple!("aarch64-pc-windows-msvc")
3835 )
3836 .unwrap()
3837 .is_some());
3838 }
3839
3840 #[test]
3841 fn test_run_python_script() {
3842 let interpreter = make_interpreter_config()
3844 .expect("could not get InterpreterConfig from installed interpreter");
3845 let out = interpreter
3846 .run_python_script("print(2 + 2)")
3847 .expect("failed to run Python script");
3848 assert_eq!(out.trim_end(), "4");
3849 }
3850
3851 #[test]
3852 fn test_run_python_script_with_envs() {
3853 let interpreter = make_interpreter_config()
3855 .expect("could not get InterpreterConfig from installed interpreter");
3856 let out = interpreter
3857 .run_python_script_with_envs(
3858 "import os; print(os.getenv('PYO3_TEST'))",
3859 vec![("PYO3_TEST", "42")],
3860 )
3861 .expect("failed to run Python script");
3862 assert_eq!(out.trim_end(), "42");
3863 }
3864
3865 #[test]
3866 fn test_build_script_outputs_base() {
3867 let implementation = PythonImplementation::CPython;
3868 let version = PythonVersion::PY311;
3869 let interpreter_config = InterpreterConfigBuilder::new(implementation, version)
3870 .finalize()
3871 .unwrap();
3872 assert_eq!(
3873 interpreter_config.build_script_outputs(),
3874 [
3875 "cargo:rustc-cfg=Py_3_8".to_owned(),
3876 "cargo:rustc-cfg=Py_3_9".to_owned(),
3877 "cargo:rustc-cfg=Py_3_10".to_owned(),
3878 "cargo:rustc-cfg=Py_3_11".to_owned(),
3879 ]
3880 );
3881
3882 let interpreter_config = InterpreterConfigBuilder::new(PythonImplementation::PyPy, version)
3883 .finalize()
3884 .unwrap();
3885 assert_eq!(
3886 interpreter_config.build_script_outputs(),
3887 [
3888 "cargo:rustc-cfg=Py_3_8".to_owned(),
3889 "cargo:rustc-cfg=Py_3_9".to_owned(),
3890 "cargo:rustc-cfg=Py_3_10".to_owned(),
3891 "cargo:rustc-cfg=Py_3_11".to_owned(),
3892 "cargo:rustc-cfg=PyPy".to_owned(),
3893 ]
3894 );
3895
3896 let interpreter_config =
3897 InterpreterConfigBuilder::new(PythonImplementation::RustPython, version)
3898 .finalize()
3899 .unwrap();
3900 assert_eq!(
3901 interpreter_config.build_script_outputs(),
3902 [
3903 "cargo:rustc-cfg=Py_3_8".to_owned(),
3904 "cargo:rustc-cfg=Py_3_9".to_owned(),
3905 "cargo:rustc-cfg=Py_3_10".to_owned(),
3906 "cargo:rustc-cfg=Py_3_11".to_owned(),
3907 "cargo:rustc-cfg=RustPython".to_owned(),
3908 "cargo:rustc-cfg=Py_LIMITED_API".to_owned(),
3909 "cargo:rustc-cfg=Py_GIL_DISABLED".to_owned(),
3910 ]
3911 );
3912 }
3913
3914 #[test]
3915 fn test_build_script_outputs_abi3() {
3916 let implementation = PythonImplementation::CPython;
3917 let version = PythonVersion::PY39;
3918 let interpreter_config = InterpreterConfigBuilder::new(implementation, version)
3919 .stable_abi(StableAbi::Abi3)
3920 .finalize()
3921 .unwrap();
3922
3923 assert_eq!(
3924 interpreter_config.build_script_outputs(),
3925 [
3926 "cargo:rustc-cfg=Py_3_8".to_owned(),
3927 "cargo:rustc-cfg=Py_3_9".to_owned(),
3928 "cargo:rustc-cfg=Py_LIMITED_API".to_owned(),
3929 ]
3930 );
3931
3932 let interpreter_config = InterpreterConfigBuilder::new(PythonImplementation::PyPy, version)
3933 .stable_abi(StableAbi::Abi3)
3934 .finalize()
3935 .unwrap();
3936 assert_eq!(
3937 interpreter_config.build_script_outputs(),
3938 [
3939 "cargo:rustc-cfg=Py_3_8".to_owned(),
3940 "cargo:rustc-cfg=Py_3_9".to_owned(),
3941 "cargo:rustc-cfg=PyPy".to_owned(),
3942 "cargo:rustc-cfg=Py_LIMITED_API".to_owned(),
3943 ]
3944 );
3945
3946 let interpreter_config =
3947 InterpreterConfigBuilder::new(PythonImplementation::CPython, PythonVersion::PY315)
3948 .stable_abi(StableAbi::Abi3)
3949 .finalize()
3950 .unwrap();
3951 assert_eq!(
3952 interpreter_config.build_script_outputs(),
3953 [
3954 "cargo:rustc-cfg=Py_3_8".to_owned(),
3955 "cargo:rustc-cfg=Py_3_9".to_owned(),
3956 "cargo:rustc-cfg=Py_3_10".to_owned(),
3957 "cargo:rustc-cfg=Py_3_11".to_owned(),
3958 "cargo:rustc-cfg=Py_3_12".to_owned(),
3959 "cargo:rustc-cfg=Py_3_13".to_owned(),
3960 "cargo:rustc-cfg=Py_3_14".to_owned(),
3961 "cargo:rustc-cfg=Py_3_15".to_owned(),
3962 "cargo:rustc-cfg=Py_LIMITED_API".to_owned(),
3963 ]
3964 );
3965 }
3966
3967 #[test]
3968 fn test_build_script_outputs_gil_disabled() {
3969 let interpreter_config =
3970 InterpreterConfigBuilder::new(PythonImplementation::CPython, PythonVersion::PY313)
3971 .free_threaded()
3972 .unwrap()
3973 .finalize()
3974 .unwrap();
3975 assert_eq!(
3976 interpreter_config.build_script_outputs(),
3977 [
3978 "cargo:rustc-cfg=Py_3_8".to_owned(),
3979 "cargo:rustc-cfg=Py_3_9".to_owned(),
3980 "cargo:rustc-cfg=Py_3_10".to_owned(),
3981 "cargo:rustc-cfg=Py_3_11".to_owned(),
3982 "cargo:rustc-cfg=Py_3_12".to_owned(),
3983 "cargo:rustc-cfg=Py_3_13".to_owned(),
3984 "cargo:rustc-cfg=Py_GIL_DISABLED".to_owned(),
3985 ]
3986 );
3987 }
3988
3989 #[test]
3990 fn test_interpreter_config_builder_gil_disabled_flag() {
3991 let builder = InterpreterConfigBuilder::new(
3992 PythonImplementation::CPython,
3993 PythonVersion {
3994 major: 3,
3995 minor: 14,
3996 },
3997 );
3998 let mut flags = BuildFlags::new();
3999 flags.0.insert(BuildFlag::Py_GIL_DISABLED);
4000 let config = builder
4001 .stable_abi(StableAbi::Abi3)
4002 .build_flags(flags)
4003 .finalize()
4004 .unwrap();
4005 assert!(config.target_abi.kind() == PythonAbiKind::VersionSpecific(GilUsed::FreeThreaded));
4007
4008 let builder = InterpreterConfigBuilder::new(
4011 PythonImplementation::CPython,
4012 PythonVersion {
4013 major: 3,
4014 minor: 14,
4015 },
4016 );
4017 let mut flags = BuildFlags::new();
4018 flags.0.insert(BuildFlag::Py_GIL_DISABLED);
4019 let config = builder
4020 .build_flags(flags)
4021 .stable_abi(StableAbi::Abi3)
4022 .finalize()
4023 .unwrap();
4024 assert!(config.target_abi.kind() == PythonAbiKind::VersionSpecific(GilUsed::FreeThreaded));
4025
4026 let builder = InterpreterConfigBuilder::new(
4029 PythonImplementation::CPython,
4030 PythonVersion {
4031 major: 3,
4032 minor: 14,
4033 },
4034 );
4035 let target_abi = PythonAbiBuilder::new(
4036 PythonImplementation::CPython,
4037 PythonVersion {
4038 major: 3,
4039 minor: 14,
4040 },
4041 )
4042 .finalize()
4043 .unwrap();
4044 let mut flags = BuildFlags::new();
4045 flags.0.insert(BuildFlag::Py_GIL_DISABLED);
4046 assert!(builder
4047 .target_abi(target_abi)
4048 .build_flags(flags)
4049 .finalize()
4050 .is_err());
4051
4052 let builder = InterpreterConfigBuilder::new(
4053 PythonImplementation::CPython,
4054 PythonVersion {
4055 major: 3,
4056 minor: 14,
4057 },
4058 );
4059 let config = builder.free_threaded().unwrap().finalize().unwrap();
4060 assert!(config.target_abi.kind().is_free_threaded());
4061 assert!(config.build_flags.0.contains(&BuildFlag::Py_GIL_DISABLED));
4062 }
4063
4064 #[test]
4065 fn test_build_script_outputs_debug() {
4066 let mut build_flags = BuildFlags::default();
4067 build_flags.0.insert(BuildFlag::Py_DEBUG);
4068 let implementation = PythonImplementation::CPython;
4069 let version = PythonVersion::PY38;
4070 let interpreter_config = InterpreterConfigBuilder::new(implementation, version)
4071 .build_flags(build_flags)
4072 .finalize()
4073 .unwrap();
4074 assert_eq!(
4075 interpreter_config.build_script_outputs(),
4076 [
4077 "cargo:rustc-cfg=Py_3_8".to_owned(),
4078 "cargo:rustc-cfg=py_sys_config=\"Py_DEBUG\"".to_owned(),
4079 ]
4080 );
4081 }
4082
4083 #[test]
4084 fn test_find_sysconfigdata_in_invalid_lib_dir() {
4085 let e = find_all_sysconfigdata(&CrossCompileConfig {
4086 lib_dir: Some(PathBuf::from("/abc/123/not/a/real/path")),
4087 version: None,
4088 implementation: None,
4089 target: triple!("x86_64-unknown-linux-gnu"),
4090 abiflags: None,
4091 })
4092 .unwrap_err();
4093
4094 assert!(e.report().to_string().starts_with(
4096 "failed to search the lib dir at 'PYO3_CROSS_LIB_DIR=/abc/123/not/a/real/path'\n\
4097 caused by:\n \
4098 - 0: failed to list the entries in '/abc/123/not/a/real/path'\n \
4099 - 1: \
4100 "
4101 ));
4102 }
4103
4104 #[test]
4105 fn test_from_pyo3_config_file_env_rebuild() {
4106 READ_ENV_VARS.with(|vars| vars.borrow_mut().clear());
4107 let _ = InterpreterConfig::from_pyo3_config_file_env(&Triple::host());
4108 READ_ENV_VARS.with(|vars| assert!(vars.borrow().contains(&"PYO3_CONFIG_FILE".to_string())));
4110 }
4111
4112 #[test]
4113 fn test_default_lib_name_for_target() {
4114 let cpython = PythonImplementation::CPython;
4115 let pypy = PythonImplementation::PyPy;
4116 let py39 = PythonVersion::PY39;
4117 let py311 = PythonVersion {
4118 major: 3,
4119 minor: 11,
4120 };
4121 let py313 = PythonVersion {
4122 major: 3,
4123 minor: 13,
4124 };
4125 let cpy39 = PythonAbiBuilder::new(cpython, py39).finalize().unwrap();
4126 let pypy311 = PythonAbiBuilder::new(pypy, py311).finalize().unwrap();
4127 let cpy313t = PythonAbiBuilder::new(cpython, py313)
4128 .free_threaded()
4129 .finalize()
4130 .unwrap();
4131 let cpy313_abi3 = PythonAbiBuilder::new(cpython, py313)
4132 .stable_abi(StableAbi::Abi3)
4133 .finalize()
4134 .unwrap();
4135
4136 let unix = Triple::from_str("x86_64-unknown-linux-gnu").unwrap();
4137 let win_x64 = Triple::from_str("x86_64-pc-windows-msvc").unwrap();
4138 let win_arm64 = Triple::from_str("aarch64-pc-windows-msvc").unwrap();
4139
4140 let lib_name = default_lib_name_for_target(cpy39, &unix);
4141 assert_eq!(lib_name, "python3.9");
4142
4143 let lib_name = default_lib_name_for_target(cpy39, &win_x64);
4144 assert_eq!(lib_name, "python39");
4145
4146 let lib_name = default_lib_name_for_target(cpy39, &win_arm64);
4147 assert_eq!(lib_name, "python39");
4148
4149 let lib_name = default_lib_name_for_target(pypy311, &unix);
4151 assert_eq!(lib_name, "pypy3.11-c");
4152
4153 let lib_name = default_lib_name_for_target(pypy311, &win_x64);
4154 assert_eq!(lib_name, "libpypy3.11-c");
4155
4156 let lib_name = default_lib_name_for_target(cpy313t, &unix);
4158 assert_eq!(lib_name, "python3.13t");
4159
4160 let lib_name = default_lib_name_for_target(cpy313t, &win_x64);
4161 assert_eq!(lib_name, "python313t");
4162
4163 let lib_name = default_lib_name_for_target(cpy313t, &win_arm64);
4164 assert_eq!(lib_name, "python313t");
4165
4166 let lib_name = default_lib_name_for_target(cpy313_abi3, &unix);
4168 assert_eq!(lib_name, "python3.13");
4169
4170 let lib_name = default_lib_name_for_target(cpy313_abi3, &win_x64);
4171 assert_eq!(lib_name, "python3");
4172
4173 let lib_name = default_lib_name_for_target(cpy313_abi3, &win_arm64);
4174 assert_eq!(lib_name, "python3");
4175 }
4176}