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