1#[cfg(test)]
5use std::cell::RefCell;
6use std::{
7 collections::{HashMap, HashSet},
8 env,
9 ffi::{OsStr, OsString},
10 fmt::Display,
11 fs::{self, DirEntry},
12 io::{BufRead, BufReader, Read, Write},
13 path::{Path, PathBuf},
14 process::{Command, Stdio},
15 str::{self, FromStr},
16};
17
18pub use target_lexicon::Triple;
19
20use target_lexicon::{Architecture, Environment, OperatingSystem, Vendor};
21
22use crate::{
23 bail, ensure,
24 errors::{Context, Error, Result},
25 warn,
26};
27
28pub(crate) const MINIMUM_SUPPORTED_VERSION: PythonVersion = PythonVersion { major: 3, minor: 8 };
30
31pub(crate) const MINIMUM_SUPPORTED_VERSION_PYPY: PythonVersion = PythonVersion {
32 major: 3,
33 minor: 11,
34};
35pub(crate) const MAXIMUM_SUPPORTED_VERSION_PYPY: PythonVersion = PythonVersion {
36 major: 3,
37 minor: 11,
38};
39
40const MINIMUM_SUPPORTED_VERSION_GRAALPY: PythonVersion = PythonVersion {
42 major: 25,
43 minor: 0,
44};
45
46pub(crate) const ABI3_MAX_MINOR: u8 = 14;
48
49#[cfg(test)]
50thread_local! {
51 static READ_ENV_VARS: RefCell<Vec<String>> = const { RefCell::new(Vec::new()) };
52}
53
54pub fn cargo_env_var(var: &str) -> Option<String> {
58 env::var_os(var).map(|os_string| os_string.to_str().unwrap().into())
59}
60
61pub fn env_var(var: &str) -> Option<OsString> {
64 if cfg!(feature = "resolve-config") {
65 println!("cargo:rerun-if-env-changed={var}");
66 }
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
86#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
94pub struct InterpreterConfig {
95 pub implementation: PythonImplementation,
99
100 pub version: PythonVersion,
104
105 pub shared: bool,
109
110 pub abi3: bool,
114
115 pub lib_name: Option<String>,
123
124 pub lib_dir: Option<String>,
131
132 pub executable: Option<String>,
140
141 pub pointer_width: Option<u32>,
145
146 pub build_flags: BuildFlags,
150
151 pub suppress_build_script_link_lines: bool,
162
163 pub extra_build_script_lines: Vec<String>,
174 pub python_framework_prefix: Option<String>,
176}
177
178impl InterpreterConfig {
179 #[doc(hidden)]
180 pub fn build_script_outputs(&self) -> Vec<String> {
181 assert!(self.version >= MINIMUM_SUPPORTED_VERSION);
183
184 let mut out = vec![];
185
186 for i in MINIMUM_SUPPORTED_VERSION.minor..=self.version.minor {
187 out.push(format!("cargo:rustc-cfg=Py_3_{i}"));
188 }
189
190 match self.implementation {
191 PythonImplementation::CPython => {}
192 PythonImplementation::PyPy => out.push("cargo:rustc-cfg=PyPy".to_owned()),
193 PythonImplementation::GraalPy => out.push("cargo:rustc-cfg=GraalPy".to_owned()),
194 }
195
196 if self.abi3 && !self.is_free_threaded() {
198 out.push("cargo:rustc-cfg=Py_LIMITED_API".to_owned());
199 }
200
201 for flag in &self.build_flags.0 {
202 match flag {
203 BuildFlag::Py_GIL_DISABLED => {
204 out.push("cargo:rustc-cfg=Py_GIL_DISABLED".to_owned())
205 }
206 flag => out.push(format!("cargo:rustc-cfg=py_sys_config=\"{flag}\"")),
207 }
208 }
209
210 out
211 }
212
213 #[doc(hidden)]
214 pub fn from_interpreter(interpreter: impl AsRef<Path>) -> Result<Self> {
215 const SCRIPT: &str = r#"
216# Allow the script to run on Python 2, so that nicer error can be printed later.
217from __future__ import print_function
218
219import os.path
220import platform
221import struct
222import sys
223from sysconfig import get_config_var, get_platform
224
225PYPY = platform.python_implementation() == "PyPy"
226GRAALPY = platform.python_implementation() == "GraalVM"
227
228if GRAALPY:
229 graalpy_ver = map(int, __graalpython__.get_graalvm_version().split('.'));
230 print("graalpy_major", next(graalpy_ver))
231 print("graalpy_minor", next(graalpy_ver))
232
233# sys.base_prefix is missing on Python versions older than 3.3; this allows the script to continue
234# so that the version mismatch can be reported in a nicer way later.
235base_prefix = getattr(sys, "base_prefix", None)
236
237if base_prefix:
238 # Anaconda based python distributions have a static python executable, but include
239 # the shared library. Use the shared library for embedding to avoid rust trying to
240 # LTO the static library (and failing with newer gcc's, because it is old).
241 ANACONDA = os.path.exists(os.path.join(base_prefix, "conda-meta"))
242else:
243 ANACONDA = False
244
245def print_if_set(varname, value):
246 if value is not None:
247 print(varname, value)
248
249# Windows always uses shared linking
250WINDOWS = platform.system() == "Windows"
251
252# macOS framework packages use shared linking
253FRAMEWORK = bool(get_config_var("PYTHONFRAMEWORK"))
254FRAMEWORK_PREFIX = get_config_var("PYTHONFRAMEWORKPREFIX")
255
256# unix-style shared library enabled
257SHARED = bool(get_config_var("Py_ENABLE_SHARED"))
258
259print("implementation", platform.python_implementation())
260print("version_major", sys.version_info[0])
261print("version_minor", sys.version_info[1])
262print("shared", PYPY or GRAALPY or ANACONDA or WINDOWS or FRAMEWORK or SHARED)
263print("python_framework_prefix", FRAMEWORK_PREFIX)
264print_if_set("ld_version", get_config_var("LDVERSION"))
265print_if_set("libdir", get_config_var("LIBDIR"))
266print_if_set("base_prefix", base_prefix)
267print("executable", sys.executable)
268print("calcsize_pointer", struct.calcsize("P"))
269print("mingw", get_platform().startswith("mingw"))
270print("cygwin", get_platform().startswith("cygwin"))
271print("ext_suffix", get_config_var("EXT_SUFFIX"))
272print("gil_disabled", get_config_var("Py_GIL_DISABLED"))
273"#;
274 let output = run_python_script(interpreter.as_ref(), SCRIPT)?;
275 let map: HashMap<String, String> = parse_script_output(&output);
276
277 ensure!(
278 !map.is_empty(),
279 "broken Python interpreter: {}",
280 interpreter.as_ref().display()
281 );
282
283 if let Some(value) = map.get("graalpy_major") {
284 let graalpy_version = PythonVersion {
285 major: value
286 .parse()
287 .context("failed to parse GraalPy major version")?,
288 minor: map["graalpy_minor"]
289 .parse()
290 .context("failed to parse GraalPy minor version")?,
291 };
292 ensure!(
293 graalpy_version >= MINIMUM_SUPPORTED_VERSION_GRAALPY,
294 "At least GraalPy version {} needed, got {}",
295 MINIMUM_SUPPORTED_VERSION_GRAALPY,
296 graalpy_version
297 );
298 };
299
300 let shared = map["shared"].as_str() == "True";
301 let python_framework_prefix = map.get("python_framework_prefix").cloned();
302
303 let version = PythonVersion {
304 major: map["version_major"]
305 .parse()
306 .context("failed to parse major version")?,
307 minor: map["version_minor"]
308 .parse()
309 .context("failed to parse minor version")?,
310 };
311
312 let abi3 = is_abi3();
313
314 let implementation = map["implementation"].parse()?;
315
316 let gil_disabled = match map["gil_disabled"].as_str() {
317 "1" => true,
318 "0" => false,
319 "None" => false,
320 _ => panic!("Unknown Py_GIL_DISABLED value"),
321 };
322
323 let cygwin = map["cygwin"].as_str() == "True";
324
325 let lib_name = if cfg!(windows) {
326 default_lib_name_windows(
327 version,
328 implementation,
329 abi3,
330 map["mingw"].as_str() == "True",
331 map["ext_suffix"].starts_with("_d."),
335 gil_disabled,
336 )?
337 } else {
338 default_lib_name_unix(
339 version,
340 implementation,
341 abi3,
342 cygwin,
343 map.get("ld_version").map(String::as_str),
344 gil_disabled,
345 )?
346 };
347
348 let lib_dir = if cfg!(windows) {
349 map.get("base_prefix")
350 .map(|base_prefix| format!("{base_prefix}\\libs"))
351 } else {
352 map.get("libdir").cloned()
353 };
354
355 let calcsize_pointer: u32 = map["calcsize_pointer"]
361 .parse()
362 .context("failed to parse calcsize_pointer")?;
363
364 Ok(InterpreterConfig {
365 version,
366 implementation,
367 shared,
368 abi3,
369 lib_name: Some(lib_name),
370 lib_dir,
371 executable: map.get("executable").cloned(),
372 pointer_width: Some(calcsize_pointer * 8),
373 build_flags: BuildFlags::from_interpreter(interpreter)?,
374 suppress_build_script_link_lines: false,
375 extra_build_script_lines: vec![],
376 python_framework_prefix,
377 })
378 }
379
380 pub fn from_sysconfigdata(sysconfigdata: &Sysconfigdata) -> Result<Self> {
385 macro_rules! get_key {
386 ($sysconfigdata:expr, $key:literal) => {
387 $sysconfigdata
388 .get_value($key)
389 .ok_or(concat!($key, " not found in sysconfigdata file"))
390 };
391 }
392
393 macro_rules! parse_key {
394 ($sysconfigdata:expr, $key:literal) => {
395 get_key!($sysconfigdata, $key)?
396 .parse()
397 .context(concat!("could not parse value of ", $key))
398 };
399 }
400
401 let soabi = get_key!(sysconfigdata, "SOABI")?;
402 let implementation = PythonImplementation::from_soabi(soabi)?;
403 let version = parse_key!(sysconfigdata, "VERSION")?;
404 let shared = match sysconfigdata.get_value("Py_ENABLE_SHARED") {
405 Some("1") | Some("true") | Some("True") => true,
406 Some("0") | Some("false") | Some("False") => false,
407 _ => bail!("expected a bool (1/true/True or 0/false/False) for Py_ENABLE_SHARED"),
408 };
409 let framework = match sysconfigdata.get_value("PYTHONFRAMEWORK") {
411 Some(s) => !s.is_empty(),
412 _ => false,
413 };
414 let python_framework_prefix = sysconfigdata
415 .get_value("PYTHONFRAMEWORKPREFIX")
416 .map(str::to_string);
417 let lib_dir = get_key!(sysconfigdata, "LIBDIR").ok().map(str::to_string);
418 let gil_disabled = match sysconfigdata.get_value("Py_GIL_DISABLED") {
419 Some(value) => value == "1",
420 None => false,
421 };
422 let cygwin = soabi.ends_with("cygwin");
423 let abi3 = is_abi3();
424 let lib_name = Some(default_lib_name_unix(
425 version,
426 implementation,
427 abi3,
428 cygwin,
429 sysconfigdata.get_value("LDVERSION"),
430 gil_disabled,
431 )?);
432 let pointer_width = parse_key!(sysconfigdata, "SIZEOF_VOID_P")
433 .map(|bytes_width: u32| bytes_width * 8)
434 .ok();
435 let build_flags = BuildFlags::from_sysconfigdata(sysconfigdata);
436
437 Ok(InterpreterConfig {
438 implementation,
439 version,
440 shared: shared || framework,
441 abi3,
442 lib_dir,
443 lib_name,
444 executable: None,
445 pointer_width,
446 build_flags,
447 suppress_build_script_link_lines: false,
448 extra_build_script_lines: vec![],
449 python_framework_prefix,
450 })
451 }
452
453 #[allow(dead_code)] pub(super) fn from_pyo3_config_file_env() -> Option<Result<Self>> {
458 env_var("PYO3_CONFIG_FILE").map(|path| {
459 let path = Path::new(&path);
460 println!("cargo:rerun-if-changed={}", path.display());
461 ensure!(
464 path.is_absolute(),
465 "PYO3_CONFIG_FILE must be an absolute path"
466 );
467
468 let mut config = InterpreterConfig::from_path(path)
469 .context("failed to parse contents of PYO3_CONFIG_FILE")?;
470 config.abi3 |= is_abi3();
476 config.fixup_for_abi3_version(get_abi3_version())?;
477
478 Ok(config)
479 })
480 }
481
482 #[doc(hidden)]
483 pub fn from_path(path: impl AsRef<Path>) -> Result<Self> {
484 let path = path.as_ref();
485 let config_file = std::fs::File::open(path)
486 .with_context(|| format!("failed to open PyO3 config file at {}", path.display()))?;
487 let reader = std::io::BufReader::new(config_file);
488 InterpreterConfig::from_reader(reader)
489 }
490
491 #[doc(hidden)]
492 pub fn from_cargo_dep_env() -> Option<Result<Self>> {
493 cargo_env_var("DEP_PYTHON_PYO3_CONFIG")
494 .map(|buf| InterpreterConfig::from_reader(&*unescape(&buf)))
495 }
496
497 #[doc(hidden)]
498 pub fn from_reader(reader: impl Read) -> Result<Self> {
499 let reader = BufReader::new(reader);
500 let lines = reader.lines();
501
502 macro_rules! parse_value {
503 ($variable:ident, $value:ident) => {
504 $variable = Some($value.trim().parse().context(format!(
505 concat!(
506 "failed to parse ",
507 stringify!($variable),
508 " from config value '{}'"
509 ),
510 $value
511 ))?)
512 };
513 }
514
515 let mut implementation = None;
516 let mut version = None;
517 let mut shared = None;
518 let mut abi3 = None;
519 let mut lib_name = None;
520 let mut lib_dir = None;
521 let mut executable = None;
522 let mut pointer_width = None;
523 let mut build_flags: Option<BuildFlags> = None;
524 let mut suppress_build_script_link_lines = None;
525 let mut extra_build_script_lines = vec![];
526 let mut python_framework_prefix = None;
527
528 for (i, line) in lines.enumerate() {
529 let line = line.context("failed to read line from config")?;
530 let mut split = line.splitn(2, '=');
531 let (key, value) = (
532 split
533 .next()
534 .expect("first splitn value should always be present"),
535 split
536 .next()
537 .ok_or_else(|| format!("expected key=value pair on line {}", i + 1))?,
538 );
539 match key {
540 "implementation" => parse_value!(implementation, value),
541 "version" => parse_value!(version, value),
542 "shared" => parse_value!(shared, value),
543 "abi3" => parse_value!(abi3, value),
544 "lib_name" => parse_value!(lib_name, value),
545 "lib_dir" => parse_value!(lib_dir, value),
546 "executable" => parse_value!(executable, value),
547 "pointer_width" => parse_value!(pointer_width, value),
548 "build_flags" => parse_value!(build_flags, value),
549 "suppress_build_script_link_lines" => {
550 parse_value!(suppress_build_script_link_lines, value)
551 }
552 "extra_build_script_line" => {
553 extra_build_script_lines.push(value.to_string());
554 }
555 "python_framework_prefix" => parse_value!(python_framework_prefix, value),
556 unknown => warn!("unknown config key `{}`", unknown),
557 }
558 }
559
560 let version = version.ok_or("missing value for version")?;
561 let implementation = implementation.unwrap_or(PythonImplementation::CPython);
562 let abi3 = abi3.unwrap_or(false);
563 let build_flags = build_flags.unwrap_or_default();
564
565 Ok(InterpreterConfig {
566 implementation,
567 version,
568 shared: shared.unwrap_or(true),
569 abi3,
570 lib_name,
571 lib_dir,
572 executable,
573 pointer_width,
574 build_flags,
575 suppress_build_script_link_lines: suppress_build_script_link_lines.unwrap_or(false),
576 extra_build_script_lines,
577 python_framework_prefix,
578 })
579 }
580
581 #[cfg(any(test, feature = "resolve-config"))]
587 pub(crate) fn apply_default_lib_name_to_config_file(&mut self, target: &Triple) {
588 if self.lib_name.is_none() {
589 self.lib_name = Some(default_lib_name_for_target(
590 self.version,
591 self.implementation,
592 self.abi3,
593 self.is_free_threaded(),
594 target,
595 ));
596 }
597 }
598
599 #[doc(hidden)]
600 pub fn to_cargo_dep_env(&self) -> Result<()> {
611 let mut buf = Vec::new();
612 self.to_writer(&mut buf)?;
613 println!("cargo:PYO3_CONFIG={}", escape(&buf));
615 Ok(())
616 }
617
618 #[doc(hidden)]
619 pub fn to_writer(&self, mut writer: impl Write) -> Result<()> {
620 macro_rules! write_line {
621 ($value:ident) => {
622 writeln!(writer, "{}={}", stringify!($value), self.$value).context(concat!(
623 "failed to write ",
624 stringify!($value),
625 " to config"
626 ))
627 };
628 }
629
630 macro_rules! write_option_line {
631 ($value:ident) => {
632 if let Some(value) = &self.$value {
633 writeln!(writer, "{}={}", stringify!($value), value).context(concat!(
634 "failed to write ",
635 stringify!($value),
636 " to config"
637 ))
638 } else {
639 Ok(())
640 }
641 };
642 }
643
644 write_line!(implementation)?;
645 write_line!(version)?;
646 write_line!(shared)?;
647 write_line!(abi3)?;
648 write_option_line!(lib_name)?;
649 write_option_line!(lib_dir)?;
650 write_option_line!(executable)?;
651 write_option_line!(pointer_width)?;
652 write_line!(build_flags)?;
653 write_option_line!(python_framework_prefix)?;
654 write_line!(suppress_build_script_link_lines)?;
655 for line in &self.extra_build_script_lines {
656 writeln!(writer, "extra_build_script_line={line}")
657 .context("failed to write extra_build_script_line")?;
658 }
659 Ok(())
660 }
661
662 pub fn run_python_script(&self, script: &str) -> Result<String> {
668 run_python_script_with_envs(
669 Path::new(self.executable.as_ref().expect("no interpreter executable")),
670 script,
671 std::iter::empty::<(&str, &str)>(),
672 )
673 }
674
675 pub fn run_python_script_with_envs<I, K, V>(&self, script: &str, envs: I) -> Result<String>
682 where
683 I: IntoIterator<Item = (K, V)>,
684 K: AsRef<OsStr>,
685 V: AsRef<OsStr>,
686 {
687 run_python_script_with_envs(
688 Path::new(self.executable.as_ref().expect("no interpreter executable")),
689 script,
690 envs,
691 )
692 }
693
694 pub fn is_free_threaded(&self) -> bool {
695 self.build_flags.0.contains(&BuildFlag::Py_GIL_DISABLED)
696 }
697
698 fn fixup_for_abi3_version(&mut self, abi3_version: Option<PythonVersion>) -> Result<()> {
701 if self.implementation.is_pypy()
703 || self.implementation.is_graalpy()
704 || self.is_free_threaded()
705 {
706 return Ok(());
707 }
708
709 if let Some(version) = abi3_version {
710 ensure!(
711 version <= self.version,
712 "cannot set a minimum Python version {} higher than the interpreter version {} \
713 (the minimum Python version is implied by the abi3-py3{} feature)",
714 version,
715 self.version,
716 version.minor,
717 );
718
719 self.version = version;
720 } else if is_abi3() && self.version.minor > ABI3_MAX_MINOR {
721 warn!("Automatically falling back to abi3-py3{ABI3_MAX_MINOR} because current Python is higher than the maximum supported");
722 self.version.minor = ABI3_MAX_MINOR;
723 }
724
725 Ok(())
726 }
727}
728
729#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
730pub struct PythonVersion {
731 pub major: u8,
732 pub minor: u8,
733}
734
735impl PythonVersion {
736 pub const PY315: Self = PythonVersion {
737 major: 3,
738 minor: 15,
739 };
740 pub const PY313: Self = PythonVersion {
741 major: 3,
742 minor: 13,
743 };
744 pub const PY312: Self = PythonVersion {
745 major: 3,
746 minor: 12,
747 };
748 const PY310: Self = PythonVersion {
749 major: 3,
750 minor: 10,
751 };
752}
753
754impl Display for PythonVersion {
755 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
756 write!(f, "{}.{}", self.major, self.minor)
757 }
758}
759
760impl FromStr for PythonVersion {
761 type Err = crate::errors::Error;
762
763 fn from_str(value: &str) -> Result<Self, Self::Err> {
764 let mut split = value.splitn(2, '.');
765 let (major, minor) = (
766 split
767 .next()
768 .expect("first splitn value should always be present"),
769 split.next().ok_or("expected major.minor version")?,
770 );
771 Ok(Self {
772 major: major.parse().context("failed to parse major version")?,
773 minor: minor.parse().context("failed to parse minor version")?,
774 })
775 }
776}
777
778#[derive(Debug, Copy, Clone, PartialEq, Eq)]
779pub enum PythonImplementation {
780 CPython,
781 PyPy,
782 GraalPy,
783}
784
785impl PythonImplementation {
786 #[doc(hidden)]
787 pub fn is_pypy(self) -> bool {
788 self == PythonImplementation::PyPy
789 }
790
791 #[doc(hidden)]
792 pub fn is_graalpy(self) -> bool {
793 self == PythonImplementation::GraalPy
794 }
795
796 #[doc(hidden)]
797 pub fn from_soabi(soabi: &str) -> Result<Self> {
798 if soabi.starts_with("pypy") {
799 Ok(PythonImplementation::PyPy)
800 } else if soabi.starts_with("cpython") {
801 Ok(PythonImplementation::CPython)
802 } else if soabi.starts_with("graalpy") {
803 Ok(PythonImplementation::GraalPy)
804 } else {
805 bail!("unsupported Python interpreter");
806 }
807 }
808}
809
810impl Display for PythonImplementation {
811 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
812 match self {
813 PythonImplementation::CPython => write!(f, "CPython"),
814 PythonImplementation::PyPy => write!(f, "PyPy"),
815 PythonImplementation::GraalPy => write!(f, "GraalVM"),
816 }
817 }
818}
819
820impl FromStr for PythonImplementation {
821 type Err = Error;
822 fn from_str(s: &str) -> Result<Self> {
823 match s {
824 "CPython" => Ok(PythonImplementation::CPython),
825 "PyPy" => Ok(PythonImplementation::PyPy),
826 "GraalVM" => Ok(PythonImplementation::GraalPy),
827 _ => bail!("unknown interpreter: {}", s),
828 }
829 }
830}
831
832fn have_python_interpreter() -> bool {
837 env_var("PYO3_NO_PYTHON").is_none()
838}
839
840fn is_abi3() -> bool {
844 cargo_env_var("CARGO_FEATURE_ABI3").is_some()
845 || env_var("PYO3_USE_ABI3_FORWARD_COMPATIBILITY").is_some_and(|os_str| os_str == "1")
846}
847
848pub fn get_abi3_version() -> Option<PythonVersion> {
852 let minor_version = (MINIMUM_SUPPORTED_VERSION.minor..=ABI3_MAX_MINOR)
853 .find(|i| cargo_env_var(&format!("CARGO_FEATURE_ABI3_PY3{i}")).is_some());
854 minor_version.map(|minor| PythonVersion { major: 3, minor })
855}
856
857pub fn is_extension_module() -> bool {
865 cargo_env_var("CARGO_FEATURE_EXTENSION_MODULE").is_some()
866 || env_var("PYO3_BUILD_EXTENSION_MODULE").is_some()
867}
868
869pub fn is_linking_libpython_for_target(target: &Triple) -> bool {
873 target.operating_system == OperatingSystem::Windows
874 || target.operating_system == OperatingSystem::Aix
876 || target.environment == Environment::Android
877 || target.environment == Environment::Androideabi
878 || target.operating_system == OperatingSystem::Cygwin
879 || matches!(target.operating_system, OperatingSystem::IOS(_))
880 || !is_extension_module()
881}
882
883fn require_libdir_for_target(target: &Triple) -> bool {
888 if target.operating_system == OperatingSystem::Windows {
891 return false;
892 }
893
894 is_linking_libpython_for_target(target)
895}
896
897#[derive(Debug, PartialEq, Eq)]
902pub struct CrossCompileConfig {
903 pub lib_dir: Option<PathBuf>,
905
906 version: Option<PythonVersion>,
908
909 implementation: Option<PythonImplementation>,
911
912 target: Triple,
914
915 abiflags: Option<String>,
917}
918
919impl CrossCompileConfig {
920 fn try_from_env_vars_host_target(
925 env_vars: CrossCompileEnvVars,
926 host: &Triple,
927 target: &Triple,
928 ) -> Result<Option<Self>> {
929 if env_vars.any() || Self::is_cross_compiling_from_to(host, target) {
930 let lib_dir = env_vars.lib_dir_path()?;
931 let (version, abiflags) = env_vars.parse_version()?;
932 let implementation = env_vars.parse_implementation()?;
933 let target = target.clone();
934
935 Ok(Some(CrossCompileConfig {
936 lib_dir,
937 version,
938 implementation,
939 target,
940 abiflags,
941 }))
942 } else {
943 Ok(None)
944 }
945 }
946
947 fn is_cross_compiling_from_to(host: &Triple, target: &Triple) -> bool {
951 let mut compatible = host.architecture == target.architecture
955 && (host.vendor == target.vendor
956 || (host.vendor == Vendor::Pc && target.vendor.as_str() == "win7"))
958 && host.operating_system == target.operating_system;
959
960 compatible |= target.operating_system == OperatingSystem::Windows
962 && host.operating_system == OperatingSystem::Windows
963 && matches!(target.architecture, Architecture::X86_32(_))
964 && host.architecture == Architecture::X86_64;
965
966 compatible |= matches!(target.operating_system, OperatingSystem::Darwin(_))
968 && matches!(host.operating_system, OperatingSystem::Darwin(_));
969
970 compatible |= matches!(target.operating_system, OperatingSystem::IOS(_));
971
972 !compatible
973 }
974
975 #[allow(dead_code)]
980 fn lib_dir_string(&self) -> Option<String> {
981 self.lib_dir
982 .as_ref()
983 .map(|s| s.to_str().unwrap().to_owned())
984 }
985}
986
987struct CrossCompileEnvVars {
989 pyo3_cross: Option<OsString>,
991 pyo3_cross_lib_dir: Option<OsString>,
993 pyo3_cross_python_version: Option<OsString>,
995 pyo3_cross_python_implementation: Option<OsString>,
997}
998
999impl CrossCompileEnvVars {
1000 fn from_env() -> Self {
1004 CrossCompileEnvVars {
1005 pyo3_cross: env_var("PYO3_CROSS"),
1006 pyo3_cross_lib_dir: env_var("PYO3_CROSS_LIB_DIR"),
1007 pyo3_cross_python_version: env_var("PYO3_CROSS_PYTHON_VERSION"),
1008 pyo3_cross_python_implementation: env_var("PYO3_CROSS_PYTHON_IMPLEMENTATION"),
1009 }
1010 }
1011
1012 fn any(&self) -> bool {
1014 self.pyo3_cross.is_some()
1015 || self.pyo3_cross_lib_dir.is_some()
1016 || self.pyo3_cross_python_version.is_some()
1017 || self.pyo3_cross_python_implementation.is_some()
1018 }
1019
1020 fn parse_version(&self) -> Result<(Option<PythonVersion>, Option<String>)> {
1023 match self.pyo3_cross_python_version.as_ref() {
1024 Some(os_string) => {
1025 let utf8_str = os_string
1026 .to_str()
1027 .ok_or("PYO3_CROSS_PYTHON_VERSION is not valid a UTF-8 string")?;
1028 let (utf8_str, abiflags) = if let Some(version) = utf8_str.strip_suffix('t') {
1029 (version, Some("t".to_string()))
1030 } else {
1031 (utf8_str, None)
1032 };
1033 let version = utf8_str
1034 .parse()
1035 .context("failed to parse PYO3_CROSS_PYTHON_VERSION")?;
1036 Ok((Some(version), abiflags))
1037 }
1038 None => Ok((None, None)),
1039 }
1040 }
1041
1042 fn parse_implementation(&self) -> Result<Option<PythonImplementation>> {
1045 let implementation = self
1046 .pyo3_cross_python_implementation
1047 .as_ref()
1048 .map(|os_string| {
1049 let utf8_str = os_string
1050 .to_str()
1051 .ok_or("PYO3_CROSS_PYTHON_IMPLEMENTATION is not valid a UTF-8 string")?;
1052 utf8_str
1053 .parse()
1054 .context("failed to parse PYO3_CROSS_PYTHON_IMPLEMENTATION")
1055 })
1056 .transpose()?;
1057
1058 Ok(implementation)
1059 }
1060
1061 fn lib_dir_path(&self) -> Result<Option<PathBuf>> {
1066 let lib_dir = self.pyo3_cross_lib_dir.as_ref().map(PathBuf::from);
1067
1068 if let Some(dir) = lib_dir.as_ref() {
1069 ensure!(
1070 dir.to_str().is_some(),
1071 "PYO3_CROSS_LIB_DIR variable value is not a valid UTF-8 string"
1072 );
1073 }
1074
1075 Ok(lib_dir)
1076 }
1077}
1078
1079pub fn cross_compiling_from_to(
1094 host: &Triple,
1095 target: &Triple,
1096) -> Result<Option<CrossCompileConfig>> {
1097 let env_vars = CrossCompileEnvVars::from_env();
1098 CrossCompileConfig::try_from_env_vars_host_target(env_vars, host, target)
1099}
1100
1101#[allow(dead_code)]
1107pub fn cross_compiling_from_cargo_env() -> Result<Option<CrossCompileConfig>> {
1108 let env_vars = CrossCompileEnvVars::from_env();
1109 let host = Triple::host();
1110 let target = target_triple_from_env();
1111
1112 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
1113}
1114
1115#[allow(non_camel_case_types)]
1116#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1117pub enum BuildFlag {
1118 Py_DEBUG,
1119 Py_REF_DEBUG,
1120 #[deprecated(since = "0.29.0", note = "no longer supported by PyO3")]
1121 Py_TRACE_REFS,
1122 Py_GIL_DISABLED,
1123 COUNT_ALLOCS,
1124 Other(String),
1125}
1126
1127impl Display for BuildFlag {
1128 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1129 match self {
1130 BuildFlag::Other(flag) => write!(f, "{flag}"),
1131 _ => write!(f, "{self:?}"),
1132 }
1133 }
1134}
1135
1136impl FromStr for BuildFlag {
1137 type Err = std::convert::Infallible;
1138 fn from_str(s: &str) -> Result<Self, Self::Err> {
1139 match s {
1140 "Py_DEBUG" => Ok(BuildFlag::Py_DEBUG),
1141 "Py_REF_DEBUG" => Ok(BuildFlag::Py_REF_DEBUG),
1142 "Py_GIL_DISABLED" => Ok(BuildFlag::Py_GIL_DISABLED),
1143 "COUNT_ALLOCS" => Ok(BuildFlag::COUNT_ALLOCS),
1144 other => Ok(BuildFlag::Other(other.to_owned())),
1145 }
1146 }
1147}
1148
1149#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
1163#[derive(Clone, Default)]
1164pub struct BuildFlags(pub HashSet<BuildFlag>);
1165
1166impl BuildFlags {
1167 const ALL: [BuildFlag; 4] = [
1168 BuildFlag::Py_DEBUG,
1169 BuildFlag::Py_REF_DEBUG,
1170 BuildFlag::Py_GIL_DISABLED,
1171 BuildFlag::COUNT_ALLOCS,
1172 ];
1173
1174 pub fn new() -> Self {
1175 BuildFlags(HashSet::new())
1176 }
1177
1178 fn from_sysconfigdata(config_map: &Sysconfigdata) -> Self {
1179 Self(
1180 BuildFlags::ALL
1181 .iter()
1182 .filter(|flag| config_map.get_value(flag.to_string()) == Some("1"))
1183 .cloned()
1184 .collect(),
1185 )
1186 .fixup()
1187 }
1188
1189 fn from_interpreter(interpreter: impl AsRef<Path>) -> Result<Self> {
1193 if cfg!(windows) {
1197 let script = String::from("import sys;print(sys.version_info < (3, 13))");
1198 let stdout = run_python_script(interpreter.as_ref(), &script)?;
1199 if stdout.trim_end() == "True" {
1200 return Ok(Self::new());
1201 }
1202 }
1203
1204 let mut script = String::from("import sysconfig\n");
1205 script.push_str("config = sysconfig.get_config_vars()\n");
1206
1207 for k in &BuildFlags::ALL {
1208 use std::fmt::Write;
1209 writeln!(&mut script, "print(config.get('{k}', '0'))").unwrap();
1210 }
1211
1212 let stdout = run_python_script(interpreter.as_ref(), &script)?;
1213 let split_stdout: Vec<&str> = stdout.trim_end().lines().collect();
1214 ensure!(
1215 split_stdout.len() == BuildFlags::ALL.len(),
1216 "Python stdout len didn't return expected number of lines: {}",
1217 split_stdout.len()
1218 );
1219 let flags = BuildFlags::ALL
1220 .iter()
1221 .zip(split_stdout)
1222 .filter(|(_, flag_value)| *flag_value == "1")
1223 .map(|(flag, _)| flag.clone())
1224 .collect();
1225
1226 Ok(Self(flags).fixup())
1227 }
1228
1229 fn fixup(mut self) -> Self {
1230 if self.0.contains(&BuildFlag::Py_DEBUG) {
1231 self.0.insert(BuildFlag::Py_REF_DEBUG);
1232 }
1233
1234 self
1235 }
1236}
1237
1238impl Display for BuildFlags {
1239 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1240 let mut first = true;
1241 for flag in &self.0 {
1242 if first {
1243 first = false;
1244 } else {
1245 write!(f, ",")?;
1246 }
1247 write!(f, "{flag}")?;
1248 }
1249 Ok(())
1250 }
1251}
1252
1253impl FromStr for BuildFlags {
1254 type Err = std::convert::Infallible;
1255
1256 fn from_str(value: &str) -> Result<Self, Self::Err> {
1257 let mut flags = HashSet::new();
1258 for flag in value.split_terminator(',') {
1259 flags.insert(flag.parse().unwrap());
1260 }
1261 Ok(BuildFlags(flags))
1262 }
1263}
1264
1265fn parse_script_output(output: &str) -> HashMap<String, String> {
1266 output
1267 .lines()
1268 .filter_map(|line| {
1269 let mut i = line.splitn(2, ' ');
1270 Some((i.next()?.into(), i.next()?.into()))
1271 })
1272 .collect()
1273}
1274
1275pub struct Sysconfigdata(HashMap<String, String>);
1279
1280impl Sysconfigdata {
1281 pub fn get_value<S: AsRef<str>>(&self, k: S) -> Option<&str> {
1282 self.0.get(k.as_ref()).map(String::as_str)
1283 }
1284
1285 #[allow(dead_code)]
1286 fn new() -> Self {
1287 Sysconfigdata(HashMap::new())
1288 }
1289
1290 #[allow(dead_code)]
1291 fn insert<S: Into<String>>(&mut self, k: S, v: S) {
1292 self.0.insert(k.into(), v.into());
1293 }
1294}
1295
1296pub fn parse_sysconfigdata(sysconfigdata_path: impl AsRef<Path>) -> Result<Sysconfigdata> {
1304 let sysconfigdata_path = sysconfigdata_path.as_ref();
1305 let mut script = fs::read_to_string(sysconfigdata_path).with_context(|| {
1306 format!(
1307 "failed to read config from {}",
1308 sysconfigdata_path.display()
1309 )
1310 })?;
1311 script += r#"
1312for key, val in build_time_vars.items():
1313 # (ana)conda(-forge) built Pythons are statically linked but ship the shared library with them.
1314 # We detect them based on the magic prefix directory they have encoded in their builds.
1315 if key == "Py_ENABLE_SHARED" and "_h_env_placehold" in build_time_vars.get("prefix"):
1316 val = 1
1317 print(key, val)
1318"#;
1319
1320 let output = run_python_script(&find_interpreter()?, &script)?;
1321
1322 Ok(Sysconfigdata(parse_script_output(&output)))
1323}
1324
1325fn starts_with(entry: &DirEntry, pat: &str) -> bool {
1326 let name = entry.file_name();
1327 name.to_string_lossy().starts_with(pat)
1328}
1329fn ends_with(entry: &DirEntry, pat: &str) -> bool {
1330 let name = entry.file_name();
1331 name.to_string_lossy().ends_with(pat)
1332}
1333
1334#[allow(dead_code)]
1339fn find_sysconfigdata(cross: &CrossCompileConfig) -> Result<Option<PathBuf>> {
1340 let mut sysconfig_paths = find_all_sysconfigdata(cross)?;
1341 if sysconfig_paths.is_empty() {
1342 if let Some(lib_dir) = cross.lib_dir.as_ref() {
1343 bail!("Could not find _sysconfigdata*.py in {}", lib_dir.display());
1344 } else {
1345 return Ok(None);
1347 }
1348 } else if sysconfig_paths.len() > 1 {
1349 let mut error_msg = String::from(
1350 "Detected multiple possible Python versions. Please set either the \
1351 PYO3_CROSS_PYTHON_VERSION variable to the wanted version or the \
1352 _PYTHON_SYSCONFIGDATA_NAME variable to the wanted sysconfigdata file name.\n\n\
1353 sysconfigdata files found:",
1354 );
1355 for path in sysconfig_paths {
1356 use std::fmt::Write;
1357 write!(&mut error_msg, "\n\t{}", path.display()).unwrap();
1358 }
1359 bail!("{}\n", error_msg);
1360 }
1361
1362 Ok(Some(sysconfig_paths.remove(0)))
1363}
1364
1365pub fn find_all_sysconfigdata(cross: &CrossCompileConfig) -> Result<Vec<PathBuf>> {
1404 let sysconfig_paths = if let Some(lib_dir) = cross.lib_dir.as_ref() {
1405 search_lib_dir(lib_dir, cross).with_context(|| {
1406 format!(
1407 "failed to search the lib dir at 'PYO3_CROSS_LIB_DIR={}'",
1408 lib_dir.display()
1409 )
1410 })?
1411 } else {
1412 return Ok(Vec::new());
1413 };
1414
1415 let sysconfig_name = env_var("_PYTHON_SYSCONFIGDATA_NAME");
1416 let mut sysconfig_paths = sysconfig_paths
1417 .iter()
1418 .filter_map(|p| {
1419 let canonical = fs::canonicalize(p).ok();
1420 match &sysconfig_name {
1421 Some(_) => canonical.filter(|p| p.file_stem() == sysconfig_name.as_deref()),
1422 None => canonical,
1423 }
1424 })
1425 .collect::<Vec<PathBuf>>();
1426
1427 sysconfig_paths.sort();
1428 sysconfig_paths.dedup();
1429
1430 Ok(sysconfig_paths)
1431}
1432
1433fn is_pypy_lib_dir(path: &str, v: &Option<PythonVersion>) -> bool {
1434 let pypy_version_pat = if let Some(v) = v {
1435 format!("pypy{v}")
1436 } else {
1437 "pypy3.".into()
1438 };
1439 path == "lib_pypy" || path.starts_with(&pypy_version_pat)
1440}
1441
1442fn is_graalpy_lib_dir(path: &str, v: &Option<PythonVersion>) -> bool {
1443 let graalpy_version_pat = if let Some(v) = v {
1444 format!("graalpy{v}")
1445 } else {
1446 "graalpy2".into()
1447 };
1448 path == "lib_graalpython" || path.starts_with(&graalpy_version_pat)
1449}
1450
1451fn is_cpython_lib_dir(path: &str, v: &Option<PythonVersion>) -> bool {
1452 let cpython_version_pat = if let Some(v) = v {
1453 format!("python{v}")
1454 } else {
1455 "python3.".into()
1456 };
1457 path.starts_with(&cpython_version_pat)
1458}
1459
1460fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Result<Vec<PathBuf>> {
1462 let mut sysconfig_paths = vec![];
1463 for f in fs::read_dir(path.as_ref()).with_context(|| {
1464 format!(
1465 "failed to list the entries in '{}'",
1466 path.as_ref().display()
1467 )
1468 })? {
1469 sysconfig_paths.extend(match &f {
1470 Ok(f) if starts_with(f, "_sysconfigdata_") && ends_with(f, "py") => vec![f.path()],
1472 Ok(f) if f.metadata().is_ok_and(|metadata| metadata.is_dir()) => {
1473 let file_name = f.file_name();
1474 let file_name = file_name.to_string_lossy();
1475 if file_name == "build" || file_name == "lib" {
1476 search_lib_dir(f.path(), cross)?
1477 } else if file_name.starts_with("lib.") {
1478 if !file_name.contains(&cross.target.operating_system.to_string()) {
1480 continue;
1481 }
1482 if !file_name.contains(&cross.target.architecture.to_string()) {
1484 continue;
1485 }
1486 search_lib_dir(f.path(), cross)?
1487 } else if is_cpython_lib_dir(&file_name, &cross.version)
1488 || is_pypy_lib_dir(&file_name, &cross.version)
1489 || is_graalpy_lib_dir(&file_name, &cross.version)
1490 {
1491 search_lib_dir(f.path(), cross)?
1492 } else {
1493 continue;
1494 }
1495 }
1496 _ => continue,
1497 });
1498 }
1499 if sysconfig_paths.len() > 1 {
1507 let temp = sysconfig_paths
1508 .iter()
1509 .filter(|p| {
1510 p.to_string_lossy()
1511 .contains(&cross.target.architecture.to_string())
1512 })
1513 .cloned()
1514 .collect::<Vec<PathBuf>>();
1515 if !temp.is_empty() {
1516 sysconfig_paths = temp;
1517 }
1518 }
1519
1520 Ok(sysconfig_paths)
1521}
1522
1523#[allow(dead_code)]
1531fn cross_compile_from_sysconfigdata(
1532 cross_compile_config: &CrossCompileConfig,
1533) -> Result<Option<InterpreterConfig>> {
1534 if let Some(path) = find_sysconfigdata(cross_compile_config)? {
1535 let data = parse_sysconfigdata(path)?;
1536 let mut config = InterpreterConfig::from_sysconfigdata(&data)?;
1537 if let Some(cross_lib_dir) = cross_compile_config.lib_dir_string() {
1538 config.lib_dir = Some(cross_lib_dir)
1539 }
1540
1541 Ok(Some(config))
1542 } else {
1543 Ok(None)
1544 }
1545}
1546
1547#[allow(unused_mut, dead_code)]
1554fn default_cross_compile(cross_compile_config: &CrossCompileConfig) -> Result<InterpreterConfig> {
1555 let version = cross_compile_config
1556 .version
1557 .or_else(get_abi3_version)
1558 .ok_or_else(||
1559 format!(
1560 "PYO3_CROSS_PYTHON_VERSION or an abi3-py3* feature must be specified \
1561 when cross-compiling and PYO3_CROSS_LIB_DIR is not set.\n\
1562 = help: see the PyO3 user guide for more information: https://pyo3.rs/v{}/building-and-distribution.html#cross-compiling",
1563 env!("CARGO_PKG_VERSION")
1564 )
1565 )?;
1566
1567 let abi3 = is_abi3();
1568 let implementation = cross_compile_config
1569 .implementation
1570 .unwrap_or(PythonImplementation::CPython);
1571 let gil_disabled: bool = cross_compile_config.abiflags.as_deref() == Some("t");
1572
1573 let lib_name = default_lib_name_for_target(
1574 version,
1575 implementation,
1576 abi3,
1577 gil_disabled,
1578 &cross_compile_config.target,
1579 );
1580
1581 let mut lib_dir = cross_compile_config.lib_dir_string();
1582
1583 Ok(InterpreterConfig {
1584 implementation,
1585 version,
1586 shared: true,
1587 abi3,
1588 lib_name: Some(lib_name),
1589 lib_dir,
1590 executable: None,
1591 pointer_width: None,
1592 build_flags: BuildFlags::default(),
1593 suppress_build_script_link_lines: false,
1594 extra_build_script_lines: vec![],
1595 python_framework_prefix: None,
1596 })
1597}
1598
1599fn default_abi3_config(host: &Triple, version: PythonVersion) -> Result<InterpreterConfig> {
1609 let implementation = PythonImplementation::CPython;
1611 let abi3 = true;
1612
1613 let lib_name = if host.operating_system == OperatingSystem::Windows {
1614 Some(default_lib_name_windows(
1615 version,
1616 implementation,
1617 abi3,
1618 false,
1619 false,
1620 false,
1621 )?)
1622 } else {
1623 None
1624 };
1625
1626 Ok(InterpreterConfig {
1627 implementation,
1628 version,
1629 shared: true,
1630 abi3,
1631 lib_name,
1632 lib_dir: None,
1633 executable: None,
1634 pointer_width: None,
1635 build_flags: BuildFlags::default(),
1636 suppress_build_script_link_lines: false,
1637 extra_build_script_lines: vec![],
1638 python_framework_prefix: None,
1639 })
1640}
1641
1642#[allow(dead_code)]
1650fn load_cross_compile_config(
1651 cross_compile_config: CrossCompileConfig,
1652) -> Result<InterpreterConfig> {
1653 let windows = cross_compile_config.target.operating_system == OperatingSystem::Windows;
1654
1655 let config = if windows || !have_python_interpreter() {
1656 default_cross_compile(&cross_compile_config)?
1660 } else if let Some(config) = cross_compile_from_sysconfigdata(&cross_compile_config)? {
1661 config
1663 } else {
1664 default_cross_compile(&cross_compile_config)?
1666 };
1667
1668 Ok(config)
1669}
1670
1671const WINDOWS_ABI3_LIB_NAME: &str = "python3";
1673const WINDOWS_ABI3_DEBUG_LIB_NAME: &str = "python3_d";
1674
1675#[allow(dead_code)]
1677fn default_lib_name_for_target(
1678 version: PythonVersion,
1679 implementation: PythonImplementation,
1680 abi3: bool,
1681 gil_disabled: bool,
1682 target: &Triple,
1683) -> String {
1684 if target.operating_system == OperatingSystem::Windows {
1685 default_lib_name_windows(version, implementation, abi3, false, false, gil_disabled).unwrap()
1686 } else {
1687 default_lib_name_unix(
1688 version,
1689 implementation,
1690 abi3,
1691 target.operating_system == OperatingSystem::Cygwin,
1692 None,
1693 gil_disabled,
1694 )
1695 .unwrap()
1696 }
1697}
1698
1699fn default_lib_name_windows(
1700 version: PythonVersion,
1701 implementation: PythonImplementation,
1702 abi3: bool,
1703 mingw: bool,
1704 debug: bool,
1705 gil_disabled: bool,
1706) -> Result<String> {
1707 if implementation.is_pypy() {
1708 Ok(format!("libpypy{}.{}-c", version.major, version.minor))
1712 } else if debug && version < PythonVersion::PY310 {
1713 Ok(format!("python{}{}_d", version.major, version.minor))
1716 } else if abi3 && !(gil_disabled || implementation.is_pypy() || implementation.is_graalpy()) {
1717 if debug {
1718 Ok(WINDOWS_ABI3_DEBUG_LIB_NAME.to_owned())
1719 } else {
1720 Ok(WINDOWS_ABI3_LIB_NAME.to_owned())
1721 }
1722 } else if mingw {
1723 ensure!(
1724 !gil_disabled,
1725 "MinGW free-threaded builds are not currently tested or supported"
1726 );
1727 Ok(format!("python{}.{}", version.major, version.minor))
1729 } else if gil_disabled {
1730 ensure!(version >= PythonVersion::PY313, "Cannot compile C extensions for the free-threaded build on Python versions earlier than 3.13, found {}.{}", version.major, version.minor);
1731 if debug {
1732 Ok(format!("python{}{}t_d", version.major, version.minor))
1733 } else {
1734 Ok(format!("python{}{}t", version.major, version.minor))
1735 }
1736 } else if debug {
1737 Ok(format!("python{}{}_d", version.major, version.minor))
1738 } else {
1739 Ok(format!("python{}{}", version.major, version.minor))
1740 }
1741}
1742
1743fn default_lib_name_unix(
1744 version: PythonVersion,
1745 implementation: PythonImplementation,
1746 abi3: bool,
1747 cygwin: bool,
1748 ld_version: Option<&str>,
1749 gil_disabled: bool,
1750) -> Result<String> {
1751 match implementation {
1752 PythonImplementation::CPython => match ld_version {
1753 Some(ld_version) => Ok(format!("python{ld_version}")),
1754 None => {
1755 if cygwin && abi3 {
1756 Ok("python3".to_string())
1757 } else if gil_disabled {
1758 ensure!(version >= PythonVersion::PY313, "Cannot compile C extensions for the free-threaded build on Python versions earlier than 3.13, found {}.{}", version.major, version.minor);
1759 Ok(format!("python{}.{}t", version.major, version.minor))
1760 } else {
1761 Ok(format!("python{}.{}", version.major, version.minor))
1762 }
1763 }
1764 },
1765 PythonImplementation::PyPy => match ld_version {
1766 Some(ld_version) => Ok(format!("pypy{ld_version}-c")),
1767 None => Ok(format!("pypy{}.{}-c", version.major, version.minor)),
1768 },
1769
1770 PythonImplementation::GraalPy => Ok("python-native".to_string()),
1771 }
1772}
1773
1774fn run_python_script(interpreter: &Path, script: &str) -> Result<String> {
1776 run_python_script_with_envs(interpreter, script, std::iter::empty::<(&str, &str)>())
1777}
1778
1779fn run_python_script_with_envs<I, K, V>(interpreter: &Path, script: &str, envs: I) -> Result<String>
1782where
1783 I: IntoIterator<Item = (K, V)>,
1784 K: AsRef<OsStr>,
1785 V: AsRef<OsStr>,
1786{
1787 let out = Command::new(interpreter)
1788 .env("PYTHONIOENCODING", "utf-8")
1789 .envs(envs)
1790 .stdin(Stdio::piped())
1791 .stdout(Stdio::piped())
1792 .stderr(Stdio::inherit())
1793 .spawn()
1794 .and_then(|mut child| {
1795 child
1796 .stdin
1797 .as_mut()
1798 .expect("piped stdin")
1799 .write_all(script.as_bytes())?;
1800 child.wait_with_output()
1801 });
1802
1803 match out {
1804 Err(err) => bail!(
1805 "failed to run the Python interpreter at {}: {}",
1806 interpreter.display(),
1807 err
1808 ),
1809 Ok(ok) if !ok.status.success() => bail!("Python script failed"),
1810 Ok(ok) => Ok(String::from_utf8(ok.stdout)
1811 .context("failed to parse Python script output as utf-8")?),
1812 }
1813}
1814
1815fn venv_interpreter(virtual_env: &OsStr, windows: bool) -> PathBuf {
1816 if windows {
1817 Path::new(virtual_env).join("Scripts").join("python.exe")
1818 } else {
1819 Path::new(virtual_env).join("bin").join("python")
1820 }
1821}
1822
1823fn conda_env_interpreter(conda_prefix: &OsStr, windows: bool) -> PathBuf {
1824 if windows {
1825 Path::new(conda_prefix).join("python.exe")
1826 } else {
1827 Path::new(conda_prefix).join("bin").join("python")
1828 }
1829}
1830
1831fn get_env_interpreter() -> Option<PathBuf> {
1832 match (env_var("VIRTUAL_ENV"), env_var("CONDA_PREFIX")) {
1833 (Some(dir), None) => Some(venv_interpreter(&dir, cfg!(windows))),
1836 (None, Some(dir)) => Some(conda_env_interpreter(&dir, cfg!(windows))),
1837 (Some(_), Some(_)) => {
1838 warn!(
1839 "Both VIRTUAL_ENV and CONDA_PREFIX are set. PyO3 will ignore both of these for \
1840 locating the Python interpreter until you unset one of them."
1841 );
1842 None
1843 }
1844 (None, None) => None,
1845 }
1846}
1847
1848pub fn find_interpreter() -> Result<PathBuf> {
1856 println!("cargo:rerun-if-env-changed=PYO3_ENVIRONMENT_SIGNATURE");
1859
1860 if let Some(exe) = env_var("PYO3_PYTHON") {
1861 Ok(exe.into())
1862 } else if let Some(env_interpreter) = get_env_interpreter() {
1863 Ok(env_interpreter)
1864 } else {
1865 println!("cargo:rerun-if-env-changed=PATH");
1866 ["python", "python3"]
1867 .iter()
1868 .find(|bin| {
1869 if let Ok(out) = Command::new(bin).arg("--version").output() {
1870 out.stdout.starts_with(b"Python 3")
1872 || out.stderr.starts_with(b"Python 3")
1873 || out.stdout.starts_with(b"GraalPy 3")
1874 } else {
1875 false
1876 }
1877 })
1878 .map(PathBuf::from)
1879 .ok_or_else(|| "no Python 3.x interpreter found".into())
1880 }
1881}
1882
1883fn get_host_interpreter(abi3_version: Option<PythonVersion>) -> Result<InterpreterConfig> {
1887 let interpreter_path = find_interpreter()?;
1888
1889 let mut interpreter_config = InterpreterConfig::from_interpreter(interpreter_path)?;
1890 interpreter_config.fixup_for_abi3_version(abi3_version)?;
1891
1892 Ok(interpreter_config)
1893}
1894
1895#[allow(dead_code)]
1900pub fn make_cross_compile_config() -> Result<Option<InterpreterConfig>> {
1901 let interpreter_config = if let Some(cross_config) = cross_compiling_from_cargo_env()? {
1902 let mut interpreter_config = load_cross_compile_config(cross_config)?;
1903 interpreter_config.fixup_for_abi3_version(get_abi3_version())?;
1904 Some(interpreter_config)
1905 } else {
1906 None
1907 };
1908
1909 Ok(interpreter_config)
1910}
1911
1912#[allow(dead_code, unused_mut)]
1915pub fn make_interpreter_config() -> Result<InterpreterConfig> {
1916 let host = Triple::host();
1917 let abi3_version = get_abi3_version();
1918
1919 let need_interpreter = abi3_version.is_none() || require_libdir_for_target(&host);
1922
1923 if have_python_interpreter() {
1924 match get_host_interpreter(abi3_version) {
1925 Ok(interpreter_config) => return Ok(interpreter_config),
1926 Err(e) if need_interpreter => return Err(e),
1928 _ => {
1929 warn!("Compiling without a working Python interpreter.");
1932 }
1933 }
1934 } else {
1935 ensure!(
1936 abi3_version.is_some(),
1937 "An abi3-py3* feature must be specified when compiling without a Python interpreter."
1938 );
1939 };
1940
1941 let interpreter_config = default_abi3_config(&host, abi3_version.unwrap())?;
1942
1943 Ok(interpreter_config)
1944}
1945
1946fn escape(bytes: &[u8]) -> String {
1947 let mut escaped = String::with_capacity(2 * bytes.len());
1948
1949 for byte in bytes {
1950 const LUT: &[u8; 16] = b"0123456789abcdef";
1951
1952 escaped.push(LUT[(byte >> 4) as usize] as char);
1953 escaped.push(LUT[(byte & 0x0F) as usize] as char);
1954 }
1955
1956 escaped
1957}
1958
1959fn unescape(escaped: &str) -> Vec<u8> {
1960 assert_eq!(escaped.len() % 2, 0, "invalid hex encoding");
1961
1962 let mut bytes = Vec::with_capacity(escaped.len() / 2);
1963
1964 for chunk in escaped.as_bytes().chunks_exact(2) {
1965 fn unhex(hex: u8) -> u8 {
1966 match hex {
1967 b'a'..=b'f' => hex - b'a' + 10,
1968 b'0'..=b'9' => hex - b'0',
1969 _ => panic!("invalid hex encoding"),
1970 }
1971 }
1972
1973 bytes.push((unhex(chunk[0]) << 4) | unhex(chunk[1]));
1974 }
1975
1976 bytes
1977}
1978
1979#[cfg(test)]
1980mod tests {
1981 use target_lexicon::triple;
1982
1983 use super::*;
1984
1985 #[test]
1986 fn test_config_file_roundtrip() {
1987 let config = InterpreterConfig {
1988 abi3: true,
1989 build_flags: BuildFlags::default(),
1990 pointer_width: Some(32),
1991 executable: Some("executable".into()),
1992 implementation: PythonImplementation::CPython,
1993 lib_name: Some("lib_name".into()),
1994 lib_dir: Some("lib_dir".into()),
1995 shared: true,
1996 version: MINIMUM_SUPPORTED_VERSION,
1997 suppress_build_script_link_lines: true,
1998 extra_build_script_lines: vec!["cargo:test1".to_string(), "cargo:test2".to_string()],
1999 python_framework_prefix: None,
2000 };
2001 let mut buf: Vec<u8> = Vec::new();
2002 config.to_writer(&mut buf).unwrap();
2003
2004 assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap());
2005
2006 let config = InterpreterConfig {
2009 abi3: false,
2010 build_flags: {
2011 let mut flags = HashSet::new();
2012 flags.insert(BuildFlag::Py_DEBUG);
2013 flags.insert(BuildFlag::Other(String::from("Py_SOME_FLAG")));
2014 BuildFlags(flags)
2015 },
2016 pointer_width: None,
2017 executable: None,
2018 implementation: PythonImplementation::PyPy,
2019 lib_dir: None,
2020 lib_name: None,
2021 shared: true,
2022 version: PythonVersion {
2023 major: 3,
2024 minor: 10,
2025 },
2026 suppress_build_script_link_lines: false,
2027 extra_build_script_lines: vec![],
2028 python_framework_prefix: None,
2029 };
2030 let mut buf: Vec<u8> = Vec::new();
2031 config.to_writer(&mut buf).unwrap();
2032
2033 assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap());
2034 }
2035
2036 #[test]
2037 fn test_config_file_roundtrip_with_escaping() {
2038 let config = InterpreterConfig {
2039 abi3: true,
2040 build_flags: BuildFlags::default(),
2041 pointer_width: Some(32),
2042 executable: Some("executable".into()),
2043 implementation: PythonImplementation::CPython,
2044 lib_name: Some("lib_name".into()),
2045 lib_dir: Some("lib_dir\\n".into()),
2046 shared: true,
2047 version: MINIMUM_SUPPORTED_VERSION,
2048 suppress_build_script_link_lines: true,
2049 extra_build_script_lines: vec!["cargo:test1".to_string(), "cargo:test2".to_string()],
2050 python_framework_prefix: None,
2051 };
2052 let mut buf: Vec<u8> = Vec::new();
2053 config.to_writer(&mut buf).unwrap();
2054
2055 let buf = unescape(&escape(&buf));
2056
2057 assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap());
2058 }
2059
2060 #[test]
2061 fn test_config_file_defaults() {
2062 assert_eq!(
2064 InterpreterConfig::from_reader("version=3.8".as_bytes()).unwrap(),
2065 InterpreterConfig {
2066 version: PythonVersion { major: 3, minor: 8 },
2067 implementation: PythonImplementation::CPython,
2068 shared: true,
2069 abi3: false,
2070 lib_name: None,
2071 lib_dir: None,
2072 executable: None,
2073 pointer_width: None,
2074 build_flags: BuildFlags::default(),
2075 suppress_build_script_link_lines: false,
2076 extra_build_script_lines: vec![],
2077 python_framework_prefix: None,
2078 }
2079 )
2080 }
2081
2082 #[test]
2083 fn test_config_file_unknown_keys() {
2084 assert_eq!(
2086 InterpreterConfig::from_reader("version=3.8\next_suffix=.python38.so".as_bytes())
2087 .unwrap(),
2088 InterpreterConfig {
2089 version: PythonVersion { major: 3, minor: 8 },
2090 implementation: PythonImplementation::CPython,
2091 shared: true,
2092 abi3: false,
2093 lib_name: None,
2094 lib_dir: None,
2095 executable: None,
2096 pointer_width: None,
2097 build_flags: BuildFlags::default(),
2098 suppress_build_script_link_lines: false,
2099 extra_build_script_lines: vec![],
2100 python_framework_prefix: None,
2101 }
2102 )
2103 }
2104
2105 #[test]
2106 fn build_flags_default() {
2107 assert_eq!(BuildFlags::default(), BuildFlags::new());
2108 }
2109
2110 #[test]
2111 fn build_flags_from_sysconfigdata() {
2112 let mut sysconfigdata = Sysconfigdata::new();
2113
2114 assert_eq!(
2115 BuildFlags::from_sysconfigdata(&sysconfigdata).0,
2116 HashSet::new()
2117 );
2118
2119 for flag in &BuildFlags::ALL {
2120 sysconfigdata.insert(flag.to_string(), "0".into());
2121 }
2122
2123 assert_eq!(
2124 BuildFlags::from_sysconfigdata(&sysconfigdata).0,
2125 HashSet::new()
2126 );
2127
2128 let mut expected_flags = HashSet::new();
2129 for flag in &BuildFlags::ALL {
2130 sysconfigdata.insert(flag.to_string(), "1".into());
2131 expected_flags.insert(flag.clone());
2132 }
2133
2134 assert_eq!(
2135 BuildFlags::from_sysconfigdata(&sysconfigdata).0,
2136 expected_flags
2137 );
2138 }
2139
2140 #[test]
2141 fn build_flags_fixup() {
2142 let mut build_flags = BuildFlags::new();
2143
2144 build_flags = build_flags.fixup();
2145 assert!(build_flags.0.is_empty());
2146
2147 build_flags.0.insert(BuildFlag::Py_DEBUG);
2148
2149 build_flags = build_flags.fixup();
2150
2151 assert!(build_flags.0.contains(&BuildFlag::Py_REF_DEBUG));
2153 }
2154
2155 #[test]
2156 fn parse_script_output() {
2157 let output = "foo bar\nbar foobar\n\n";
2158 let map = super::parse_script_output(output);
2159 assert_eq!(map.len(), 2);
2160 assert_eq!(map["foo"], "bar");
2161 assert_eq!(map["bar"], "foobar");
2162 }
2163
2164 #[test]
2165 fn config_from_interpreter() {
2166 assert!(make_interpreter_config().is_ok())
2170 }
2171
2172 #[test]
2173 fn config_from_empty_sysconfigdata() {
2174 let sysconfigdata = Sysconfigdata::new();
2175 assert!(InterpreterConfig::from_sysconfigdata(&sysconfigdata).is_err());
2176 }
2177
2178 #[test]
2179 fn config_from_sysconfigdata() {
2180 let mut sysconfigdata = Sysconfigdata::new();
2181 sysconfigdata.insert("SOABI", "cpython-38-x86_64-linux-gnu");
2184 sysconfigdata.insert("VERSION", "3.8");
2185 sysconfigdata.insert("Py_ENABLE_SHARED", "1");
2186 sysconfigdata.insert("LIBDIR", "/usr/lib");
2187 sysconfigdata.insert("LDVERSION", "3.8");
2188 sysconfigdata.insert("SIZEOF_VOID_P", "8");
2189 assert_eq!(
2190 InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(),
2191 InterpreterConfig {
2192 abi3: false,
2193 build_flags: BuildFlags::from_sysconfigdata(&sysconfigdata),
2194 pointer_width: Some(64),
2195 executable: None,
2196 implementation: PythonImplementation::CPython,
2197 lib_dir: Some("/usr/lib".into()),
2198 lib_name: Some("python3.8".into()),
2199 shared: true,
2200 version: PythonVersion { major: 3, minor: 8 },
2201 suppress_build_script_link_lines: false,
2202 extra_build_script_lines: vec![],
2203 python_framework_prefix: None,
2204 }
2205 );
2206 }
2207
2208 #[test]
2209 fn config_from_sysconfigdata_framework() {
2210 let mut sysconfigdata = Sysconfigdata::new();
2211 sysconfigdata.insert("SOABI", "cpython-38-x86_64-linux-gnu");
2212 sysconfigdata.insert("VERSION", "3.8");
2213 sysconfigdata.insert("Py_ENABLE_SHARED", "0");
2215 sysconfigdata.insert("PYTHONFRAMEWORK", "Python");
2216 sysconfigdata.insert("LIBDIR", "/usr/lib");
2217 sysconfigdata.insert("LDVERSION", "3.8");
2218 sysconfigdata.insert("SIZEOF_VOID_P", "8");
2219 assert_eq!(
2220 InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(),
2221 InterpreterConfig {
2222 abi3: false,
2223 build_flags: BuildFlags::from_sysconfigdata(&sysconfigdata),
2224 pointer_width: Some(64),
2225 executable: None,
2226 implementation: PythonImplementation::CPython,
2227 lib_dir: Some("/usr/lib".into()),
2228 lib_name: Some("python3.8".into()),
2229 shared: true,
2230 version: PythonVersion { major: 3, minor: 8 },
2231 suppress_build_script_link_lines: false,
2232 extra_build_script_lines: vec![],
2233 python_framework_prefix: None,
2234 }
2235 );
2236
2237 sysconfigdata = Sysconfigdata::new();
2238 sysconfigdata.insert("SOABI", "cpython-38-x86_64-linux-gnu");
2239 sysconfigdata.insert("VERSION", "3.8");
2240 sysconfigdata.insert("Py_ENABLE_SHARED", "0");
2242 sysconfigdata.insert("PYTHONFRAMEWORK", "");
2243 sysconfigdata.insert("LIBDIR", "/usr/lib");
2244 sysconfigdata.insert("LDVERSION", "3.8");
2245 sysconfigdata.insert("SIZEOF_VOID_P", "8");
2246 assert_eq!(
2247 InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(),
2248 InterpreterConfig {
2249 abi3: false,
2250 build_flags: BuildFlags::from_sysconfigdata(&sysconfigdata),
2251 pointer_width: Some(64),
2252 executable: None,
2253 implementation: PythonImplementation::CPython,
2254 lib_dir: Some("/usr/lib".into()),
2255 lib_name: Some("python3.8".into()),
2256 shared: false,
2257 version: PythonVersion { major: 3, minor: 8 },
2258 suppress_build_script_link_lines: false,
2259 extra_build_script_lines: vec![],
2260 python_framework_prefix: None,
2261 }
2262 );
2263 }
2264
2265 #[test]
2266 fn windows_hardcoded_abi3_compile() {
2267 let host = triple!("x86_64-pc-windows-msvc");
2268 let min_version = "3.8".parse().unwrap();
2269
2270 assert_eq!(
2271 default_abi3_config(&host, min_version).unwrap(),
2272 InterpreterConfig {
2273 implementation: PythonImplementation::CPython,
2274 version: PythonVersion { major: 3, minor: 8 },
2275 shared: true,
2276 abi3: true,
2277 lib_name: Some("python3".into()),
2278 lib_dir: None,
2279 executable: None,
2280 pointer_width: None,
2281 build_flags: BuildFlags::default(),
2282 suppress_build_script_link_lines: false,
2283 extra_build_script_lines: vec![],
2284 python_framework_prefix: None,
2285 }
2286 );
2287 }
2288
2289 #[test]
2290 fn unix_hardcoded_abi3_compile() {
2291 let host = triple!("x86_64-unknown-linux-gnu");
2292 let min_version = "3.9".parse().unwrap();
2293
2294 assert_eq!(
2295 default_abi3_config(&host, min_version).unwrap(),
2296 InterpreterConfig {
2297 implementation: PythonImplementation::CPython,
2298 version: PythonVersion { major: 3, minor: 9 },
2299 shared: true,
2300 abi3: true,
2301 lib_name: None,
2302 lib_dir: None,
2303 executable: None,
2304 pointer_width: None,
2305 build_flags: BuildFlags::default(),
2306 suppress_build_script_link_lines: false,
2307 extra_build_script_lines: vec![],
2308 python_framework_prefix: None,
2309 }
2310 );
2311 }
2312
2313 #[test]
2314 fn windows_hardcoded_cross_compile() {
2315 let env_vars = CrossCompileEnvVars {
2316 pyo3_cross: None,
2317 pyo3_cross_lib_dir: Some("C:\\some\\path".into()),
2318 pyo3_cross_python_implementation: None,
2319 pyo3_cross_python_version: Some("3.8".into()),
2320 };
2321
2322 let host = triple!("x86_64-unknown-linux-gnu");
2323 let target = triple!("i686-pc-windows-msvc");
2324 let cross_config =
2325 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
2326 .unwrap()
2327 .unwrap();
2328
2329 assert_eq!(
2330 default_cross_compile(&cross_config).unwrap(),
2331 InterpreterConfig {
2332 implementation: PythonImplementation::CPython,
2333 version: PythonVersion { major: 3, minor: 8 },
2334 shared: true,
2335 abi3: false,
2336 lib_name: Some("python38".into()),
2337 lib_dir: Some("C:\\some\\path".into()),
2338 executable: None,
2339 pointer_width: None,
2340 build_flags: BuildFlags::default(),
2341 suppress_build_script_link_lines: false,
2342 extra_build_script_lines: vec![],
2343 python_framework_prefix: None,
2344 }
2345 );
2346 }
2347
2348 #[test]
2349 fn mingw_hardcoded_cross_compile() {
2350 let env_vars = CrossCompileEnvVars {
2351 pyo3_cross: None,
2352 pyo3_cross_lib_dir: Some("/usr/lib/mingw".into()),
2353 pyo3_cross_python_implementation: None,
2354 pyo3_cross_python_version: Some("3.8".into()),
2355 };
2356
2357 let host = triple!("x86_64-unknown-linux-gnu");
2358 let target = triple!("i686-pc-windows-gnu");
2359 let cross_config =
2360 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
2361 .unwrap()
2362 .unwrap();
2363
2364 assert_eq!(
2365 default_cross_compile(&cross_config).unwrap(),
2366 InterpreterConfig {
2367 implementation: PythonImplementation::CPython,
2368 version: PythonVersion { major: 3, minor: 8 },
2369 shared: true,
2370 abi3: false,
2371 lib_name: Some("python38".into()),
2372 lib_dir: Some("/usr/lib/mingw".into()),
2373 executable: None,
2374 pointer_width: None,
2375 build_flags: BuildFlags::default(),
2376 suppress_build_script_link_lines: false,
2377 extra_build_script_lines: vec![],
2378 python_framework_prefix: None,
2379 }
2380 );
2381 }
2382
2383 #[test]
2384 fn unix_hardcoded_cross_compile() {
2385 let env_vars = CrossCompileEnvVars {
2386 pyo3_cross: None,
2387 pyo3_cross_lib_dir: Some("/usr/arm64/lib".into()),
2388 pyo3_cross_python_implementation: None,
2389 pyo3_cross_python_version: Some("3.9".into()),
2390 };
2391
2392 let host = triple!("x86_64-unknown-linux-gnu");
2393 let target = triple!("aarch64-unknown-linux-gnu");
2394 let cross_config =
2395 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
2396 .unwrap()
2397 .unwrap();
2398
2399 assert_eq!(
2400 default_cross_compile(&cross_config).unwrap(),
2401 InterpreterConfig {
2402 implementation: PythonImplementation::CPython,
2403 version: PythonVersion { major: 3, minor: 9 },
2404 shared: true,
2405 abi3: false,
2406 lib_name: Some("python3.9".into()),
2407 lib_dir: Some("/usr/arm64/lib".into()),
2408 executable: None,
2409 pointer_width: None,
2410 build_flags: BuildFlags::default(),
2411 suppress_build_script_link_lines: false,
2412 extra_build_script_lines: vec![],
2413 python_framework_prefix: None,
2414 }
2415 );
2416 }
2417
2418 #[test]
2419 fn pypy_hardcoded_cross_compile() {
2420 let env_vars = CrossCompileEnvVars {
2421 pyo3_cross: None,
2422 pyo3_cross_lib_dir: None,
2423 pyo3_cross_python_implementation: Some("PyPy".into()),
2424 pyo3_cross_python_version: Some("3.11".into()),
2425 };
2426
2427 let triple = triple!("x86_64-unknown-linux-gnu");
2428 let cross_config =
2429 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &triple, &triple)
2430 .unwrap()
2431 .unwrap();
2432
2433 assert_eq!(
2434 default_cross_compile(&cross_config).unwrap(),
2435 InterpreterConfig {
2436 implementation: PythonImplementation::PyPy,
2437 version: PythonVersion {
2438 major: 3,
2439 minor: 11
2440 },
2441 shared: true,
2442 abi3: false,
2443 lib_name: Some("pypy3.11-c".into()),
2444 lib_dir: None,
2445 executable: None,
2446 pointer_width: None,
2447 build_flags: BuildFlags::default(),
2448 suppress_build_script_link_lines: false,
2449 extra_build_script_lines: vec![],
2450 python_framework_prefix: None,
2451 }
2452 );
2453 }
2454
2455 #[test]
2456 fn default_lib_name_windows() {
2457 use PythonImplementation::*;
2458 assert_eq!(
2459 super::default_lib_name_windows(
2460 PythonVersion { major: 3, minor: 9 },
2461 CPython,
2462 false,
2463 false,
2464 false,
2465 false,
2466 )
2467 .unwrap(),
2468 "python39",
2469 );
2470 assert!(super::default_lib_name_windows(
2471 PythonVersion { major: 3, minor: 9 },
2472 CPython,
2473 false,
2474 false,
2475 false,
2476 true,
2477 )
2478 .is_err());
2479 assert_eq!(
2480 super::default_lib_name_windows(
2481 PythonVersion { major: 3, minor: 9 },
2482 CPython,
2483 true,
2484 false,
2485 false,
2486 false,
2487 )
2488 .unwrap(),
2489 "python3",
2490 );
2491 assert_eq!(
2492 super::default_lib_name_windows(
2493 PythonVersion { major: 3, minor: 9 },
2494 CPython,
2495 false,
2496 true,
2497 false,
2498 false,
2499 )
2500 .unwrap(),
2501 "python3.9",
2502 );
2503 assert_eq!(
2504 super::default_lib_name_windows(
2505 PythonVersion { major: 3, minor: 9 },
2506 CPython,
2507 true,
2508 true,
2509 false,
2510 false,
2511 )
2512 .unwrap(),
2513 "python3",
2514 );
2515 assert_eq!(
2516 super::default_lib_name_windows(
2517 PythonVersion { major: 3, minor: 9 },
2518 PyPy,
2519 true,
2520 false,
2521 false,
2522 false,
2523 )
2524 .unwrap(),
2525 "libpypy3.9-c",
2526 );
2527 assert_eq!(
2528 super::default_lib_name_windows(
2529 PythonVersion {
2530 major: 3,
2531 minor: 11
2532 },
2533 PyPy,
2534 false,
2535 false,
2536 false,
2537 false,
2538 )
2539 .unwrap(),
2540 "libpypy3.11-c",
2541 );
2542 assert_eq!(
2543 super::default_lib_name_windows(
2544 PythonVersion { major: 3, minor: 9 },
2545 CPython,
2546 false,
2547 false,
2548 true,
2549 false,
2550 )
2551 .unwrap(),
2552 "python39_d",
2553 );
2554 assert_eq!(
2557 super::default_lib_name_windows(
2558 PythonVersion { major: 3, minor: 9 },
2559 CPython,
2560 true,
2561 false,
2562 true,
2563 false,
2564 )
2565 .unwrap(),
2566 "python39_d",
2567 );
2568 assert_eq!(
2569 super::default_lib_name_windows(
2570 PythonVersion {
2571 major: 3,
2572 minor: 10
2573 },
2574 CPython,
2575 true,
2576 false,
2577 true,
2578 false,
2579 )
2580 .unwrap(),
2581 "python3_d",
2582 );
2583 assert!(super::default_lib_name_windows(
2585 PythonVersion {
2586 major: 3,
2587 minor: 12,
2588 },
2589 CPython,
2590 false,
2591 false,
2592 false,
2593 true,
2594 )
2595 .is_err());
2596 assert!(super::default_lib_name_windows(
2598 PythonVersion {
2599 major: 3,
2600 minor: 12,
2601 },
2602 CPython,
2603 false,
2604 true,
2605 false,
2606 true,
2607 )
2608 .is_err());
2609 assert_eq!(
2610 super::default_lib_name_windows(
2611 PythonVersion {
2612 major: 3,
2613 minor: 13
2614 },
2615 CPython,
2616 false,
2617 false,
2618 false,
2619 true,
2620 )
2621 .unwrap(),
2622 "python313t",
2623 );
2624 assert_eq!(
2625 super::default_lib_name_windows(
2626 PythonVersion {
2627 major: 3,
2628 minor: 13
2629 },
2630 CPython,
2631 true, false,
2633 false,
2634 true,
2635 )
2636 .unwrap(),
2637 "python313t",
2638 );
2639 assert_eq!(
2640 super::default_lib_name_windows(
2641 PythonVersion {
2642 major: 3,
2643 minor: 13
2644 },
2645 CPython,
2646 false,
2647 false,
2648 true,
2649 true,
2650 )
2651 .unwrap(),
2652 "python313t_d",
2653 );
2654 }
2655
2656 #[test]
2657 fn default_lib_name_unix() {
2658 use PythonImplementation::*;
2659 assert_eq!(
2661 super::default_lib_name_unix(
2662 PythonVersion { major: 3, minor: 8 },
2663 CPython,
2664 false,
2665 false,
2666 None,
2667 false
2668 )
2669 .unwrap(),
2670 "python3.8",
2671 );
2672 assert_eq!(
2673 super::default_lib_name_unix(
2674 PythonVersion { major: 3, minor: 9 },
2675 CPython,
2676 false,
2677 false,
2678 None,
2679 false
2680 )
2681 .unwrap(),
2682 "python3.9",
2683 );
2684 assert_eq!(
2686 super::default_lib_name_unix(
2687 PythonVersion { major: 3, minor: 9 },
2688 CPython,
2689 false,
2690 false,
2691 Some("3.8d"),
2692 false
2693 )
2694 .unwrap(),
2695 "python3.8d",
2696 );
2697
2698 assert_eq!(
2700 super::default_lib_name_unix(
2701 PythonVersion {
2702 major: 3,
2703 minor: 11
2704 },
2705 PyPy,
2706 false,
2707 false,
2708 None,
2709 false
2710 )
2711 .unwrap(),
2712 "pypy3.11-c",
2713 );
2714
2715 assert_eq!(
2716 super::default_lib_name_unix(
2717 PythonVersion { major: 3, minor: 9 },
2718 PyPy,
2719 false,
2720 false,
2721 Some("3.11d"),
2722 false
2723 )
2724 .unwrap(),
2725 "pypy3.11d-c",
2726 );
2727
2728 assert_eq!(
2730 super::default_lib_name_unix(
2731 PythonVersion {
2732 major: 3,
2733 minor: 13
2734 },
2735 CPython,
2736 false,
2737 false,
2738 None,
2739 true
2740 )
2741 .unwrap(),
2742 "python3.13t",
2743 );
2744 assert!(super::default_lib_name_unix(
2746 PythonVersion {
2747 major: 3,
2748 minor: 12,
2749 },
2750 CPython,
2751 false,
2752 false,
2753 None,
2754 true,
2755 )
2756 .is_err());
2757 assert_eq!(
2759 super::default_lib_name_unix(
2760 PythonVersion {
2761 major: 3,
2762 minor: 13
2763 },
2764 CPython,
2765 true,
2766 true,
2767 None,
2768 false
2769 )
2770 .unwrap(),
2771 "python3",
2772 );
2773 }
2774
2775 #[test]
2776 fn parse_cross_python_version() {
2777 let env_vars = CrossCompileEnvVars {
2778 pyo3_cross: None,
2779 pyo3_cross_lib_dir: None,
2780 pyo3_cross_python_version: Some("3.9".into()),
2781 pyo3_cross_python_implementation: None,
2782 };
2783
2784 assert_eq!(
2785 env_vars.parse_version().unwrap(),
2786 (Some(PythonVersion { major: 3, minor: 9 }), None),
2787 );
2788
2789 let env_vars = CrossCompileEnvVars {
2790 pyo3_cross: None,
2791 pyo3_cross_lib_dir: None,
2792 pyo3_cross_python_version: None,
2793 pyo3_cross_python_implementation: None,
2794 };
2795
2796 assert_eq!(env_vars.parse_version().unwrap(), (None, None));
2797
2798 let env_vars = CrossCompileEnvVars {
2799 pyo3_cross: None,
2800 pyo3_cross_lib_dir: None,
2801 pyo3_cross_python_version: Some("3.13t".into()),
2802 pyo3_cross_python_implementation: None,
2803 };
2804
2805 assert_eq!(
2806 env_vars.parse_version().unwrap(),
2807 (
2808 Some(PythonVersion {
2809 major: 3,
2810 minor: 13
2811 }),
2812 Some("t".into())
2813 ),
2814 );
2815
2816 let env_vars = CrossCompileEnvVars {
2817 pyo3_cross: None,
2818 pyo3_cross_lib_dir: None,
2819 pyo3_cross_python_version: Some("100".into()),
2820 pyo3_cross_python_implementation: None,
2821 };
2822
2823 assert!(env_vars.parse_version().is_err());
2824 }
2825
2826 #[test]
2827 fn interpreter_version_reduced_to_abi3() {
2828 let mut config = InterpreterConfig {
2829 abi3: true,
2830 build_flags: BuildFlags::default(),
2831 pointer_width: None,
2832 executable: None,
2833 implementation: PythonImplementation::CPython,
2834 lib_dir: None,
2835 lib_name: None,
2836 shared: true,
2837 version: PythonVersion { major: 3, minor: 9 },
2839 suppress_build_script_link_lines: false,
2840 extra_build_script_lines: vec![],
2841 python_framework_prefix: None,
2842 };
2843
2844 config
2845 .fixup_for_abi3_version(Some(PythonVersion { major: 3, minor: 8 }))
2846 .unwrap();
2847 assert_eq!(config.version, PythonVersion { major: 3, minor: 8 });
2848 }
2849
2850 #[test]
2851 fn abi3_version_cannot_be_higher_than_interpreter() {
2852 let mut config = InterpreterConfig {
2853 abi3: true,
2854 build_flags: BuildFlags::new(),
2855 pointer_width: None,
2856 executable: None,
2857 implementation: PythonImplementation::CPython,
2858 lib_dir: None,
2859 lib_name: None,
2860 shared: true,
2861 version: PythonVersion { major: 3, minor: 8 },
2862 suppress_build_script_link_lines: false,
2863 extra_build_script_lines: vec![],
2864 python_framework_prefix: None,
2865 };
2866
2867 assert!(config
2868 .fixup_for_abi3_version(Some(PythonVersion { major: 3, minor: 9 }))
2869 .unwrap_err()
2870 .to_string()
2871 .contains(
2872 "cannot set a minimum Python version 3.9 higher than the interpreter version 3.8"
2873 ));
2874 }
2875
2876 #[test]
2877 #[cfg(all(
2878 target_os = "linux",
2879 target_arch = "x86_64",
2880 feature = "resolve-config"
2881 ))]
2882 fn parse_sysconfigdata() {
2883 let interpreter_config = crate::get();
2888
2889 let lib_dir = match &interpreter_config.lib_dir {
2890 Some(lib_dir) => Path::new(lib_dir),
2891 None => return,
2893 };
2894
2895 let cross = CrossCompileConfig {
2896 lib_dir: Some(lib_dir.into()),
2897 version: Some(interpreter_config.version),
2898 implementation: Some(interpreter_config.implementation),
2899 target: triple!("x86_64-unknown-linux-gnu"),
2900 abiflags: if interpreter_config.is_free_threaded() {
2901 Some("t".into())
2902 } else {
2903 None
2904 },
2905 };
2906
2907 let sysconfigdata_path = match find_sysconfigdata(&cross) {
2908 Ok(Some(path)) => path,
2909 _ => return,
2911 };
2912 let sysconfigdata = super::parse_sysconfigdata(sysconfigdata_path).unwrap();
2913 let parsed_config = InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap();
2914
2915 assert_eq!(
2916 parsed_config,
2917 InterpreterConfig {
2918 abi3: false,
2919 build_flags: BuildFlags(interpreter_config.build_flags.0.clone()),
2920 pointer_width: Some(64),
2921 executable: None,
2922 implementation: PythonImplementation::CPython,
2923 lib_dir: interpreter_config.lib_dir.to_owned(),
2924 lib_name: interpreter_config.lib_name.to_owned(),
2925 shared: true,
2926 version: interpreter_config.version,
2927 suppress_build_script_link_lines: false,
2928 extra_build_script_lines: vec![],
2929 python_framework_prefix: None,
2930 }
2931 )
2932 }
2933
2934 #[test]
2935 fn test_venv_interpreter() {
2936 let base = OsStr::new("base");
2937 assert_eq!(
2938 venv_interpreter(base, true),
2939 PathBuf::from_iter(&["base", "Scripts", "python.exe"])
2940 );
2941 assert_eq!(
2942 venv_interpreter(base, false),
2943 PathBuf::from_iter(&["base", "bin", "python"])
2944 );
2945 }
2946
2947 #[test]
2948 fn test_conda_env_interpreter() {
2949 let base = OsStr::new("base");
2950 assert_eq!(
2951 conda_env_interpreter(base, true),
2952 PathBuf::from_iter(&["base", "python.exe"])
2953 );
2954 assert_eq!(
2955 conda_env_interpreter(base, false),
2956 PathBuf::from_iter(&["base", "bin", "python"])
2957 );
2958 }
2959
2960 #[test]
2961 fn test_not_cross_compiling_from_to() {
2962 assert!(cross_compiling_from_to(
2963 &triple!("x86_64-unknown-linux-gnu"),
2964 &triple!("x86_64-unknown-linux-gnu"),
2965 )
2966 .unwrap()
2967 .is_none());
2968
2969 assert!(cross_compiling_from_to(
2970 &triple!("x86_64-apple-darwin"),
2971 &triple!("x86_64-apple-darwin")
2972 )
2973 .unwrap()
2974 .is_none());
2975
2976 assert!(cross_compiling_from_to(
2977 &triple!("aarch64-apple-darwin"),
2978 &triple!("x86_64-apple-darwin")
2979 )
2980 .unwrap()
2981 .is_none());
2982
2983 assert!(cross_compiling_from_to(
2984 &triple!("x86_64-apple-darwin"),
2985 &triple!("aarch64-apple-darwin")
2986 )
2987 .unwrap()
2988 .is_none());
2989
2990 assert!(cross_compiling_from_to(
2991 &triple!("x86_64-pc-windows-msvc"),
2992 &triple!("i686-pc-windows-msvc")
2993 )
2994 .unwrap()
2995 .is_none());
2996
2997 assert!(cross_compiling_from_to(
2998 &triple!("x86_64-unknown-linux-gnu"),
2999 &triple!("x86_64-unknown-linux-musl")
3000 )
3001 .unwrap()
3002 .is_none());
3003
3004 assert!(cross_compiling_from_to(
3005 &triple!("x86_64-pc-windows-msvc"),
3006 &triple!("x86_64-win7-windows-msvc"),
3007 )
3008 .unwrap()
3009 .is_none());
3010 }
3011
3012 #[test]
3013 fn test_is_cross_compiling_from_to() {
3014 assert!(cross_compiling_from_to(
3015 &triple!("x86_64-pc-windows-msvc"),
3016 &triple!("aarch64-pc-windows-msvc")
3017 )
3018 .unwrap()
3019 .is_some());
3020 }
3021
3022 #[test]
3023 fn test_run_python_script() {
3024 let interpreter = make_interpreter_config()
3026 .expect("could not get InterpreterConfig from installed interpreter");
3027 let out = interpreter
3028 .run_python_script("print(2 + 2)")
3029 .expect("failed to run Python script");
3030 assert_eq!(out.trim_end(), "4");
3031 }
3032
3033 #[test]
3034 fn test_run_python_script_with_envs() {
3035 let interpreter = make_interpreter_config()
3037 .expect("could not get InterpreterConfig from installed interpreter");
3038 let out = interpreter
3039 .run_python_script_with_envs(
3040 "import os; print(os.getenv('PYO3_TEST'))",
3041 vec![("PYO3_TEST", "42")],
3042 )
3043 .expect("failed to run Python script");
3044 assert_eq!(out.trim_end(), "42");
3045 }
3046
3047 #[test]
3048 fn test_build_script_outputs_base() {
3049 let interpreter_config = InterpreterConfig {
3050 implementation: PythonImplementation::CPython,
3051 version: PythonVersion {
3052 major: 3,
3053 minor: 11,
3054 },
3055 shared: true,
3056 abi3: false,
3057 lib_name: Some("python3".into()),
3058 lib_dir: None,
3059 executable: None,
3060 pointer_width: None,
3061 build_flags: BuildFlags::default(),
3062 suppress_build_script_link_lines: false,
3063 extra_build_script_lines: vec![],
3064 python_framework_prefix: None,
3065 };
3066 assert_eq!(
3067 interpreter_config.build_script_outputs(),
3068 [
3069 "cargo:rustc-cfg=Py_3_8".to_owned(),
3070 "cargo:rustc-cfg=Py_3_9".to_owned(),
3071 "cargo:rustc-cfg=Py_3_10".to_owned(),
3072 "cargo:rustc-cfg=Py_3_11".to_owned(),
3073 ]
3074 );
3075
3076 let interpreter_config = InterpreterConfig {
3077 implementation: PythonImplementation::PyPy,
3078 ..interpreter_config
3079 };
3080 assert_eq!(
3081 interpreter_config.build_script_outputs(),
3082 [
3083 "cargo:rustc-cfg=Py_3_8".to_owned(),
3084 "cargo:rustc-cfg=Py_3_9".to_owned(),
3085 "cargo:rustc-cfg=Py_3_10".to_owned(),
3086 "cargo:rustc-cfg=Py_3_11".to_owned(),
3087 "cargo:rustc-cfg=PyPy".to_owned(),
3088 ]
3089 );
3090 }
3091
3092 #[test]
3093 fn test_build_script_outputs_abi3() {
3094 let interpreter_config = InterpreterConfig {
3095 implementation: PythonImplementation::CPython,
3096 version: PythonVersion { major: 3, minor: 9 },
3097 shared: true,
3098 abi3: true,
3099 lib_name: Some("python3".into()),
3100 lib_dir: None,
3101 executable: None,
3102 pointer_width: None,
3103 build_flags: BuildFlags::default(),
3104 suppress_build_script_link_lines: false,
3105 extra_build_script_lines: vec![],
3106 python_framework_prefix: None,
3107 };
3108
3109 assert_eq!(
3110 interpreter_config.build_script_outputs(),
3111 [
3112 "cargo:rustc-cfg=Py_3_8".to_owned(),
3113 "cargo:rustc-cfg=Py_3_9".to_owned(),
3114 "cargo:rustc-cfg=Py_LIMITED_API".to_owned(),
3115 ]
3116 );
3117
3118 let interpreter_config = InterpreterConfig {
3119 implementation: PythonImplementation::PyPy,
3120 ..interpreter_config
3121 };
3122 assert_eq!(
3123 interpreter_config.build_script_outputs(),
3124 [
3125 "cargo:rustc-cfg=Py_3_8".to_owned(),
3126 "cargo:rustc-cfg=Py_3_9".to_owned(),
3127 "cargo:rustc-cfg=PyPy".to_owned(),
3128 "cargo:rustc-cfg=Py_LIMITED_API".to_owned(),
3129 ]
3130 );
3131 }
3132
3133 #[test]
3134 fn test_build_script_outputs_gil_disabled() {
3135 let mut build_flags = BuildFlags::default();
3136 build_flags.0.insert(BuildFlag::Py_GIL_DISABLED);
3137 let interpreter_config = InterpreterConfig {
3138 implementation: PythonImplementation::CPython,
3139 version: PythonVersion {
3140 major: 3,
3141 minor: 13,
3142 },
3143 shared: true,
3144 abi3: false,
3145 lib_name: Some("python3".into()),
3146 lib_dir: None,
3147 executable: None,
3148 pointer_width: None,
3149 build_flags,
3150 suppress_build_script_link_lines: false,
3151 extra_build_script_lines: vec![],
3152 python_framework_prefix: None,
3153 };
3154
3155 assert_eq!(
3156 interpreter_config.build_script_outputs(),
3157 [
3158 "cargo:rustc-cfg=Py_3_8".to_owned(),
3159 "cargo:rustc-cfg=Py_3_9".to_owned(),
3160 "cargo:rustc-cfg=Py_3_10".to_owned(),
3161 "cargo:rustc-cfg=Py_3_11".to_owned(),
3162 "cargo:rustc-cfg=Py_3_12".to_owned(),
3163 "cargo:rustc-cfg=Py_3_13".to_owned(),
3164 "cargo:rustc-cfg=Py_GIL_DISABLED".to_owned(),
3165 ]
3166 );
3167 }
3168
3169 #[test]
3170 fn test_build_script_outputs_debug() {
3171 let mut build_flags = BuildFlags::default();
3172 build_flags.0.insert(BuildFlag::Py_DEBUG);
3173 let interpreter_config = InterpreterConfig {
3174 implementation: PythonImplementation::CPython,
3175 version: PythonVersion { major: 3, minor: 8 },
3176 shared: true,
3177 abi3: false,
3178 lib_name: Some("python3".into()),
3179 lib_dir: None,
3180 executable: None,
3181 pointer_width: None,
3182 build_flags,
3183 suppress_build_script_link_lines: false,
3184 extra_build_script_lines: vec![],
3185 python_framework_prefix: None,
3186 };
3187
3188 assert_eq!(
3189 interpreter_config.build_script_outputs(),
3190 [
3191 "cargo:rustc-cfg=Py_3_8".to_owned(),
3192 "cargo:rustc-cfg=py_sys_config=\"Py_DEBUG\"".to_owned(),
3193 ]
3194 );
3195 }
3196
3197 #[test]
3198 fn test_find_sysconfigdata_in_invalid_lib_dir() {
3199 let e = find_all_sysconfigdata(&CrossCompileConfig {
3200 lib_dir: Some(PathBuf::from("/abc/123/not/a/real/path")),
3201 version: None,
3202 implementation: None,
3203 target: triple!("x86_64-unknown-linux-gnu"),
3204 abiflags: None,
3205 })
3206 .unwrap_err();
3207
3208 assert!(e.report().to_string().starts_with(
3210 "failed to search the lib dir at 'PYO3_CROSS_LIB_DIR=/abc/123/not/a/real/path'\n\
3211 caused by:\n \
3212 - 0: failed to list the entries in '/abc/123/not/a/real/path'\n \
3213 - 1: \
3214 "
3215 ));
3216 }
3217
3218 #[test]
3219 fn test_from_pyo3_config_file_env_rebuild() {
3220 READ_ENV_VARS.with(|vars| vars.borrow_mut().clear());
3221 let _ = InterpreterConfig::from_pyo3_config_file_env();
3222 READ_ENV_VARS.with(|vars| assert!(vars.borrow().contains(&"PYO3_CONFIG_FILE".to_string())));
3224 }
3225
3226 #[test]
3227 fn test_apply_default_lib_name_to_config_file() {
3228 let mut config = InterpreterConfig {
3229 implementation: PythonImplementation::CPython,
3230 version: PythonVersion { major: 3, minor: 9 },
3231 shared: true,
3232 abi3: false,
3233 lib_name: None,
3234 lib_dir: None,
3235 executable: None,
3236 pointer_width: None,
3237 build_flags: BuildFlags::default(),
3238 suppress_build_script_link_lines: false,
3239 extra_build_script_lines: vec![],
3240 python_framework_prefix: None,
3241 };
3242
3243 let unix = Triple::from_str("x86_64-unknown-linux-gnu").unwrap();
3244 let win_x64 = Triple::from_str("x86_64-pc-windows-msvc").unwrap();
3245 let win_arm64 = Triple::from_str("aarch64-pc-windows-msvc").unwrap();
3246
3247 config.apply_default_lib_name_to_config_file(&unix);
3248 assert_eq!(config.lib_name, Some("python3.9".into()));
3249
3250 config.lib_name = None;
3251 config.apply_default_lib_name_to_config_file(&win_x64);
3252 assert_eq!(config.lib_name, Some("python39".into()));
3253
3254 config.lib_name = None;
3255 config.apply_default_lib_name_to_config_file(&win_arm64);
3256 assert_eq!(config.lib_name, Some("python39".into()));
3257
3258 config.implementation = PythonImplementation::PyPy;
3260 config.version = PythonVersion {
3261 major: 3,
3262 minor: 11,
3263 };
3264 config.lib_name = None;
3265 config.apply_default_lib_name_to_config_file(&unix);
3266 assert_eq!(config.lib_name, Some("pypy3.11-c".into()));
3267
3268 config.lib_name = None;
3269 config.apply_default_lib_name_to_config_file(&win_x64);
3270 assert_eq!(config.lib_name, Some("libpypy3.11-c".into()));
3271
3272 config.implementation = PythonImplementation::CPython;
3273
3274 config.build_flags.0.insert(BuildFlag::Py_GIL_DISABLED);
3276 config.version = PythonVersion {
3277 major: 3,
3278 minor: 13,
3279 };
3280 config.lib_name = None;
3281 config.apply_default_lib_name_to_config_file(&unix);
3282 assert_eq!(config.lib_name, Some("python3.13t".into()));
3283
3284 config.lib_name = None;
3285 config.apply_default_lib_name_to_config_file(&win_x64);
3286 assert_eq!(config.lib_name, Some("python313t".into()));
3287
3288 config.lib_name = None;
3289 config.apply_default_lib_name_to_config_file(&win_arm64);
3290 assert_eq!(config.lib_name, Some("python313t".into()));
3291
3292 config.build_flags.0.remove(&BuildFlag::Py_GIL_DISABLED);
3293
3294 config.abi3 = true;
3296 config.lib_name = None;
3297 config.apply_default_lib_name_to_config_file(&unix);
3298 assert_eq!(config.lib_name, Some("python3.13".into()));
3299
3300 config.lib_name = None;
3301 config.apply_default_lib_name_to_config_file(&win_x64);
3302 assert_eq!(config.lib_name, Some("python3".into()));
3303
3304 config.lib_name = None;
3305 config.apply_default_lib_name_to_config_file(&win_arm64);
3306 assert_eq!(config.lib_name, Some("python3".into()));
3307 }
3308}