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 PY315: Self = PythonVersion {
768 major: 3,
769 minor: 15,
770 };
771 pub const PY313: Self = PythonVersion {
772 major: 3,
773 minor: 13,
774 };
775 pub const PY312: Self = PythonVersion {
776 major: 3,
777 minor: 12,
778 };
779 const PY310: Self = PythonVersion {
780 major: 3,
781 minor: 10,
782 };
783 const PY37: Self = PythonVersion { major: 3, minor: 7 };
784}
785
786impl Display for PythonVersion {
787 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
788 write!(f, "{}.{}", self.major, self.minor)
789 }
790}
791
792impl FromStr for PythonVersion {
793 type Err = crate::errors::Error;
794
795 fn from_str(value: &str) -> Result<Self, Self::Err> {
796 let mut split = value.splitn(2, '.');
797 let (major, minor) = (
798 split
799 .next()
800 .expect("first splitn value should always be present"),
801 split.next().ok_or("expected major.minor version")?,
802 );
803 Ok(Self {
804 major: major.parse().context("failed to parse major version")?,
805 minor: minor.parse().context("failed to parse minor version")?,
806 })
807 }
808}
809
810#[derive(Debug, Copy, Clone, PartialEq, Eq)]
811pub enum PythonImplementation {
812 CPython,
813 PyPy,
814 GraalPy,
815}
816
817impl PythonImplementation {
818 #[doc(hidden)]
819 pub fn is_pypy(self) -> bool {
820 self == PythonImplementation::PyPy
821 }
822
823 #[doc(hidden)]
824 pub fn is_graalpy(self) -> bool {
825 self == PythonImplementation::GraalPy
826 }
827
828 #[doc(hidden)]
829 pub fn from_soabi(soabi: &str) -> Result<Self> {
830 if soabi.starts_with("pypy") {
831 Ok(PythonImplementation::PyPy)
832 } else if soabi.starts_with("cpython") {
833 Ok(PythonImplementation::CPython)
834 } else if soabi.starts_with("graalpy") {
835 Ok(PythonImplementation::GraalPy)
836 } else {
837 bail!("unsupported Python interpreter");
838 }
839 }
840}
841
842impl Display for PythonImplementation {
843 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
844 match self {
845 PythonImplementation::CPython => write!(f, "CPython"),
846 PythonImplementation::PyPy => write!(f, "PyPy"),
847 PythonImplementation::GraalPy => write!(f, "GraalVM"),
848 }
849 }
850}
851
852impl FromStr for PythonImplementation {
853 type Err = Error;
854 fn from_str(s: &str) -> Result<Self> {
855 match s {
856 "CPython" => Ok(PythonImplementation::CPython),
857 "PyPy" => Ok(PythonImplementation::PyPy),
858 "GraalVM" => Ok(PythonImplementation::GraalPy),
859 _ => bail!("unknown interpreter: {}", s),
860 }
861 }
862}
863
864fn have_python_interpreter() -> bool {
869 env_var("PYO3_NO_PYTHON").is_none()
870}
871
872fn is_abi3() -> bool {
876 cargo_env_var("CARGO_FEATURE_ABI3").is_some()
877 || env_var("PYO3_USE_ABI3_FORWARD_COMPATIBILITY").is_some_and(|os_str| os_str == "1")
878}
879
880pub fn get_abi3_version() -> Option<PythonVersion> {
884 let minor_version = (MINIMUM_SUPPORTED_VERSION.minor..=ABI3_MAX_MINOR)
885 .find(|i| cargo_env_var(&format!("CARGO_FEATURE_ABI3_PY3{i}")).is_some());
886 minor_version.map(|minor| PythonVersion { major: 3, minor })
887}
888
889pub fn is_extension_module() -> bool {
897 cargo_env_var("CARGO_FEATURE_EXTENSION_MODULE").is_some()
898 || env_var("PYO3_BUILD_EXTENSION_MODULE").is_some()
899}
900
901pub fn is_linking_libpython_for_target(target: &Triple) -> bool {
905 target.operating_system == OperatingSystem::Windows
906 || target.operating_system == OperatingSystem::Aix
908 || target.environment == Environment::Android
909 || target.environment == Environment::Androideabi
910 || target.operating_system == OperatingSystem::Cygwin
911 || matches!(target.operating_system, OperatingSystem::IOS(_))
912 || !is_extension_module()
913}
914
915fn require_libdir_for_target(target: &Triple) -> bool {
920 let is_generating_libpython = cfg!(feature = "generate-import-lib")
921 && target.operating_system == OperatingSystem::Windows
922 && is_abi3();
923
924 is_linking_libpython_for_target(target) && !is_generating_libpython
925}
926
927#[derive(Debug, PartialEq, Eq)]
932pub struct CrossCompileConfig {
933 pub lib_dir: Option<PathBuf>,
935
936 version: Option<PythonVersion>,
938
939 implementation: Option<PythonImplementation>,
941
942 target: Triple,
944
945 abiflags: Option<String>,
947}
948
949impl CrossCompileConfig {
950 fn try_from_env_vars_host_target(
955 env_vars: CrossCompileEnvVars,
956 host: &Triple,
957 target: &Triple,
958 ) -> Result<Option<Self>> {
959 if env_vars.any() || Self::is_cross_compiling_from_to(host, target) {
960 let lib_dir = env_vars.lib_dir_path()?;
961 let (version, abiflags) = env_vars.parse_version()?;
962 let implementation = env_vars.parse_implementation()?;
963 let target = target.clone();
964
965 Ok(Some(CrossCompileConfig {
966 lib_dir,
967 version,
968 implementation,
969 target,
970 abiflags,
971 }))
972 } else {
973 Ok(None)
974 }
975 }
976
977 fn is_cross_compiling_from_to(host: &Triple, target: &Triple) -> bool {
981 let mut compatible = host.architecture == target.architecture
985 && (host.vendor == target.vendor
986 || (host.vendor == Vendor::Pc && target.vendor.as_str() == "win7"))
988 && host.operating_system == target.operating_system;
989
990 compatible |= target.operating_system == OperatingSystem::Windows
992 && host.operating_system == OperatingSystem::Windows
993 && matches!(target.architecture, Architecture::X86_32(_))
994 && host.architecture == Architecture::X86_64;
995
996 compatible |= matches!(target.operating_system, OperatingSystem::Darwin(_))
998 && matches!(host.operating_system, OperatingSystem::Darwin(_));
999
1000 compatible |= matches!(target.operating_system, OperatingSystem::IOS(_));
1001
1002 !compatible
1003 }
1004
1005 #[allow(dead_code)]
1010 fn lib_dir_string(&self) -> Option<String> {
1011 self.lib_dir
1012 .as_ref()
1013 .map(|s| s.to_str().unwrap().to_owned())
1014 }
1015}
1016
1017struct CrossCompileEnvVars {
1019 pyo3_cross: Option<OsString>,
1021 pyo3_cross_lib_dir: Option<OsString>,
1023 pyo3_cross_python_version: Option<OsString>,
1025 pyo3_cross_python_implementation: Option<OsString>,
1027}
1028
1029impl CrossCompileEnvVars {
1030 fn from_env() -> Self {
1034 CrossCompileEnvVars {
1035 pyo3_cross: env_var("PYO3_CROSS"),
1036 pyo3_cross_lib_dir: env_var("PYO3_CROSS_LIB_DIR"),
1037 pyo3_cross_python_version: env_var("PYO3_CROSS_PYTHON_VERSION"),
1038 pyo3_cross_python_implementation: env_var("PYO3_CROSS_PYTHON_IMPLEMENTATION"),
1039 }
1040 }
1041
1042 fn any(&self) -> bool {
1044 self.pyo3_cross.is_some()
1045 || self.pyo3_cross_lib_dir.is_some()
1046 || self.pyo3_cross_python_version.is_some()
1047 || self.pyo3_cross_python_implementation.is_some()
1048 }
1049
1050 fn parse_version(&self) -> Result<(Option<PythonVersion>, Option<String>)> {
1053 match self.pyo3_cross_python_version.as_ref() {
1054 Some(os_string) => {
1055 let utf8_str = os_string
1056 .to_str()
1057 .ok_or("PYO3_CROSS_PYTHON_VERSION is not valid a UTF-8 string")?;
1058 let (utf8_str, abiflags) = if let Some(version) = utf8_str.strip_suffix('t') {
1059 (version, Some("t".to_string()))
1060 } else {
1061 (utf8_str, None)
1062 };
1063 let version = utf8_str
1064 .parse()
1065 .context("failed to parse PYO3_CROSS_PYTHON_VERSION")?;
1066 Ok((Some(version), abiflags))
1067 }
1068 None => Ok((None, None)),
1069 }
1070 }
1071
1072 fn parse_implementation(&self) -> Result<Option<PythonImplementation>> {
1075 let implementation = self
1076 .pyo3_cross_python_implementation
1077 .as_ref()
1078 .map(|os_string| {
1079 let utf8_str = os_string
1080 .to_str()
1081 .ok_or("PYO3_CROSS_PYTHON_IMPLEMENTATION is not valid a UTF-8 string")?;
1082 utf8_str
1083 .parse()
1084 .context("failed to parse PYO3_CROSS_PYTHON_IMPLEMENTATION")
1085 })
1086 .transpose()?;
1087
1088 Ok(implementation)
1089 }
1090
1091 fn lib_dir_path(&self) -> Result<Option<PathBuf>> {
1096 let lib_dir = self.pyo3_cross_lib_dir.as_ref().map(PathBuf::from);
1097
1098 if let Some(dir) = lib_dir.as_ref() {
1099 ensure!(
1100 dir.to_str().is_some(),
1101 "PYO3_CROSS_LIB_DIR variable value is not a valid UTF-8 string"
1102 );
1103 }
1104
1105 Ok(lib_dir)
1106 }
1107}
1108
1109pub fn cross_compiling_from_to(
1124 host: &Triple,
1125 target: &Triple,
1126) -> Result<Option<CrossCompileConfig>> {
1127 let env_vars = CrossCompileEnvVars::from_env();
1128 CrossCompileConfig::try_from_env_vars_host_target(env_vars, host, target)
1129}
1130
1131#[allow(dead_code)]
1137pub fn cross_compiling_from_cargo_env() -> Result<Option<CrossCompileConfig>> {
1138 let env_vars = CrossCompileEnvVars::from_env();
1139 let host = Triple::host();
1140 let target = target_triple_from_env();
1141
1142 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
1143}
1144
1145#[allow(non_camel_case_types)]
1146#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1147pub enum BuildFlag {
1148 Py_DEBUG,
1149 Py_REF_DEBUG,
1150 Py_TRACE_REFS,
1151 Py_GIL_DISABLED,
1152 COUNT_ALLOCS,
1153 Other(String),
1154}
1155
1156impl Display for BuildFlag {
1157 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1158 match self {
1159 BuildFlag::Other(flag) => write!(f, "{flag}"),
1160 _ => write!(f, "{self:?}"),
1161 }
1162 }
1163}
1164
1165impl FromStr for BuildFlag {
1166 type Err = std::convert::Infallible;
1167 fn from_str(s: &str) -> Result<Self, Self::Err> {
1168 match s {
1169 "Py_DEBUG" => Ok(BuildFlag::Py_DEBUG),
1170 "Py_REF_DEBUG" => Ok(BuildFlag::Py_REF_DEBUG),
1171 "Py_TRACE_REFS" => Ok(BuildFlag::Py_TRACE_REFS),
1172 "Py_GIL_DISABLED" => Ok(BuildFlag::Py_GIL_DISABLED),
1173 "COUNT_ALLOCS" => Ok(BuildFlag::COUNT_ALLOCS),
1174 other => Ok(BuildFlag::Other(other.to_owned())),
1175 }
1176 }
1177}
1178
1179#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
1193#[derive(Clone, Default)]
1194pub struct BuildFlags(pub HashSet<BuildFlag>);
1195
1196impl BuildFlags {
1197 const ALL: [BuildFlag; 5] = [
1198 BuildFlag::Py_DEBUG,
1199 BuildFlag::Py_REF_DEBUG,
1200 BuildFlag::Py_TRACE_REFS,
1201 BuildFlag::Py_GIL_DISABLED,
1202 BuildFlag::COUNT_ALLOCS,
1203 ];
1204
1205 pub fn new() -> Self {
1206 BuildFlags(HashSet::new())
1207 }
1208
1209 fn from_sysconfigdata(config_map: &Sysconfigdata) -> Self {
1210 Self(
1211 BuildFlags::ALL
1212 .iter()
1213 .filter(|flag| config_map.get_value(flag.to_string()) == Some("1"))
1214 .cloned()
1215 .collect(),
1216 )
1217 .fixup()
1218 }
1219
1220 fn from_interpreter(interpreter: impl AsRef<Path>) -> Result<Self> {
1224 if cfg!(windows) {
1228 let script = String::from("import sys;print(sys.version_info < (3, 13))");
1229 let stdout = run_python_script(interpreter.as_ref(), &script)?;
1230 if stdout.trim_end() == "True" {
1231 return Ok(Self::new());
1232 }
1233 }
1234
1235 let mut script = String::from("import sysconfig\n");
1236 script.push_str("config = sysconfig.get_config_vars()\n");
1237
1238 for k in &BuildFlags::ALL {
1239 use std::fmt::Write;
1240 writeln!(&mut script, "print(config.get('{k}', '0'))").unwrap();
1241 }
1242
1243 let stdout = run_python_script(interpreter.as_ref(), &script)?;
1244 let split_stdout: Vec<&str> = stdout.trim_end().lines().collect();
1245 ensure!(
1246 split_stdout.len() == BuildFlags::ALL.len(),
1247 "Python stdout len didn't return expected number of lines: {}",
1248 split_stdout.len()
1249 );
1250 let flags = BuildFlags::ALL
1251 .iter()
1252 .zip(split_stdout)
1253 .filter(|(_, flag_value)| *flag_value == "1")
1254 .map(|(flag, _)| flag.clone())
1255 .collect();
1256
1257 Ok(Self(flags).fixup())
1258 }
1259
1260 fn fixup(mut self) -> Self {
1261 if self.0.contains(&BuildFlag::Py_DEBUG) {
1262 self.0.insert(BuildFlag::Py_REF_DEBUG);
1263 }
1264
1265 self
1266 }
1267}
1268
1269impl Display for BuildFlags {
1270 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1271 let mut first = true;
1272 for flag in &self.0 {
1273 if first {
1274 first = false;
1275 } else {
1276 write!(f, ",")?;
1277 }
1278 write!(f, "{flag}")?;
1279 }
1280 Ok(())
1281 }
1282}
1283
1284impl FromStr for BuildFlags {
1285 type Err = std::convert::Infallible;
1286
1287 fn from_str(value: &str) -> Result<Self, Self::Err> {
1288 let mut flags = HashSet::new();
1289 for flag in value.split_terminator(',') {
1290 flags.insert(flag.parse().unwrap());
1291 }
1292 Ok(BuildFlags(flags))
1293 }
1294}
1295
1296fn parse_script_output(output: &str) -> HashMap<String, String> {
1297 output
1298 .lines()
1299 .filter_map(|line| {
1300 let mut i = line.splitn(2, ' ');
1301 Some((i.next()?.into(), i.next()?.into()))
1302 })
1303 .collect()
1304}
1305
1306pub struct Sysconfigdata(HashMap<String, String>);
1310
1311impl Sysconfigdata {
1312 pub fn get_value<S: AsRef<str>>(&self, k: S) -> Option<&str> {
1313 self.0.get(k.as_ref()).map(String::as_str)
1314 }
1315
1316 #[allow(dead_code)]
1317 fn new() -> Self {
1318 Sysconfigdata(HashMap::new())
1319 }
1320
1321 #[allow(dead_code)]
1322 fn insert<S: Into<String>>(&mut self, k: S, v: S) {
1323 self.0.insert(k.into(), v.into());
1324 }
1325}
1326
1327pub fn parse_sysconfigdata(sysconfigdata_path: impl AsRef<Path>) -> Result<Sysconfigdata> {
1335 let sysconfigdata_path = sysconfigdata_path.as_ref();
1336 let mut script = fs::read_to_string(sysconfigdata_path).with_context(|| {
1337 format!(
1338 "failed to read config from {}",
1339 sysconfigdata_path.display()
1340 )
1341 })?;
1342 script += r#"
1343for key, val in build_time_vars.items():
1344 # (ana)conda(-forge) built Pythons are statically linked but ship the shared library with them.
1345 # We detect them based on the magic prefix directory they have encoded in their builds.
1346 if key == "Py_ENABLE_SHARED" and "_h_env_placehold" in build_time_vars.get("prefix"):
1347 val = 1
1348 print(key, val)
1349"#;
1350
1351 let output = run_python_script(&find_interpreter()?, &script)?;
1352
1353 Ok(Sysconfigdata(parse_script_output(&output)))
1354}
1355
1356fn starts_with(entry: &DirEntry, pat: &str) -> bool {
1357 let name = entry.file_name();
1358 name.to_string_lossy().starts_with(pat)
1359}
1360fn ends_with(entry: &DirEntry, pat: &str) -> bool {
1361 let name = entry.file_name();
1362 name.to_string_lossy().ends_with(pat)
1363}
1364
1365#[allow(dead_code)]
1370fn find_sysconfigdata(cross: &CrossCompileConfig) -> Result<Option<PathBuf>> {
1371 let mut sysconfig_paths = find_all_sysconfigdata(cross)?;
1372 if sysconfig_paths.is_empty() {
1373 if let Some(lib_dir) = cross.lib_dir.as_ref() {
1374 bail!("Could not find _sysconfigdata*.py in {}", lib_dir.display());
1375 } else {
1376 return Ok(None);
1378 }
1379 } else if sysconfig_paths.len() > 1 {
1380 let mut error_msg = String::from(
1381 "Detected multiple possible Python versions. Please set either the \
1382 PYO3_CROSS_PYTHON_VERSION variable to the wanted version or the \
1383 _PYTHON_SYSCONFIGDATA_NAME variable to the wanted sysconfigdata file name.\n\n\
1384 sysconfigdata files found:",
1385 );
1386 for path in sysconfig_paths {
1387 use std::fmt::Write;
1388 write!(&mut error_msg, "\n\t{}", path.display()).unwrap();
1389 }
1390 bail!("{}\n", error_msg);
1391 }
1392
1393 Ok(Some(sysconfig_paths.remove(0)))
1394}
1395
1396pub fn find_all_sysconfigdata(cross: &CrossCompileConfig) -> Result<Vec<PathBuf>> {
1435 let sysconfig_paths = if let Some(lib_dir) = cross.lib_dir.as_ref() {
1436 search_lib_dir(lib_dir, cross).with_context(|| {
1437 format!(
1438 "failed to search the lib dir at 'PYO3_CROSS_LIB_DIR={}'",
1439 lib_dir.display()
1440 )
1441 })?
1442 } else {
1443 return Ok(Vec::new());
1444 };
1445
1446 let sysconfig_name = env_var("_PYTHON_SYSCONFIGDATA_NAME");
1447 let mut sysconfig_paths = sysconfig_paths
1448 .iter()
1449 .filter_map(|p| {
1450 let canonical = fs::canonicalize(p).ok();
1451 match &sysconfig_name {
1452 Some(_) => canonical.filter(|p| p.file_stem() == sysconfig_name.as_deref()),
1453 None => canonical,
1454 }
1455 })
1456 .collect::<Vec<PathBuf>>();
1457
1458 sysconfig_paths.sort();
1459 sysconfig_paths.dedup();
1460
1461 Ok(sysconfig_paths)
1462}
1463
1464fn is_pypy_lib_dir(path: &str, v: &Option<PythonVersion>) -> bool {
1465 let pypy_version_pat = if let Some(v) = v {
1466 format!("pypy{v}")
1467 } else {
1468 "pypy3.".into()
1469 };
1470 path == "lib_pypy" || path.starts_with(&pypy_version_pat)
1471}
1472
1473fn is_graalpy_lib_dir(path: &str, v: &Option<PythonVersion>) -> bool {
1474 let graalpy_version_pat = if let Some(v) = v {
1475 format!("graalpy{v}")
1476 } else {
1477 "graalpy2".into()
1478 };
1479 path == "lib_graalpython" || path.starts_with(&graalpy_version_pat)
1480}
1481
1482fn is_cpython_lib_dir(path: &str, v: &Option<PythonVersion>) -> bool {
1483 let cpython_version_pat = if let Some(v) = v {
1484 format!("python{v}")
1485 } else {
1486 "python3.".into()
1487 };
1488 path.starts_with(&cpython_version_pat)
1489}
1490
1491fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Result<Vec<PathBuf>> {
1493 let mut sysconfig_paths = vec![];
1494 for f in fs::read_dir(path.as_ref()).with_context(|| {
1495 format!(
1496 "failed to list the entries in '{}'",
1497 path.as_ref().display()
1498 )
1499 })? {
1500 sysconfig_paths.extend(match &f {
1501 Ok(f) if starts_with(f, "_sysconfigdata_") && ends_with(f, "py") => vec![f.path()],
1503 Ok(f) if f.metadata().is_ok_and(|metadata| metadata.is_dir()) => {
1504 let file_name = f.file_name();
1505 let file_name = file_name.to_string_lossy();
1506 if file_name == "build" || file_name == "lib" {
1507 search_lib_dir(f.path(), cross)?
1508 } else if file_name.starts_with("lib.") {
1509 if !file_name.contains(&cross.target.operating_system.to_string()) {
1511 continue;
1512 }
1513 if !file_name.contains(&cross.target.architecture.to_string()) {
1515 continue;
1516 }
1517 search_lib_dir(f.path(), cross)?
1518 } else if is_cpython_lib_dir(&file_name, &cross.version)
1519 || is_pypy_lib_dir(&file_name, &cross.version)
1520 || is_graalpy_lib_dir(&file_name, &cross.version)
1521 {
1522 search_lib_dir(f.path(), cross)?
1523 } else {
1524 continue;
1525 }
1526 }
1527 _ => continue,
1528 });
1529 }
1530 if sysconfig_paths.len() > 1 {
1538 let temp = sysconfig_paths
1539 .iter()
1540 .filter(|p| {
1541 p.to_string_lossy()
1542 .contains(&cross.target.architecture.to_string())
1543 })
1544 .cloned()
1545 .collect::<Vec<PathBuf>>();
1546 if !temp.is_empty() {
1547 sysconfig_paths = temp;
1548 }
1549 }
1550
1551 Ok(sysconfig_paths)
1552}
1553
1554#[allow(dead_code)]
1562fn cross_compile_from_sysconfigdata(
1563 cross_compile_config: &CrossCompileConfig,
1564) -> Result<Option<InterpreterConfig>> {
1565 if let Some(path) = find_sysconfigdata(cross_compile_config)? {
1566 let data = parse_sysconfigdata(path)?;
1567 let mut config = InterpreterConfig::from_sysconfigdata(&data)?;
1568 if let Some(cross_lib_dir) = cross_compile_config.lib_dir_string() {
1569 config.lib_dir = Some(cross_lib_dir)
1570 }
1571
1572 Ok(Some(config))
1573 } else {
1574 Ok(None)
1575 }
1576}
1577
1578#[allow(unused_mut, dead_code)]
1585fn default_cross_compile(cross_compile_config: &CrossCompileConfig) -> Result<InterpreterConfig> {
1586 let version = cross_compile_config
1587 .version
1588 .or_else(get_abi3_version)
1589 .ok_or_else(||
1590 format!(
1591 "PYO3_CROSS_PYTHON_VERSION or an abi3-py3* feature must be specified \
1592 when cross-compiling and PYO3_CROSS_LIB_DIR is not set.\n\
1593 = help: see the PyO3 user guide for more information: https://pyo3.rs/v{}/building-and-distribution.html#cross-compiling",
1594 env!("CARGO_PKG_VERSION")
1595 )
1596 )?;
1597
1598 let abi3 = is_abi3();
1599 let implementation = cross_compile_config
1600 .implementation
1601 .unwrap_or(PythonImplementation::CPython);
1602 let gil_disabled: bool = cross_compile_config.abiflags.as_deref() == Some("t");
1603
1604 let lib_name = default_lib_name_for_target(
1605 version,
1606 implementation,
1607 abi3,
1608 gil_disabled,
1609 &cross_compile_config.target,
1610 );
1611
1612 let mut lib_dir = cross_compile_config.lib_dir_string();
1613
1614 #[cfg(feature = "generate-import-lib")]
1616 if lib_dir.is_none() {
1617 let py_version = if implementation == PythonImplementation::CPython && abi3 && !gil_disabled
1618 {
1619 None
1620 } else {
1621 Some(version)
1622 };
1623 lib_dir = self::import_lib::generate_import_lib(
1624 &cross_compile_config.target,
1625 cross_compile_config
1626 .implementation
1627 .unwrap_or(PythonImplementation::CPython),
1628 py_version,
1629 None,
1630 )?;
1631 }
1632
1633 Ok(InterpreterConfig {
1634 implementation,
1635 version,
1636 shared: true,
1637 abi3,
1638 lib_name: Some(lib_name),
1639 lib_dir,
1640 executable: None,
1641 pointer_width: None,
1642 build_flags: BuildFlags::default(),
1643 suppress_build_script_link_lines: false,
1644 extra_build_script_lines: vec![],
1645 python_framework_prefix: None,
1646 })
1647}
1648
1649fn default_abi3_config(host: &Triple, version: PythonVersion) -> Result<InterpreterConfig> {
1659 let implementation = PythonImplementation::CPython;
1661 let abi3 = true;
1662
1663 let lib_name = if host.operating_system == OperatingSystem::Windows {
1664 Some(default_lib_name_windows(
1665 version,
1666 implementation,
1667 abi3,
1668 false,
1669 false,
1670 false,
1671 )?)
1672 } else {
1673 None
1674 };
1675
1676 Ok(InterpreterConfig {
1677 implementation,
1678 version,
1679 shared: true,
1680 abi3,
1681 lib_name,
1682 lib_dir: None,
1683 executable: None,
1684 pointer_width: None,
1685 build_flags: BuildFlags::default(),
1686 suppress_build_script_link_lines: false,
1687 extra_build_script_lines: vec![],
1688 python_framework_prefix: None,
1689 })
1690}
1691
1692#[allow(dead_code)]
1700fn load_cross_compile_config(
1701 cross_compile_config: CrossCompileConfig,
1702) -> Result<InterpreterConfig> {
1703 let windows = cross_compile_config.target.operating_system == OperatingSystem::Windows;
1704
1705 let config = if windows || !have_python_interpreter() {
1706 default_cross_compile(&cross_compile_config)?
1710 } else if let Some(config) = cross_compile_from_sysconfigdata(&cross_compile_config)? {
1711 config
1713 } else {
1714 default_cross_compile(&cross_compile_config)?
1716 };
1717
1718 Ok(config)
1719}
1720
1721const WINDOWS_ABI3_LIB_NAME: &str = "python3";
1723const WINDOWS_ABI3_DEBUG_LIB_NAME: &str = "python3_d";
1724
1725#[allow(dead_code)]
1727fn default_lib_name_for_target(
1728 version: PythonVersion,
1729 implementation: PythonImplementation,
1730 abi3: bool,
1731 gil_disabled: bool,
1732 target: &Triple,
1733) -> String {
1734 if target.operating_system == OperatingSystem::Windows {
1735 default_lib_name_windows(version, implementation, abi3, false, false, gil_disabled).unwrap()
1736 } else {
1737 default_lib_name_unix(
1738 version,
1739 implementation,
1740 abi3,
1741 target.operating_system == OperatingSystem::Cygwin,
1742 None,
1743 gil_disabled,
1744 )
1745 .unwrap()
1746 }
1747}
1748
1749fn default_lib_name_windows(
1750 version: PythonVersion,
1751 implementation: PythonImplementation,
1752 abi3: bool,
1753 mingw: bool,
1754 debug: bool,
1755 gil_disabled: bool,
1756) -> Result<String> {
1757 if debug && version < PythonVersion::PY310 {
1758 Ok(format!("python{}{}_d", version.major, version.minor))
1761 } else if abi3 && !(gil_disabled || implementation.is_pypy() || implementation.is_graalpy()) {
1762 if debug {
1763 Ok(WINDOWS_ABI3_DEBUG_LIB_NAME.to_owned())
1764 } else {
1765 Ok(WINDOWS_ABI3_LIB_NAME.to_owned())
1766 }
1767 } else if mingw {
1768 ensure!(
1769 !gil_disabled,
1770 "MinGW free-threaded builds are not currently tested or supported"
1771 );
1772 Ok(format!("python{}.{}", version.major, version.minor))
1774 } else if gil_disabled {
1775 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);
1776 if debug {
1777 Ok(format!("python{}{}t_d", version.major, version.minor))
1778 } else {
1779 Ok(format!("python{}{}t", version.major, version.minor))
1780 }
1781 } else if debug {
1782 Ok(format!("python{}{}_d", version.major, version.minor))
1783 } else {
1784 Ok(format!("python{}{}", version.major, version.minor))
1785 }
1786}
1787
1788fn default_lib_name_unix(
1789 version: PythonVersion,
1790 implementation: PythonImplementation,
1791 abi3: bool,
1792 cygwin: bool,
1793 ld_version: Option<&str>,
1794 gil_disabled: bool,
1795) -> Result<String> {
1796 match implementation {
1797 PythonImplementation::CPython => match ld_version {
1798 Some(ld_version) => Ok(format!("python{ld_version}")),
1799 None => {
1800 if cygwin && abi3 {
1801 Ok("python3".to_string())
1802 } else if version > PythonVersion::PY37 {
1803 if gil_disabled {
1805 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);
1806 Ok(format!("python{}.{}t", version.major, version.minor))
1807 } else {
1808 Ok(format!("python{}.{}", version.major, version.minor))
1809 }
1810 } else {
1811 Ok(format!("python{}.{}m", version.major, version.minor))
1813 }
1814 }
1815 },
1816 PythonImplementation::PyPy => match ld_version {
1817 Some(ld_version) => Ok(format!("pypy{ld_version}-c")),
1818 None => Ok(format!("pypy{}.{}-c", version.major, version.minor)),
1819 },
1820
1821 PythonImplementation::GraalPy => Ok("python-native".to_string()),
1822 }
1823}
1824
1825fn run_python_script(interpreter: &Path, script: &str) -> Result<String> {
1827 run_python_script_with_envs(interpreter, script, std::iter::empty::<(&str, &str)>())
1828}
1829
1830fn run_python_script_with_envs<I, K, V>(interpreter: &Path, script: &str, envs: I) -> Result<String>
1833where
1834 I: IntoIterator<Item = (K, V)>,
1835 K: AsRef<OsStr>,
1836 V: AsRef<OsStr>,
1837{
1838 let out = Command::new(interpreter)
1839 .env("PYTHONIOENCODING", "utf-8")
1840 .envs(envs)
1841 .stdin(Stdio::piped())
1842 .stdout(Stdio::piped())
1843 .stderr(Stdio::inherit())
1844 .spawn()
1845 .and_then(|mut child| {
1846 child
1847 .stdin
1848 .as_mut()
1849 .expect("piped stdin")
1850 .write_all(script.as_bytes())?;
1851 child.wait_with_output()
1852 });
1853
1854 match out {
1855 Err(err) => bail!(
1856 "failed to run the Python interpreter at {}: {}",
1857 interpreter.display(),
1858 err
1859 ),
1860 Ok(ok) if !ok.status.success() => bail!("Python script failed"),
1861 Ok(ok) => Ok(String::from_utf8(ok.stdout)
1862 .context("failed to parse Python script output as utf-8")?),
1863 }
1864}
1865
1866fn venv_interpreter(virtual_env: &OsStr, windows: bool) -> PathBuf {
1867 if windows {
1868 Path::new(virtual_env).join("Scripts").join("python.exe")
1869 } else {
1870 Path::new(virtual_env).join("bin").join("python")
1871 }
1872}
1873
1874fn conda_env_interpreter(conda_prefix: &OsStr, windows: bool) -> PathBuf {
1875 if windows {
1876 Path::new(conda_prefix).join("python.exe")
1877 } else {
1878 Path::new(conda_prefix).join("bin").join("python")
1879 }
1880}
1881
1882fn get_env_interpreter() -> Option<PathBuf> {
1883 match (env_var("VIRTUAL_ENV"), env_var("CONDA_PREFIX")) {
1884 (Some(dir), None) => Some(venv_interpreter(&dir, cfg!(windows))),
1887 (None, Some(dir)) => Some(conda_env_interpreter(&dir, cfg!(windows))),
1888 (Some(_), Some(_)) => {
1889 warn!(
1890 "Both VIRTUAL_ENV and CONDA_PREFIX are set. PyO3 will ignore both of these for \
1891 locating the Python interpreter until you unset one of them."
1892 );
1893 None
1894 }
1895 (None, None) => None,
1896 }
1897}
1898
1899pub fn find_interpreter() -> Result<PathBuf> {
1907 println!("cargo:rerun-if-env-changed=PYO3_ENVIRONMENT_SIGNATURE");
1910
1911 if let Some(exe) = env_var("PYO3_PYTHON") {
1912 Ok(exe.into())
1913 } else if let Some(env_interpreter) = get_env_interpreter() {
1914 Ok(env_interpreter)
1915 } else {
1916 println!("cargo:rerun-if-env-changed=PATH");
1917 ["python", "python3"]
1918 .iter()
1919 .find(|bin| {
1920 if let Ok(out) = Command::new(bin).arg("--version").output() {
1921 out.stdout.starts_with(b"Python 3")
1923 || out.stderr.starts_with(b"Python 3")
1924 || out.stdout.starts_with(b"GraalPy 3")
1925 } else {
1926 false
1927 }
1928 })
1929 .map(PathBuf::from)
1930 .ok_or_else(|| "no Python 3.x interpreter found".into())
1931 }
1932}
1933
1934fn get_host_interpreter(abi3_version: Option<PythonVersion>) -> Result<InterpreterConfig> {
1938 let interpreter_path = find_interpreter()?;
1939
1940 let mut interpreter_config = InterpreterConfig::from_interpreter(interpreter_path)?;
1941 interpreter_config.fixup_for_abi3_version(abi3_version)?;
1942
1943 Ok(interpreter_config)
1944}
1945
1946#[allow(dead_code)]
1951pub fn make_cross_compile_config() -> Result<Option<InterpreterConfig>> {
1952 let interpreter_config = if let Some(cross_config) = cross_compiling_from_cargo_env()? {
1953 let mut interpreter_config = load_cross_compile_config(cross_config)?;
1954 interpreter_config.fixup_for_abi3_version(get_abi3_version())?;
1955 Some(interpreter_config)
1956 } else {
1957 None
1958 };
1959
1960 Ok(interpreter_config)
1961}
1962
1963#[allow(dead_code, unused_mut)]
1966pub fn make_interpreter_config() -> Result<InterpreterConfig> {
1967 let host = Triple::host();
1968 let abi3_version = get_abi3_version();
1969
1970 let need_interpreter = abi3_version.is_none() || require_libdir_for_target(&host);
1973
1974 if have_python_interpreter() {
1975 match get_host_interpreter(abi3_version) {
1976 Ok(interpreter_config) => return Ok(interpreter_config),
1977 Err(e) if need_interpreter => return Err(e),
1979 _ => {
1980 warn!("Compiling without a working Python interpreter.");
1983 }
1984 }
1985 } else {
1986 ensure!(
1987 abi3_version.is_some(),
1988 "An abi3-py3* feature must be specified when compiling without a Python interpreter."
1989 );
1990 };
1991
1992 let mut interpreter_config = default_abi3_config(&host, abi3_version.unwrap())?;
1993
1994 #[cfg(feature = "generate-import-lib")]
1996 {
1997 let gil_disabled = interpreter_config
1998 .build_flags
1999 .0
2000 .contains(&BuildFlag::Py_GIL_DISABLED);
2001 let py_version = if interpreter_config.implementation == PythonImplementation::CPython
2002 && interpreter_config.abi3
2003 && !gil_disabled
2004 {
2005 None
2006 } else {
2007 Some(interpreter_config.version)
2008 };
2009 interpreter_config.lib_dir = self::import_lib::generate_import_lib(
2010 &host,
2011 interpreter_config.implementation,
2012 py_version,
2013 None,
2014 )?;
2015 }
2016
2017 Ok(interpreter_config)
2018}
2019
2020fn escape(bytes: &[u8]) -> String {
2021 let mut escaped = String::with_capacity(2 * bytes.len());
2022
2023 for byte in bytes {
2024 const LUT: &[u8; 16] = b"0123456789abcdef";
2025
2026 escaped.push(LUT[(byte >> 4) as usize] as char);
2027 escaped.push(LUT[(byte & 0x0F) as usize] as char);
2028 }
2029
2030 escaped
2031}
2032
2033fn unescape(escaped: &str) -> Vec<u8> {
2034 assert_eq!(escaped.len() % 2, 0, "invalid hex encoding");
2035
2036 let mut bytes = Vec::with_capacity(escaped.len() / 2);
2037
2038 for chunk in escaped.as_bytes().chunks_exact(2) {
2039 fn unhex(hex: u8) -> u8 {
2040 match hex {
2041 b'a'..=b'f' => hex - b'a' + 10,
2042 b'0'..=b'9' => hex - b'0',
2043 _ => panic!("invalid hex encoding"),
2044 }
2045 }
2046
2047 bytes.push((unhex(chunk[0]) << 4) | unhex(chunk[1]));
2048 }
2049
2050 bytes
2051}
2052
2053#[cfg(test)]
2054mod tests {
2055 use target_lexicon::triple;
2056
2057 use super::*;
2058
2059 #[test]
2060 fn test_config_file_roundtrip() {
2061 let config = InterpreterConfig {
2062 abi3: true,
2063 build_flags: BuildFlags::default(),
2064 pointer_width: Some(32),
2065 executable: Some("executable".into()),
2066 implementation: PythonImplementation::CPython,
2067 lib_name: Some("lib_name".into()),
2068 lib_dir: Some("lib_dir".into()),
2069 shared: true,
2070 version: MINIMUM_SUPPORTED_VERSION,
2071 suppress_build_script_link_lines: true,
2072 extra_build_script_lines: vec!["cargo:test1".to_string(), "cargo:test2".to_string()],
2073 python_framework_prefix: None,
2074 };
2075 let mut buf: Vec<u8> = Vec::new();
2076 config.to_writer(&mut buf).unwrap();
2077
2078 assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap());
2079
2080 let config = InterpreterConfig {
2083 abi3: false,
2084 build_flags: {
2085 let mut flags = HashSet::new();
2086 flags.insert(BuildFlag::Py_DEBUG);
2087 flags.insert(BuildFlag::Other(String::from("Py_SOME_FLAG")));
2088 BuildFlags(flags)
2089 },
2090 pointer_width: None,
2091 executable: None,
2092 implementation: PythonImplementation::PyPy,
2093 lib_dir: None,
2094 lib_name: None,
2095 shared: true,
2096 version: PythonVersion {
2097 major: 3,
2098 minor: 10,
2099 },
2100 suppress_build_script_link_lines: false,
2101 extra_build_script_lines: vec![],
2102 python_framework_prefix: None,
2103 };
2104 let mut buf: Vec<u8> = Vec::new();
2105 config.to_writer(&mut buf).unwrap();
2106
2107 assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap());
2108 }
2109
2110 #[test]
2111 fn test_config_file_roundtrip_with_escaping() {
2112 let config = InterpreterConfig {
2113 abi3: true,
2114 build_flags: BuildFlags::default(),
2115 pointer_width: Some(32),
2116 executable: Some("executable".into()),
2117 implementation: PythonImplementation::CPython,
2118 lib_name: Some("lib_name".into()),
2119 lib_dir: Some("lib_dir\\n".into()),
2120 shared: true,
2121 version: MINIMUM_SUPPORTED_VERSION,
2122 suppress_build_script_link_lines: true,
2123 extra_build_script_lines: vec!["cargo:test1".to_string(), "cargo:test2".to_string()],
2124 python_framework_prefix: None,
2125 };
2126 let mut buf: Vec<u8> = Vec::new();
2127 config.to_writer(&mut buf).unwrap();
2128
2129 let buf = unescape(&escape(&buf));
2130
2131 assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap());
2132 }
2133
2134 #[test]
2135 fn test_config_file_defaults() {
2136 assert_eq!(
2138 InterpreterConfig::from_reader("version=3.7".as_bytes()).unwrap(),
2139 InterpreterConfig {
2140 version: PythonVersion { major: 3, minor: 7 },
2141 implementation: PythonImplementation::CPython,
2142 shared: true,
2143 abi3: false,
2144 lib_name: None,
2145 lib_dir: None,
2146 executable: None,
2147 pointer_width: None,
2148 build_flags: BuildFlags::default(),
2149 suppress_build_script_link_lines: false,
2150 extra_build_script_lines: vec![],
2151 python_framework_prefix: None,
2152 }
2153 )
2154 }
2155
2156 #[test]
2157 fn test_config_file_unknown_keys() {
2158 assert_eq!(
2160 InterpreterConfig::from_reader("version=3.7\next_suffix=.python37.so".as_bytes())
2161 .unwrap(),
2162 InterpreterConfig {
2163 version: PythonVersion { major: 3, minor: 7 },
2164 implementation: PythonImplementation::CPython,
2165 shared: true,
2166 abi3: false,
2167 lib_name: None,
2168 lib_dir: None,
2169 executable: None,
2170 pointer_width: None,
2171 build_flags: BuildFlags::default(),
2172 suppress_build_script_link_lines: false,
2173 extra_build_script_lines: vec![],
2174 python_framework_prefix: None,
2175 }
2176 )
2177 }
2178
2179 #[test]
2180 fn build_flags_default() {
2181 assert_eq!(BuildFlags::default(), BuildFlags::new());
2182 }
2183
2184 #[test]
2185 fn build_flags_from_sysconfigdata() {
2186 let mut sysconfigdata = Sysconfigdata::new();
2187
2188 assert_eq!(
2189 BuildFlags::from_sysconfigdata(&sysconfigdata).0,
2190 HashSet::new()
2191 );
2192
2193 for flag in &BuildFlags::ALL {
2194 sysconfigdata.insert(flag.to_string(), "0".into());
2195 }
2196
2197 assert_eq!(
2198 BuildFlags::from_sysconfigdata(&sysconfigdata).0,
2199 HashSet::new()
2200 );
2201
2202 let mut expected_flags = HashSet::new();
2203 for flag in &BuildFlags::ALL {
2204 sysconfigdata.insert(flag.to_string(), "1".into());
2205 expected_flags.insert(flag.clone());
2206 }
2207
2208 assert_eq!(
2209 BuildFlags::from_sysconfigdata(&sysconfigdata).0,
2210 expected_flags
2211 );
2212 }
2213
2214 #[test]
2215 fn build_flags_fixup() {
2216 let mut build_flags = BuildFlags::new();
2217
2218 build_flags = build_flags.fixup();
2219 assert!(build_flags.0.is_empty());
2220
2221 build_flags.0.insert(BuildFlag::Py_DEBUG);
2222
2223 build_flags = build_flags.fixup();
2224
2225 assert!(build_flags.0.contains(&BuildFlag::Py_REF_DEBUG));
2227 }
2228
2229 #[test]
2230 fn parse_script_output() {
2231 let output = "foo bar\nbar foobar\n\n";
2232 let map = super::parse_script_output(output);
2233 assert_eq!(map.len(), 2);
2234 assert_eq!(map["foo"], "bar");
2235 assert_eq!(map["bar"], "foobar");
2236 }
2237
2238 #[test]
2239 fn config_from_interpreter() {
2240 assert!(make_interpreter_config().is_ok())
2244 }
2245
2246 #[test]
2247 fn config_from_empty_sysconfigdata() {
2248 let sysconfigdata = Sysconfigdata::new();
2249 assert!(InterpreterConfig::from_sysconfigdata(&sysconfigdata).is_err());
2250 }
2251
2252 #[test]
2253 fn config_from_sysconfigdata() {
2254 let mut sysconfigdata = Sysconfigdata::new();
2255 sysconfigdata.insert("SOABI", "cpython-37m-x86_64-linux-gnu");
2258 sysconfigdata.insert("VERSION", "3.7");
2259 sysconfigdata.insert("Py_ENABLE_SHARED", "1");
2260 sysconfigdata.insert("LIBDIR", "/usr/lib");
2261 sysconfigdata.insert("LDVERSION", "3.7m");
2262 sysconfigdata.insert("SIZEOF_VOID_P", "8");
2263 assert_eq!(
2264 InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(),
2265 InterpreterConfig {
2266 abi3: false,
2267 build_flags: BuildFlags::from_sysconfigdata(&sysconfigdata),
2268 pointer_width: Some(64),
2269 executable: None,
2270 implementation: PythonImplementation::CPython,
2271 lib_dir: Some("/usr/lib".into()),
2272 lib_name: Some("python3.7m".into()),
2273 shared: true,
2274 version: PythonVersion::PY37,
2275 suppress_build_script_link_lines: false,
2276 extra_build_script_lines: vec![],
2277 python_framework_prefix: None,
2278 }
2279 );
2280 }
2281
2282 #[test]
2283 fn config_from_sysconfigdata_framework() {
2284 let mut sysconfigdata = Sysconfigdata::new();
2285 sysconfigdata.insert("SOABI", "cpython-37m-x86_64-linux-gnu");
2286 sysconfigdata.insert("VERSION", "3.7");
2287 sysconfigdata.insert("Py_ENABLE_SHARED", "0");
2289 sysconfigdata.insert("PYTHONFRAMEWORK", "Python");
2290 sysconfigdata.insert("LIBDIR", "/usr/lib");
2291 sysconfigdata.insert("LDVERSION", "3.7m");
2292 sysconfigdata.insert("SIZEOF_VOID_P", "8");
2293 assert_eq!(
2294 InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(),
2295 InterpreterConfig {
2296 abi3: false,
2297 build_flags: BuildFlags::from_sysconfigdata(&sysconfigdata),
2298 pointer_width: Some(64),
2299 executable: None,
2300 implementation: PythonImplementation::CPython,
2301 lib_dir: Some("/usr/lib".into()),
2302 lib_name: Some("python3.7m".into()),
2303 shared: true,
2304 version: PythonVersion::PY37,
2305 suppress_build_script_link_lines: false,
2306 extra_build_script_lines: vec![],
2307 python_framework_prefix: None,
2308 }
2309 );
2310
2311 sysconfigdata = Sysconfigdata::new();
2312 sysconfigdata.insert("SOABI", "cpython-37m-x86_64-linux-gnu");
2313 sysconfigdata.insert("VERSION", "3.7");
2314 sysconfigdata.insert("Py_ENABLE_SHARED", "0");
2316 sysconfigdata.insert("PYTHONFRAMEWORK", "");
2317 sysconfigdata.insert("LIBDIR", "/usr/lib");
2318 sysconfigdata.insert("LDVERSION", "3.7m");
2319 sysconfigdata.insert("SIZEOF_VOID_P", "8");
2320 assert_eq!(
2321 InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(),
2322 InterpreterConfig {
2323 abi3: false,
2324 build_flags: BuildFlags::from_sysconfigdata(&sysconfigdata),
2325 pointer_width: Some(64),
2326 executable: None,
2327 implementation: PythonImplementation::CPython,
2328 lib_dir: Some("/usr/lib".into()),
2329 lib_name: Some("python3.7m".into()),
2330 shared: false,
2331 version: PythonVersion::PY37,
2332 suppress_build_script_link_lines: false,
2333 extra_build_script_lines: vec![],
2334 python_framework_prefix: None,
2335 }
2336 );
2337 }
2338
2339 #[test]
2340 fn windows_hardcoded_abi3_compile() {
2341 let host = triple!("x86_64-pc-windows-msvc");
2342 let min_version = "3.7".parse().unwrap();
2343
2344 assert_eq!(
2345 default_abi3_config(&host, min_version).unwrap(),
2346 InterpreterConfig {
2347 implementation: PythonImplementation::CPython,
2348 version: PythonVersion { major: 3, minor: 7 },
2349 shared: true,
2350 abi3: true,
2351 lib_name: Some("python3".into()),
2352 lib_dir: None,
2353 executable: None,
2354 pointer_width: None,
2355 build_flags: BuildFlags::default(),
2356 suppress_build_script_link_lines: false,
2357 extra_build_script_lines: vec![],
2358 python_framework_prefix: None,
2359 }
2360 );
2361 }
2362
2363 #[test]
2364 fn unix_hardcoded_abi3_compile() {
2365 let host = triple!("x86_64-unknown-linux-gnu");
2366 let min_version = "3.9".parse().unwrap();
2367
2368 assert_eq!(
2369 default_abi3_config(&host, min_version).unwrap(),
2370 InterpreterConfig {
2371 implementation: PythonImplementation::CPython,
2372 version: PythonVersion { major: 3, minor: 9 },
2373 shared: true,
2374 abi3: true,
2375 lib_name: None,
2376 lib_dir: None,
2377 executable: None,
2378 pointer_width: None,
2379 build_flags: BuildFlags::default(),
2380 suppress_build_script_link_lines: false,
2381 extra_build_script_lines: vec![],
2382 python_framework_prefix: None,
2383 }
2384 );
2385 }
2386
2387 #[test]
2388 fn windows_hardcoded_cross_compile() {
2389 let env_vars = CrossCompileEnvVars {
2390 pyo3_cross: None,
2391 pyo3_cross_lib_dir: Some("C:\\some\\path".into()),
2392 pyo3_cross_python_implementation: None,
2393 pyo3_cross_python_version: Some("3.7".into()),
2394 };
2395
2396 let host = triple!("x86_64-unknown-linux-gnu");
2397 let target = triple!("i686-pc-windows-msvc");
2398 let cross_config =
2399 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
2400 .unwrap()
2401 .unwrap();
2402
2403 assert_eq!(
2404 default_cross_compile(&cross_config).unwrap(),
2405 InterpreterConfig {
2406 implementation: PythonImplementation::CPython,
2407 version: PythonVersion { major: 3, minor: 7 },
2408 shared: true,
2409 abi3: false,
2410 lib_name: Some("python37".into()),
2411 lib_dir: Some("C:\\some\\path".into()),
2412 executable: None,
2413 pointer_width: None,
2414 build_flags: BuildFlags::default(),
2415 suppress_build_script_link_lines: false,
2416 extra_build_script_lines: vec![],
2417 python_framework_prefix: None,
2418 }
2419 );
2420 }
2421
2422 #[test]
2423 fn mingw_hardcoded_cross_compile() {
2424 let env_vars = CrossCompileEnvVars {
2425 pyo3_cross: None,
2426 pyo3_cross_lib_dir: Some("/usr/lib/mingw".into()),
2427 pyo3_cross_python_implementation: None,
2428 pyo3_cross_python_version: Some("3.8".into()),
2429 };
2430
2431 let host = triple!("x86_64-unknown-linux-gnu");
2432 let target = triple!("i686-pc-windows-gnu");
2433 let cross_config =
2434 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
2435 .unwrap()
2436 .unwrap();
2437
2438 assert_eq!(
2439 default_cross_compile(&cross_config).unwrap(),
2440 InterpreterConfig {
2441 implementation: PythonImplementation::CPython,
2442 version: PythonVersion { major: 3, minor: 8 },
2443 shared: true,
2444 abi3: false,
2445 lib_name: Some("python38".into()),
2446 lib_dir: Some("/usr/lib/mingw".into()),
2447 executable: None,
2448 pointer_width: None,
2449 build_flags: BuildFlags::default(),
2450 suppress_build_script_link_lines: false,
2451 extra_build_script_lines: vec![],
2452 python_framework_prefix: None,
2453 }
2454 );
2455 }
2456
2457 #[test]
2458 fn unix_hardcoded_cross_compile() {
2459 let env_vars = CrossCompileEnvVars {
2460 pyo3_cross: None,
2461 pyo3_cross_lib_dir: Some("/usr/arm64/lib".into()),
2462 pyo3_cross_python_implementation: None,
2463 pyo3_cross_python_version: Some("3.9".into()),
2464 };
2465
2466 let host = triple!("x86_64-unknown-linux-gnu");
2467 let target = triple!("aarch64-unknown-linux-gnu");
2468 let cross_config =
2469 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
2470 .unwrap()
2471 .unwrap();
2472
2473 assert_eq!(
2474 default_cross_compile(&cross_config).unwrap(),
2475 InterpreterConfig {
2476 implementation: PythonImplementation::CPython,
2477 version: PythonVersion { major: 3, minor: 9 },
2478 shared: true,
2479 abi3: false,
2480 lib_name: Some("python3.9".into()),
2481 lib_dir: Some("/usr/arm64/lib".into()),
2482 executable: None,
2483 pointer_width: None,
2484 build_flags: BuildFlags::default(),
2485 suppress_build_script_link_lines: false,
2486 extra_build_script_lines: vec![],
2487 python_framework_prefix: None,
2488 }
2489 );
2490 }
2491
2492 #[test]
2493 fn pypy_hardcoded_cross_compile() {
2494 let env_vars = CrossCompileEnvVars {
2495 pyo3_cross: None,
2496 pyo3_cross_lib_dir: None,
2497 pyo3_cross_python_implementation: Some("PyPy".into()),
2498 pyo3_cross_python_version: Some("3.11".into()),
2499 };
2500
2501 let triple = triple!("x86_64-unknown-linux-gnu");
2502 let cross_config =
2503 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &triple, &triple)
2504 .unwrap()
2505 .unwrap();
2506
2507 assert_eq!(
2508 default_cross_compile(&cross_config).unwrap(),
2509 InterpreterConfig {
2510 implementation: PythonImplementation::PyPy,
2511 version: PythonVersion {
2512 major: 3,
2513 minor: 11
2514 },
2515 shared: true,
2516 abi3: false,
2517 lib_name: Some("pypy3.11-c".into()),
2518 lib_dir: None,
2519 executable: None,
2520 pointer_width: None,
2521 build_flags: BuildFlags::default(),
2522 suppress_build_script_link_lines: false,
2523 extra_build_script_lines: vec![],
2524 python_framework_prefix: None,
2525 }
2526 );
2527 }
2528
2529 #[test]
2530 fn default_lib_name_windows() {
2531 use PythonImplementation::*;
2532 assert_eq!(
2533 super::default_lib_name_windows(
2534 PythonVersion { major: 3, minor: 9 },
2535 CPython,
2536 false,
2537 false,
2538 false,
2539 false,
2540 )
2541 .unwrap(),
2542 "python39",
2543 );
2544 assert!(super::default_lib_name_windows(
2545 PythonVersion { major: 3, minor: 9 },
2546 CPython,
2547 false,
2548 false,
2549 false,
2550 true,
2551 )
2552 .is_err());
2553 assert_eq!(
2554 super::default_lib_name_windows(
2555 PythonVersion { major: 3, minor: 9 },
2556 CPython,
2557 true,
2558 false,
2559 false,
2560 false,
2561 )
2562 .unwrap(),
2563 "python3",
2564 );
2565 assert_eq!(
2566 super::default_lib_name_windows(
2567 PythonVersion { major: 3, minor: 9 },
2568 CPython,
2569 false,
2570 true,
2571 false,
2572 false,
2573 )
2574 .unwrap(),
2575 "python3.9",
2576 );
2577 assert_eq!(
2578 super::default_lib_name_windows(
2579 PythonVersion { major: 3, minor: 9 },
2580 CPython,
2581 true,
2582 true,
2583 false,
2584 false,
2585 )
2586 .unwrap(),
2587 "python3",
2588 );
2589 assert_eq!(
2590 super::default_lib_name_windows(
2591 PythonVersion { major: 3, minor: 9 },
2592 PyPy,
2593 true,
2594 false,
2595 false,
2596 false,
2597 )
2598 .unwrap(),
2599 "python39",
2600 );
2601 assert_eq!(
2602 super::default_lib_name_windows(
2603 PythonVersion { major: 3, minor: 9 },
2604 CPython,
2605 false,
2606 false,
2607 true,
2608 false,
2609 )
2610 .unwrap(),
2611 "python39_d",
2612 );
2613 assert_eq!(
2616 super::default_lib_name_windows(
2617 PythonVersion { major: 3, minor: 9 },
2618 CPython,
2619 true,
2620 false,
2621 true,
2622 false,
2623 )
2624 .unwrap(),
2625 "python39_d",
2626 );
2627 assert_eq!(
2628 super::default_lib_name_windows(
2629 PythonVersion {
2630 major: 3,
2631 minor: 10
2632 },
2633 CPython,
2634 true,
2635 false,
2636 true,
2637 false,
2638 )
2639 .unwrap(),
2640 "python3_d",
2641 );
2642 assert!(super::default_lib_name_windows(
2644 PythonVersion {
2645 major: 3,
2646 minor: 12,
2647 },
2648 CPython,
2649 false,
2650 false,
2651 false,
2652 true,
2653 )
2654 .is_err());
2655 assert!(super::default_lib_name_windows(
2657 PythonVersion {
2658 major: 3,
2659 minor: 12,
2660 },
2661 CPython,
2662 false,
2663 true,
2664 false,
2665 true,
2666 )
2667 .is_err());
2668 assert_eq!(
2669 super::default_lib_name_windows(
2670 PythonVersion {
2671 major: 3,
2672 minor: 13
2673 },
2674 CPython,
2675 false,
2676 false,
2677 false,
2678 true,
2679 )
2680 .unwrap(),
2681 "python313t",
2682 );
2683 assert_eq!(
2684 super::default_lib_name_windows(
2685 PythonVersion {
2686 major: 3,
2687 minor: 13
2688 },
2689 CPython,
2690 true, false,
2692 false,
2693 true,
2694 )
2695 .unwrap(),
2696 "python313t",
2697 );
2698 assert_eq!(
2699 super::default_lib_name_windows(
2700 PythonVersion {
2701 major: 3,
2702 minor: 13
2703 },
2704 CPython,
2705 false,
2706 false,
2707 true,
2708 true,
2709 )
2710 .unwrap(),
2711 "python313t_d",
2712 );
2713 }
2714
2715 #[test]
2716 fn default_lib_name_unix() {
2717 use PythonImplementation::*;
2718 assert_eq!(
2720 super::default_lib_name_unix(
2721 PythonVersion { major: 3, minor: 7 },
2722 CPython,
2723 false,
2724 false,
2725 None,
2726 false
2727 )
2728 .unwrap(),
2729 "python3.7m",
2730 );
2731 assert_eq!(
2733 super::default_lib_name_unix(
2734 PythonVersion { major: 3, minor: 8 },
2735 CPython,
2736 false,
2737 false,
2738 None,
2739 false
2740 )
2741 .unwrap(),
2742 "python3.8",
2743 );
2744 assert_eq!(
2745 super::default_lib_name_unix(
2746 PythonVersion { major: 3, minor: 9 },
2747 CPython,
2748 false,
2749 false,
2750 None,
2751 false
2752 )
2753 .unwrap(),
2754 "python3.9",
2755 );
2756 assert_eq!(
2758 super::default_lib_name_unix(
2759 PythonVersion { major: 3, minor: 9 },
2760 CPython,
2761 false,
2762 false,
2763 Some("3.7md"),
2764 false
2765 )
2766 .unwrap(),
2767 "python3.7md",
2768 );
2769
2770 assert_eq!(
2772 super::default_lib_name_unix(
2773 PythonVersion {
2774 major: 3,
2775 minor: 11
2776 },
2777 PyPy,
2778 false,
2779 false,
2780 None,
2781 false
2782 )
2783 .unwrap(),
2784 "pypy3.11-c",
2785 );
2786
2787 assert_eq!(
2788 super::default_lib_name_unix(
2789 PythonVersion { major: 3, minor: 9 },
2790 PyPy,
2791 false,
2792 false,
2793 Some("3.11d"),
2794 false
2795 )
2796 .unwrap(),
2797 "pypy3.11d-c",
2798 );
2799
2800 assert_eq!(
2802 super::default_lib_name_unix(
2803 PythonVersion {
2804 major: 3,
2805 minor: 13
2806 },
2807 CPython,
2808 false,
2809 false,
2810 None,
2811 true
2812 )
2813 .unwrap(),
2814 "python3.13t",
2815 );
2816 assert!(super::default_lib_name_unix(
2818 PythonVersion {
2819 major: 3,
2820 minor: 12,
2821 },
2822 CPython,
2823 false,
2824 false,
2825 None,
2826 true,
2827 )
2828 .is_err());
2829 assert_eq!(
2831 super::default_lib_name_unix(
2832 PythonVersion {
2833 major: 3,
2834 minor: 13
2835 },
2836 CPython,
2837 true,
2838 true,
2839 None,
2840 false
2841 )
2842 .unwrap(),
2843 "python3",
2844 );
2845 }
2846
2847 #[test]
2848 fn parse_cross_python_version() {
2849 let env_vars = CrossCompileEnvVars {
2850 pyo3_cross: None,
2851 pyo3_cross_lib_dir: None,
2852 pyo3_cross_python_version: Some("3.9".into()),
2853 pyo3_cross_python_implementation: None,
2854 };
2855
2856 assert_eq!(
2857 env_vars.parse_version().unwrap(),
2858 (Some(PythonVersion { major: 3, minor: 9 }), None),
2859 );
2860
2861 let env_vars = CrossCompileEnvVars {
2862 pyo3_cross: None,
2863 pyo3_cross_lib_dir: None,
2864 pyo3_cross_python_version: None,
2865 pyo3_cross_python_implementation: None,
2866 };
2867
2868 assert_eq!(env_vars.parse_version().unwrap(), (None, None));
2869
2870 let env_vars = CrossCompileEnvVars {
2871 pyo3_cross: None,
2872 pyo3_cross_lib_dir: None,
2873 pyo3_cross_python_version: Some("3.13t".into()),
2874 pyo3_cross_python_implementation: None,
2875 };
2876
2877 assert_eq!(
2878 env_vars.parse_version().unwrap(),
2879 (
2880 Some(PythonVersion {
2881 major: 3,
2882 minor: 13
2883 }),
2884 Some("t".into())
2885 ),
2886 );
2887
2888 let env_vars = CrossCompileEnvVars {
2889 pyo3_cross: None,
2890 pyo3_cross_lib_dir: None,
2891 pyo3_cross_python_version: Some("100".into()),
2892 pyo3_cross_python_implementation: None,
2893 };
2894
2895 assert!(env_vars.parse_version().is_err());
2896 }
2897
2898 #[test]
2899 fn interpreter_version_reduced_to_abi3() {
2900 let mut config = InterpreterConfig {
2901 abi3: true,
2902 build_flags: BuildFlags::default(),
2903 pointer_width: None,
2904 executable: None,
2905 implementation: PythonImplementation::CPython,
2906 lib_dir: None,
2907 lib_name: None,
2908 shared: true,
2909 version: PythonVersion { major: 3, minor: 7 },
2910 suppress_build_script_link_lines: false,
2911 extra_build_script_lines: vec![],
2912 python_framework_prefix: None,
2913 };
2914
2915 config
2916 .fixup_for_abi3_version(Some(PythonVersion { major: 3, minor: 7 }))
2917 .unwrap();
2918 assert_eq!(config.version, PythonVersion { major: 3, minor: 7 });
2919 }
2920
2921 #[test]
2922 fn abi3_version_cannot_be_higher_than_interpreter() {
2923 let mut config = InterpreterConfig {
2924 abi3: true,
2925 build_flags: BuildFlags::new(),
2926 pointer_width: None,
2927 executable: None,
2928 implementation: PythonImplementation::CPython,
2929 lib_dir: None,
2930 lib_name: None,
2931 shared: true,
2932 version: PythonVersion { major: 3, minor: 7 },
2933 suppress_build_script_link_lines: false,
2934 extra_build_script_lines: vec![],
2935 python_framework_prefix: None,
2936 };
2937
2938 assert!(config
2939 .fixup_for_abi3_version(Some(PythonVersion { major: 3, minor: 8 }))
2940 .unwrap_err()
2941 .to_string()
2942 .contains(
2943 "cannot set a minimum Python version 3.8 higher than the interpreter version 3.7"
2944 ));
2945 }
2946
2947 #[test]
2948 #[cfg(all(
2949 target_os = "linux",
2950 target_arch = "x86_64",
2951 feature = "resolve-config"
2952 ))]
2953 fn parse_sysconfigdata() {
2954 let interpreter_config = crate::get();
2959
2960 let lib_dir = match &interpreter_config.lib_dir {
2961 Some(lib_dir) => Path::new(lib_dir),
2962 None => return,
2964 };
2965
2966 let cross = CrossCompileConfig {
2967 lib_dir: Some(lib_dir.into()),
2968 version: Some(interpreter_config.version),
2969 implementation: Some(interpreter_config.implementation),
2970 target: triple!("x86_64-unknown-linux-gnu"),
2971 abiflags: if interpreter_config.is_free_threaded() {
2972 Some("t".into())
2973 } else {
2974 None
2975 },
2976 };
2977
2978 let sysconfigdata_path = match find_sysconfigdata(&cross) {
2979 Ok(Some(path)) => path,
2980 _ => return,
2982 };
2983 let sysconfigdata = super::parse_sysconfigdata(sysconfigdata_path).unwrap();
2984 let parsed_config = InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap();
2985
2986 assert_eq!(
2987 parsed_config,
2988 InterpreterConfig {
2989 abi3: false,
2990 build_flags: BuildFlags(interpreter_config.build_flags.0.clone()),
2991 pointer_width: Some(64),
2992 executable: None,
2993 implementation: PythonImplementation::CPython,
2994 lib_dir: interpreter_config.lib_dir.to_owned(),
2995 lib_name: interpreter_config.lib_name.to_owned(),
2996 shared: true,
2997 version: interpreter_config.version,
2998 suppress_build_script_link_lines: false,
2999 extra_build_script_lines: vec![],
3000 python_framework_prefix: None,
3001 }
3002 )
3003 }
3004
3005 #[test]
3006 fn test_venv_interpreter() {
3007 let base = OsStr::new("base");
3008 assert_eq!(
3009 venv_interpreter(base, true),
3010 PathBuf::from_iter(&["base", "Scripts", "python.exe"])
3011 );
3012 assert_eq!(
3013 venv_interpreter(base, false),
3014 PathBuf::from_iter(&["base", "bin", "python"])
3015 );
3016 }
3017
3018 #[test]
3019 fn test_conda_env_interpreter() {
3020 let base = OsStr::new("base");
3021 assert_eq!(
3022 conda_env_interpreter(base, true),
3023 PathBuf::from_iter(&["base", "python.exe"])
3024 );
3025 assert_eq!(
3026 conda_env_interpreter(base, false),
3027 PathBuf::from_iter(&["base", "bin", "python"])
3028 );
3029 }
3030
3031 #[test]
3032 fn test_not_cross_compiling_from_to() {
3033 assert!(cross_compiling_from_to(
3034 &triple!("x86_64-unknown-linux-gnu"),
3035 &triple!("x86_64-unknown-linux-gnu"),
3036 )
3037 .unwrap()
3038 .is_none());
3039
3040 assert!(cross_compiling_from_to(
3041 &triple!("x86_64-apple-darwin"),
3042 &triple!("x86_64-apple-darwin")
3043 )
3044 .unwrap()
3045 .is_none());
3046
3047 assert!(cross_compiling_from_to(
3048 &triple!("aarch64-apple-darwin"),
3049 &triple!("x86_64-apple-darwin")
3050 )
3051 .unwrap()
3052 .is_none());
3053
3054 assert!(cross_compiling_from_to(
3055 &triple!("x86_64-apple-darwin"),
3056 &triple!("aarch64-apple-darwin")
3057 )
3058 .unwrap()
3059 .is_none());
3060
3061 assert!(cross_compiling_from_to(
3062 &triple!("x86_64-pc-windows-msvc"),
3063 &triple!("i686-pc-windows-msvc")
3064 )
3065 .unwrap()
3066 .is_none());
3067
3068 assert!(cross_compiling_from_to(
3069 &triple!("x86_64-unknown-linux-gnu"),
3070 &triple!("x86_64-unknown-linux-musl")
3071 )
3072 .unwrap()
3073 .is_none());
3074
3075 assert!(cross_compiling_from_to(
3076 &triple!("x86_64-pc-windows-msvc"),
3077 &triple!("x86_64-win7-windows-msvc"),
3078 )
3079 .unwrap()
3080 .is_none());
3081 }
3082
3083 #[test]
3084 fn test_is_cross_compiling_from_to() {
3085 assert!(cross_compiling_from_to(
3086 &triple!("x86_64-pc-windows-msvc"),
3087 &triple!("aarch64-pc-windows-msvc")
3088 )
3089 .unwrap()
3090 .is_some());
3091 }
3092
3093 #[test]
3094 fn test_run_python_script() {
3095 let interpreter = make_interpreter_config()
3097 .expect("could not get InterpreterConfig from installed interpreter");
3098 let out = interpreter
3099 .run_python_script("print(2 + 2)")
3100 .expect("failed to run Python script");
3101 assert_eq!(out.trim_end(), "4");
3102 }
3103
3104 #[test]
3105 fn test_run_python_script_with_envs() {
3106 let interpreter = make_interpreter_config()
3108 .expect("could not get InterpreterConfig from installed interpreter");
3109 let out = interpreter
3110 .run_python_script_with_envs(
3111 "import os; print(os.getenv('PYO3_TEST'))",
3112 vec![("PYO3_TEST", "42")],
3113 )
3114 .expect("failed to run Python script");
3115 assert_eq!(out.trim_end(), "42");
3116 }
3117
3118 #[test]
3119 fn test_build_script_outputs_base() {
3120 let interpreter_config = InterpreterConfig {
3121 implementation: PythonImplementation::CPython,
3122 version: PythonVersion {
3123 major: 3,
3124 minor: 11,
3125 },
3126 shared: true,
3127 abi3: false,
3128 lib_name: Some("python3".into()),
3129 lib_dir: None,
3130 executable: None,
3131 pointer_width: None,
3132 build_flags: BuildFlags::default(),
3133 suppress_build_script_link_lines: false,
3134 extra_build_script_lines: vec![],
3135 python_framework_prefix: None,
3136 };
3137 assert_eq!(
3138 interpreter_config.build_script_outputs(),
3139 [
3140 "cargo:rustc-cfg=Py_3_7".to_owned(),
3141 "cargo:rustc-cfg=Py_3_8".to_owned(),
3142 "cargo:rustc-cfg=Py_3_9".to_owned(),
3143 "cargo:rustc-cfg=Py_3_10".to_owned(),
3144 "cargo:rustc-cfg=Py_3_11".to_owned(),
3145 ]
3146 );
3147
3148 let interpreter_config = InterpreterConfig {
3149 implementation: PythonImplementation::PyPy,
3150 ..interpreter_config
3151 };
3152 assert_eq!(
3153 interpreter_config.build_script_outputs(),
3154 [
3155 "cargo:rustc-cfg=Py_3_7".to_owned(),
3156 "cargo:rustc-cfg=Py_3_8".to_owned(),
3157 "cargo:rustc-cfg=Py_3_9".to_owned(),
3158 "cargo:rustc-cfg=Py_3_10".to_owned(),
3159 "cargo:rustc-cfg=Py_3_11".to_owned(),
3160 "cargo:rustc-cfg=PyPy".to_owned(),
3161 ]
3162 );
3163 }
3164
3165 #[test]
3166 fn test_build_script_outputs_abi3() {
3167 let interpreter_config = InterpreterConfig {
3168 implementation: PythonImplementation::CPython,
3169 version: PythonVersion { major: 3, minor: 9 },
3170 shared: true,
3171 abi3: true,
3172 lib_name: Some("python3".into()),
3173 lib_dir: None,
3174 executable: None,
3175 pointer_width: None,
3176 build_flags: BuildFlags::default(),
3177 suppress_build_script_link_lines: false,
3178 extra_build_script_lines: vec![],
3179 python_framework_prefix: None,
3180 };
3181
3182 assert_eq!(
3183 interpreter_config.build_script_outputs(),
3184 [
3185 "cargo:rustc-cfg=Py_3_7".to_owned(),
3186 "cargo:rustc-cfg=Py_3_8".to_owned(),
3187 "cargo:rustc-cfg=Py_3_9".to_owned(),
3188 "cargo:rustc-cfg=Py_LIMITED_API".to_owned(),
3189 ]
3190 );
3191
3192 let interpreter_config = InterpreterConfig {
3193 implementation: PythonImplementation::PyPy,
3194 ..interpreter_config
3195 };
3196 assert_eq!(
3197 interpreter_config.build_script_outputs(),
3198 [
3199 "cargo:rustc-cfg=Py_3_7".to_owned(),
3200 "cargo:rustc-cfg=Py_3_8".to_owned(),
3201 "cargo:rustc-cfg=Py_3_9".to_owned(),
3202 "cargo:rustc-cfg=PyPy".to_owned(),
3203 "cargo:rustc-cfg=Py_LIMITED_API".to_owned(),
3204 ]
3205 );
3206 }
3207
3208 #[test]
3209 fn test_build_script_outputs_gil_disabled() {
3210 let mut build_flags = BuildFlags::default();
3211 build_flags.0.insert(BuildFlag::Py_GIL_DISABLED);
3212 let interpreter_config = InterpreterConfig {
3213 implementation: PythonImplementation::CPython,
3214 version: PythonVersion {
3215 major: 3,
3216 minor: 13,
3217 },
3218 shared: true,
3219 abi3: false,
3220 lib_name: Some("python3".into()),
3221 lib_dir: None,
3222 executable: None,
3223 pointer_width: None,
3224 build_flags,
3225 suppress_build_script_link_lines: false,
3226 extra_build_script_lines: vec![],
3227 python_framework_prefix: None,
3228 };
3229
3230 assert_eq!(
3231 interpreter_config.build_script_outputs(),
3232 [
3233 "cargo:rustc-cfg=Py_3_7".to_owned(),
3234 "cargo:rustc-cfg=Py_3_8".to_owned(),
3235 "cargo:rustc-cfg=Py_3_9".to_owned(),
3236 "cargo:rustc-cfg=Py_3_10".to_owned(),
3237 "cargo:rustc-cfg=Py_3_11".to_owned(),
3238 "cargo:rustc-cfg=Py_3_12".to_owned(),
3239 "cargo:rustc-cfg=Py_3_13".to_owned(),
3240 "cargo:rustc-cfg=Py_GIL_DISABLED".to_owned(),
3241 ]
3242 );
3243 }
3244
3245 #[test]
3246 fn test_build_script_outputs_debug() {
3247 let mut build_flags = BuildFlags::default();
3248 build_flags.0.insert(BuildFlag::Py_DEBUG);
3249 let interpreter_config = InterpreterConfig {
3250 implementation: PythonImplementation::CPython,
3251 version: PythonVersion { major: 3, minor: 7 },
3252 shared: true,
3253 abi3: false,
3254 lib_name: Some("python3".into()),
3255 lib_dir: None,
3256 executable: None,
3257 pointer_width: None,
3258 build_flags,
3259 suppress_build_script_link_lines: false,
3260 extra_build_script_lines: vec![],
3261 python_framework_prefix: None,
3262 };
3263
3264 assert_eq!(
3265 interpreter_config.build_script_outputs(),
3266 [
3267 "cargo:rustc-cfg=Py_3_7".to_owned(),
3268 "cargo:rustc-cfg=py_sys_config=\"Py_DEBUG\"".to_owned(),
3269 ]
3270 );
3271 }
3272
3273 #[test]
3274 fn test_find_sysconfigdata_in_invalid_lib_dir() {
3275 let e = find_all_sysconfigdata(&CrossCompileConfig {
3276 lib_dir: Some(PathBuf::from("/abc/123/not/a/real/path")),
3277 version: None,
3278 implementation: None,
3279 target: triple!("x86_64-unknown-linux-gnu"),
3280 abiflags: None,
3281 })
3282 .unwrap_err();
3283
3284 assert!(e.report().to_string().starts_with(
3286 "failed to search the lib dir at 'PYO3_CROSS_LIB_DIR=/abc/123/not/a/real/path'\n\
3287 caused by:\n \
3288 - 0: failed to list the entries in '/abc/123/not/a/real/path'\n \
3289 - 1: \
3290 "
3291 ));
3292 }
3293
3294 #[test]
3295 fn test_from_pyo3_config_file_env_rebuild() {
3296 READ_ENV_VARS.with(|vars| vars.borrow_mut().clear());
3297 let _ = InterpreterConfig::from_pyo3_config_file_env();
3298 READ_ENV_VARS.with(|vars| assert!(vars.borrow().contains(&"PYO3_CONFIG_FILE".to_string())));
3300 }
3301
3302 #[test]
3303 fn test_apply_default_lib_name_to_config_file() {
3304 let mut config = InterpreterConfig {
3305 implementation: PythonImplementation::CPython,
3306 version: PythonVersion { major: 3, minor: 9 },
3307 shared: true,
3308 abi3: false,
3309 lib_name: None,
3310 lib_dir: None,
3311 executable: None,
3312 pointer_width: None,
3313 build_flags: BuildFlags::default(),
3314 suppress_build_script_link_lines: false,
3315 extra_build_script_lines: vec![],
3316 python_framework_prefix: None,
3317 };
3318
3319 let unix = Triple::from_str("x86_64-unknown-linux-gnu").unwrap();
3320 let win_x64 = Triple::from_str("x86_64-pc-windows-msvc").unwrap();
3321 let win_arm64 = Triple::from_str("aarch64-pc-windows-msvc").unwrap();
3322
3323 config.apply_default_lib_name_to_config_file(&unix);
3324 assert_eq!(config.lib_name, Some("python3.9".into()));
3325
3326 config.lib_name = None;
3327 config.apply_default_lib_name_to_config_file(&win_x64);
3328 assert_eq!(config.lib_name, Some("python39".into()));
3329
3330 config.lib_name = None;
3331 config.apply_default_lib_name_to_config_file(&win_arm64);
3332 assert_eq!(config.lib_name, Some("python39".into()));
3333
3334 config.implementation = PythonImplementation::PyPy;
3336 config.version = PythonVersion {
3337 major: 3,
3338 minor: 11,
3339 };
3340 config.lib_name = None;
3341 config.apply_default_lib_name_to_config_file(&unix);
3342 assert_eq!(config.lib_name, Some("pypy3.11-c".into()));
3343
3344 config.implementation = PythonImplementation::CPython;
3345
3346 config.build_flags.0.insert(BuildFlag::Py_GIL_DISABLED);
3348 config.version = PythonVersion {
3349 major: 3,
3350 minor: 13,
3351 };
3352 config.lib_name = None;
3353 config.apply_default_lib_name_to_config_file(&unix);
3354 assert_eq!(config.lib_name, Some("python3.13t".into()));
3355
3356 config.lib_name = None;
3357 config.apply_default_lib_name_to_config_file(&win_x64);
3358 assert_eq!(config.lib_name, Some("python313t".into()));
3359
3360 config.lib_name = None;
3361 config.apply_default_lib_name_to_config_file(&win_arm64);
3362 assert_eq!(config.lib_name, Some("python313t".into()));
3363
3364 config.build_flags.0.remove(&BuildFlag::Py_GIL_DISABLED);
3365
3366 config.abi3 = true;
3368 config.lib_name = None;
3369 config.apply_default_lib_name_to_config_file(&unix);
3370 assert_eq!(config.lib_name, Some("python3.13".into()));
3371
3372 config.lib_name = None;
3373 config.apply_default_lib_name_to_config_file(&win_x64);
3374 assert_eq!(config.lib_name, Some("python3".into()));
3375
3376 config.lib_name = None;
3377 config.apply_default_lib_name_to_config_file(&win_arm64);
3378 assert_eq!(config.lib_name, Some("python3".into()));
3379 }
3380}