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