Skip to main content

pyo3_build_config/
impl_.rs

1//! Main implementation module included in both the `pyo3-build-config` library crate
2//! and its build script.
3
4// Optional python3.dll import library generator for Windows
5#[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
33/// Minimum Python version PyO3 supports.
34pub(crate) const MINIMUM_SUPPORTED_VERSION: PythonVersion = PythonVersion { major: 3, minor: 7 };
35
36/// GraalPy may implement the same CPython version over multiple releases.
37const MINIMUM_SUPPORTED_VERSION_GRAALPY: PythonVersion = PythonVersion {
38    major: 25,
39    minor: 0,
40};
41
42/// Maximum Python version that can be used as minimum required Python version with abi3.
43pub(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
50/// Gets an environment variable owned by cargo.
51///
52/// Environment variables set by cargo are expected to be valid UTF8.
53pub fn cargo_env_var(var: &str) -> Option<String> {
54    env::var_os(var).map(|os_string| os_string.to_str().unwrap().into())
55}
56
57/// Gets an external environment variable, and registers the build script to rerun if
58/// the variable changes.
59pub 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
72/// Gets the compilation target triple from environment variables set by Cargo.
73///
74/// Must be called from a crate build script.
75pub 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/// Configuration needed by PyO3 to build for the correct Python implementation.
83///
84/// Usually this is queried directly from the Python interpreter, or overridden using the
85/// `PYO3_CONFIG_FILE` environment variable.
86///
87/// When the `PYO3_NO_PYTHON` variable is set, or during cross compile situations, then alternative
88/// strategies are used to populate this type.
89#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
90pub struct InterpreterConfig {
91    /// The Python implementation flavor.
92    ///
93    /// Serialized to `implementation`.
94    pub implementation: PythonImplementation,
95
96    /// Python `X.Y` version. e.g. `3.9`.
97    ///
98    /// Serialized to `version`.
99    pub version: PythonVersion,
100
101    /// Whether link library is shared.
102    ///
103    /// Serialized to `shared`.
104    pub shared: bool,
105
106    /// Whether linking against the stable/limited Python 3 API.
107    ///
108    /// Serialized to `abi3`.
109    pub abi3: bool,
110
111    /// The name of the link library defining Python.
112    ///
113    /// This effectively controls the `cargo:rustc-link-lib=<name>` value to
114    /// control how libpython is linked. Values should not contain the `lib`
115    /// prefix.
116    ///
117    /// Serialized to `lib_name`.
118    pub lib_name: Option<String>,
119
120    /// The directory containing the Python library to link against.
121    ///
122    /// The effectively controls the `cargo:rustc-link-search=native=<path>` value
123    /// to add an additional library search path for the linker.
124    ///
125    /// Serialized to `lib_dir`.
126    pub lib_dir: Option<String>,
127
128    /// Path of host `python` executable.
129    ///
130    /// This is a valid executable capable of running on the host/building machine.
131    /// For configurations derived by invoking a Python interpreter, it was the
132    /// executable invoked.
133    ///
134    /// Serialized to `executable`.
135    pub executable: Option<String>,
136
137    /// Width in bits of pointers on the target machine.
138    ///
139    /// Serialized to `pointer_width`.
140    pub pointer_width: Option<u32>,
141
142    /// Additional relevant Python build flags / configuration settings.
143    ///
144    /// Serialized to `build_flags`.
145    pub build_flags: BuildFlags,
146
147    /// Whether to suppress emitting of `cargo:rustc-link-*` lines from the build script.
148    ///
149    /// Typically, `pyo3`'s build script will emit `cargo:rustc-link-lib=` and
150    /// `cargo:rustc-link-search=` lines derived from other fields in this struct. In
151    /// advanced building configurations, the default logic to derive these lines may not
152    /// be sufficient. This field can be set to `Some(true)` to suppress the emission
153    /// of these lines.
154    ///
155    /// If suppression is enabled, `extra_build_script_lines` should contain equivalent
156    /// functionality or else a build failure is likely.
157    pub suppress_build_script_link_lines: bool,
158
159    /// Additional lines to `println!()` from Cargo build scripts.
160    ///
161    /// This field can be populated to enable the `pyo3` crate to emit additional lines from its
162    /// its Cargo build script.
163    ///
164    /// This crate doesn't populate this field itself. Rather, it is intended to be used with
165    /// externally provided config files to give them significant control over how the crate
166    /// is build/configured.
167    ///
168    /// Serialized to multiple `extra_build_script_line` values.
169    pub extra_build_script_lines: Vec<String>,
170    /// macOS Python3.framework requires special rpath handling
171    pub python_framework_prefix: Option<String>,
172}
173
174impl InterpreterConfig {
175    #[doc(hidden)]
176    pub fn build_script_outputs(&self) -> Vec<String> {
177        // This should have been checked during pyo3-build-config build time.
178        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 Py_GIL_DISABLED is set, do not build with limited API support
193        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                // This is the best heuristic currently available to detect debug build
328                // on Windows from sysconfig - e.g. ext_suffix may be
329                // `_d.cp312-win_amd64.pyd` for 3.12 debug build
330                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        // The reason we don't use platform.architecture() here is that it's not
352        // reliable on macOS. See https://stackoverflow.com/a/1405971/823869.
353        // Similarly, sys.maxsize is not reliable on Windows. See
354        // https://stackoverflow.com/questions/1405913/how-do-i-determine-if-my-python-shell-is-executing-in-32bit-or-64bit-mode-on-os/1405971#comment6209952_1405971
355        // and https://stackoverflow.com/a/3411134/823869.
356        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    /// Generate from parsed sysconfigdata file
377    ///
378    /// Use [`parse_sysconfigdata`] to generate a hash map of configuration values which may be
379    /// used to build an [`InterpreterConfig`].
380    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        // macOS framework packages use shared linking (PYTHONFRAMEWORK is the framework name, hence the empty check)
406        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    /// Import an externally-provided config file.
450    ///
451    /// The `abi3` features, if set, may apply an `abi3` constraint to the Python version.
452    #[allow(dead_code)] // only used in build.rs
453    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            // Absolute path is necessary because this build script is run with a cwd different to the
458            // original `cargo build` instruction.
459            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            // If the abi3 feature is enabled, the minimum Python version is constrained by the abi3
467            // feature.
468            //
469            // TODO: abi3 is a property of the build mode, not the interpreter. Should this be
470            // removed from `InterpreterConfig`?
471            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    /// Helper function to apply a default lib_name if none is set in `PYO3_CONFIG_FILE`.
578    ///
579    /// This requires knowledge of the final target, so cannot be done when the config file is
580    /// inlined into `pyo3-build-config` at build time and instead needs to be done when
581    /// resolving the build config for linking.
582    #[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        // Auto generate python3.dll import libraries for Windows targets.
599        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    /// Serialize the `InterpreterConfig` and print it to the environment for Cargo to pass along
632    /// to dependent packages during build time.
633    ///
634    /// NB: writing to the cargo environment requires the
635    /// [`links`](https://doc.rust-lang.org/cargo/reference/build-scripts.html#the-links-manifest-key)
636    /// manifest key to be set. In this case that means this is called by the `pyo3-ffi` crate and
637    /// available for dependent package build scripts in `DEP_PYTHON_PYO3_CONFIG`. See
638    /// documentation for the
639    /// [`DEP_<name>_<key>`](https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts)
640    /// environment variable.
641    pub fn to_cargo_dep_env(&self) -> Result<()> {
642        let mut buf = Vec::new();
643        self.to_writer(&mut buf)?;
644        // escape newlines in env var
645        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    /// Run a python script using the [`InterpreterConfig::executable`].
694    ///
695    /// # Panics
696    ///
697    /// This function will panic if the [`executable`](InterpreterConfig::executable) is `None`.
698    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    /// Run a python script using the [`InterpreterConfig::executable`] with additional
707    /// environment variables (e.g. PYTHONPATH) set.
708    ///
709    /// # Panics
710    ///
711    /// This function will panic if the [`executable`](InterpreterConfig::executable) is `None`.
712    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    /// Updates configured ABI to build for to the requested abi3 version
730    /// This is a no-op for platforms where abi3 is not supported
731    fn fixup_for_abi3_version(&mut self, abi3_version: Option<PythonVersion>) -> Result<()> {
732        // PyPy, GraalPy, and the free-threaded build don't support abi3; don't adjust the version
733        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
864/// Checks if we should look for a Python interpreter installation
865/// to get the target interpreter configuration.
866///
867/// Returns `false` if `PYO3_NO_PYTHON` environment variable is set.
868fn have_python_interpreter() -> bool {
869    env_var("PYO3_NO_PYTHON").is_none()
870}
871
872/// Checks if `abi3` or any of the `abi3-py3*` features is enabled for the PyO3 crate.
873///
874/// Must be called from a PyO3 crate build script.
875fn 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
880/// Gets the minimum supported Python version from PyO3 `abi3-py*` features.
881///
882/// Must be called from a PyO3 crate build script.
883pub 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
889/// Checks if the `extension-module` feature is enabled for the PyO3 crate.
890///
891/// This can be triggered either by:
892/// - The `extension-module` Cargo feature (deprecated)
893/// - Setting the `PYO3_BUILD_EXTENSION_MODULE` environment variable
894///
895/// Must be called from a PyO3 crate build script.
896pub 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
901/// Checks if we need to link to `libpython` for the target.
902///
903/// Must be called from a PyO3 crate build script.
904pub fn is_linking_libpython_for_target(target: &Triple) -> bool {
905    target.operating_system == OperatingSystem::Windows
906        // See https://github.com/PyO3/pyo3/issues/4068#issuecomment-2051159852
907        || 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
915/// Checks if we need to discover the Python library directory
916/// to link the extension module binary.
917///
918/// Must be called from a PyO3 crate build script.
919fn 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/// Configuration needed by PyO3 to cross-compile for a target platform.
928///
929/// Usually this is collected from the environment (i.e. `PYO3_CROSS_*` and `CARGO_CFG_TARGET_*`)
930/// when a cross-compilation configuration is detected.
931#[derive(Debug, PartialEq, Eq)]
932pub struct CrossCompileConfig {
933    /// The directory containing the Python library to link against.
934    pub lib_dir: Option<PathBuf>,
935
936    /// The version of the Python library to link against.
937    version: Option<PythonVersion>,
938
939    /// The target Python implementation hint (CPython, PyPy, GraalPy, ...)
940    implementation: Option<PythonImplementation>,
941
942    /// The compile target triple (e.g. aarch64-unknown-linux-gnu)
943    target: Triple,
944
945    /// Python ABI flags, used to detect free-threaded Python builds.
946    abiflags: Option<String>,
947}
948
949impl CrossCompileConfig {
950    /// Creates a new cross compile config struct from PyO3 environment variables
951    /// and the build environment when cross compilation mode is detected.
952    ///
953    /// Returns `None` when not cross compiling.
954    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    /// Checks if compiling on `host` for `target` required "real" cross compilation.
978    ///
979    /// Returns `false` if the target Python interpreter can run on the host.
980    fn is_cross_compiling_from_to(host: &Triple, target: &Triple) -> bool {
981        // Not cross-compiling if arch-vendor-os is all the same
982        // e.g. x86_64-unknown-linux-musl on x86_64-unknown-linux-gnu host
983        //      x86_64-pc-windows-gnu on x86_64-pc-windows-msvc host
984        let mut compatible = host.architecture == target.architecture
985            && (host.vendor == target.vendor
986                // Don't treat `-pc-` to `-win7-` as cross-compiling
987                || (host.vendor == Vendor::Pc && target.vendor.as_str() == "win7"))
988            && host.operating_system == target.operating_system;
989
990        // Not cross-compiling to compile for 32-bit Python from windows 64-bit
991        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        // Not cross-compiling to compile for x86-64 Python from macOS arm64 and vice versa
997        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    /// Converts `lib_dir` member field to an UTF-8 string.
1006    ///
1007    /// The conversion can not fail because `PYO3_CROSS_LIB_DIR` variable
1008    /// is ensured contain a valid UTF-8 string.
1009    #[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
1017/// PyO3-specific cross compile environment variable values
1018struct CrossCompileEnvVars {
1019    /// `PYO3_CROSS`
1020    pyo3_cross: Option<OsString>,
1021    /// `PYO3_CROSS_LIB_DIR`
1022    pyo3_cross_lib_dir: Option<OsString>,
1023    /// `PYO3_CROSS_PYTHON_VERSION`
1024    pyo3_cross_python_version: Option<OsString>,
1025    /// `PYO3_CROSS_PYTHON_IMPLEMENTATION`
1026    pyo3_cross_python_implementation: Option<OsString>,
1027}
1028
1029impl CrossCompileEnvVars {
1030    /// Grabs the PyO3 cross-compile variables from the environment.
1031    ///
1032    /// Registers the build script to rerun if any of the variables changes.
1033    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    /// Checks if any of the variables is set.
1043    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    /// Parses `PYO3_CROSS_PYTHON_VERSION` environment variable value
1051    /// into `PythonVersion` and ABI flags.
1052    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    /// Parses `PYO3_CROSS_PYTHON_IMPLEMENTATION` environment variable value
1073    /// into `PythonImplementation`.
1074    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    /// Converts the stored `PYO3_CROSS_LIB_DIR` variable value (if any)
1092    /// into a `PathBuf` instance.
1093    ///
1094    /// Ensures that the path is a valid UTF-8 string.
1095    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
1109/// Detect whether we are cross compiling and return an assembled CrossCompileConfig if so.
1110///
1111/// This function relies on PyO3 cross-compiling environment variables:
1112///
1113/// * `PYO3_CROSS`: If present, forces PyO3 to configure as a cross-compilation.
1114/// * `PYO3_CROSS_LIB_DIR`: If present, must be set to the directory containing
1115///   the target's libpython DSO and the associated `_sysconfigdata*.py` file for
1116///   Unix-like targets, or the Python DLL import libraries for the Windows target.
1117/// * `PYO3_CROSS_PYTHON_VERSION`: Major and minor version (e.g. 3.9) of the target Python
1118///   installation. This variable is only needed if PyO3 cannot determine the version to target
1119///   from `abi3-py3*` features, or if there are multiple versions of Python present in
1120///   `PYO3_CROSS_LIB_DIR`.
1121///
1122/// See the [PyO3 User Guide](https://pyo3.rs/) for more info on cross-compiling.
1123pub 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/// Detect whether we are cross compiling from Cargo and `PYO3_CROSS_*` environment
1132/// variables and return an assembled `CrossCompileConfig` if so.
1133///
1134/// This must be called from PyO3's build script, because it relies on environment
1135/// variables such as `CARGO_CFG_TARGET_OS` which aren't available at any other time.
1136#[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/// A list of python interpreter compile-time preprocessor defines.
1180///
1181/// PyO3 will pick these up and pass to rustc via `--cfg=py_sys_config={varname}`;
1182/// this allows using them conditional cfg attributes in the .rs files, so
1183///
1184/// ```rust,no_run
1185/// #[cfg(py_sys_config="{varname}")]
1186/// # struct Foo;
1187/// ```
1188///
1189/// is the equivalent of `#ifdef {varname}` in C.
1190///
1191/// see Misc/SpecialBuilds.txt in the python source for what these mean.
1192#[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    /// Examine python's compile flags to pass to cfg by launching
1221    /// the interpreter and printing variables of interest from
1222    /// sysconfig.get_config_vars.
1223    fn from_interpreter(interpreter: impl AsRef<Path>) -> Result<Self> {
1224        // sysconfig is missing all the flags on windows for Python 3.12 and
1225        // older, so we can't actually query the interpreter directly for its
1226        // build flags on those versions.
1227        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
1306/// Parsed data from Python sysconfigdata file
1307///
1308/// A hash map of all values from a sysconfigdata file.
1309pub 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
1327/// Parse sysconfigdata file
1328///
1329/// The sysconfigdata is simply a dictionary containing all the build time variables used for the
1330/// python executable and library. This function necessitates a python interpreter on the host
1331/// machine to work. Here it is read into a `Sysconfigdata` (hash map), which can be turned into an
1332/// [`InterpreterConfig`] using
1333/// [`from_sysconfigdata`](InterpreterConfig::from_sysconfigdata).
1334pub 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/// Finds the sysconfigdata file when the target Python library directory is set.
1366///
1367/// Returns `None` if the library directory is not available, and a runtime error
1368/// when no or multiple sysconfigdata files are found.
1369#[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            // Continue with the default configuration when PYO3_CROSS_LIB_DIR is not set.
1377            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
1396/// Finds `_sysconfigdata*.py` files for detected Python interpreters.
1397///
1398/// From the python source for `_sysconfigdata*.py` is always going to be located at
1399/// `build/lib.{PLATFORM}-{PY_MINOR_VERSION}` when built from source. The [exact line][1] is defined as:
1400///
1401/// ```py
1402/// pybuilddir = 'build/lib.%s-%s' % (get_platform(), sys.version_info[:2])
1403/// ```
1404///
1405/// Where get_platform returns a kebab-case formatted string containing the os, the architecture and
1406/// possibly the os' kernel version (not the case on linux). However, when installed using a package
1407/// manager, the `_sysconfigdata*.py` file is installed in the `${PREFIX}/lib/python3.Y/` directory.
1408/// The `_sysconfigdata*.py` is generally in a sub-directory of the location of `libpython3.Y.so`.
1409/// So we must find the file in the following possible locations:
1410///
1411/// ```sh
1412/// # distribution from package manager, (lib_dir may or may not include lib/)
1413/// ${INSTALL_PREFIX}/lib/python3.Y/_sysconfigdata*.py
1414/// ${INSTALL_PREFIX}/lib/libpython3.Y.so
1415/// ${INSTALL_PREFIX}/lib/python3.Y/config-3.Y-${HOST_TRIPLE}/libpython3.Y.so
1416///
1417/// # Built from source from host
1418/// ${CROSS_COMPILED_LOCATION}/build/lib.linux-x86_64-Y/_sysconfigdata*.py
1419/// ${CROSS_COMPILED_LOCATION}/libpython3.Y.so
1420///
1421/// # if cross compiled, kernel release is only present on certain OS targets.
1422/// ${CROSS_COMPILED_LOCATION}/build/lib.{OS}(-{OS-KERNEL-RELEASE})?-{ARCH}-Y/_sysconfigdata*.py
1423/// ${CROSS_COMPILED_LOCATION}/libpython3.Y.so
1424///
1425/// # PyPy includes a similar file since v73
1426/// ${INSTALL_PREFIX}/lib/pypy3.Y/_sysconfigdata.py
1427/// ${INSTALL_PREFIX}/lib_pypy/_sysconfigdata.py
1428/// ```
1429///
1430/// [1]: https://github.com/python/cpython/blob/3.5/Lib/sysconfig.py#L389
1431///
1432/// Returns an empty vector when the target Python library directory
1433/// is not set via `PYO3_CROSS_LIB_DIR`.
1434pub 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
1491/// recursive search for _sysconfigdata, returns all possibilities of sysconfigdata paths
1492fn 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            // Python 3.7+ sysconfigdata with platform specifics
1502            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                    // check if right target os
1510                    if !file_name.contains(&cross.target.operating_system.to_string()) {
1511                        continue;
1512                    }
1513                    // Check if right arch
1514                    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 we got more than one file, only take those that contain the arch name.
1531    // For ubuntu 20.04 with host architecture x86_64 and a foreign architecture of armhf
1532    // this reduces the number of candidates to 1:
1533    //
1534    // $ find /usr/lib/python3.8/ -name '_sysconfigdata*.py' -not -lname '*'
1535    //  /usr/lib/python3.8/_sysconfigdata__x86_64-linux-gnu.py
1536    //  /usr/lib/python3.8/_sysconfigdata__arm-linux-gnueabihf.py
1537    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/// Find cross compilation information from sysconfigdata file
1555///
1556/// first find sysconfigdata file which follows the pattern [`_sysconfigdata_{abi}_{platform}_{multiarch}`][1]
1557///
1558/// [1]: https://github.com/python/cpython/blob/3.8/Lib/sysconfig.py#L348
1559///
1560/// Returns `None` when the target Python library directory is not set.
1561#[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/// Generates "default" cross compilation information for the target.
1579///
1580/// This should work for most CPython extension modules when targeting
1581/// Windows, macOS and Linux.
1582///
1583/// Must be called from a PyO3 crate build script.
1584#[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    // Auto generate python3.dll import libraries for Windows targets.
1615    #[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
1649/// Generates "default" interpreter configuration when compiling "abi3" extensions
1650/// without a working Python interpreter.
1651///
1652/// `version` specifies the minimum supported Stable ABI CPython version.
1653///
1654/// This should work for most CPython extension modules when compiling on
1655/// Windows, macOS and Linux.
1656///
1657/// Must be called from a PyO3 crate build script.
1658fn default_abi3_config(host: &Triple, version: PythonVersion) -> Result<InterpreterConfig> {
1659    // FIXME: PyPy & GraalPy do not support the Stable ABI.
1660    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/// Detects the cross compilation target interpreter configuration from all
1693/// available sources (PyO3 environment variables, Python sysconfigdata, etc.).
1694///
1695/// Returns the "default" target interpreter configuration for Windows and
1696/// when no target Python interpreter is found.
1697///
1698/// Must be called from a PyO3 crate build script.
1699#[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        // Load the defaults for Windows even when `PYO3_CROSS_LIB_DIR` is set
1707        // since it has no sysconfigdata files in it.
1708        // Also, do not try to look for sysconfigdata when `PYO3_NO_PYTHON` variable is set.
1709        default_cross_compile(&cross_compile_config)?
1710    } else if let Some(config) = cross_compile_from_sysconfigdata(&cross_compile_config)? {
1711        // Try to find and parse sysconfigdata files on other targets.
1712        config
1713    } else {
1714        // Fall back to the defaults when nothing else can be done.
1715        default_cross_compile(&cross_compile_config)?
1716    };
1717
1718    Ok(config)
1719}
1720
1721// These contains only the limited ABI symbols.
1722const WINDOWS_ABI3_LIB_NAME: &str = "python3";
1723const WINDOWS_ABI3_DEBUG_LIB_NAME: &str = "python3_d";
1724
1725/// Generates the default library name for the target platform.
1726#[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        // CPython bug: linking against python3_d.dll raises error
1759        // https://github.com/python/cpython/issues/101614
1760        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        // https://packages.msys2.org/base/mingw-w64-python
1773        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                    // PEP 3149 ABI version tags are finally gone
1804                    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                    // Work around https://bugs.python.org/issue36707
1812                    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
1825/// Run a python script using the specified interpreter binary.
1826fn run_python_script(interpreter: &Path, script: &str) -> Result<String> {
1827    run_python_script_with_envs(interpreter, script, std::iter::empty::<(&str, &str)>())
1828}
1829
1830/// Run a python script using the specified interpreter binary with additional environment
1831/// variables (e.g. PYTHONPATH) set.
1832fn 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        // Use cfg rather than CARGO_CFG_TARGET_OS because this affects where files are located on the
1885        // build host
1886        (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
1899/// Attempts to locate a python interpreter.
1900///
1901/// Locations are checked in the order listed:
1902///   1. If `PYO3_PYTHON` is set, this interpreter is used.
1903///   2. If in a virtualenv, that environment's interpreter is used.
1904///   3. `python`, if this is functional a Python 3.x interpreter
1905///   4. `python3`, as above
1906pub fn find_interpreter() -> Result<PathBuf> {
1907    // Trigger rebuilds when `PYO3_ENVIRONMENT_SIGNATURE` env var value changes
1908    // See https://github.com/PyO3/pyo3/issues/2724
1909    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                    // begin with `Python 3.X.X :: additional info`
1922                    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
1934/// Locates and extracts the build host Python interpreter configuration.
1935///
1936/// Lowers the configured Python version to `abi3_version` if required.
1937fn 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/// Generates an interpreter config suitable for cross-compilation.
1947///
1948/// This must be called from PyO3's build script, because it relies on environment variables such as
1949/// CARGO_CFG_TARGET_OS which aren't available at any other time.
1950#[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/// Generates an interpreter config which will be hard-coded into the pyo3-build-config crate.
1964/// Only used by `pyo3-build-config` build script.
1965#[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    // See if we can safely skip the Python interpreter configuration detection.
1971    // Unix "abi3" extension modules can usually be built without any interpreter.
1972    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            // Bail if the interpreter configuration is required to build.
1978            Err(e) if need_interpreter => return Err(e),
1979            _ => {
1980                // Fall back to the "abi3" defaults just as if `PYO3_NO_PYTHON`
1981                // environment variable was set.
1982                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    // Auto generate python3.dll import libraries for Windows targets.
1995    #[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        // And some different options, for variety
2081
2082        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        // Only version is required
2137        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        // ext_suffix is unknown to pyo3-build-config, but it shouldn't error
2159        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        // Py_DEBUG implies Py_REF_DEBUG
2226        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        // Smoke test to just see whether this works
2241        //
2242        // PyO3's CI is dependent on Python being installed, so this should be reliable.
2243        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        // these are the minimal values required such that InterpreterConfig::from_sysconfigdata
2256        // does not error
2257        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        // PYTHONFRAMEWORK should override Py_ENABLE_SHARED
2288        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        // An empty PYTHONFRAMEWORK means it is not a framework
2315        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        // abi3 debug builds on windows use version-specific lib on 3.9 and older
2614        // to workaround https://github.com/python/cpython/issues/101614
2615        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        // Python versions older than 3.13 don't support gil_disabled
2643        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        // mingw and free-threading are incompatible (until someone adds support)
2656        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, // abi3 true should not affect the free-threaded lib name
2691                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        // Defaults to python3.7m for CPython 3.7
2719        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        // Defaults to pythonX.Y for CPython 3.8+
2732        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        // Can use ldversion to override for CPython
2757        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        // PyPy 3.11 includes ldversion
2771        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        // free-threading adds a t suffix
2801        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        // 3.12 and older are incompatible with gil_disabled
2817        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        // cygwin abi3 links to unversioned libpython
2830        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        // A best effort attempt to get test coverage for the sysconfigdata parsing.
2955        // Might not complete successfully depending on host installation; that's ok as long as
2956        // CI demonstrates this path is covered!
2957
2958        let interpreter_config = crate::get();
2959
2960        let lib_dir = match &interpreter_config.lib_dir {
2961            Some(lib_dir) => Path::new(lib_dir),
2962            // Don't know where to search for sysconfigdata; never mind.
2963            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            // Couldn't find a matching sysconfigdata; never mind!
2981            _ => 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        // as above, this should be okay in CI where Python is presumed installed
3096        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        // as above, this should be okay in CI where Python is presumed installed
3107        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        // actual error message is platform-dependent, so just check the context we add
3285        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        // it's possible that other env vars were also read, hence just checking for contains
3299        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        // PyPy
3335        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        // Free-threaded
3347        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        // abi3
3367        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}