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: 25,
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("cygwin", get_platform().startswith("cygwin"))
267print("ext_suffix", get_config_var("EXT_SUFFIX"))
268print("gil_disabled", get_config_var("Py_GIL_DISABLED"))
269"#;
270 let output = run_python_script(interpreter.as_ref(), SCRIPT)?;
271 let map: HashMap<String, String> = parse_script_output(&output);
272
273 ensure!(
274 !map.is_empty(),
275 "broken Python interpreter: {}",
276 interpreter.as_ref().display()
277 );
278
279 if let Some(value) = map.get("graalpy_major") {
280 let graalpy_version = PythonVersion {
281 major: value
282 .parse()
283 .context("failed to parse GraalPy major version")?,
284 minor: map["graalpy_minor"]
285 .parse()
286 .context("failed to parse GraalPy minor version")?,
287 };
288 ensure!(
289 graalpy_version >= MINIMUM_SUPPORTED_VERSION_GRAALPY,
290 "At least GraalPy version {} needed, got {}",
291 MINIMUM_SUPPORTED_VERSION_GRAALPY,
292 graalpy_version
293 );
294 };
295
296 let shared = map["shared"].as_str() == "True";
297 let python_framework_prefix = map.get("python_framework_prefix").cloned();
298
299 let version = PythonVersion {
300 major: map["version_major"]
301 .parse()
302 .context("failed to parse major version")?,
303 minor: map["version_minor"]
304 .parse()
305 .context("failed to parse minor version")?,
306 };
307
308 let abi3 = is_abi3();
309
310 let implementation = map["implementation"].parse()?;
311
312 let gil_disabled = match map["gil_disabled"].as_str() {
313 "1" => true,
314 "0" => false,
315 "None" => false,
316 _ => panic!("Unknown Py_GIL_DISABLED value"),
317 };
318
319 let cygwin = map["cygwin"].as_str() == "True";
320
321 let lib_name = if cfg!(windows) {
322 default_lib_name_windows(
323 version,
324 implementation,
325 abi3,
326 map["mingw"].as_str() == "True",
327 map["ext_suffix"].starts_with("_d."),
331 gil_disabled,
332 )?
333 } else {
334 default_lib_name_unix(
335 version,
336 implementation,
337 abi3,
338 cygwin,
339 map.get("ld_version").map(String::as_str),
340 gil_disabled,
341 )?
342 };
343
344 let lib_dir = if cfg!(windows) {
345 map.get("base_prefix")
346 .map(|base_prefix| format!("{base_prefix}\\libs"))
347 } else {
348 map.get("libdir").cloned()
349 };
350
351 let calcsize_pointer: u32 = map["calcsize_pointer"]
357 .parse()
358 .context("failed to parse calcsize_pointer")?;
359
360 Ok(InterpreterConfig {
361 version,
362 implementation,
363 shared,
364 abi3,
365 lib_name: Some(lib_name),
366 lib_dir,
367 executable: map.get("executable").cloned(),
368 pointer_width: Some(calcsize_pointer * 8),
369 build_flags: BuildFlags::from_interpreter(interpreter)?,
370 suppress_build_script_link_lines: false,
371 extra_build_script_lines: vec![],
372 python_framework_prefix,
373 })
374 }
375
376 pub fn from_sysconfigdata(sysconfigdata: &Sysconfigdata) -> Result<Self> {
381 macro_rules! get_key {
382 ($sysconfigdata:expr, $key:literal) => {
383 $sysconfigdata
384 .get_value($key)
385 .ok_or(concat!($key, " not found in sysconfigdata file"))
386 };
387 }
388
389 macro_rules! parse_key {
390 ($sysconfigdata:expr, $key:literal) => {
391 get_key!($sysconfigdata, $key)?
392 .parse()
393 .context(concat!("could not parse value of ", $key))
394 };
395 }
396
397 let soabi = get_key!(sysconfigdata, "SOABI")?;
398 let implementation = PythonImplementation::from_soabi(soabi)?;
399 let version = parse_key!(sysconfigdata, "VERSION")?;
400 let shared = match sysconfigdata.get_value("Py_ENABLE_SHARED") {
401 Some("1") | Some("true") | Some("True") => true,
402 Some("0") | Some("false") | Some("False") => false,
403 _ => bail!("expected a bool (1/true/True or 0/false/False) for Py_ENABLE_SHARED"),
404 };
405 let framework = match sysconfigdata.get_value("PYTHONFRAMEWORK") {
407 Some(s) => !s.is_empty(),
408 _ => false,
409 };
410 let python_framework_prefix = sysconfigdata
411 .get_value("PYTHONFRAMEWORKPREFIX")
412 .map(str::to_string);
413 let lib_dir = get_key!(sysconfigdata, "LIBDIR").ok().map(str::to_string);
414 let gil_disabled = match sysconfigdata.get_value("Py_GIL_DISABLED") {
415 Some(value) => value == "1",
416 None => false,
417 };
418 let cygwin = soabi.ends_with("cygwin");
419 let abi3 = is_abi3();
420 let lib_name = Some(default_lib_name_unix(
421 version,
422 implementation,
423 abi3,
424 cygwin,
425 sysconfigdata.get_value("LDVERSION"),
426 gil_disabled,
427 )?);
428 let pointer_width = parse_key!(sysconfigdata, "SIZEOF_VOID_P")
429 .map(|bytes_width: u32| bytes_width * 8)
430 .ok();
431 let build_flags = BuildFlags::from_sysconfigdata(sysconfigdata);
432
433 Ok(InterpreterConfig {
434 implementation,
435 version,
436 shared: shared || framework,
437 abi3,
438 lib_dir,
439 lib_name,
440 executable: None,
441 pointer_width,
442 build_flags,
443 suppress_build_script_link_lines: false,
444 extra_build_script_lines: vec![],
445 python_framework_prefix,
446 })
447 }
448
449 #[allow(dead_code)] pub(super) fn from_pyo3_config_file_env() -> Option<Result<Self>> {
454 env_var("PYO3_CONFIG_FILE").map(|path| {
455 let path = Path::new(&path);
456 println!("cargo:rerun-if-changed={}", path.display());
457 ensure!(
460 path.is_absolute(),
461 "PYO3_CONFIG_FILE must be an absolute path"
462 );
463
464 let mut config = InterpreterConfig::from_path(path)
465 .context("failed to parse contents of PYO3_CONFIG_FILE")?;
466 config.abi3 |= is_abi3();
472 config.fixup_for_abi3_version(get_abi3_version())?;
473
474 Ok(config)
475 })
476 }
477
478 #[doc(hidden)]
479 pub fn from_path(path: impl AsRef<Path>) -> Result<Self> {
480 let path = path.as_ref();
481 let config_file = std::fs::File::open(path)
482 .with_context(|| format!("failed to open PyO3 config file at {}", path.display()))?;
483 let reader = std::io::BufReader::new(config_file);
484 InterpreterConfig::from_reader(reader)
485 }
486
487 #[doc(hidden)]
488 pub fn from_cargo_dep_env() -> Option<Result<Self>> {
489 cargo_env_var("DEP_PYTHON_PYO3_CONFIG")
490 .map(|buf| InterpreterConfig::from_reader(&*unescape(&buf)))
491 }
492
493 #[doc(hidden)]
494 pub fn from_reader(reader: impl Read) -> Result<Self> {
495 let reader = BufReader::new(reader);
496 let lines = reader.lines();
497
498 macro_rules! parse_value {
499 ($variable:ident, $value:ident) => {
500 $variable = Some($value.trim().parse().context(format!(
501 concat!(
502 "failed to parse ",
503 stringify!($variable),
504 " from config value '{}'"
505 ),
506 $value
507 ))?)
508 };
509 }
510
511 let mut implementation = None;
512 let mut version = None;
513 let mut shared = None;
514 let mut abi3 = None;
515 let mut lib_name = None;
516 let mut lib_dir = None;
517 let mut executable = None;
518 let mut pointer_width = None;
519 let mut build_flags: Option<BuildFlags> = None;
520 let mut suppress_build_script_link_lines = None;
521 let mut extra_build_script_lines = vec![];
522 let mut python_framework_prefix = None;
523
524 for (i, line) in lines.enumerate() {
525 let line = line.context("failed to read line from config")?;
526 let mut split = line.splitn(2, '=');
527 let (key, value) = (
528 split
529 .next()
530 .expect("first splitn value should always be present"),
531 split
532 .next()
533 .ok_or_else(|| format!("expected key=value pair on line {}", i + 1))?,
534 );
535 match key {
536 "implementation" => parse_value!(implementation, value),
537 "version" => parse_value!(version, value),
538 "shared" => parse_value!(shared, value),
539 "abi3" => parse_value!(abi3, value),
540 "lib_name" => parse_value!(lib_name, value),
541 "lib_dir" => parse_value!(lib_dir, value),
542 "executable" => parse_value!(executable, value),
543 "pointer_width" => parse_value!(pointer_width, value),
544 "build_flags" => parse_value!(build_flags, value),
545 "suppress_build_script_link_lines" => {
546 parse_value!(suppress_build_script_link_lines, value)
547 }
548 "extra_build_script_line" => {
549 extra_build_script_lines.push(value.to_string());
550 }
551 "python_framework_prefix" => parse_value!(python_framework_prefix, value),
552 unknown => warn!("unknown config key `{}`", unknown),
553 }
554 }
555
556 let version = version.ok_or("missing value for version")?;
557 let implementation = implementation.unwrap_or(PythonImplementation::CPython);
558 let abi3 = abi3.unwrap_or(false);
559 let build_flags = build_flags.unwrap_or_default();
560
561 Ok(InterpreterConfig {
562 implementation,
563 version,
564 shared: shared.unwrap_or(true),
565 abi3,
566 lib_name,
567 lib_dir,
568 executable,
569 pointer_width,
570 build_flags,
571 suppress_build_script_link_lines: suppress_build_script_link_lines.unwrap_or(false),
572 extra_build_script_lines,
573 python_framework_prefix,
574 })
575 }
576
577 #[cfg(any(test, feature = "resolve-config"))]
583 pub(crate) fn apply_default_lib_name_to_config_file(&mut self, target: &Triple) {
584 if self.lib_name.is_none() {
585 self.lib_name = Some(default_lib_name_for_target(
586 self.version,
587 self.implementation,
588 self.abi3,
589 self.is_free_threaded(),
590 target,
591 ));
592 }
593 }
594
595 #[cfg(feature = "generate-import-lib")]
596 #[allow(clippy::unnecessary_wraps)]
597 pub fn generate_import_libs(&mut self) -> Result<()> {
598 if self.lib_dir.is_none() {
600 let target = target_triple_from_env();
601 let py_version = if self.implementation == PythonImplementation::CPython
602 && self.abi3
603 && !self.is_free_threaded()
604 {
605 None
606 } else {
607 Some(self.version)
608 };
609 let abiflags = if self.is_free_threaded() {
610 Some("t")
611 } else {
612 None
613 };
614 self.lib_dir = import_lib::generate_import_lib(
615 &target,
616 self.implementation,
617 py_version,
618 abiflags,
619 )?;
620 }
621 Ok(())
622 }
623
624 #[cfg(not(feature = "generate-import-lib"))]
625 #[allow(clippy::unnecessary_wraps)]
626 pub fn generate_import_libs(&mut self) -> Result<()> {
627 Ok(())
628 }
629
630 #[doc(hidden)]
631 pub fn to_cargo_dep_env(&self) -> Result<()> {
642 let mut buf = Vec::new();
643 self.to_writer(&mut buf)?;
644 println!("cargo:PYO3_CONFIG={}", escape(&buf));
646 Ok(())
647 }
648
649 #[doc(hidden)]
650 pub fn to_writer(&self, mut writer: impl Write) -> Result<()> {
651 macro_rules! write_line {
652 ($value:ident) => {
653 writeln!(writer, "{}={}", stringify!($value), self.$value).context(concat!(
654 "failed to write ",
655 stringify!($value),
656 " to config"
657 ))
658 };
659 }
660
661 macro_rules! write_option_line {
662 ($value:ident) => {
663 if let Some(value) = &self.$value {
664 writeln!(writer, "{}={}", stringify!($value), value).context(concat!(
665 "failed to write ",
666 stringify!($value),
667 " to config"
668 ))
669 } else {
670 Ok(())
671 }
672 };
673 }
674
675 write_line!(implementation)?;
676 write_line!(version)?;
677 write_line!(shared)?;
678 write_line!(abi3)?;
679 write_option_line!(lib_name)?;
680 write_option_line!(lib_dir)?;
681 write_option_line!(executable)?;
682 write_option_line!(pointer_width)?;
683 write_line!(build_flags)?;
684 write_option_line!(python_framework_prefix)?;
685 write_line!(suppress_build_script_link_lines)?;
686 for line in &self.extra_build_script_lines {
687 writeln!(writer, "extra_build_script_line={line}")
688 .context("failed to write extra_build_script_line")?;
689 }
690 Ok(())
691 }
692
693 pub fn run_python_script(&self, script: &str) -> Result<String> {
699 run_python_script_with_envs(
700 Path::new(self.executable.as_ref().expect("no interpreter executable")),
701 script,
702 std::iter::empty::<(&str, &str)>(),
703 )
704 }
705
706 pub fn run_python_script_with_envs<I, K, V>(&self, script: &str, envs: I) -> Result<String>
713 where
714 I: IntoIterator<Item = (K, V)>,
715 K: AsRef<OsStr>,
716 V: AsRef<OsStr>,
717 {
718 run_python_script_with_envs(
719 Path::new(self.executable.as_ref().expect("no interpreter executable")),
720 script,
721 envs,
722 )
723 }
724
725 pub fn is_free_threaded(&self) -> bool {
726 self.build_flags.0.contains(&BuildFlag::Py_GIL_DISABLED)
727 }
728
729 fn fixup_for_abi3_version(&mut self, abi3_version: Option<PythonVersion>) -> Result<()> {
732 if self.implementation.is_pypy()
734 || self.implementation.is_graalpy()
735 || self.is_free_threaded()
736 {
737 return Ok(());
738 }
739
740 if let Some(version) = abi3_version {
741 ensure!(
742 version <= self.version,
743 "cannot set a minimum Python version {} higher than the interpreter version {} \
744 (the minimum Python version is implied by the abi3-py3{} feature)",
745 version,
746 self.version,
747 version.minor,
748 );
749
750 self.version = version;
751 } else if is_abi3() && self.version.minor > ABI3_MAX_MINOR {
752 warn!("Automatically falling back to abi3-py3{ABI3_MAX_MINOR} because current Python is higher than the maximum supported");
753 self.version.minor = ABI3_MAX_MINOR;
754 }
755
756 Ok(())
757 }
758}
759
760#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
761pub struct PythonVersion {
762 pub major: u8,
763 pub minor: u8,
764}
765
766impl PythonVersion {
767 pub const PY313: Self = PythonVersion {
768 major: 3,
769 minor: 13,
770 };
771 const PY310: Self = PythonVersion {
772 major: 3,
773 minor: 10,
774 };
775 const PY37: Self = PythonVersion { major: 3, minor: 7 };
776}
777
778impl Display for PythonVersion {
779 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
780 write!(f, "{}.{}", self.major, self.minor)
781 }
782}
783
784impl FromStr for PythonVersion {
785 type Err = crate::errors::Error;
786
787 fn from_str(value: &str) -> Result<Self, Self::Err> {
788 let mut split = value.splitn(2, '.');
789 let (major, minor) = (
790 split
791 .next()
792 .expect("first splitn value should always be present"),
793 split.next().ok_or("expected major.minor version")?,
794 );
795 Ok(Self {
796 major: major.parse().context("failed to parse major version")?,
797 minor: minor.parse().context("failed to parse minor version")?,
798 })
799 }
800}
801
802#[derive(Debug, Copy, Clone, PartialEq, Eq)]
803pub enum PythonImplementation {
804 CPython,
805 PyPy,
806 GraalPy,
807}
808
809impl PythonImplementation {
810 #[doc(hidden)]
811 pub fn is_pypy(self) -> bool {
812 self == PythonImplementation::PyPy
813 }
814
815 #[doc(hidden)]
816 pub fn is_graalpy(self) -> bool {
817 self == PythonImplementation::GraalPy
818 }
819
820 #[doc(hidden)]
821 pub fn from_soabi(soabi: &str) -> Result<Self> {
822 if soabi.starts_with("pypy") {
823 Ok(PythonImplementation::PyPy)
824 } else if soabi.starts_with("cpython") {
825 Ok(PythonImplementation::CPython)
826 } else if soabi.starts_with("graalpy") {
827 Ok(PythonImplementation::GraalPy)
828 } else {
829 bail!("unsupported Python interpreter");
830 }
831 }
832}
833
834impl Display for PythonImplementation {
835 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
836 match self {
837 PythonImplementation::CPython => write!(f, "CPython"),
838 PythonImplementation::PyPy => write!(f, "PyPy"),
839 PythonImplementation::GraalPy => write!(f, "GraalVM"),
840 }
841 }
842}
843
844impl FromStr for PythonImplementation {
845 type Err = Error;
846 fn from_str(s: &str) -> Result<Self> {
847 match s {
848 "CPython" => Ok(PythonImplementation::CPython),
849 "PyPy" => Ok(PythonImplementation::PyPy),
850 "GraalVM" => Ok(PythonImplementation::GraalPy),
851 _ => bail!("unknown interpreter: {}", s),
852 }
853 }
854}
855
856fn have_python_interpreter() -> bool {
861 env_var("PYO3_NO_PYTHON").is_none()
862}
863
864fn is_abi3() -> bool {
868 cargo_env_var("CARGO_FEATURE_ABI3").is_some()
869 || env_var("PYO3_USE_ABI3_FORWARD_COMPATIBILITY").is_some_and(|os_str| os_str == "1")
870}
871
872pub fn get_abi3_version() -> Option<PythonVersion> {
876 let minor_version = (MINIMUM_SUPPORTED_VERSION.minor..=ABI3_MAX_MINOR)
877 .find(|i| cargo_env_var(&format!("CARGO_FEATURE_ABI3_PY3{i}")).is_some());
878 minor_version.map(|minor| PythonVersion { major: 3, minor })
879}
880
881pub fn is_extension_module() -> bool {
889 cargo_env_var("CARGO_FEATURE_EXTENSION_MODULE").is_some()
890 || env_var("PYO3_BUILD_EXTENSION_MODULE").is_some()
891}
892
893pub fn is_linking_libpython_for_target(target: &Triple) -> bool {
897 target.operating_system == OperatingSystem::Windows
898 || target.operating_system == OperatingSystem::Aix
900 || target.environment == Environment::Android
901 || target.environment == Environment::Androideabi
902 || target.operating_system == OperatingSystem::Cygwin
903 || matches!(target.operating_system, OperatingSystem::IOS(_))
904 || !is_extension_module()
905}
906
907fn require_libdir_for_target(target: &Triple) -> bool {
912 let is_generating_libpython = cfg!(feature = "generate-import-lib")
913 && target.operating_system == OperatingSystem::Windows
914 && is_abi3();
915
916 is_linking_libpython_for_target(target) && !is_generating_libpython
917}
918
919#[derive(Debug, PartialEq, Eq)]
924pub struct CrossCompileConfig {
925 pub lib_dir: Option<PathBuf>,
927
928 version: Option<PythonVersion>,
930
931 implementation: Option<PythonImplementation>,
933
934 target: Triple,
936
937 abiflags: Option<String>,
939}
940
941impl CrossCompileConfig {
942 fn try_from_env_vars_host_target(
947 env_vars: CrossCompileEnvVars,
948 host: &Triple,
949 target: &Triple,
950 ) -> Result<Option<Self>> {
951 if env_vars.any() || Self::is_cross_compiling_from_to(host, target) {
952 let lib_dir = env_vars.lib_dir_path()?;
953 let (version, abiflags) = env_vars.parse_version()?;
954 let implementation = env_vars.parse_implementation()?;
955 let target = target.clone();
956
957 Ok(Some(CrossCompileConfig {
958 lib_dir,
959 version,
960 implementation,
961 target,
962 abiflags,
963 }))
964 } else {
965 Ok(None)
966 }
967 }
968
969 fn is_cross_compiling_from_to(host: &Triple, target: &Triple) -> bool {
973 let mut compatible = host.architecture == target.architecture
977 && (host.vendor == target.vendor
978 || (host.vendor == Vendor::Pc && target.vendor.as_str() == "win7"))
980 && host.operating_system == target.operating_system;
981
982 compatible |= target.operating_system == OperatingSystem::Windows
984 && host.operating_system == OperatingSystem::Windows
985 && matches!(target.architecture, Architecture::X86_32(_))
986 && host.architecture == Architecture::X86_64;
987
988 compatible |= matches!(target.operating_system, OperatingSystem::Darwin(_))
990 && matches!(host.operating_system, OperatingSystem::Darwin(_));
991
992 compatible |= matches!(target.operating_system, OperatingSystem::IOS(_));
993
994 !compatible
995 }
996
997 #[allow(dead_code)]
1002 fn lib_dir_string(&self) -> Option<String> {
1003 self.lib_dir
1004 .as_ref()
1005 .map(|s| s.to_str().unwrap().to_owned())
1006 }
1007}
1008
1009struct CrossCompileEnvVars {
1011 pyo3_cross: Option<OsString>,
1013 pyo3_cross_lib_dir: Option<OsString>,
1015 pyo3_cross_python_version: Option<OsString>,
1017 pyo3_cross_python_implementation: Option<OsString>,
1019}
1020
1021impl CrossCompileEnvVars {
1022 fn from_env() -> Self {
1026 CrossCompileEnvVars {
1027 pyo3_cross: env_var("PYO3_CROSS"),
1028 pyo3_cross_lib_dir: env_var("PYO3_CROSS_LIB_DIR"),
1029 pyo3_cross_python_version: env_var("PYO3_CROSS_PYTHON_VERSION"),
1030 pyo3_cross_python_implementation: env_var("PYO3_CROSS_PYTHON_IMPLEMENTATION"),
1031 }
1032 }
1033
1034 fn any(&self) -> bool {
1036 self.pyo3_cross.is_some()
1037 || self.pyo3_cross_lib_dir.is_some()
1038 || self.pyo3_cross_python_version.is_some()
1039 || self.pyo3_cross_python_implementation.is_some()
1040 }
1041
1042 fn parse_version(&self) -> Result<(Option<PythonVersion>, Option<String>)> {
1045 match self.pyo3_cross_python_version.as_ref() {
1046 Some(os_string) => {
1047 let utf8_str = os_string
1048 .to_str()
1049 .ok_or("PYO3_CROSS_PYTHON_VERSION is not valid a UTF-8 string")?;
1050 let (utf8_str, abiflags) = if let Some(version) = utf8_str.strip_suffix('t') {
1051 (version, Some("t".to_string()))
1052 } else {
1053 (utf8_str, None)
1054 };
1055 let version = utf8_str
1056 .parse()
1057 .context("failed to parse PYO3_CROSS_PYTHON_VERSION")?;
1058 Ok((Some(version), abiflags))
1059 }
1060 None => Ok((None, None)),
1061 }
1062 }
1063
1064 fn parse_implementation(&self) -> Result<Option<PythonImplementation>> {
1067 let implementation = self
1068 .pyo3_cross_python_implementation
1069 .as_ref()
1070 .map(|os_string| {
1071 let utf8_str = os_string
1072 .to_str()
1073 .ok_or("PYO3_CROSS_PYTHON_IMPLEMENTATION is not valid a UTF-8 string")?;
1074 utf8_str
1075 .parse()
1076 .context("failed to parse PYO3_CROSS_PYTHON_IMPLEMENTATION")
1077 })
1078 .transpose()?;
1079
1080 Ok(implementation)
1081 }
1082
1083 fn lib_dir_path(&self) -> Result<Option<PathBuf>> {
1088 let lib_dir = self.pyo3_cross_lib_dir.as_ref().map(PathBuf::from);
1089
1090 if let Some(dir) = lib_dir.as_ref() {
1091 ensure!(
1092 dir.to_str().is_some(),
1093 "PYO3_CROSS_LIB_DIR variable value is not a valid UTF-8 string"
1094 );
1095 }
1096
1097 Ok(lib_dir)
1098 }
1099}
1100
1101pub fn cross_compiling_from_to(
1116 host: &Triple,
1117 target: &Triple,
1118) -> Result<Option<CrossCompileConfig>> {
1119 let env_vars = CrossCompileEnvVars::from_env();
1120 CrossCompileConfig::try_from_env_vars_host_target(env_vars, host, target)
1121}
1122
1123#[allow(dead_code)]
1129pub fn cross_compiling_from_cargo_env() -> Result<Option<CrossCompileConfig>> {
1130 let env_vars = CrossCompileEnvVars::from_env();
1131 let host = Triple::host();
1132 let target = target_triple_from_env();
1133
1134 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
1135}
1136
1137#[allow(non_camel_case_types)]
1138#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1139pub enum BuildFlag {
1140 Py_DEBUG,
1141 Py_REF_DEBUG,
1142 Py_TRACE_REFS,
1143 Py_GIL_DISABLED,
1144 COUNT_ALLOCS,
1145 Other(String),
1146}
1147
1148impl Display for BuildFlag {
1149 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1150 match self {
1151 BuildFlag::Other(flag) => write!(f, "{flag}"),
1152 _ => write!(f, "{self:?}"),
1153 }
1154 }
1155}
1156
1157impl FromStr for BuildFlag {
1158 type Err = std::convert::Infallible;
1159 fn from_str(s: &str) -> Result<Self, Self::Err> {
1160 match s {
1161 "Py_DEBUG" => Ok(BuildFlag::Py_DEBUG),
1162 "Py_REF_DEBUG" => Ok(BuildFlag::Py_REF_DEBUG),
1163 "Py_TRACE_REFS" => Ok(BuildFlag::Py_TRACE_REFS),
1164 "Py_GIL_DISABLED" => Ok(BuildFlag::Py_GIL_DISABLED),
1165 "COUNT_ALLOCS" => Ok(BuildFlag::COUNT_ALLOCS),
1166 other => Ok(BuildFlag::Other(other.to_owned())),
1167 }
1168 }
1169}
1170
1171#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
1185#[derive(Clone, Default)]
1186pub struct BuildFlags(pub HashSet<BuildFlag>);
1187
1188impl BuildFlags {
1189 const ALL: [BuildFlag; 5] = [
1190 BuildFlag::Py_DEBUG,
1191 BuildFlag::Py_REF_DEBUG,
1192 BuildFlag::Py_TRACE_REFS,
1193 BuildFlag::Py_GIL_DISABLED,
1194 BuildFlag::COUNT_ALLOCS,
1195 ];
1196
1197 pub fn new() -> Self {
1198 BuildFlags(HashSet::new())
1199 }
1200
1201 fn from_sysconfigdata(config_map: &Sysconfigdata) -> Self {
1202 Self(
1203 BuildFlags::ALL
1204 .iter()
1205 .filter(|flag| config_map.get_value(flag.to_string()) == Some("1"))
1206 .cloned()
1207 .collect(),
1208 )
1209 .fixup()
1210 }
1211
1212 fn from_interpreter(interpreter: impl AsRef<Path>) -> Result<Self> {
1216 if cfg!(windows) {
1220 let script = String::from("import sys;print(sys.version_info < (3, 13))");
1221 let stdout = run_python_script(interpreter.as_ref(), &script)?;
1222 if stdout.trim_end() == "True" {
1223 return Ok(Self::new());
1224 }
1225 }
1226
1227 let mut script = String::from("import sysconfig\n");
1228 script.push_str("config = sysconfig.get_config_vars()\n");
1229
1230 for k in &BuildFlags::ALL {
1231 use std::fmt::Write;
1232 writeln!(&mut script, "print(config.get('{k}', '0'))").unwrap();
1233 }
1234
1235 let stdout = run_python_script(interpreter.as_ref(), &script)?;
1236 let split_stdout: Vec<&str> = stdout.trim_end().lines().collect();
1237 ensure!(
1238 split_stdout.len() == BuildFlags::ALL.len(),
1239 "Python stdout len didn't return expected number of lines: {}",
1240 split_stdout.len()
1241 );
1242 let flags = BuildFlags::ALL
1243 .iter()
1244 .zip(split_stdout)
1245 .filter(|(_, flag_value)| *flag_value == "1")
1246 .map(|(flag, _)| flag.clone())
1247 .collect();
1248
1249 Ok(Self(flags).fixup())
1250 }
1251
1252 fn fixup(mut self) -> Self {
1253 if self.0.contains(&BuildFlag::Py_DEBUG) {
1254 self.0.insert(BuildFlag::Py_REF_DEBUG);
1255 }
1256
1257 self
1258 }
1259}
1260
1261impl Display for BuildFlags {
1262 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1263 let mut first = true;
1264 for flag in &self.0 {
1265 if first {
1266 first = false;
1267 } else {
1268 write!(f, ",")?;
1269 }
1270 write!(f, "{flag}")?;
1271 }
1272 Ok(())
1273 }
1274}
1275
1276impl FromStr for BuildFlags {
1277 type Err = std::convert::Infallible;
1278
1279 fn from_str(value: &str) -> Result<Self, Self::Err> {
1280 let mut flags = HashSet::new();
1281 for flag in value.split_terminator(',') {
1282 flags.insert(flag.parse().unwrap());
1283 }
1284 Ok(BuildFlags(flags))
1285 }
1286}
1287
1288fn parse_script_output(output: &str) -> HashMap<String, String> {
1289 output
1290 .lines()
1291 .filter_map(|line| {
1292 let mut i = line.splitn(2, ' ');
1293 Some((i.next()?.into(), i.next()?.into()))
1294 })
1295 .collect()
1296}
1297
1298pub struct Sysconfigdata(HashMap<String, String>);
1302
1303impl Sysconfigdata {
1304 pub fn get_value<S: AsRef<str>>(&self, k: S) -> Option<&str> {
1305 self.0.get(k.as_ref()).map(String::as_str)
1306 }
1307
1308 #[allow(dead_code)]
1309 fn new() -> Self {
1310 Sysconfigdata(HashMap::new())
1311 }
1312
1313 #[allow(dead_code)]
1314 fn insert<S: Into<String>>(&mut self, k: S, v: S) {
1315 self.0.insert(k.into(), v.into());
1316 }
1317}
1318
1319pub fn parse_sysconfigdata(sysconfigdata_path: impl AsRef<Path>) -> Result<Sysconfigdata> {
1327 let sysconfigdata_path = sysconfigdata_path.as_ref();
1328 let mut script = fs::read_to_string(sysconfigdata_path).with_context(|| {
1329 format!(
1330 "failed to read config from {}",
1331 sysconfigdata_path.display()
1332 )
1333 })?;
1334 script += r#"
1335for key, val in build_time_vars.items():
1336 # (ana)conda(-forge) built Pythons are statically linked but ship the shared library with them.
1337 # We detect them based on the magic prefix directory they have encoded in their builds.
1338 if key == "Py_ENABLE_SHARED" and "_h_env_placehold" in build_time_vars.get("prefix"):
1339 val = 1
1340 print(key, val)
1341"#;
1342
1343 let output = run_python_script(&find_interpreter()?, &script)?;
1344
1345 Ok(Sysconfigdata(parse_script_output(&output)))
1346}
1347
1348fn starts_with(entry: &DirEntry, pat: &str) -> bool {
1349 let name = entry.file_name();
1350 name.to_string_lossy().starts_with(pat)
1351}
1352fn ends_with(entry: &DirEntry, pat: &str) -> bool {
1353 let name = entry.file_name();
1354 name.to_string_lossy().ends_with(pat)
1355}
1356
1357#[allow(dead_code)]
1362fn find_sysconfigdata(cross: &CrossCompileConfig) -> Result<Option<PathBuf>> {
1363 let mut sysconfig_paths = find_all_sysconfigdata(cross)?;
1364 if sysconfig_paths.is_empty() {
1365 if let Some(lib_dir) = cross.lib_dir.as_ref() {
1366 bail!("Could not find _sysconfigdata*.py in {}", lib_dir.display());
1367 } else {
1368 return Ok(None);
1370 }
1371 } else if sysconfig_paths.len() > 1 {
1372 let mut error_msg = String::from(
1373 "Detected multiple possible Python versions. Please set either the \
1374 PYO3_CROSS_PYTHON_VERSION variable to the wanted version or the \
1375 _PYTHON_SYSCONFIGDATA_NAME variable to the wanted sysconfigdata file name.\n\n\
1376 sysconfigdata files found:",
1377 );
1378 for path in sysconfig_paths {
1379 use std::fmt::Write;
1380 write!(&mut error_msg, "\n\t{}", path.display()).unwrap();
1381 }
1382 bail!("{}\n", error_msg);
1383 }
1384
1385 Ok(Some(sysconfig_paths.remove(0)))
1386}
1387
1388pub fn find_all_sysconfigdata(cross: &CrossCompileConfig) -> Result<Vec<PathBuf>> {
1427 let sysconfig_paths = if let Some(lib_dir) = cross.lib_dir.as_ref() {
1428 search_lib_dir(lib_dir, cross).with_context(|| {
1429 format!(
1430 "failed to search the lib dir at 'PYO3_CROSS_LIB_DIR={}'",
1431 lib_dir.display()
1432 )
1433 })?
1434 } else {
1435 return Ok(Vec::new());
1436 };
1437
1438 let sysconfig_name = env_var("_PYTHON_SYSCONFIGDATA_NAME");
1439 let mut sysconfig_paths = sysconfig_paths
1440 .iter()
1441 .filter_map(|p| {
1442 let canonical = fs::canonicalize(p).ok();
1443 match &sysconfig_name {
1444 Some(_) => canonical.filter(|p| p.file_stem() == sysconfig_name.as_deref()),
1445 None => canonical,
1446 }
1447 })
1448 .collect::<Vec<PathBuf>>();
1449
1450 sysconfig_paths.sort();
1451 sysconfig_paths.dedup();
1452
1453 Ok(sysconfig_paths)
1454}
1455
1456fn is_pypy_lib_dir(path: &str, v: &Option<PythonVersion>) -> bool {
1457 let pypy_version_pat = if let Some(v) = v {
1458 format!("pypy{v}")
1459 } else {
1460 "pypy3.".into()
1461 };
1462 path == "lib_pypy" || path.starts_with(&pypy_version_pat)
1463}
1464
1465fn is_graalpy_lib_dir(path: &str, v: &Option<PythonVersion>) -> bool {
1466 let graalpy_version_pat = if let Some(v) = v {
1467 format!("graalpy{v}")
1468 } else {
1469 "graalpy2".into()
1470 };
1471 path == "lib_graalpython" || path.starts_with(&graalpy_version_pat)
1472}
1473
1474fn is_cpython_lib_dir(path: &str, v: &Option<PythonVersion>) -> bool {
1475 let cpython_version_pat = if let Some(v) = v {
1476 format!("python{v}")
1477 } else {
1478 "python3.".into()
1479 };
1480 path.starts_with(&cpython_version_pat)
1481}
1482
1483fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Result<Vec<PathBuf>> {
1485 let mut sysconfig_paths = vec![];
1486 for f in fs::read_dir(path.as_ref()).with_context(|| {
1487 format!(
1488 "failed to list the entries in '{}'",
1489 path.as_ref().display()
1490 )
1491 })? {
1492 sysconfig_paths.extend(match &f {
1493 Ok(f) if starts_with(f, "_sysconfigdata_") && ends_with(f, "py") => vec![f.path()],
1495 Ok(f) if f.metadata().is_ok_and(|metadata| metadata.is_dir()) => {
1496 let file_name = f.file_name();
1497 let file_name = file_name.to_string_lossy();
1498 if file_name == "build" || file_name == "lib" {
1499 search_lib_dir(f.path(), cross)?
1500 } else if file_name.starts_with("lib.") {
1501 if !file_name.contains(&cross.target.operating_system.to_string()) {
1503 continue;
1504 }
1505 if !file_name.contains(&cross.target.architecture.to_string()) {
1507 continue;
1508 }
1509 search_lib_dir(f.path(), cross)?
1510 } else if is_cpython_lib_dir(&file_name, &cross.version)
1511 || is_pypy_lib_dir(&file_name, &cross.version)
1512 || is_graalpy_lib_dir(&file_name, &cross.version)
1513 {
1514 search_lib_dir(f.path(), cross)?
1515 } else {
1516 continue;
1517 }
1518 }
1519 _ => continue,
1520 });
1521 }
1522 if sysconfig_paths.len() > 1 {
1530 let temp = sysconfig_paths
1531 .iter()
1532 .filter(|p| {
1533 p.to_string_lossy()
1534 .contains(&cross.target.architecture.to_string())
1535 })
1536 .cloned()
1537 .collect::<Vec<PathBuf>>();
1538 if !temp.is_empty() {
1539 sysconfig_paths = temp;
1540 }
1541 }
1542
1543 Ok(sysconfig_paths)
1544}
1545
1546#[allow(dead_code)]
1554fn cross_compile_from_sysconfigdata(
1555 cross_compile_config: &CrossCompileConfig,
1556) -> Result<Option<InterpreterConfig>> {
1557 if let Some(path) = find_sysconfigdata(cross_compile_config)? {
1558 let data = parse_sysconfigdata(path)?;
1559 let mut config = InterpreterConfig::from_sysconfigdata(&data)?;
1560 if let Some(cross_lib_dir) = cross_compile_config.lib_dir_string() {
1561 config.lib_dir = Some(cross_lib_dir)
1562 }
1563
1564 Ok(Some(config))
1565 } else {
1566 Ok(None)
1567 }
1568}
1569
1570#[allow(unused_mut, dead_code)]
1577fn default_cross_compile(cross_compile_config: &CrossCompileConfig) -> Result<InterpreterConfig> {
1578 let version = cross_compile_config
1579 .version
1580 .or_else(get_abi3_version)
1581 .ok_or_else(||
1582 format!(
1583 "PYO3_CROSS_PYTHON_VERSION or an abi3-py3* feature must be specified \
1584 when cross-compiling and PYO3_CROSS_LIB_DIR is not set.\n\
1585 = help: see the PyO3 user guide for more information: https://pyo3.rs/v{}/building-and-distribution.html#cross-compiling",
1586 env!("CARGO_PKG_VERSION")
1587 )
1588 )?;
1589
1590 let abi3 = is_abi3();
1591 let implementation = cross_compile_config
1592 .implementation
1593 .unwrap_or(PythonImplementation::CPython);
1594 let gil_disabled: bool = cross_compile_config.abiflags.as_deref() == Some("t");
1595
1596 let lib_name = default_lib_name_for_target(
1597 version,
1598 implementation,
1599 abi3,
1600 gil_disabled,
1601 &cross_compile_config.target,
1602 );
1603
1604 let mut lib_dir = cross_compile_config.lib_dir_string();
1605
1606 #[cfg(feature = "generate-import-lib")]
1608 if lib_dir.is_none() {
1609 let py_version = if implementation == PythonImplementation::CPython && abi3 && !gil_disabled
1610 {
1611 None
1612 } else {
1613 Some(version)
1614 };
1615 lib_dir = self::import_lib::generate_import_lib(
1616 &cross_compile_config.target,
1617 cross_compile_config
1618 .implementation
1619 .unwrap_or(PythonImplementation::CPython),
1620 py_version,
1621 None,
1622 )?;
1623 }
1624
1625 Ok(InterpreterConfig {
1626 implementation,
1627 version,
1628 shared: true,
1629 abi3,
1630 lib_name: Some(lib_name),
1631 lib_dir,
1632 executable: None,
1633 pointer_width: None,
1634 build_flags: BuildFlags::default(),
1635 suppress_build_script_link_lines: false,
1636 extra_build_script_lines: vec![],
1637 python_framework_prefix: None,
1638 })
1639}
1640
1641fn default_abi3_config(host: &Triple, version: PythonVersion) -> Result<InterpreterConfig> {
1651 let implementation = PythonImplementation::CPython;
1653 let abi3 = true;
1654
1655 let lib_name = if host.operating_system == OperatingSystem::Windows {
1656 Some(default_lib_name_windows(
1657 version,
1658 implementation,
1659 abi3,
1660 false,
1661 false,
1662 false,
1663 )?)
1664 } else {
1665 None
1666 };
1667
1668 Ok(InterpreterConfig {
1669 implementation,
1670 version,
1671 shared: true,
1672 abi3,
1673 lib_name,
1674 lib_dir: None,
1675 executable: None,
1676 pointer_width: None,
1677 build_flags: BuildFlags::default(),
1678 suppress_build_script_link_lines: false,
1679 extra_build_script_lines: vec![],
1680 python_framework_prefix: None,
1681 })
1682}
1683
1684#[allow(dead_code)]
1692fn load_cross_compile_config(
1693 cross_compile_config: CrossCompileConfig,
1694) -> Result<InterpreterConfig> {
1695 let windows = cross_compile_config.target.operating_system == OperatingSystem::Windows;
1696
1697 let config = if windows || !have_python_interpreter() {
1698 default_cross_compile(&cross_compile_config)?
1702 } else if let Some(config) = cross_compile_from_sysconfigdata(&cross_compile_config)? {
1703 config
1705 } else {
1706 default_cross_compile(&cross_compile_config)?
1708 };
1709
1710 Ok(config)
1711}
1712
1713const WINDOWS_ABI3_LIB_NAME: &str = "python3";
1715const WINDOWS_ABI3_DEBUG_LIB_NAME: &str = "python3_d";
1716
1717#[allow(dead_code)]
1719fn default_lib_name_for_target(
1720 version: PythonVersion,
1721 implementation: PythonImplementation,
1722 abi3: bool,
1723 gil_disabled: bool,
1724 target: &Triple,
1725) -> String {
1726 if target.operating_system == OperatingSystem::Windows {
1727 default_lib_name_windows(version, implementation, abi3, false, false, gil_disabled).unwrap()
1728 } else {
1729 default_lib_name_unix(
1730 version,
1731 implementation,
1732 abi3,
1733 target.operating_system == OperatingSystem::Cygwin,
1734 None,
1735 gil_disabled,
1736 )
1737 .unwrap()
1738 }
1739}
1740
1741fn default_lib_name_windows(
1742 version: PythonVersion,
1743 implementation: PythonImplementation,
1744 abi3: bool,
1745 mingw: bool,
1746 debug: bool,
1747 gil_disabled: bool,
1748) -> Result<String> {
1749 if debug && version < PythonVersion::PY310 {
1750 Ok(format!("python{}{}_d", version.major, version.minor))
1753 } else if abi3 && !(gil_disabled || implementation.is_pypy() || implementation.is_graalpy()) {
1754 if debug {
1755 Ok(WINDOWS_ABI3_DEBUG_LIB_NAME.to_owned())
1756 } else {
1757 Ok(WINDOWS_ABI3_LIB_NAME.to_owned())
1758 }
1759 } else if mingw {
1760 ensure!(
1761 !gil_disabled,
1762 "MinGW free-threaded builds are not currently tested or supported"
1763 );
1764 Ok(format!("python{}.{}", version.major, version.minor))
1766 } else if gil_disabled {
1767 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);
1768 if debug {
1769 Ok(format!("python{}{}t_d", version.major, version.minor))
1770 } else {
1771 Ok(format!("python{}{}t", version.major, version.minor))
1772 }
1773 } else if debug {
1774 Ok(format!("python{}{}_d", version.major, version.minor))
1775 } else {
1776 Ok(format!("python{}{}", version.major, version.minor))
1777 }
1778}
1779
1780fn default_lib_name_unix(
1781 version: PythonVersion,
1782 implementation: PythonImplementation,
1783 abi3: bool,
1784 cygwin: bool,
1785 ld_version: Option<&str>,
1786 gil_disabled: bool,
1787) -> Result<String> {
1788 match implementation {
1789 PythonImplementation::CPython => match ld_version {
1790 Some(ld_version) => Ok(format!("python{ld_version}")),
1791 None => {
1792 if cygwin && abi3 {
1793 Ok("python3".to_string())
1794 } else if version > PythonVersion::PY37 {
1795 if gil_disabled {
1797 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);
1798 Ok(format!("python{}.{}t", version.major, version.minor))
1799 } else {
1800 Ok(format!("python{}.{}", version.major, version.minor))
1801 }
1802 } else {
1803 Ok(format!("python{}.{}m", version.major, version.minor))
1805 }
1806 }
1807 },
1808 PythonImplementation::PyPy => match ld_version {
1809 Some(ld_version) => Ok(format!("pypy{ld_version}-c")),
1810 None => Ok(format!("pypy{}.{}-c", version.major, version.minor)),
1811 },
1812
1813 PythonImplementation::GraalPy => Ok("python-native".to_string()),
1814 }
1815}
1816
1817fn run_python_script(interpreter: &Path, script: &str) -> Result<String> {
1819 run_python_script_with_envs(interpreter, script, std::iter::empty::<(&str, &str)>())
1820}
1821
1822fn run_python_script_with_envs<I, K, V>(interpreter: &Path, script: &str, envs: I) -> Result<String>
1825where
1826 I: IntoIterator<Item = (K, V)>,
1827 K: AsRef<OsStr>,
1828 V: AsRef<OsStr>,
1829{
1830 let out = Command::new(interpreter)
1831 .env("PYTHONIOENCODING", "utf-8")
1832 .envs(envs)
1833 .stdin(Stdio::piped())
1834 .stdout(Stdio::piped())
1835 .stderr(Stdio::inherit())
1836 .spawn()
1837 .and_then(|mut child| {
1838 child
1839 .stdin
1840 .as_mut()
1841 .expect("piped stdin")
1842 .write_all(script.as_bytes())?;
1843 child.wait_with_output()
1844 });
1845
1846 match out {
1847 Err(err) => bail!(
1848 "failed to run the Python interpreter at {}: {}",
1849 interpreter.display(),
1850 err
1851 ),
1852 Ok(ok) if !ok.status.success() => bail!("Python script failed"),
1853 Ok(ok) => Ok(String::from_utf8(ok.stdout)
1854 .context("failed to parse Python script output as utf-8")?),
1855 }
1856}
1857
1858fn venv_interpreter(virtual_env: &OsStr, windows: bool) -> PathBuf {
1859 if windows {
1860 Path::new(virtual_env).join("Scripts").join("python.exe")
1861 } else {
1862 Path::new(virtual_env).join("bin").join("python")
1863 }
1864}
1865
1866fn conda_env_interpreter(conda_prefix: &OsStr, windows: bool) -> PathBuf {
1867 if windows {
1868 Path::new(conda_prefix).join("python.exe")
1869 } else {
1870 Path::new(conda_prefix).join("bin").join("python")
1871 }
1872}
1873
1874fn get_env_interpreter() -> Option<PathBuf> {
1875 match (env_var("VIRTUAL_ENV"), env_var("CONDA_PREFIX")) {
1876 (Some(dir), None) => Some(venv_interpreter(&dir, cfg!(windows))),
1879 (None, Some(dir)) => Some(conda_env_interpreter(&dir, cfg!(windows))),
1880 (Some(_), Some(_)) => {
1881 warn!(
1882 "Both VIRTUAL_ENV and CONDA_PREFIX are set. PyO3 will ignore both of these for \
1883 locating the Python interpreter until you unset one of them."
1884 );
1885 None
1886 }
1887 (None, None) => None,
1888 }
1889}
1890
1891pub fn find_interpreter() -> Result<PathBuf> {
1899 println!("cargo:rerun-if-env-changed=PYO3_ENVIRONMENT_SIGNATURE");
1902
1903 if let Some(exe) = env_var("PYO3_PYTHON") {
1904 Ok(exe.into())
1905 } else if let Some(env_interpreter) = get_env_interpreter() {
1906 Ok(env_interpreter)
1907 } else {
1908 println!("cargo:rerun-if-env-changed=PATH");
1909 ["python", "python3"]
1910 .iter()
1911 .find(|bin| {
1912 if let Ok(out) = Command::new(bin).arg("--version").output() {
1913 out.stdout.starts_with(b"Python 3")
1915 || out.stderr.starts_with(b"Python 3")
1916 || out.stdout.starts_with(b"GraalPy 3")
1917 } else {
1918 false
1919 }
1920 })
1921 .map(PathBuf::from)
1922 .ok_or_else(|| "no Python 3.x interpreter found".into())
1923 }
1924}
1925
1926fn get_host_interpreter(abi3_version: Option<PythonVersion>) -> Result<InterpreterConfig> {
1930 let interpreter_path = find_interpreter()?;
1931
1932 let mut interpreter_config = InterpreterConfig::from_interpreter(interpreter_path)?;
1933 interpreter_config.fixup_for_abi3_version(abi3_version)?;
1934
1935 Ok(interpreter_config)
1936}
1937
1938#[allow(dead_code)]
1943pub fn make_cross_compile_config() -> Result<Option<InterpreterConfig>> {
1944 let interpreter_config = if let Some(cross_config) = cross_compiling_from_cargo_env()? {
1945 let mut interpreter_config = load_cross_compile_config(cross_config)?;
1946 interpreter_config.fixup_for_abi3_version(get_abi3_version())?;
1947 Some(interpreter_config)
1948 } else {
1949 None
1950 };
1951
1952 Ok(interpreter_config)
1953}
1954
1955#[allow(dead_code, unused_mut)]
1958pub fn make_interpreter_config() -> Result<InterpreterConfig> {
1959 let host = Triple::host();
1960 let abi3_version = get_abi3_version();
1961
1962 let need_interpreter = abi3_version.is_none() || require_libdir_for_target(&host);
1965
1966 if have_python_interpreter() {
1967 match get_host_interpreter(abi3_version) {
1968 Ok(interpreter_config) => return Ok(interpreter_config),
1969 Err(e) if need_interpreter => return Err(e),
1971 _ => {
1972 warn!("Compiling without a working Python interpreter.");
1975 }
1976 }
1977 } else {
1978 ensure!(
1979 abi3_version.is_some(),
1980 "An abi3-py3* feature must be specified when compiling without a Python interpreter."
1981 );
1982 };
1983
1984 let mut interpreter_config = default_abi3_config(&host, abi3_version.unwrap())?;
1985
1986 #[cfg(feature = "generate-import-lib")]
1988 {
1989 let gil_disabled = interpreter_config
1990 .build_flags
1991 .0
1992 .contains(&BuildFlag::Py_GIL_DISABLED);
1993 let py_version = if interpreter_config.implementation == PythonImplementation::CPython
1994 && interpreter_config.abi3
1995 && !gil_disabled
1996 {
1997 None
1998 } else {
1999 Some(interpreter_config.version)
2000 };
2001 interpreter_config.lib_dir = self::import_lib::generate_import_lib(
2002 &host,
2003 interpreter_config.implementation,
2004 py_version,
2005 None,
2006 )?;
2007 }
2008
2009 Ok(interpreter_config)
2010}
2011
2012fn escape(bytes: &[u8]) -> String {
2013 let mut escaped = String::with_capacity(2 * bytes.len());
2014
2015 for byte in bytes {
2016 const LUT: &[u8; 16] = b"0123456789abcdef";
2017
2018 escaped.push(LUT[(byte >> 4) as usize] as char);
2019 escaped.push(LUT[(byte & 0x0F) as usize] as char);
2020 }
2021
2022 escaped
2023}
2024
2025fn unescape(escaped: &str) -> Vec<u8> {
2026 assert_eq!(escaped.len() % 2, 0, "invalid hex encoding");
2027
2028 let mut bytes = Vec::with_capacity(escaped.len() / 2);
2029
2030 for chunk in escaped.as_bytes().chunks_exact(2) {
2031 fn unhex(hex: u8) -> u8 {
2032 match hex {
2033 b'a'..=b'f' => hex - b'a' + 10,
2034 b'0'..=b'9' => hex - b'0',
2035 _ => panic!("invalid hex encoding"),
2036 }
2037 }
2038
2039 bytes.push((unhex(chunk[0]) << 4) | unhex(chunk[1]));
2040 }
2041
2042 bytes
2043}
2044
2045#[cfg(test)]
2046mod tests {
2047 use target_lexicon::triple;
2048
2049 use super::*;
2050
2051 #[test]
2052 fn test_config_file_roundtrip() {
2053 let config = InterpreterConfig {
2054 abi3: true,
2055 build_flags: BuildFlags::default(),
2056 pointer_width: Some(32),
2057 executable: Some("executable".into()),
2058 implementation: PythonImplementation::CPython,
2059 lib_name: Some("lib_name".into()),
2060 lib_dir: Some("lib_dir".into()),
2061 shared: true,
2062 version: MINIMUM_SUPPORTED_VERSION,
2063 suppress_build_script_link_lines: true,
2064 extra_build_script_lines: vec!["cargo:test1".to_string(), "cargo:test2".to_string()],
2065 python_framework_prefix: None,
2066 };
2067 let mut buf: Vec<u8> = Vec::new();
2068 config.to_writer(&mut buf).unwrap();
2069
2070 assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap());
2071
2072 let config = InterpreterConfig {
2075 abi3: false,
2076 build_flags: {
2077 let mut flags = HashSet::new();
2078 flags.insert(BuildFlag::Py_DEBUG);
2079 flags.insert(BuildFlag::Other(String::from("Py_SOME_FLAG")));
2080 BuildFlags(flags)
2081 },
2082 pointer_width: None,
2083 executable: None,
2084 implementation: PythonImplementation::PyPy,
2085 lib_dir: None,
2086 lib_name: None,
2087 shared: true,
2088 version: PythonVersion {
2089 major: 3,
2090 minor: 10,
2091 },
2092 suppress_build_script_link_lines: false,
2093 extra_build_script_lines: vec![],
2094 python_framework_prefix: None,
2095 };
2096 let mut buf: Vec<u8> = Vec::new();
2097 config.to_writer(&mut buf).unwrap();
2098
2099 assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap());
2100 }
2101
2102 #[test]
2103 fn test_config_file_roundtrip_with_escaping() {
2104 let config = InterpreterConfig {
2105 abi3: true,
2106 build_flags: BuildFlags::default(),
2107 pointer_width: Some(32),
2108 executable: Some("executable".into()),
2109 implementation: PythonImplementation::CPython,
2110 lib_name: Some("lib_name".into()),
2111 lib_dir: Some("lib_dir\\n".into()),
2112 shared: true,
2113 version: MINIMUM_SUPPORTED_VERSION,
2114 suppress_build_script_link_lines: true,
2115 extra_build_script_lines: vec!["cargo:test1".to_string(), "cargo:test2".to_string()],
2116 python_framework_prefix: None,
2117 };
2118 let mut buf: Vec<u8> = Vec::new();
2119 config.to_writer(&mut buf).unwrap();
2120
2121 let buf = unescape(&escape(&buf));
2122
2123 assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap());
2124 }
2125
2126 #[test]
2127 fn test_config_file_defaults() {
2128 assert_eq!(
2130 InterpreterConfig::from_reader("version=3.7".as_bytes()).unwrap(),
2131 InterpreterConfig {
2132 version: PythonVersion { major: 3, minor: 7 },
2133 implementation: PythonImplementation::CPython,
2134 shared: true,
2135 abi3: false,
2136 lib_name: None,
2137 lib_dir: None,
2138 executable: None,
2139 pointer_width: None,
2140 build_flags: BuildFlags::default(),
2141 suppress_build_script_link_lines: false,
2142 extra_build_script_lines: vec![],
2143 python_framework_prefix: None,
2144 }
2145 )
2146 }
2147
2148 #[test]
2149 fn test_config_file_unknown_keys() {
2150 assert_eq!(
2152 InterpreterConfig::from_reader("version=3.7\next_suffix=.python37.so".as_bytes())
2153 .unwrap(),
2154 InterpreterConfig {
2155 version: PythonVersion { major: 3, minor: 7 },
2156 implementation: PythonImplementation::CPython,
2157 shared: true,
2158 abi3: false,
2159 lib_name: None,
2160 lib_dir: None,
2161 executable: None,
2162 pointer_width: None,
2163 build_flags: BuildFlags::default(),
2164 suppress_build_script_link_lines: false,
2165 extra_build_script_lines: vec![],
2166 python_framework_prefix: None,
2167 }
2168 )
2169 }
2170
2171 #[test]
2172 fn build_flags_default() {
2173 assert_eq!(BuildFlags::default(), BuildFlags::new());
2174 }
2175
2176 #[test]
2177 fn build_flags_from_sysconfigdata() {
2178 let mut sysconfigdata = Sysconfigdata::new();
2179
2180 assert_eq!(
2181 BuildFlags::from_sysconfigdata(&sysconfigdata).0,
2182 HashSet::new()
2183 );
2184
2185 for flag in &BuildFlags::ALL {
2186 sysconfigdata.insert(flag.to_string(), "0".into());
2187 }
2188
2189 assert_eq!(
2190 BuildFlags::from_sysconfigdata(&sysconfigdata).0,
2191 HashSet::new()
2192 );
2193
2194 let mut expected_flags = HashSet::new();
2195 for flag in &BuildFlags::ALL {
2196 sysconfigdata.insert(flag.to_string(), "1".into());
2197 expected_flags.insert(flag.clone());
2198 }
2199
2200 assert_eq!(
2201 BuildFlags::from_sysconfigdata(&sysconfigdata).0,
2202 expected_flags
2203 );
2204 }
2205
2206 #[test]
2207 fn build_flags_fixup() {
2208 let mut build_flags = BuildFlags::new();
2209
2210 build_flags = build_flags.fixup();
2211 assert!(build_flags.0.is_empty());
2212
2213 build_flags.0.insert(BuildFlag::Py_DEBUG);
2214
2215 build_flags = build_flags.fixup();
2216
2217 assert!(build_flags.0.contains(&BuildFlag::Py_REF_DEBUG));
2219 }
2220
2221 #[test]
2222 fn parse_script_output() {
2223 let output = "foo bar\nbar foobar\n\n";
2224 let map = super::parse_script_output(output);
2225 assert_eq!(map.len(), 2);
2226 assert_eq!(map["foo"], "bar");
2227 assert_eq!(map["bar"], "foobar");
2228 }
2229
2230 #[test]
2231 fn config_from_interpreter() {
2232 assert!(make_interpreter_config().is_ok())
2236 }
2237
2238 #[test]
2239 fn config_from_empty_sysconfigdata() {
2240 let sysconfigdata = Sysconfigdata::new();
2241 assert!(InterpreterConfig::from_sysconfigdata(&sysconfigdata).is_err());
2242 }
2243
2244 #[test]
2245 fn config_from_sysconfigdata() {
2246 let mut sysconfigdata = Sysconfigdata::new();
2247 sysconfigdata.insert("SOABI", "cpython-37m-x86_64-linux-gnu");
2250 sysconfigdata.insert("VERSION", "3.7");
2251 sysconfigdata.insert("Py_ENABLE_SHARED", "1");
2252 sysconfigdata.insert("LIBDIR", "/usr/lib");
2253 sysconfigdata.insert("LDVERSION", "3.7m");
2254 sysconfigdata.insert("SIZEOF_VOID_P", "8");
2255 assert_eq!(
2256 InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(),
2257 InterpreterConfig {
2258 abi3: false,
2259 build_flags: BuildFlags::from_sysconfigdata(&sysconfigdata),
2260 pointer_width: Some(64),
2261 executable: None,
2262 implementation: PythonImplementation::CPython,
2263 lib_dir: Some("/usr/lib".into()),
2264 lib_name: Some("python3.7m".into()),
2265 shared: true,
2266 version: PythonVersion::PY37,
2267 suppress_build_script_link_lines: false,
2268 extra_build_script_lines: vec![],
2269 python_framework_prefix: None,
2270 }
2271 );
2272 }
2273
2274 #[test]
2275 fn config_from_sysconfigdata_framework() {
2276 let mut sysconfigdata = Sysconfigdata::new();
2277 sysconfigdata.insert("SOABI", "cpython-37m-x86_64-linux-gnu");
2278 sysconfigdata.insert("VERSION", "3.7");
2279 sysconfigdata.insert("Py_ENABLE_SHARED", "0");
2281 sysconfigdata.insert("PYTHONFRAMEWORK", "Python");
2282 sysconfigdata.insert("LIBDIR", "/usr/lib");
2283 sysconfigdata.insert("LDVERSION", "3.7m");
2284 sysconfigdata.insert("SIZEOF_VOID_P", "8");
2285 assert_eq!(
2286 InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(),
2287 InterpreterConfig {
2288 abi3: false,
2289 build_flags: BuildFlags::from_sysconfigdata(&sysconfigdata),
2290 pointer_width: Some(64),
2291 executable: None,
2292 implementation: PythonImplementation::CPython,
2293 lib_dir: Some("/usr/lib".into()),
2294 lib_name: Some("python3.7m".into()),
2295 shared: true,
2296 version: PythonVersion::PY37,
2297 suppress_build_script_link_lines: false,
2298 extra_build_script_lines: vec![],
2299 python_framework_prefix: None,
2300 }
2301 );
2302
2303 sysconfigdata = Sysconfigdata::new();
2304 sysconfigdata.insert("SOABI", "cpython-37m-x86_64-linux-gnu");
2305 sysconfigdata.insert("VERSION", "3.7");
2306 sysconfigdata.insert("Py_ENABLE_SHARED", "0");
2308 sysconfigdata.insert("PYTHONFRAMEWORK", "");
2309 sysconfigdata.insert("LIBDIR", "/usr/lib");
2310 sysconfigdata.insert("LDVERSION", "3.7m");
2311 sysconfigdata.insert("SIZEOF_VOID_P", "8");
2312 assert_eq!(
2313 InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(),
2314 InterpreterConfig {
2315 abi3: false,
2316 build_flags: BuildFlags::from_sysconfigdata(&sysconfigdata),
2317 pointer_width: Some(64),
2318 executable: None,
2319 implementation: PythonImplementation::CPython,
2320 lib_dir: Some("/usr/lib".into()),
2321 lib_name: Some("python3.7m".into()),
2322 shared: false,
2323 version: PythonVersion::PY37,
2324 suppress_build_script_link_lines: false,
2325 extra_build_script_lines: vec![],
2326 python_framework_prefix: None,
2327 }
2328 );
2329 }
2330
2331 #[test]
2332 fn windows_hardcoded_abi3_compile() {
2333 let host = triple!("x86_64-pc-windows-msvc");
2334 let min_version = "3.7".parse().unwrap();
2335
2336 assert_eq!(
2337 default_abi3_config(&host, min_version).unwrap(),
2338 InterpreterConfig {
2339 implementation: PythonImplementation::CPython,
2340 version: PythonVersion { major: 3, minor: 7 },
2341 shared: true,
2342 abi3: true,
2343 lib_name: Some("python3".into()),
2344 lib_dir: None,
2345 executable: None,
2346 pointer_width: None,
2347 build_flags: BuildFlags::default(),
2348 suppress_build_script_link_lines: false,
2349 extra_build_script_lines: vec![],
2350 python_framework_prefix: None,
2351 }
2352 );
2353 }
2354
2355 #[test]
2356 fn unix_hardcoded_abi3_compile() {
2357 let host = triple!("x86_64-unknown-linux-gnu");
2358 let min_version = "3.9".parse().unwrap();
2359
2360 assert_eq!(
2361 default_abi3_config(&host, min_version).unwrap(),
2362 InterpreterConfig {
2363 implementation: PythonImplementation::CPython,
2364 version: PythonVersion { major: 3, minor: 9 },
2365 shared: true,
2366 abi3: true,
2367 lib_name: None,
2368 lib_dir: None,
2369 executable: None,
2370 pointer_width: None,
2371 build_flags: BuildFlags::default(),
2372 suppress_build_script_link_lines: false,
2373 extra_build_script_lines: vec![],
2374 python_framework_prefix: None,
2375 }
2376 );
2377 }
2378
2379 #[test]
2380 fn windows_hardcoded_cross_compile() {
2381 let env_vars = CrossCompileEnvVars {
2382 pyo3_cross: None,
2383 pyo3_cross_lib_dir: Some("C:\\some\\path".into()),
2384 pyo3_cross_python_implementation: None,
2385 pyo3_cross_python_version: Some("3.7".into()),
2386 };
2387
2388 let host = triple!("x86_64-unknown-linux-gnu");
2389 let target = triple!("i686-pc-windows-msvc");
2390 let cross_config =
2391 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
2392 .unwrap()
2393 .unwrap();
2394
2395 assert_eq!(
2396 default_cross_compile(&cross_config).unwrap(),
2397 InterpreterConfig {
2398 implementation: PythonImplementation::CPython,
2399 version: PythonVersion { major: 3, minor: 7 },
2400 shared: true,
2401 abi3: false,
2402 lib_name: Some("python37".into()),
2403 lib_dir: Some("C:\\some\\path".into()),
2404 executable: None,
2405 pointer_width: None,
2406 build_flags: BuildFlags::default(),
2407 suppress_build_script_link_lines: false,
2408 extra_build_script_lines: vec![],
2409 python_framework_prefix: None,
2410 }
2411 );
2412 }
2413
2414 #[test]
2415 fn mingw_hardcoded_cross_compile() {
2416 let env_vars = CrossCompileEnvVars {
2417 pyo3_cross: None,
2418 pyo3_cross_lib_dir: Some("/usr/lib/mingw".into()),
2419 pyo3_cross_python_implementation: None,
2420 pyo3_cross_python_version: Some("3.8".into()),
2421 };
2422
2423 let host = triple!("x86_64-unknown-linux-gnu");
2424 let target = triple!("i686-pc-windows-gnu");
2425 let cross_config =
2426 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
2427 .unwrap()
2428 .unwrap();
2429
2430 assert_eq!(
2431 default_cross_compile(&cross_config).unwrap(),
2432 InterpreterConfig {
2433 implementation: PythonImplementation::CPython,
2434 version: PythonVersion { major: 3, minor: 8 },
2435 shared: true,
2436 abi3: false,
2437 lib_name: Some("python38".into()),
2438 lib_dir: Some("/usr/lib/mingw".into()),
2439 executable: None,
2440 pointer_width: None,
2441 build_flags: BuildFlags::default(),
2442 suppress_build_script_link_lines: false,
2443 extra_build_script_lines: vec![],
2444 python_framework_prefix: None,
2445 }
2446 );
2447 }
2448
2449 #[test]
2450 fn unix_hardcoded_cross_compile() {
2451 let env_vars = CrossCompileEnvVars {
2452 pyo3_cross: None,
2453 pyo3_cross_lib_dir: Some("/usr/arm64/lib".into()),
2454 pyo3_cross_python_implementation: None,
2455 pyo3_cross_python_version: Some("3.9".into()),
2456 };
2457
2458 let host = triple!("x86_64-unknown-linux-gnu");
2459 let target = triple!("aarch64-unknown-linux-gnu");
2460 let cross_config =
2461 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
2462 .unwrap()
2463 .unwrap();
2464
2465 assert_eq!(
2466 default_cross_compile(&cross_config).unwrap(),
2467 InterpreterConfig {
2468 implementation: PythonImplementation::CPython,
2469 version: PythonVersion { major: 3, minor: 9 },
2470 shared: true,
2471 abi3: false,
2472 lib_name: Some("python3.9".into()),
2473 lib_dir: Some("/usr/arm64/lib".into()),
2474 executable: None,
2475 pointer_width: None,
2476 build_flags: BuildFlags::default(),
2477 suppress_build_script_link_lines: false,
2478 extra_build_script_lines: vec![],
2479 python_framework_prefix: None,
2480 }
2481 );
2482 }
2483
2484 #[test]
2485 fn pypy_hardcoded_cross_compile() {
2486 let env_vars = CrossCompileEnvVars {
2487 pyo3_cross: None,
2488 pyo3_cross_lib_dir: None,
2489 pyo3_cross_python_implementation: Some("PyPy".into()),
2490 pyo3_cross_python_version: Some("3.11".into()),
2491 };
2492
2493 let triple = triple!("x86_64-unknown-linux-gnu");
2494 let cross_config =
2495 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &triple, &triple)
2496 .unwrap()
2497 .unwrap();
2498
2499 assert_eq!(
2500 default_cross_compile(&cross_config).unwrap(),
2501 InterpreterConfig {
2502 implementation: PythonImplementation::PyPy,
2503 version: PythonVersion {
2504 major: 3,
2505 minor: 11
2506 },
2507 shared: true,
2508 abi3: false,
2509 lib_name: Some("pypy3.11-c".into()),
2510 lib_dir: None,
2511 executable: None,
2512 pointer_width: None,
2513 build_flags: BuildFlags::default(),
2514 suppress_build_script_link_lines: false,
2515 extra_build_script_lines: vec![],
2516 python_framework_prefix: None,
2517 }
2518 );
2519 }
2520
2521 #[test]
2522 fn default_lib_name_windows() {
2523 use PythonImplementation::*;
2524 assert_eq!(
2525 super::default_lib_name_windows(
2526 PythonVersion { major: 3, minor: 9 },
2527 CPython,
2528 false,
2529 false,
2530 false,
2531 false,
2532 )
2533 .unwrap(),
2534 "python39",
2535 );
2536 assert!(super::default_lib_name_windows(
2537 PythonVersion { major: 3, minor: 9 },
2538 CPython,
2539 false,
2540 false,
2541 false,
2542 true,
2543 )
2544 .is_err());
2545 assert_eq!(
2546 super::default_lib_name_windows(
2547 PythonVersion { major: 3, minor: 9 },
2548 CPython,
2549 true,
2550 false,
2551 false,
2552 false,
2553 )
2554 .unwrap(),
2555 "python3",
2556 );
2557 assert_eq!(
2558 super::default_lib_name_windows(
2559 PythonVersion { major: 3, minor: 9 },
2560 CPython,
2561 false,
2562 true,
2563 false,
2564 false,
2565 )
2566 .unwrap(),
2567 "python3.9",
2568 );
2569 assert_eq!(
2570 super::default_lib_name_windows(
2571 PythonVersion { major: 3, minor: 9 },
2572 CPython,
2573 true,
2574 true,
2575 false,
2576 false,
2577 )
2578 .unwrap(),
2579 "python3",
2580 );
2581 assert_eq!(
2582 super::default_lib_name_windows(
2583 PythonVersion { major: 3, minor: 9 },
2584 PyPy,
2585 true,
2586 false,
2587 false,
2588 false,
2589 )
2590 .unwrap(),
2591 "python39",
2592 );
2593 assert_eq!(
2594 super::default_lib_name_windows(
2595 PythonVersion { major: 3, minor: 9 },
2596 CPython,
2597 false,
2598 false,
2599 true,
2600 false,
2601 )
2602 .unwrap(),
2603 "python39_d",
2604 );
2605 assert_eq!(
2608 super::default_lib_name_windows(
2609 PythonVersion { major: 3, minor: 9 },
2610 CPython,
2611 true,
2612 false,
2613 true,
2614 false,
2615 )
2616 .unwrap(),
2617 "python39_d",
2618 );
2619 assert_eq!(
2620 super::default_lib_name_windows(
2621 PythonVersion {
2622 major: 3,
2623 minor: 10
2624 },
2625 CPython,
2626 true,
2627 false,
2628 true,
2629 false,
2630 )
2631 .unwrap(),
2632 "python3_d",
2633 );
2634 assert!(super::default_lib_name_windows(
2636 PythonVersion {
2637 major: 3,
2638 minor: 12,
2639 },
2640 CPython,
2641 false,
2642 false,
2643 false,
2644 true,
2645 )
2646 .is_err());
2647 assert!(super::default_lib_name_windows(
2649 PythonVersion {
2650 major: 3,
2651 minor: 12,
2652 },
2653 CPython,
2654 false,
2655 true,
2656 false,
2657 true,
2658 )
2659 .is_err());
2660 assert_eq!(
2661 super::default_lib_name_windows(
2662 PythonVersion {
2663 major: 3,
2664 minor: 13
2665 },
2666 CPython,
2667 false,
2668 false,
2669 false,
2670 true,
2671 )
2672 .unwrap(),
2673 "python313t",
2674 );
2675 assert_eq!(
2676 super::default_lib_name_windows(
2677 PythonVersion {
2678 major: 3,
2679 minor: 13
2680 },
2681 CPython,
2682 true, false,
2684 false,
2685 true,
2686 )
2687 .unwrap(),
2688 "python313t",
2689 );
2690 assert_eq!(
2691 super::default_lib_name_windows(
2692 PythonVersion {
2693 major: 3,
2694 minor: 13
2695 },
2696 CPython,
2697 false,
2698 false,
2699 true,
2700 true,
2701 )
2702 .unwrap(),
2703 "python313t_d",
2704 );
2705 }
2706
2707 #[test]
2708 fn default_lib_name_unix() {
2709 use PythonImplementation::*;
2710 assert_eq!(
2712 super::default_lib_name_unix(
2713 PythonVersion { major: 3, minor: 7 },
2714 CPython,
2715 false,
2716 false,
2717 None,
2718 false
2719 )
2720 .unwrap(),
2721 "python3.7m",
2722 );
2723 assert_eq!(
2725 super::default_lib_name_unix(
2726 PythonVersion { major: 3, minor: 8 },
2727 CPython,
2728 false,
2729 false,
2730 None,
2731 false
2732 )
2733 .unwrap(),
2734 "python3.8",
2735 );
2736 assert_eq!(
2737 super::default_lib_name_unix(
2738 PythonVersion { major: 3, minor: 9 },
2739 CPython,
2740 false,
2741 false,
2742 None,
2743 false
2744 )
2745 .unwrap(),
2746 "python3.9",
2747 );
2748 assert_eq!(
2750 super::default_lib_name_unix(
2751 PythonVersion { major: 3, minor: 9 },
2752 CPython,
2753 false,
2754 false,
2755 Some("3.7md"),
2756 false
2757 )
2758 .unwrap(),
2759 "python3.7md",
2760 );
2761
2762 assert_eq!(
2764 super::default_lib_name_unix(
2765 PythonVersion {
2766 major: 3,
2767 minor: 11
2768 },
2769 PyPy,
2770 false,
2771 false,
2772 None,
2773 false
2774 )
2775 .unwrap(),
2776 "pypy3.11-c",
2777 );
2778
2779 assert_eq!(
2780 super::default_lib_name_unix(
2781 PythonVersion { major: 3, minor: 9 },
2782 PyPy,
2783 false,
2784 false,
2785 Some("3.11d"),
2786 false
2787 )
2788 .unwrap(),
2789 "pypy3.11d-c",
2790 );
2791
2792 assert_eq!(
2794 super::default_lib_name_unix(
2795 PythonVersion {
2796 major: 3,
2797 minor: 13
2798 },
2799 CPython,
2800 false,
2801 false,
2802 None,
2803 true
2804 )
2805 .unwrap(),
2806 "python3.13t",
2807 );
2808 assert!(super::default_lib_name_unix(
2810 PythonVersion {
2811 major: 3,
2812 minor: 12,
2813 },
2814 CPython,
2815 false,
2816 false,
2817 None,
2818 true,
2819 )
2820 .is_err());
2821 assert_eq!(
2823 super::default_lib_name_unix(
2824 PythonVersion {
2825 major: 3,
2826 minor: 13
2827 },
2828 CPython,
2829 true,
2830 true,
2831 None,
2832 false
2833 )
2834 .unwrap(),
2835 "python3",
2836 );
2837 }
2838
2839 #[test]
2840 fn parse_cross_python_version() {
2841 let env_vars = CrossCompileEnvVars {
2842 pyo3_cross: None,
2843 pyo3_cross_lib_dir: None,
2844 pyo3_cross_python_version: Some("3.9".into()),
2845 pyo3_cross_python_implementation: None,
2846 };
2847
2848 assert_eq!(
2849 env_vars.parse_version().unwrap(),
2850 (Some(PythonVersion { major: 3, minor: 9 }), None),
2851 );
2852
2853 let env_vars = CrossCompileEnvVars {
2854 pyo3_cross: None,
2855 pyo3_cross_lib_dir: None,
2856 pyo3_cross_python_version: None,
2857 pyo3_cross_python_implementation: None,
2858 };
2859
2860 assert_eq!(env_vars.parse_version().unwrap(), (None, None));
2861
2862 let env_vars = CrossCompileEnvVars {
2863 pyo3_cross: None,
2864 pyo3_cross_lib_dir: None,
2865 pyo3_cross_python_version: Some("3.13t".into()),
2866 pyo3_cross_python_implementation: None,
2867 };
2868
2869 assert_eq!(
2870 env_vars.parse_version().unwrap(),
2871 (
2872 Some(PythonVersion {
2873 major: 3,
2874 minor: 13
2875 }),
2876 Some("t".into())
2877 ),
2878 );
2879
2880 let env_vars = CrossCompileEnvVars {
2881 pyo3_cross: None,
2882 pyo3_cross_lib_dir: None,
2883 pyo3_cross_python_version: Some("100".into()),
2884 pyo3_cross_python_implementation: None,
2885 };
2886
2887 assert!(env_vars.parse_version().is_err());
2888 }
2889
2890 #[test]
2891 fn interpreter_version_reduced_to_abi3() {
2892 let mut config = InterpreterConfig {
2893 abi3: true,
2894 build_flags: BuildFlags::default(),
2895 pointer_width: None,
2896 executable: None,
2897 implementation: PythonImplementation::CPython,
2898 lib_dir: None,
2899 lib_name: None,
2900 shared: true,
2901 version: PythonVersion { major: 3, minor: 7 },
2902 suppress_build_script_link_lines: false,
2903 extra_build_script_lines: vec![],
2904 python_framework_prefix: None,
2905 };
2906
2907 config
2908 .fixup_for_abi3_version(Some(PythonVersion { major: 3, minor: 7 }))
2909 .unwrap();
2910 assert_eq!(config.version, PythonVersion { major: 3, minor: 7 });
2911 }
2912
2913 #[test]
2914 fn abi3_version_cannot_be_higher_than_interpreter() {
2915 let mut config = InterpreterConfig {
2916 abi3: true,
2917 build_flags: BuildFlags::new(),
2918 pointer_width: None,
2919 executable: None,
2920 implementation: PythonImplementation::CPython,
2921 lib_dir: None,
2922 lib_name: None,
2923 shared: true,
2924 version: PythonVersion { major: 3, minor: 7 },
2925 suppress_build_script_link_lines: false,
2926 extra_build_script_lines: vec![],
2927 python_framework_prefix: None,
2928 };
2929
2930 assert!(config
2931 .fixup_for_abi3_version(Some(PythonVersion { major: 3, minor: 8 }))
2932 .unwrap_err()
2933 .to_string()
2934 .contains(
2935 "cannot set a minimum Python version 3.8 higher than the interpreter version 3.7"
2936 ));
2937 }
2938
2939 #[test]
2940 #[cfg(all(
2941 target_os = "linux",
2942 target_arch = "x86_64",
2943 feature = "resolve-config"
2944 ))]
2945 fn parse_sysconfigdata() {
2946 let interpreter_config = crate::get();
2951
2952 let lib_dir = match &interpreter_config.lib_dir {
2953 Some(lib_dir) => Path::new(lib_dir),
2954 None => return,
2956 };
2957
2958 let cross = CrossCompileConfig {
2959 lib_dir: Some(lib_dir.into()),
2960 version: Some(interpreter_config.version),
2961 implementation: Some(interpreter_config.implementation),
2962 target: triple!("x86_64-unknown-linux-gnu"),
2963 abiflags: if interpreter_config.is_free_threaded() {
2964 Some("t".into())
2965 } else {
2966 None
2967 },
2968 };
2969
2970 let sysconfigdata_path = match find_sysconfigdata(&cross) {
2971 Ok(Some(path)) => path,
2972 _ => return,
2974 };
2975 let sysconfigdata = super::parse_sysconfigdata(sysconfigdata_path).unwrap();
2976 let parsed_config = InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap();
2977
2978 assert_eq!(
2979 parsed_config,
2980 InterpreterConfig {
2981 abi3: false,
2982 build_flags: BuildFlags(interpreter_config.build_flags.0.clone()),
2983 pointer_width: Some(64),
2984 executable: None,
2985 implementation: PythonImplementation::CPython,
2986 lib_dir: interpreter_config.lib_dir.to_owned(),
2987 lib_name: interpreter_config.lib_name.to_owned(),
2988 shared: true,
2989 version: interpreter_config.version,
2990 suppress_build_script_link_lines: false,
2991 extra_build_script_lines: vec![],
2992 python_framework_prefix: None,
2993 }
2994 )
2995 }
2996
2997 #[test]
2998 fn test_venv_interpreter() {
2999 let base = OsStr::new("base");
3000 assert_eq!(
3001 venv_interpreter(base, true),
3002 PathBuf::from_iter(&["base", "Scripts", "python.exe"])
3003 );
3004 assert_eq!(
3005 venv_interpreter(base, false),
3006 PathBuf::from_iter(&["base", "bin", "python"])
3007 );
3008 }
3009
3010 #[test]
3011 fn test_conda_env_interpreter() {
3012 let base = OsStr::new("base");
3013 assert_eq!(
3014 conda_env_interpreter(base, true),
3015 PathBuf::from_iter(&["base", "python.exe"])
3016 );
3017 assert_eq!(
3018 conda_env_interpreter(base, false),
3019 PathBuf::from_iter(&["base", "bin", "python"])
3020 );
3021 }
3022
3023 #[test]
3024 fn test_not_cross_compiling_from_to() {
3025 assert!(cross_compiling_from_to(
3026 &triple!("x86_64-unknown-linux-gnu"),
3027 &triple!("x86_64-unknown-linux-gnu"),
3028 )
3029 .unwrap()
3030 .is_none());
3031
3032 assert!(cross_compiling_from_to(
3033 &triple!("x86_64-apple-darwin"),
3034 &triple!("x86_64-apple-darwin")
3035 )
3036 .unwrap()
3037 .is_none());
3038
3039 assert!(cross_compiling_from_to(
3040 &triple!("aarch64-apple-darwin"),
3041 &triple!("x86_64-apple-darwin")
3042 )
3043 .unwrap()
3044 .is_none());
3045
3046 assert!(cross_compiling_from_to(
3047 &triple!("x86_64-apple-darwin"),
3048 &triple!("aarch64-apple-darwin")
3049 )
3050 .unwrap()
3051 .is_none());
3052
3053 assert!(cross_compiling_from_to(
3054 &triple!("x86_64-pc-windows-msvc"),
3055 &triple!("i686-pc-windows-msvc")
3056 )
3057 .unwrap()
3058 .is_none());
3059
3060 assert!(cross_compiling_from_to(
3061 &triple!("x86_64-unknown-linux-gnu"),
3062 &triple!("x86_64-unknown-linux-musl")
3063 )
3064 .unwrap()
3065 .is_none());
3066
3067 assert!(cross_compiling_from_to(
3068 &triple!("x86_64-pc-windows-msvc"),
3069 &triple!("x86_64-win7-windows-msvc"),
3070 )
3071 .unwrap()
3072 .is_none());
3073 }
3074
3075 #[test]
3076 fn test_is_cross_compiling_from_to() {
3077 assert!(cross_compiling_from_to(
3078 &triple!("x86_64-pc-windows-msvc"),
3079 &triple!("aarch64-pc-windows-msvc")
3080 )
3081 .unwrap()
3082 .is_some());
3083 }
3084
3085 #[test]
3086 fn test_run_python_script() {
3087 let interpreter = make_interpreter_config()
3089 .expect("could not get InterpreterConfig from installed interpreter");
3090 let out = interpreter
3091 .run_python_script("print(2 + 2)")
3092 .expect("failed to run Python script");
3093 assert_eq!(out.trim_end(), "4");
3094 }
3095
3096 #[test]
3097 fn test_run_python_script_with_envs() {
3098 let interpreter = make_interpreter_config()
3100 .expect("could not get InterpreterConfig from installed interpreter");
3101 let out = interpreter
3102 .run_python_script_with_envs(
3103 "import os; print(os.getenv('PYO3_TEST'))",
3104 vec![("PYO3_TEST", "42")],
3105 )
3106 .expect("failed to run Python script");
3107 assert_eq!(out.trim_end(), "42");
3108 }
3109
3110 #[test]
3111 fn test_build_script_outputs_base() {
3112 let interpreter_config = InterpreterConfig {
3113 implementation: PythonImplementation::CPython,
3114 version: PythonVersion {
3115 major: 3,
3116 minor: 11,
3117 },
3118 shared: true,
3119 abi3: false,
3120 lib_name: Some("python3".into()),
3121 lib_dir: None,
3122 executable: None,
3123 pointer_width: None,
3124 build_flags: BuildFlags::default(),
3125 suppress_build_script_link_lines: false,
3126 extra_build_script_lines: vec![],
3127 python_framework_prefix: None,
3128 };
3129 assert_eq!(
3130 interpreter_config.build_script_outputs(),
3131 [
3132 "cargo:rustc-cfg=Py_3_7".to_owned(),
3133 "cargo:rustc-cfg=Py_3_8".to_owned(),
3134 "cargo:rustc-cfg=Py_3_9".to_owned(),
3135 "cargo:rustc-cfg=Py_3_10".to_owned(),
3136 "cargo:rustc-cfg=Py_3_11".to_owned(),
3137 ]
3138 );
3139
3140 let interpreter_config = InterpreterConfig {
3141 implementation: PythonImplementation::PyPy,
3142 ..interpreter_config
3143 };
3144 assert_eq!(
3145 interpreter_config.build_script_outputs(),
3146 [
3147 "cargo:rustc-cfg=Py_3_7".to_owned(),
3148 "cargo:rustc-cfg=Py_3_8".to_owned(),
3149 "cargo:rustc-cfg=Py_3_9".to_owned(),
3150 "cargo:rustc-cfg=Py_3_10".to_owned(),
3151 "cargo:rustc-cfg=Py_3_11".to_owned(),
3152 "cargo:rustc-cfg=PyPy".to_owned(),
3153 ]
3154 );
3155 }
3156
3157 #[test]
3158 fn test_build_script_outputs_abi3() {
3159 let interpreter_config = InterpreterConfig {
3160 implementation: PythonImplementation::CPython,
3161 version: PythonVersion { major: 3, minor: 9 },
3162 shared: true,
3163 abi3: true,
3164 lib_name: Some("python3".into()),
3165 lib_dir: None,
3166 executable: None,
3167 pointer_width: None,
3168 build_flags: BuildFlags::default(),
3169 suppress_build_script_link_lines: false,
3170 extra_build_script_lines: vec![],
3171 python_framework_prefix: None,
3172 };
3173
3174 assert_eq!(
3175 interpreter_config.build_script_outputs(),
3176 [
3177 "cargo:rustc-cfg=Py_3_7".to_owned(),
3178 "cargo:rustc-cfg=Py_3_8".to_owned(),
3179 "cargo:rustc-cfg=Py_3_9".to_owned(),
3180 "cargo:rustc-cfg=Py_LIMITED_API".to_owned(),
3181 ]
3182 );
3183
3184 let interpreter_config = InterpreterConfig {
3185 implementation: PythonImplementation::PyPy,
3186 ..interpreter_config
3187 };
3188 assert_eq!(
3189 interpreter_config.build_script_outputs(),
3190 [
3191 "cargo:rustc-cfg=Py_3_7".to_owned(),
3192 "cargo:rustc-cfg=Py_3_8".to_owned(),
3193 "cargo:rustc-cfg=Py_3_9".to_owned(),
3194 "cargo:rustc-cfg=PyPy".to_owned(),
3195 "cargo:rustc-cfg=Py_LIMITED_API".to_owned(),
3196 ]
3197 );
3198 }
3199
3200 #[test]
3201 fn test_build_script_outputs_gil_disabled() {
3202 let mut build_flags = BuildFlags::default();
3203 build_flags.0.insert(BuildFlag::Py_GIL_DISABLED);
3204 let interpreter_config = InterpreterConfig {
3205 implementation: PythonImplementation::CPython,
3206 version: PythonVersion {
3207 major: 3,
3208 minor: 13,
3209 },
3210 shared: true,
3211 abi3: false,
3212 lib_name: Some("python3".into()),
3213 lib_dir: None,
3214 executable: None,
3215 pointer_width: None,
3216 build_flags,
3217 suppress_build_script_link_lines: false,
3218 extra_build_script_lines: vec![],
3219 python_framework_prefix: None,
3220 };
3221
3222 assert_eq!(
3223 interpreter_config.build_script_outputs(),
3224 [
3225 "cargo:rustc-cfg=Py_3_7".to_owned(),
3226 "cargo:rustc-cfg=Py_3_8".to_owned(),
3227 "cargo:rustc-cfg=Py_3_9".to_owned(),
3228 "cargo:rustc-cfg=Py_3_10".to_owned(),
3229 "cargo:rustc-cfg=Py_3_11".to_owned(),
3230 "cargo:rustc-cfg=Py_3_12".to_owned(),
3231 "cargo:rustc-cfg=Py_3_13".to_owned(),
3232 "cargo:rustc-cfg=Py_GIL_DISABLED".to_owned(),
3233 ]
3234 );
3235 }
3236
3237 #[test]
3238 fn test_build_script_outputs_debug() {
3239 let mut build_flags = BuildFlags::default();
3240 build_flags.0.insert(BuildFlag::Py_DEBUG);
3241 let interpreter_config = InterpreterConfig {
3242 implementation: PythonImplementation::CPython,
3243 version: PythonVersion { major: 3, minor: 7 },
3244 shared: true,
3245 abi3: false,
3246 lib_name: Some("python3".into()),
3247 lib_dir: None,
3248 executable: None,
3249 pointer_width: None,
3250 build_flags,
3251 suppress_build_script_link_lines: false,
3252 extra_build_script_lines: vec![],
3253 python_framework_prefix: None,
3254 };
3255
3256 assert_eq!(
3257 interpreter_config.build_script_outputs(),
3258 [
3259 "cargo:rustc-cfg=Py_3_7".to_owned(),
3260 "cargo:rustc-cfg=py_sys_config=\"Py_DEBUG\"".to_owned(),
3261 ]
3262 );
3263 }
3264
3265 #[test]
3266 fn test_find_sysconfigdata_in_invalid_lib_dir() {
3267 let e = find_all_sysconfigdata(&CrossCompileConfig {
3268 lib_dir: Some(PathBuf::from("/abc/123/not/a/real/path")),
3269 version: None,
3270 implementation: None,
3271 target: triple!("x86_64-unknown-linux-gnu"),
3272 abiflags: None,
3273 })
3274 .unwrap_err();
3275
3276 assert!(e.report().to_string().starts_with(
3278 "failed to search the lib dir at 'PYO3_CROSS_LIB_DIR=/abc/123/not/a/real/path'\n\
3279 caused by:\n \
3280 - 0: failed to list the entries in '/abc/123/not/a/real/path'\n \
3281 - 1: \
3282 "
3283 ));
3284 }
3285
3286 #[test]
3287 fn test_from_pyo3_config_file_env_rebuild() {
3288 READ_ENV_VARS.with(|vars| vars.borrow_mut().clear());
3289 let _ = InterpreterConfig::from_pyo3_config_file_env();
3290 READ_ENV_VARS.with(|vars| assert!(vars.borrow().contains(&"PYO3_CONFIG_FILE".to_string())));
3292 }
3293
3294 #[test]
3295 fn test_apply_default_lib_name_to_config_file() {
3296 let mut config = InterpreterConfig {
3297 implementation: PythonImplementation::CPython,
3298 version: PythonVersion { major: 3, minor: 9 },
3299 shared: true,
3300 abi3: false,
3301 lib_name: None,
3302 lib_dir: None,
3303 executable: None,
3304 pointer_width: None,
3305 build_flags: BuildFlags::default(),
3306 suppress_build_script_link_lines: false,
3307 extra_build_script_lines: vec![],
3308 python_framework_prefix: None,
3309 };
3310
3311 let unix = Triple::from_str("x86_64-unknown-linux-gnu").unwrap();
3312 let win_x64 = Triple::from_str("x86_64-pc-windows-msvc").unwrap();
3313 let win_arm64 = Triple::from_str("aarch64-pc-windows-msvc").unwrap();
3314
3315 config.apply_default_lib_name_to_config_file(&unix);
3316 assert_eq!(config.lib_name, Some("python3.9".into()));
3317
3318 config.lib_name = None;
3319 config.apply_default_lib_name_to_config_file(&win_x64);
3320 assert_eq!(config.lib_name, Some("python39".into()));
3321
3322 config.lib_name = None;
3323 config.apply_default_lib_name_to_config_file(&win_arm64);
3324 assert_eq!(config.lib_name, Some("python39".into()));
3325
3326 config.implementation = PythonImplementation::PyPy;
3328 config.version = PythonVersion {
3329 major: 3,
3330 minor: 11,
3331 };
3332 config.lib_name = None;
3333 config.apply_default_lib_name_to_config_file(&unix);
3334 assert_eq!(config.lib_name, Some("pypy3.11-c".into()));
3335
3336 config.implementation = PythonImplementation::CPython;
3337
3338 config.build_flags.0.insert(BuildFlag::Py_GIL_DISABLED);
3340 config.version = PythonVersion {
3341 major: 3,
3342 minor: 13,
3343 };
3344 config.lib_name = None;
3345 config.apply_default_lib_name_to_config_file(&unix);
3346 assert_eq!(config.lib_name, Some("python3.13t".into()));
3347
3348 config.lib_name = None;
3349 config.apply_default_lib_name_to_config_file(&win_x64);
3350 assert_eq!(config.lib_name, Some("python313t".into()));
3351
3352 config.lib_name = None;
3353 config.apply_default_lib_name_to_config_file(&win_arm64);
3354 assert_eq!(config.lib_name, Some("python313t".into()));
3355
3356 config.build_flags.0.remove(&BuildFlag::Py_GIL_DISABLED);
3357
3358 config.abi3 = true;
3360 config.lib_name = None;
3361 config.apply_default_lib_name_to_config_file(&unix);
3362 assert_eq!(config.lib_name, Some("python3.13".into()));
3363
3364 config.lib_name = None;
3365 config.apply_default_lib_name_to_config_file(&win_x64);
3366 assert_eq!(config.lib_name, Some("python3".into()));
3367
3368 config.lib_name = None;
3369 config.apply_default_lib_name_to_config_file(&win_arm64);
3370 assert_eq!(config.lib_name, Some("python3".into()));
3371 }
3372}