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};
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 = 12;
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!("{}\\libs", base_prefix))
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 }
734
735 Ok(())
736 }
737}
738
739#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
740pub struct PythonVersion {
741 pub major: u8,
742 pub minor: u8,
743}
744
745impl PythonVersion {
746 pub const PY313: Self = PythonVersion {
747 major: 3,
748 minor: 13,
749 };
750 const PY310: Self = PythonVersion {
751 major: 3,
752 minor: 10,
753 };
754 const PY37: Self = PythonVersion { major: 3, minor: 7 };
755}
756
757impl Display for PythonVersion {
758 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
759 write!(f, "{}.{}", self.major, self.minor)
760 }
761}
762
763impl FromStr for PythonVersion {
764 type Err = crate::errors::Error;
765
766 fn from_str(value: &str) -> Result<Self, Self::Err> {
767 let mut split = value.splitn(2, '.');
768 let (major, minor) = (
769 split
770 .next()
771 .expect("first splitn value should always be present"),
772 split.next().ok_or("expected major.minor version")?,
773 );
774 Ok(Self {
775 major: major.parse().context("failed to parse major version")?,
776 minor: minor.parse().context("failed to parse minor version")?,
777 })
778 }
779}
780
781#[derive(Debug, Copy, Clone, PartialEq, Eq)]
782pub enum PythonImplementation {
783 CPython,
784 PyPy,
785 GraalPy,
786}
787
788impl PythonImplementation {
789 #[doc(hidden)]
790 pub fn is_pypy(self) -> bool {
791 self == PythonImplementation::PyPy
792 }
793
794 #[doc(hidden)]
795 pub fn is_graalpy(self) -> bool {
796 self == PythonImplementation::GraalPy
797 }
798
799 #[doc(hidden)]
800 pub fn from_soabi(soabi: &str) -> Result<Self> {
801 if soabi.starts_with("pypy") {
802 Ok(PythonImplementation::PyPy)
803 } else if soabi.starts_with("cpython") {
804 Ok(PythonImplementation::CPython)
805 } else if soabi.starts_with("graalpy") {
806 Ok(PythonImplementation::GraalPy)
807 } else {
808 bail!("unsupported Python interpreter");
809 }
810 }
811}
812
813impl Display for PythonImplementation {
814 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
815 match self {
816 PythonImplementation::CPython => write!(f, "CPython"),
817 PythonImplementation::PyPy => write!(f, "PyPy"),
818 PythonImplementation::GraalPy => write!(f, "GraalVM"),
819 }
820 }
821}
822
823impl FromStr for PythonImplementation {
824 type Err = Error;
825 fn from_str(s: &str) -> Result<Self> {
826 match s {
827 "CPython" => Ok(PythonImplementation::CPython),
828 "PyPy" => Ok(PythonImplementation::PyPy),
829 "GraalVM" => Ok(PythonImplementation::GraalPy),
830 _ => bail!("unknown interpreter: {}", s),
831 }
832 }
833}
834
835fn have_python_interpreter() -> bool {
840 env_var("PYO3_NO_PYTHON").is_none()
841}
842
843fn is_abi3() -> bool {
847 cargo_env_var("CARGO_FEATURE_ABI3").is_some()
848 || env_var("PYO3_USE_ABI3_FORWARD_COMPATIBILITY").map_or(false, |os_str| os_str == "1")
849}
850
851pub fn get_abi3_version() -> Option<PythonVersion> {
855 let minor_version = (MINIMUM_SUPPORTED_VERSION.minor..=ABI3_MAX_MINOR)
856 .find(|i| cargo_env_var(&format!("CARGO_FEATURE_ABI3_PY3{}", i)).is_some());
857 minor_version.map(|minor| PythonVersion { major: 3, minor })
858}
859
860pub fn is_extension_module() -> bool {
864 cargo_env_var("CARGO_FEATURE_EXTENSION_MODULE").is_some()
865}
866
867pub fn is_linking_libpython() -> bool {
871 is_linking_libpython_for_target(&target_triple_from_env())
872}
873
874fn is_linking_libpython_for_target(target: &Triple) -> bool {
878 target.operating_system == OperatingSystem::Windows
879 || target.operating_system == OperatingSystem::Aix
881 || target.environment == Environment::Android
882 || target.environment == Environment::Androideabi
883 || !is_extension_module()
884}
885
886fn require_libdir_for_target(target: &Triple) -> bool {
891 let is_generating_libpython = cfg!(feature = "python3-dll-a")
892 && target.operating_system == OperatingSystem::Windows
893 && is_abi3();
894
895 is_linking_libpython_for_target(target) && !is_generating_libpython
896}
897
898#[derive(Debug, PartialEq, Eq)]
903pub struct CrossCompileConfig {
904 pub lib_dir: Option<PathBuf>,
906
907 version: Option<PythonVersion>,
909
910 implementation: Option<PythonImplementation>,
912
913 target: Triple,
915
916 abiflags: Option<String>,
918}
919
920impl CrossCompileConfig {
921 fn try_from_env_vars_host_target(
926 env_vars: CrossCompileEnvVars,
927 host: &Triple,
928 target: &Triple,
929 ) -> Result<Option<Self>> {
930 if env_vars.any() || Self::is_cross_compiling_from_to(host, target) {
931 let lib_dir = env_vars.lib_dir_path()?;
932 let (version, abiflags) = env_vars.parse_version()?;
933 let implementation = env_vars.parse_implementation()?;
934 let target = target.clone();
935
936 Ok(Some(CrossCompileConfig {
937 lib_dir,
938 version,
939 implementation,
940 target,
941 abiflags,
942 }))
943 } else {
944 Ok(None)
945 }
946 }
947
948 fn is_cross_compiling_from_to(host: &Triple, target: &Triple) -> bool {
952 let mut compatible = host.architecture == target.architecture
956 && host.vendor == target.vendor
957 && host.operating_system == target.operating_system;
958
959 compatible |= target.operating_system == OperatingSystem::Windows
961 && host.operating_system == OperatingSystem::Windows
962 && matches!(target.architecture, Architecture::X86_32(_))
963 && host.architecture == Architecture::X86_64;
964
965 compatible |= target.operating_system == OperatingSystem::Darwin
967 && host.operating_system == OperatingSystem::Darwin;
968
969 !compatible
970 }
971
972 fn lib_dir_string(&self) -> Option<String> {
977 self.lib_dir
978 .as_ref()
979 .map(|s| s.to_str().unwrap().to_owned())
980 }
981}
982
983struct CrossCompileEnvVars {
985 pyo3_cross: Option<OsString>,
987 pyo3_cross_lib_dir: Option<OsString>,
989 pyo3_cross_python_version: Option<OsString>,
991 pyo3_cross_python_implementation: Option<OsString>,
993}
994
995impl CrossCompileEnvVars {
996 fn from_env() -> Self {
1000 CrossCompileEnvVars {
1001 pyo3_cross: env_var("PYO3_CROSS"),
1002 pyo3_cross_lib_dir: env_var("PYO3_CROSS_LIB_DIR"),
1003 pyo3_cross_python_version: env_var("PYO3_CROSS_PYTHON_VERSION"),
1004 pyo3_cross_python_implementation: env_var("PYO3_CROSS_PYTHON_IMPLEMENTATION"),
1005 }
1006 }
1007
1008 fn any(&self) -> bool {
1010 self.pyo3_cross.is_some()
1011 || self.pyo3_cross_lib_dir.is_some()
1012 || self.pyo3_cross_python_version.is_some()
1013 || self.pyo3_cross_python_implementation.is_some()
1014 }
1015
1016 fn parse_version(&self) -> Result<(Option<PythonVersion>, Option<String>)> {
1019 match self.pyo3_cross_python_version.as_ref() {
1020 Some(os_string) => {
1021 let utf8_str = os_string
1022 .to_str()
1023 .ok_or("PYO3_CROSS_PYTHON_VERSION is not valid a UTF-8 string")?;
1024 let (utf8_str, abiflags) = if let Some(version) = utf8_str.strip_suffix('t') {
1025 (version, Some("t".to_string()))
1026 } else {
1027 (utf8_str, None)
1028 };
1029 let version = utf8_str
1030 .parse()
1031 .context("failed to parse PYO3_CROSS_PYTHON_VERSION")?;
1032 Ok((Some(version), abiflags))
1033 }
1034 None => Ok((None, None)),
1035 }
1036 }
1037
1038 fn parse_implementation(&self) -> Result<Option<PythonImplementation>> {
1041 let implementation = self
1042 .pyo3_cross_python_implementation
1043 .as_ref()
1044 .map(|os_string| {
1045 let utf8_str = os_string
1046 .to_str()
1047 .ok_or("PYO3_CROSS_PYTHON_IMPLEMENTATION is not valid a UTF-8 string")?;
1048 utf8_str
1049 .parse()
1050 .context("failed to parse PYO3_CROSS_PYTHON_IMPLEMENTATION")
1051 })
1052 .transpose()?;
1053
1054 Ok(implementation)
1055 }
1056
1057 fn lib_dir_path(&self) -> Result<Option<PathBuf>> {
1062 let lib_dir = self.pyo3_cross_lib_dir.as_ref().map(PathBuf::from);
1063
1064 if let Some(dir) = lib_dir.as_ref() {
1065 ensure!(
1066 dir.to_str().is_some(),
1067 "PYO3_CROSS_LIB_DIR variable value is not a valid UTF-8 string"
1068 );
1069 }
1070
1071 Ok(lib_dir)
1072 }
1073}
1074
1075pub fn cross_compiling_from_to(
1090 host: &Triple,
1091 target: &Triple,
1092) -> Result<Option<CrossCompileConfig>> {
1093 let env_vars = CrossCompileEnvVars::from_env();
1094 CrossCompileConfig::try_from_env_vars_host_target(env_vars, host, target)
1095}
1096
1097pub fn cross_compiling_from_cargo_env() -> Result<Option<CrossCompileConfig>> {
1103 let env_vars = CrossCompileEnvVars::from_env();
1104 let host = Triple::host();
1105 let target = target_triple_from_env();
1106
1107 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
1108}
1109
1110#[allow(non_camel_case_types)]
1111#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1112pub enum BuildFlag {
1113 Py_DEBUG,
1114 Py_REF_DEBUG,
1115 Py_TRACE_REFS,
1116 Py_GIL_DISABLED,
1117 COUNT_ALLOCS,
1118 Other(String),
1119}
1120
1121impl Display for BuildFlag {
1122 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1123 match self {
1124 BuildFlag::Other(flag) => write!(f, "{}", flag),
1125 _ => write!(f, "{:?}", self),
1126 }
1127 }
1128}
1129
1130impl FromStr for BuildFlag {
1131 type Err = std::convert::Infallible;
1132 fn from_str(s: &str) -> Result<Self, Self::Err> {
1133 match s {
1134 "Py_DEBUG" => Ok(BuildFlag::Py_DEBUG),
1135 "Py_REF_DEBUG" => Ok(BuildFlag::Py_REF_DEBUG),
1136 "Py_TRACE_REFS" => Ok(BuildFlag::Py_TRACE_REFS),
1137 "Py_GIL_DISABLED" => Ok(BuildFlag::Py_GIL_DISABLED),
1138 "COUNT_ALLOCS" => Ok(BuildFlag::COUNT_ALLOCS),
1139 other => Ok(BuildFlag::Other(other.to_owned())),
1140 }
1141 }
1142}
1143
1144#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
1158#[derive(Clone, Default)]
1159pub struct BuildFlags(pub HashSet<BuildFlag>);
1160
1161impl BuildFlags {
1162 const ALL: [BuildFlag; 5] = [
1163 BuildFlag::Py_DEBUG,
1164 BuildFlag::Py_REF_DEBUG,
1165 BuildFlag::Py_TRACE_REFS,
1166 BuildFlag::Py_GIL_DISABLED,
1167 BuildFlag::COUNT_ALLOCS,
1168 ];
1169
1170 pub fn new() -> Self {
1171 BuildFlags(HashSet::new())
1172 }
1173
1174 fn from_sysconfigdata(config_map: &Sysconfigdata) -> Self {
1175 Self(
1176 BuildFlags::ALL
1177 .iter()
1178 .filter(|flag| config_map.get_value(flag.to_string()) == Some("1"))
1179 .cloned()
1180 .collect(),
1181 )
1182 .fixup()
1183 }
1184
1185 fn from_interpreter(interpreter: impl AsRef<Path>) -> Result<Self> {
1189 if cfg!(windows) {
1193 let script = String::from("import sys;print(sys.version_info < (3, 13))");
1194 let stdout = run_python_script(interpreter.as_ref(), &script)?;
1195 if stdout.trim_end() == "True" {
1196 return Ok(Self::new());
1197 }
1198 }
1199
1200 let mut script = String::from("import sysconfig\n");
1201 script.push_str("config = sysconfig.get_config_vars()\n");
1202
1203 for k in &BuildFlags::ALL {
1204 use std::fmt::Write;
1205 writeln!(&mut script, "print(config.get('{}', '0'))", k).unwrap();
1206 }
1207
1208 let stdout = run_python_script(interpreter.as_ref(), &script)?;
1209 let split_stdout: Vec<&str> = stdout.trim_end().lines().collect();
1210 ensure!(
1211 split_stdout.len() == BuildFlags::ALL.len(),
1212 "Python stdout len didn't return expected number of lines: {}",
1213 split_stdout.len()
1214 );
1215 let flags = BuildFlags::ALL
1216 .iter()
1217 .zip(split_stdout)
1218 .filter(|(_, flag_value)| *flag_value == "1")
1219 .map(|(flag, _)| flag.clone())
1220 .collect();
1221
1222 Ok(Self(flags).fixup())
1223 }
1224
1225 fn fixup(mut self) -> Self {
1226 if self.0.contains(&BuildFlag::Py_DEBUG) {
1227 self.0.insert(BuildFlag::Py_REF_DEBUG);
1228 }
1229
1230 self
1231 }
1232}
1233
1234impl Display for BuildFlags {
1235 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1236 let mut first = true;
1237 for flag in &self.0 {
1238 if first {
1239 first = false;
1240 } else {
1241 write!(f, ",")?;
1242 }
1243 write!(f, "{}", flag)?;
1244 }
1245 Ok(())
1246 }
1247}
1248
1249impl FromStr for BuildFlags {
1250 type Err = std::convert::Infallible;
1251
1252 fn from_str(value: &str) -> Result<Self, Self::Err> {
1253 let mut flags = HashSet::new();
1254 for flag in value.split_terminator(',') {
1255 flags.insert(flag.parse().unwrap());
1256 }
1257 Ok(BuildFlags(flags))
1258 }
1259}
1260
1261fn parse_script_output(output: &str) -> HashMap<String, String> {
1262 output
1263 .lines()
1264 .filter_map(|line| {
1265 let mut i = line.splitn(2, ' ');
1266 Some((i.next()?.into(), i.next()?.into()))
1267 })
1268 .collect()
1269}
1270
1271pub struct Sysconfigdata(HashMap<String, String>);
1275
1276impl Sysconfigdata {
1277 pub fn get_value<S: AsRef<str>>(&self, k: S) -> Option<&str> {
1278 self.0.get(k.as_ref()).map(String::as_str)
1279 }
1280
1281 #[allow(dead_code)]
1282 fn new() -> Self {
1283 Sysconfigdata(HashMap::new())
1284 }
1285
1286 #[allow(dead_code)]
1287 fn insert<S: Into<String>>(&mut self, k: S, v: S) {
1288 self.0.insert(k.into(), v.into());
1289 }
1290}
1291
1292pub fn parse_sysconfigdata(sysconfigdata_path: impl AsRef<Path>) -> Result<Sysconfigdata> {
1300 let sysconfigdata_path = sysconfigdata_path.as_ref();
1301 let mut script = fs::read_to_string(sysconfigdata_path).with_context(|| {
1302 format!(
1303 "failed to read config from {}",
1304 sysconfigdata_path.display()
1305 )
1306 })?;
1307 script += r#"
1308for key, val in build_time_vars.items():
1309 print(key, val)
1310"#;
1311
1312 let output = run_python_script(&find_interpreter()?, &script)?;
1313
1314 Ok(Sysconfigdata(parse_script_output(&output)))
1315}
1316
1317fn starts_with(entry: &DirEntry, pat: &str) -> bool {
1318 let name = entry.file_name();
1319 name.to_string_lossy().starts_with(pat)
1320}
1321fn ends_with(entry: &DirEntry, pat: &str) -> bool {
1322 let name = entry.file_name();
1323 name.to_string_lossy().ends_with(pat)
1324}
1325
1326fn find_sysconfigdata(cross: &CrossCompileConfig) -> Result<Option<PathBuf>> {
1331 let mut sysconfig_paths = find_all_sysconfigdata(cross)?;
1332 if sysconfig_paths.is_empty() {
1333 if let Some(lib_dir) = cross.lib_dir.as_ref() {
1334 bail!("Could not find _sysconfigdata*.py in {}", lib_dir.display());
1335 } else {
1336 return Ok(None);
1338 }
1339 } else if sysconfig_paths.len() > 1 {
1340 let mut error_msg = String::from(
1341 "Detected multiple possible Python versions. Please set either the \
1342 PYO3_CROSS_PYTHON_VERSION variable to the wanted version or the \
1343 _PYTHON_SYSCONFIGDATA_NAME variable to the wanted sysconfigdata file name.\n\n\
1344 sysconfigdata files found:",
1345 );
1346 for path in sysconfig_paths {
1347 use std::fmt::Write;
1348 write!(&mut error_msg, "\n\t{}", path.display()).unwrap();
1349 }
1350 bail!("{}\n", error_msg);
1351 }
1352
1353 Ok(Some(sysconfig_paths.remove(0)))
1354}
1355
1356pub fn find_all_sysconfigdata(cross: &CrossCompileConfig) -> Result<Vec<PathBuf>> {
1395 let sysconfig_paths = if let Some(lib_dir) = cross.lib_dir.as_ref() {
1396 search_lib_dir(lib_dir, cross).with_context(|| {
1397 format!(
1398 "failed to search the lib dir at 'PYO3_CROSS_LIB_DIR={}'",
1399 lib_dir.display()
1400 )
1401 })?
1402 } else {
1403 return Ok(Vec::new());
1404 };
1405
1406 let sysconfig_name = env_var("_PYTHON_SYSCONFIGDATA_NAME");
1407 let mut sysconfig_paths = sysconfig_paths
1408 .iter()
1409 .filter_map(|p| {
1410 let canonical = fs::canonicalize(p).ok();
1411 match &sysconfig_name {
1412 Some(_) => canonical.filter(|p| p.file_stem() == sysconfig_name.as_deref()),
1413 None => canonical,
1414 }
1415 })
1416 .collect::<Vec<PathBuf>>();
1417
1418 sysconfig_paths.sort();
1419 sysconfig_paths.dedup();
1420
1421 Ok(sysconfig_paths)
1422}
1423
1424fn is_pypy_lib_dir(path: &str, v: &Option<PythonVersion>) -> bool {
1425 let pypy_version_pat = if let Some(v) = v {
1426 format!("pypy{}", v)
1427 } else {
1428 "pypy3.".into()
1429 };
1430 path == "lib_pypy" || path.starts_with(&pypy_version_pat)
1431}
1432
1433fn is_graalpy_lib_dir(path: &str, v: &Option<PythonVersion>) -> bool {
1434 let graalpy_version_pat = if let Some(v) = v {
1435 format!("graalpy{}", v)
1436 } else {
1437 "graalpy2".into()
1438 };
1439 path == "lib_graalpython" || path.starts_with(&graalpy_version_pat)
1440}
1441
1442fn is_cpython_lib_dir(path: &str, v: &Option<PythonVersion>) -> bool {
1443 let cpython_version_pat = if let Some(v) = v {
1444 format!("python{}", v)
1445 } else {
1446 "python3.".into()
1447 };
1448 path.starts_with(&cpython_version_pat)
1449}
1450
1451fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Result<Vec<PathBuf>> {
1453 let mut sysconfig_paths = vec![];
1454 for f in fs::read_dir(path.as_ref()).with_context(|| {
1455 format!(
1456 "failed to list the entries in '{}'",
1457 path.as_ref().display()
1458 )
1459 })? {
1460 sysconfig_paths.extend(match &f {
1461 Ok(f) if starts_with(f, "_sysconfigdata_") && ends_with(f, "py") => vec![f.path()],
1463 Ok(f) if f.metadata().map_or(false, |metadata| metadata.is_dir()) => {
1464 let file_name = f.file_name();
1465 let file_name = file_name.to_string_lossy();
1466 if file_name == "build" || file_name == "lib" {
1467 search_lib_dir(f.path(), cross)?
1468 } else if file_name.starts_with("lib.") {
1469 if !file_name.contains(&cross.target.operating_system.to_string()) {
1471 continue;
1472 }
1473 if !file_name.contains(&cross.target.architecture.to_string()) {
1475 continue;
1476 }
1477 search_lib_dir(f.path(), cross)?
1478 } else if is_cpython_lib_dir(&file_name, &cross.version)
1479 || is_pypy_lib_dir(&file_name, &cross.version)
1480 || is_graalpy_lib_dir(&file_name, &cross.version)
1481 {
1482 search_lib_dir(f.path(), cross)?
1483 } else {
1484 continue;
1485 }
1486 }
1487 _ => continue,
1488 });
1489 }
1490 if sysconfig_paths.len() > 1 {
1498 let temp = sysconfig_paths
1499 .iter()
1500 .filter(|p| {
1501 p.to_string_lossy()
1502 .contains(&cross.target.architecture.to_string())
1503 })
1504 .cloned()
1505 .collect::<Vec<PathBuf>>();
1506 if !temp.is_empty() {
1507 sysconfig_paths = temp;
1508 }
1509 }
1510
1511 Ok(sysconfig_paths)
1512}
1513
1514fn cross_compile_from_sysconfigdata(
1522 cross_compile_config: &CrossCompileConfig,
1523) -> Result<Option<InterpreterConfig>> {
1524 if let Some(path) = find_sysconfigdata(cross_compile_config)? {
1525 let data = parse_sysconfigdata(path)?;
1526 let mut config = InterpreterConfig::from_sysconfigdata(&data)?;
1527 if let Some(cross_lib_dir) = cross_compile_config.lib_dir_string() {
1528 config.lib_dir = Some(cross_lib_dir)
1529 }
1530
1531 Ok(Some(config))
1532 } else {
1533 Ok(None)
1534 }
1535}
1536
1537#[allow(unused_mut)]
1544fn default_cross_compile(cross_compile_config: &CrossCompileConfig) -> Result<InterpreterConfig> {
1545 let version = cross_compile_config
1546 .version
1547 .or_else(get_abi3_version)
1548 .ok_or_else(||
1549 format!(
1550 "PYO3_CROSS_PYTHON_VERSION or an abi3-py3* feature must be specified \
1551 when cross-compiling and PYO3_CROSS_LIB_DIR is not set.\n\
1552 = help: see the PyO3 user guide for more information: https://pyo3.rs/v{}/building-and-distribution.html#cross-compiling",
1553 env!("CARGO_PKG_VERSION")
1554 )
1555 )?;
1556
1557 let abi3 = is_abi3();
1558 let implementation = cross_compile_config
1559 .implementation
1560 .unwrap_or(PythonImplementation::CPython);
1561 let gil_disabled = cross_compile_config.abiflags.as_deref() == Some("t");
1562
1563 let lib_name = default_lib_name_for_target(
1564 version,
1565 implementation,
1566 abi3,
1567 gil_disabled,
1568 &cross_compile_config.target,
1569 );
1570
1571 let mut lib_dir = cross_compile_config.lib_dir_string();
1572
1573 #[cfg(feature = "python3-dll-a")]
1575 if lib_dir.is_none() {
1576 let py_version = if implementation == PythonImplementation::CPython && abi3 && !gil_disabled
1577 {
1578 None
1579 } else {
1580 Some(version)
1581 };
1582 lib_dir = self::import_lib::generate_import_lib(
1583 &cross_compile_config.target,
1584 cross_compile_config
1585 .implementation
1586 .unwrap_or(PythonImplementation::CPython),
1587 py_version,
1588 None,
1589 )?;
1590 }
1591
1592 Ok(InterpreterConfig {
1593 implementation,
1594 version,
1595 shared: true,
1596 abi3,
1597 lib_name,
1598 lib_dir,
1599 executable: None,
1600 pointer_width: None,
1601 build_flags: BuildFlags::default(),
1602 suppress_build_script_link_lines: false,
1603 extra_build_script_lines: vec![],
1604 python_framework_prefix: None,
1605 })
1606}
1607
1608fn default_abi3_config(host: &Triple, version: PythonVersion) -> Result<InterpreterConfig> {
1618 let implementation = PythonImplementation::CPython;
1620 let abi3 = true;
1621
1622 let lib_name = if host.operating_system == OperatingSystem::Windows {
1623 Some(default_lib_name_windows(
1624 version,
1625 implementation,
1626 abi3,
1627 false,
1628 false,
1629 false,
1630 )?)
1631 } else {
1632 None
1633 };
1634
1635 Ok(InterpreterConfig {
1636 implementation,
1637 version,
1638 shared: true,
1639 abi3,
1640 lib_name,
1641 lib_dir: None,
1642 executable: None,
1643 pointer_width: None,
1644 build_flags: BuildFlags::default(),
1645 suppress_build_script_link_lines: false,
1646 extra_build_script_lines: vec![],
1647 python_framework_prefix: None,
1648 })
1649}
1650
1651fn load_cross_compile_config(
1659 cross_compile_config: CrossCompileConfig,
1660) -> Result<InterpreterConfig> {
1661 let windows = cross_compile_config.target.operating_system == OperatingSystem::Windows;
1662
1663 let config = if windows || !have_python_interpreter() {
1664 default_cross_compile(&cross_compile_config)?
1668 } else if let Some(config) = cross_compile_from_sysconfigdata(&cross_compile_config)? {
1669 config
1671 } else {
1672 default_cross_compile(&cross_compile_config)?
1674 };
1675
1676 if config.lib_name.is_some() && config.lib_dir.is_none() {
1677 warn!(
1678 "The output binary will link to libpython, \
1679 but PYO3_CROSS_LIB_DIR environment variable is not set. \
1680 Ensure that the target Python library directory is \
1681 in the rustc native library search path."
1682 );
1683 }
1684
1685 Ok(config)
1686}
1687
1688const WINDOWS_ABI3_LIB_NAME: &str = "python3";
1690const WINDOWS_ABI3_DEBUG_LIB_NAME: &str = "python3_d";
1691
1692fn default_lib_name_for_target(
1693 version: PythonVersion,
1694 implementation: PythonImplementation,
1695 abi3: bool,
1696 gil_disabled: bool,
1697 target: &Triple,
1698) -> Option<String> {
1699 if target.operating_system == OperatingSystem::Windows {
1700 Some(
1701 default_lib_name_windows(version, implementation, abi3, false, false, gil_disabled)
1702 .unwrap(),
1703 )
1704 } else if is_linking_libpython_for_target(target) {
1705 Some(default_lib_name_unix(version, implementation, None, gil_disabled).unwrap())
1706 } else {
1707 None
1708 }
1709}
1710
1711fn default_lib_name_windows(
1712 version: PythonVersion,
1713 implementation: PythonImplementation,
1714 abi3: bool,
1715 mingw: bool,
1716 debug: bool,
1717 gil_disabled: bool,
1718) -> Result<String> {
1719 if debug && version < PythonVersion::PY310 {
1720 Ok(format!("python{}{}_d", version.major, version.minor))
1723 } else if abi3 && !(gil_disabled || implementation.is_pypy() || implementation.is_graalpy()) {
1724 if debug {
1725 Ok(WINDOWS_ABI3_DEBUG_LIB_NAME.to_owned())
1726 } else {
1727 Ok(WINDOWS_ABI3_LIB_NAME.to_owned())
1728 }
1729 } else if mingw {
1730 ensure!(
1731 !gil_disabled,
1732 "MinGW free-threaded builds are not currently tested or supported"
1733 );
1734 Ok(format!("python{}.{}", version.major, version.minor))
1736 } else if gil_disabled {
1737 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);
1738 if debug {
1739 Ok(format!("python{}{}t_d", version.major, version.minor))
1740 } else {
1741 Ok(format!("python{}{}t", version.major, version.minor))
1742 }
1743 } else if debug {
1744 Ok(format!("python{}{}_d", version.major, version.minor))
1745 } else {
1746 Ok(format!("python{}{}", version.major, version.minor))
1747 }
1748}
1749
1750fn default_lib_name_unix(
1751 version: PythonVersion,
1752 implementation: PythonImplementation,
1753 ld_version: Option<&str>,
1754 gil_disabled: bool,
1755) -> Result<String> {
1756 match implementation {
1757 PythonImplementation::CPython => match ld_version {
1758 Some(ld_version) => Ok(format!("python{}", ld_version)),
1759 None => {
1760 if version > PythonVersion::PY37 {
1761 if gil_disabled {
1763 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);
1764 Ok(format!("python{}.{}t", version.major, version.minor))
1765 } else {
1766 Ok(format!("python{}.{}", version.major, version.minor))
1767 }
1768 } else {
1769 Ok(format!("python{}.{}m", version.major, version.minor))
1771 }
1772 }
1773 },
1774 PythonImplementation::PyPy => match ld_version {
1775 Some(ld_version) => Ok(format!("pypy{}-c", ld_version)),
1776 None => Ok(format!("pypy{}.{}-c", version.major, version.minor)),
1777 },
1778
1779 PythonImplementation::GraalPy => Ok("python-native".to_string()),
1780 }
1781}
1782
1783fn run_python_script(interpreter: &Path, script: &str) -> Result<String> {
1785 run_python_script_with_envs(interpreter, script, std::iter::empty::<(&str, &str)>())
1786}
1787
1788fn run_python_script_with_envs<I, K, V>(interpreter: &Path, script: &str, envs: I) -> Result<String>
1791where
1792 I: IntoIterator<Item = (K, V)>,
1793 K: AsRef<OsStr>,
1794 V: AsRef<OsStr>,
1795{
1796 let out = Command::new(interpreter)
1797 .env("PYTHONIOENCODING", "utf-8")
1798 .envs(envs)
1799 .stdin(Stdio::piped())
1800 .stdout(Stdio::piped())
1801 .stderr(Stdio::inherit())
1802 .spawn()
1803 .and_then(|mut child| {
1804 child
1805 .stdin
1806 .as_mut()
1807 .expect("piped stdin")
1808 .write_all(script.as_bytes())?;
1809 child.wait_with_output()
1810 });
1811
1812 match out {
1813 Err(err) => bail!(
1814 "failed to run the Python interpreter at {}: {}",
1815 interpreter.display(),
1816 err
1817 ),
1818 Ok(ok) if !ok.status.success() => bail!("Python script failed"),
1819 Ok(ok) => Ok(String::from_utf8(ok.stdout)
1820 .context("failed to parse Python script output as utf-8")?),
1821 }
1822}
1823
1824fn venv_interpreter(virtual_env: &OsStr, windows: bool) -> PathBuf {
1825 if windows {
1826 Path::new(virtual_env).join("Scripts").join("python.exe")
1827 } else {
1828 Path::new(virtual_env).join("bin").join("python")
1829 }
1830}
1831
1832fn conda_env_interpreter(conda_prefix: &OsStr, windows: bool) -> PathBuf {
1833 if windows {
1834 Path::new(conda_prefix).join("python.exe")
1835 } else {
1836 Path::new(conda_prefix).join("bin").join("python")
1837 }
1838}
1839
1840fn get_env_interpreter() -> Option<PathBuf> {
1841 match (env_var("VIRTUAL_ENV"), env_var("CONDA_PREFIX")) {
1842 (Some(dir), None) => Some(venv_interpreter(&dir, cfg!(windows))),
1845 (None, Some(dir)) => Some(conda_env_interpreter(&dir, cfg!(windows))),
1846 (Some(_), Some(_)) => {
1847 warn!(
1848 "Both VIRTUAL_ENV and CONDA_PREFIX are set. PyO3 will ignore both of these for \
1849 locating the Python interpreter until you unset one of them."
1850 );
1851 None
1852 }
1853 (None, None) => None,
1854 }
1855}
1856
1857pub fn find_interpreter() -> Result<PathBuf> {
1865 println!("cargo:rerun-if-env-changed=PYO3_ENVIRONMENT_SIGNATURE");
1868
1869 if let Some(exe) = env_var("PYO3_PYTHON") {
1870 Ok(exe.into())
1871 } else if let Some(env_interpreter) = get_env_interpreter() {
1872 Ok(env_interpreter)
1873 } else {
1874 println!("cargo:rerun-if-env-changed=PATH");
1875 ["python", "python3"]
1876 .iter()
1877 .find(|bin| {
1878 if let Ok(out) = Command::new(bin).arg("--version").output() {
1879 out.stdout.starts_with(b"Python 3")
1881 || out.stderr.starts_with(b"Python 3")
1882 || out.stdout.starts_with(b"GraalPy 3")
1883 } else {
1884 false
1885 }
1886 })
1887 .map(PathBuf::from)
1888 .ok_or_else(|| "no Python 3.x interpreter found".into())
1889 }
1890}
1891
1892fn get_host_interpreter(abi3_version: Option<PythonVersion>) -> Result<InterpreterConfig> {
1896 let interpreter_path = find_interpreter()?;
1897
1898 let mut interpreter_config = InterpreterConfig::from_interpreter(interpreter_path)?;
1899 interpreter_config.fixup_for_abi3_version(abi3_version)?;
1900
1901 Ok(interpreter_config)
1902}
1903
1904pub fn make_cross_compile_config() -> Result<Option<InterpreterConfig>> {
1909 let interpreter_config = if let Some(cross_config) = cross_compiling_from_cargo_env()? {
1910 let mut interpreter_config = load_cross_compile_config(cross_config)?;
1911 interpreter_config.fixup_for_abi3_version(get_abi3_version())?;
1912 Some(interpreter_config)
1913 } else {
1914 None
1915 };
1916
1917 Ok(interpreter_config)
1918}
1919
1920#[allow(dead_code, unused_mut)]
1923pub fn make_interpreter_config() -> Result<InterpreterConfig> {
1924 let host = Triple::host();
1925 let abi3_version = get_abi3_version();
1926
1927 let need_interpreter = abi3_version.is_none() || require_libdir_for_target(&host);
1930
1931 if have_python_interpreter() {
1932 match get_host_interpreter(abi3_version) {
1933 Ok(interpreter_config) => return Ok(interpreter_config),
1934 Err(e) if need_interpreter => return Err(e),
1936 _ => {
1937 warn!("Compiling without a working Python interpreter.");
1940 }
1941 }
1942 } else {
1943 ensure!(
1944 abi3_version.is_some(),
1945 "An abi3-py3* feature must be specified when compiling without a Python interpreter."
1946 );
1947 };
1948
1949 let mut interpreter_config = default_abi3_config(&host, abi3_version.unwrap())?;
1950
1951 #[cfg(feature = "python3-dll-a")]
1953 {
1954 let gil_disabled = interpreter_config
1955 .build_flags
1956 .0
1957 .contains(&BuildFlag::Py_GIL_DISABLED);
1958 let py_version = if interpreter_config.implementation == PythonImplementation::CPython
1959 && interpreter_config.abi3
1960 && !gil_disabled
1961 {
1962 None
1963 } else {
1964 Some(interpreter_config.version)
1965 };
1966 interpreter_config.lib_dir = self::import_lib::generate_import_lib(
1967 &host,
1968 interpreter_config.implementation,
1969 py_version,
1970 None,
1971 )?;
1972 }
1973
1974 Ok(interpreter_config)
1975}
1976
1977fn escape(bytes: &[u8]) -> String {
1978 let mut escaped = String::with_capacity(2 * bytes.len());
1979
1980 for byte in bytes {
1981 const LUT: &[u8; 16] = b"0123456789abcdef";
1982
1983 escaped.push(LUT[(byte >> 4) as usize] as char);
1984 escaped.push(LUT[(byte & 0x0F) as usize] as char);
1985 }
1986
1987 escaped
1988}
1989
1990fn unescape(escaped: &str) -> Vec<u8> {
1991 assert!(escaped.len() % 2 == 0, "invalid hex encoding");
1992
1993 let mut bytes = Vec::with_capacity(escaped.len() / 2);
1994
1995 for chunk in escaped.as_bytes().chunks_exact(2) {
1996 fn unhex(hex: u8) -> u8 {
1997 match hex {
1998 b'a'..=b'f' => hex - b'a' + 10,
1999 b'0'..=b'9' => hex - b'0',
2000 _ => panic!("invalid hex encoding"),
2001 }
2002 }
2003
2004 bytes.push((unhex(chunk[0]) << 4) | unhex(chunk[1]));
2005 }
2006
2007 bytes
2008}
2009
2010#[cfg(test)]
2011mod tests {
2012 use target_lexicon::triple;
2013
2014 use super::*;
2015
2016 #[test]
2017 fn test_config_file_roundtrip() {
2018 let config = InterpreterConfig {
2019 abi3: true,
2020 build_flags: BuildFlags::default(),
2021 pointer_width: Some(32),
2022 executable: Some("executable".into()),
2023 implementation: PythonImplementation::CPython,
2024 lib_name: Some("lib_name".into()),
2025 lib_dir: Some("lib_dir".into()),
2026 shared: true,
2027 version: MINIMUM_SUPPORTED_VERSION,
2028 suppress_build_script_link_lines: true,
2029 extra_build_script_lines: vec!["cargo:test1".to_string(), "cargo:test2".to_string()],
2030 python_framework_prefix: None,
2031 };
2032 let mut buf: Vec<u8> = Vec::new();
2033 config.to_writer(&mut buf).unwrap();
2034
2035 assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap());
2036
2037 let config = InterpreterConfig {
2040 abi3: false,
2041 build_flags: {
2042 let mut flags = HashSet::new();
2043 flags.insert(BuildFlag::Py_DEBUG);
2044 flags.insert(BuildFlag::Other(String::from("Py_SOME_FLAG")));
2045 BuildFlags(flags)
2046 },
2047 pointer_width: None,
2048 executable: None,
2049 implementation: PythonImplementation::PyPy,
2050 lib_dir: None,
2051 lib_name: None,
2052 shared: true,
2053 version: PythonVersion {
2054 major: 3,
2055 minor: 10,
2056 },
2057 suppress_build_script_link_lines: false,
2058 extra_build_script_lines: vec![],
2059 python_framework_prefix: None,
2060 };
2061 let mut buf: Vec<u8> = Vec::new();
2062 config.to_writer(&mut buf).unwrap();
2063
2064 assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap());
2065 }
2066
2067 #[test]
2068 fn test_config_file_roundtrip_with_escaping() {
2069 let config = InterpreterConfig {
2070 abi3: true,
2071 build_flags: BuildFlags::default(),
2072 pointer_width: Some(32),
2073 executable: Some("executable".into()),
2074 implementation: PythonImplementation::CPython,
2075 lib_name: Some("lib_name".into()),
2076 lib_dir: Some("lib_dir\\n".into()),
2077 shared: true,
2078 version: MINIMUM_SUPPORTED_VERSION,
2079 suppress_build_script_link_lines: true,
2080 extra_build_script_lines: vec!["cargo:test1".to_string(), "cargo:test2".to_string()],
2081 python_framework_prefix: None,
2082 };
2083 let mut buf: Vec<u8> = Vec::new();
2084 config.to_writer(&mut buf).unwrap();
2085
2086 let buf = unescape(&escape(&buf));
2087
2088 assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap());
2089 }
2090
2091 #[test]
2092 fn test_config_file_defaults() {
2093 assert_eq!(
2095 InterpreterConfig::from_reader("version=3.7".as_bytes()).unwrap(),
2096 InterpreterConfig {
2097 version: PythonVersion { major: 3, minor: 7 },
2098 implementation: PythonImplementation::CPython,
2099 shared: true,
2100 abi3: false,
2101 lib_name: None,
2102 lib_dir: None,
2103 executable: None,
2104 pointer_width: None,
2105 build_flags: BuildFlags::default(),
2106 suppress_build_script_link_lines: false,
2107 extra_build_script_lines: vec![],
2108 python_framework_prefix: None,
2109 }
2110 )
2111 }
2112
2113 #[test]
2114 fn test_config_file_unknown_keys() {
2115 assert_eq!(
2117 InterpreterConfig::from_reader("version=3.7\next_suffix=.python37.so".as_bytes())
2118 .unwrap(),
2119 InterpreterConfig {
2120 version: PythonVersion { major: 3, minor: 7 },
2121 implementation: PythonImplementation::CPython,
2122 shared: true,
2123 abi3: false,
2124 lib_name: None,
2125 lib_dir: None,
2126 executable: None,
2127 pointer_width: None,
2128 build_flags: BuildFlags::default(),
2129 suppress_build_script_link_lines: false,
2130 extra_build_script_lines: vec![],
2131 python_framework_prefix: None,
2132 }
2133 )
2134 }
2135
2136 #[test]
2137 fn build_flags_default() {
2138 assert_eq!(BuildFlags::default(), BuildFlags::new());
2139 }
2140
2141 #[test]
2142 fn build_flags_from_sysconfigdata() {
2143 let mut sysconfigdata = Sysconfigdata::new();
2144
2145 assert_eq!(
2146 BuildFlags::from_sysconfigdata(&sysconfigdata).0,
2147 HashSet::new()
2148 );
2149
2150 for flag in &BuildFlags::ALL {
2151 sysconfigdata.insert(flag.to_string(), "0".into());
2152 }
2153
2154 assert_eq!(
2155 BuildFlags::from_sysconfigdata(&sysconfigdata).0,
2156 HashSet::new()
2157 );
2158
2159 let mut expected_flags = HashSet::new();
2160 for flag in &BuildFlags::ALL {
2161 sysconfigdata.insert(flag.to_string(), "1".into());
2162 expected_flags.insert(flag.clone());
2163 }
2164
2165 assert_eq!(
2166 BuildFlags::from_sysconfigdata(&sysconfigdata).0,
2167 expected_flags
2168 );
2169 }
2170
2171 #[test]
2172 fn build_flags_fixup() {
2173 let mut build_flags = BuildFlags::new();
2174
2175 build_flags = build_flags.fixup();
2176 assert!(build_flags.0.is_empty());
2177
2178 build_flags.0.insert(BuildFlag::Py_DEBUG);
2179
2180 build_flags = build_flags.fixup();
2181
2182 assert!(build_flags.0.contains(&BuildFlag::Py_REF_DEBUG));
2184 }
2185
2186 #[test]
2187 fn parse_script_output() {
2188 let output = "foo bar\nbar foobar\n\n";
2189 let map = super::parse_script_output(output);
2190 assert_eq!(map.len(), 2);
2191 assert_eq!(map["foo"], "bar");
2192 assert_eq!(map["bar"], "foobar");
2193 }
2194
2195 #[test]
2196 fn config_from_interpreter() {
2197 assert!(make_interpreter_config().is_ok())
2201 }
2202
2203 #[test]
2204 fn config_from_empty_sysconfigdata() {
2205 let sysconfigdata = Sysconfigdata::new();
2206 assert!(InterpreterConfig::from_sysconfigdata(&sysconfigdata).is_err());
2207 }
2208
2209 #[test]
2210 fn config_from_sysconfigdata() {
2211 let mut sysconfigdata = Sysconfigdata::new();
2212 sysconfigdata.insert("SOABI", "cpython-37m-x86_64-linux-gnu");
2215 sysconfigdata.insert("VERSION", "3.7");
2216 sysconfigdata.insert("Py_ENABLE_SHARED", "1");
2217 sysconfigdata.insert("LIBDIR", "/usr/lib");
2218 sysconfigdata.insert("LDVERSION", "3.7m");
2219 sysconfigdata.insert("SIZEOF_VOID_P", "8");
2220 assert_eq!(
2221 InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(),
2222 InterpreterConfig {
2223 abi3: false,
2224 build_flags: BuildFlags::from_sysconfigdata(&sysconfigdata),
2225 pointer_width: Some(64),
2226 executable: None,
2227 implementation: PythonImplementation::CPython,
2228 lib_dir: Some("/usr/lib".into()),
2229 lib_name: Some("python3.7m".into()),
2230 shared: true,
2231 version: PythonVersion::PY37,
2232 suppress_build_script_link_lines: false,
2233 extra_build_script_lines: vec![],
2234 python_framework_prefix: None,
2235 }
2236 );
2237 }
2238
2239 #[test]
2240 fn config_from_sysconfigdata_framework() {
2241 let mut sysconfigdata = Sysconfigdata::new();
2242 sysconfigdata.insert("SOABI", "cpython-37m-x86_64-linux-gnu");
2243 sysconfigdata.insert("VERSION", "3.7");
2244 sysconfigdata.insert("Py_ENABLE_SHARED", "0");
2246 sysconfigdata.insert("PYTHONFRAMEWORK", "Python");
2247 sysconfigdata.insert("LIBDIR", "/usr/lib");
2248 sysconfigdata.insert("LDVERSION", "3.7m");
2249 sysconfigdata.insert("SIZEOF_VOID_P", "8");
2250 assert_eq!(
2251 InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(),
2252 InterpreterConfig {
2253 abi3: false,
2254 build_flags: BuildFlags::from_sysconfigdata(&sysconfigdata),
2255 pointer_width: Some(64),
2256 executable: None,
2257 implementation: PythonImplementation::CPython,
2258 lib_dir: Some("/usr/lib".into()),
2259 lib_name: Some("python3.7m".into()),
2260 shared: true,
2261 version: PythonVersion::PY37,
2262 suppress_build_script_link_lines: false,
2263 extra_build_script_lines: vec![],
2264 python_framework_prefix: None,
2265 }
2266 );
2267
2268 sysconfigdata = Sysconfigdata::new();
2269 sysconfigdata.insert("SOABI", "cpython-37m-x86_64-linux-gnu");
2270 sysconfigdata.insert("VERSION", "3.7");
2271 sysconfigdata.insert("Py_ENABLE_SHARED", "0");
2273 sysconfigdata.insert("PYTHONFRAMEWORK", "");
2274 sysconfigdata.insert("LIBDIR", "/usr/lib");
2275 sysconfigdata.insert("LDVERSION", "3.7m");
2276 sysconfigdata.insert("SIZEOF_VOID_P", "8");
2277 assert_eq!(
2278 InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(),
2279 InterpreterConfig {
2280 abi3: false,
2281 build_flags: BuildFlags::from_sysconfigdata(&sysconfigdata),
2282 pointer_width: Some(64),
2283 executable: None,
2284 implementation: PythonImplementation::CPython,
2285 lib_dir: Some("/usr/lib".into()),
2286 lib_name: Some("python3.7m".into()),
2287 shared: false,
2288 version: PythonVersion::PY37,
2289 suppress_build_script_link_lines: false,
2290 extra_build_script_lines: vec![],
2291 python_framework_prefix: None,
2292 }
2293 );
2294 }
2295
2296 #[test]
2297 fn windows_hardcoded_abi3_compile() {
2298 let host = triple!("x86_64-pc-windows-msvc");
2299 let min_version = "3.7".parse().unwrap();
2300
2301 assert_eq!(
2302 default_abi3_config(&host, min_version).unwrap(),
2303 InterpreterConfig {
2304 implementation: PythonImplementation::CPython,
2305 version: PythonVersion { major: 3, minor: 7 },
2306 shared: true,
2307 abi3: true,
2308 lib_name: Some("python3".into()),
2309 lib_dir: None,
2310 executable: None,
2311 pointer_width: None,
2312 build_flags: BuildFlags::default(),
2313 suppress_build_script_link_lines: false,
2314 extra_build_script_lines: vec![],
2315 python_framework_prefix: None,
2316 }
2317 );
2318 }
2319
2320 #[test]
2321 fn unix_hardcoded_abi3_compile() {
2322 let host = triple!("x86_64-unknown-linux-gnu");
2323 let min_version = "3.9".parse().unwrap();
2324
2325 assert_eq!(
2326 default_abi3_config(&host, min_version).unwrap(),
2327 InterpreterConfig {
2328 implementation: PythonImplementation::CPython,
2329 version: PythonVersion { major: 3, minor: 9 },
2330 shared: true,
2331 abi3: true,
2332 lib_name: None,
2333 lib_dir: None,
2334 executable: None,
2335 pointer_width: None,
2336 build_flags: BuildFlags::default(),
2337 suppress_build_script_link_lines: false,
2338 extra_build_script_lines: vec![],
2339 python_framework_prefix: None,
2340 }
2341 );
2342 }
2343
2344 #[test]
2345 fn windows_hardcoded_cross_compile() {
2346 let env_vars = CrossCompileEnvVars {
2347 pyo3_cross: None,
2348 pyo3_cross_lib_dir: Some("C:\\some\\path".into()),
2349 pyo3_cross_python_implementation: None,
2350 pyo3_cross_python_version: Some("3.7".into()),
2351 };
2352
2353 let host = triple!("x86_64-unknown-linux-gnu");
2354 let target = triple!("i686-pc-windows-msvc");
2355 let cross_config =
2356 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
2357 .unwrap()
2358 .unwrap();
2359
2360 assert_eq!(
2361 default_cross_compile(&cross_config).unwrap(),
2362 InterpreterConfig {
2363 implementation: PythonImplementation::CPython,
2364 version: PythonVersion { major: 3, minor: 7 },
2365 shared: true,
2366 abi3: false,
2367 lib_name: Some("python37".into()),
2368 lib_dir: Some("C:\\some\\path".into()),
2369 executable: None,
2370 pointer_width: None,
2371 build_flags: BuildFlags::default(),
2372 suppress_build_script_link_lines: false,
2373 extra_build_script_lines: vec![],
2374 python_framework_prefix: None,
2375 }
2376 );
2377 }
2378
2379 #[test]
2380 fn mingw_hardcoded_cross_compile() {
2381 let env_vars = CrossCompileEnvVars {
2382 pyo3_cross: None,
2383 pyo3_cross_lib_dir: Some("/usr/lib/mingw".into()),
2384 pyo3_cross_python_implementation: None,
2385 pyo3_cross_python_version: Some("3.8".into()),
2386 };
2387
2388 let host = triple!("x86_64-unknown-linux-gnu");
2389 let target = triple!("i686-pc-windows-gnu");
2390 let cross_config =
2391 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
2392 .unwrap()
2393 .unwrap();
2394
2395 assert_eq!(
2396 default_cross_compile(&cross_config).unwrap(),
2397 InterpreterConfig {
2398 implementation: PythonImplementation::CPython,
2399 version: PythonVersion { major: 3, minor: 8 },
2400 shared: true,
2401 abi3: false,
2402 lib_name: Some("python38".into()),
2403 lib_dir: Some("/usr/lib/mingw".into()),
2404 executable: None,
2405 pointer_width: None,
2406 build_flags: BuildFlags::default(),
2407 suppress_build_script_link_lines: false,
2408 extra_build_script_lines: vec![],
2409 python_framework_prefix: None,
2410 }
2411 );
2412 }
2413
2414 #[test]
2415 fn unix_hardcoded_cross_compile() {
2416 let env_vars = CrossCompileEnvVars {
2417 pyo3_cross: None,
2418 pyo3_cross_lib_dir: Some("/usr/arm64/lib".into()),
2419 pyo3_cross_python_implementation: None,
2420 pyo3_cross_python_version: Some("3.9".into()),
2421 };
2422
2423 let host = triple!("x86_64-unknown-linux-gnu");
2424 let target = triple!("aarch64-unknown-linux-gnu");
2425 let cross_config =
2426 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
2427 .unwrap()
2428 .unwrap();
2429
2430 assert_eq!(
2431 default_cross_compile(&cross_config).unwrap(),
2432 InterpreterConfig {
2433 implementation: PythonImplementation::CPython,
2434 version: PythonVersion { major: 3, minor: 9 },
2435 shared: true,
2436 abi3: false,
2437 lib_name: Some("python3.9".into()),
2438 lib_dir: Some("/usr/arm64/lib".into()),
2439 executable: None,
2440 pointer_width: None,
2441 build_flags: BuildFlags::default(),
2442 suppress_build_script_link_lines: false,
2443 extra_build_script_lines: vec![],
2444 python_framework_prefix: None,
2445 }
2446 );
2447 }
2448
2449 #[test]
2450 fn pypy_hardcoded_cross_compile() {
2451 let env_vars = CrossCompileEnvVars {
2452 pyo3_cross: None,
2453 pyo3_cross_lib_dir: None,
2454 pyo3_cross_python_implementation: Some("PyPy".into()),
2455 pyo3_cross_python_version: Some("3.10".into()),
2456 };
2457
2458 let triple = triple!("x86_64-unknown-linux-gnu");
2459 let cross_config =
2460 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &triple, &triple)
2461 .unwrap()
2462 .unwrap();
2463
2464 assert_eq!(
2465 default_cross_compile(&cross_config).unwrap(),
2466 InterpreterConfig {
2467 implementation: PythonImplementation::PyPy,
2468 version: PythonVersion {
2469 major: 3,
2470 minor: 10
2471 },
2472 shared: true,
2473 abi3: false,
2474 lib_name: Some("pypy3.10-c".into()),
2475 lib_dir: None,
2476 executable: None,
2477 pointer_width: None,
2478 build_flags: BuildFlags::default(),
2479 suppress_build_script_link_lines: false,
2480 extra_build_script_lines: vec![],
2481 python_framework_prefix: None,
2482 }
2483 );
2484 }
2485
2486 #[test]
2487 fn default_lib_name_windows() {
2488 use PythonImplementation::*;
2489 assert_eq!(
2490 super::default_lib_name_windows(
2491 PythonVersion { major: 3, minor: 9 },
2492 CPython,
2493 false,
2494 false,
2495 false,
2496 false,
2497 )
2498 .unwrap(),
2499 "python39",
2500 );
2501 assert!(super::default_lib_name_windows(
2502 PythonVersion { major: 3, minor: 9 },
2503 CPython,
2504 false,
2505 false,
2506 false,
2507 true,
2508 )
2509 .is_err());
2510 assert_eq!(
2511 super::default_lib_name_windows(
2512 PythonVersion { major: 3, minor: 9 },
2513 CPython,
2514 true,
2515 false,
2516 false,
2517 false,
2518 )
2519 .unwrap(),
2520 "python3",
2521 );
2522 assert_eq!(
2523 super::default_lib_name_windows(
2524 PythonVersion { major: 3, minor: 9 },
2525 CPython,
2526 false,
2527 true,
2528 false,
2529 false,
2530 )
2531 .unwrap(),
2532 "python3.9",
2533 );
2534 assert_eq!(
2535 super::default_lib_name_windows(
2536 PythonVersion { major: 3, minor: 9 },
2537 CPython,
2538 true,
2539 true,
2540 false,
2541 false,
2542 )
2543 .unwrap(),
2544 "python3",
2545 );
2546 assert_eq!(
2547 super::default_lib_name_windows(
2548 PythonVersion { major: 3, minor: 9 },
2549 PyPy,
2550 true,
2551 false,
2552 false,
2553 false,
2554 )
2555 .unwrap(),
2556 "python39",
2557 );
2558 assert_eq!(
2559 super::default_lib_name_windows(
2560 PythonVersion { major: 3, minor: 9 },
2561 CPython,
2562 false,
2563 false,
2564 true,
2565 false,
2566 )
2567 .unwrap(),
2568 "python39_d",
2569 );
2570 assert_eq!(
2573 super::default_lib_name_windows(
2574 PythonVersion { major: 3, minor: 9 },
2575 CPython,
2576 true,
2577 false,
2578 true,
2579 false,
2580 )
2581 .unwrap(),
2582 "python39_d",
2583 );
2584 assert_eq!(
2585 super::default_lib_name_windows(
2586 PythonVersion {
2587 major: 3,
2588 minor: 10
2589 },
2590 CPython,
2591 true,
2592 false,
2593 true,
2594 false,
2595 )
2596 .unwrap(),
2597 "python3_d",
2598 );
2599 assert!(super::default_lib_name_windows(
2601 PythonVersion {
2602 major: 3,
2603 minor: 12,
2604 },
2605 CPython,
2606 false,
2607 false,
2608 false,
2609 true,
2610 )
2611 .is_err());
2612 assert!(super::default_lib_name_windows(
2614 PythonVersion {
2615 major: 3,
2616 minor: 12,
2617 },
2618 CPython,
2619 false,
2620 true,
2621 false,
2622 true,
2623 )
2624 .is_err());
2625 assert_eq!(
2626 super::default_lib_name_windows(
2627 PythonVersion {
2628 major: 3,
2629 minor: 13
2630 },
2631 CPython,
2632 false,
2633 false,
2634 false,
2635 true,
2636 )
2637 .unwrap(),
2638 "python313t",
2639 );
2640 assert_eq!(
2641 super::default_lib_name_windows(
2642 PythonVersion {
2643 major: 3,
2644 minor: 13
2645 },
2646 CPython,
2647 true, false,
2649 false,
2650 true,
2651 )
2652 .unwrap(),
2653 "python313t",
2654 );
2655 assert_eq!(
2656 super::default_lib_name_windows(
2657 PythonVersion {
2658 major: 3,
2659 minor: 13
2660 },
2661 CPython,
2662 false,
2663 false,
2664 true,
2665 true,
2666 )
2667 .unwrap(),
2668 "python313t_d",
2669 );
2670 }
2671
2672 #[test]
2673 fn default_lib_name_unix() {
2674 use PythonImplementation::*;
2675 assert_eq!(
2677 super::default_lib_name_unix(
2678 PythonVersion { major: 3, minor: 7 },
2679 CPython,
2680 None,
2681 false
2682 )
2683 .unwrap(),
2684 "python3.7m",
2685 );
2686 assert_eq!(
2688 super::default_lib_name_unix(
2689 PythonVersion { major: 3, minor: 8 },
2690 CPython,
2691 None,
2692 false
2693 )
2694 .unwrap(),
2695 "python3.8",
2696 );
2697 assert_eq!(
2698 super::default_lib_name_unix(
2699 PythonVersion { major: 3, minor: 9 },
2700 CPython,
2701 None,
2702 false
2703 )
2704 .unwrap(),
2705 "python3.9",
2706 );
2707 assert_eq!(
2709 super::default_lib_name_unix(
2710 PythonVersion { major: 3, minor: 9 },
2711 CPython,
2712 Some("3.7md"),
2713 false
2714 )
2715 .unwrap(),
2716 "python3.7md",
2717 );
2718
2719 assert_eq!(
2721 super::default_lib_name_unix(PythonVersion { major: 3, minor: 9 }, PyPy, None, false)
2722 .unwrap(),
2723 "pypy3.9-c",
2724 );
2725
2726 assert_eq!(
2727 super::default_lib_name_unix(
2728 PythonVersion { major: 3, minor: 9 },
2729 PyPy,
2730 Some("3.9d"),
2731 false
2732 )
2733 .unwrap(),
2734 "pypy3.9d-c",
2735 );
2736
2737 assert_eq!(
2739 super::default_lib_name_unix(
2740 PythonVersion {
2741 major: 3,
2742 minor: 13
2743 },
2744 CPython,
2745 None,
2746 true
2747 )
2748 .unwrap(),
2749 "python3.13t",
2750 );
2751 assert!(super::default_lib_name_unix(
2753 PythonVersion {
2754 major: 3,
2755 minor: 12,
2756 },
2757 CPython,
2758 None,
2759 true,
2760 )
2761 .is_err());
2762 }
2763
2764 #[test]
2765 fn parse_cross_python_version() {
2766 let env_vars = CrossCompileEnvVars {
2767 pyo3_cross: None,
2768 pyo3_cross_lib_dir: None,
2769 pyo3_cross_python_version: Some("3.9".into()),
2770 pyo3_cross_python_implementation: None,
2771 };
2772
2773 assert_eq!(
2774 env_vars.parse_version().unwrap(),
2775 (Some(PythonVersion { major: 3, minor: 9 }), None),
2776 );
2777
2778 let env_vars = CrossCompileEnvVars {
2779 pyo3_cross: None,
2780 pyo3_cross_lib_dir: None,
2781 pyo3_cross_python_version: None,
2782 pyo3_cross_python_implementation: None,
2783 };
2784
2785 assert_eq!(env_vars.parse_version().unwrap(), (None, None));
2786
2787 let env_vars = CrossCompileEnvVars {
2788 pyo3_cross: None,
2789 pyo3_cross_lib_dir: None,
2790 pyo3_cross_python_version: Some("3.13t".into()),
2791 pyo3_cross_python_implementation: None,
2792 };
2793
2794 assert_eq!(
2795 env_vars.parse_version().unwrap(),
2796 (
2797 Some(PythonVersion {
2798 major: 3,
2799 minor: 13
2800 }),
2801 Some("t".into())
2802 ),
2803 );
2804
2805 let env_vars = CrossCompileEnvVars {
2806 pyo3_cross: None,
2807 pyo3_cross_lib_dir: None,
2808 pyo3_cross_python_version: Some("100".into()),
2809 pyo3_cross_python_implementation: None,
2810 };
2811
2812 assert!(env_vars.parse_version().is_err());
2813 }
2814
2815 #[test]
2816 fn interpreter_version_reduced_to_abi3() {
2817 let mut config = InterpreterConfig {
2818 abi3: true,
2819 build_flags: BuildFlags::default(),
2820 pointer_width: None,
2821 executable: None,
2822 implementation: PythonImplementation::CPython,
2823 lib_dir: None,
2824 lib_name: None,
2825 shared: true,
2826 version: PythonVersion { major: 3, minor: 7 },
2827 suppress_build_script_link_lines: false,
2828 extra_build_script_lines: vec![],
2829 python_framework_prefix: None,
2830 };
2831
2832 config
2833 .fixup_for_abi3_version(Some(PythonVersion { major: 3, minor: 7 }))
2834 .unwrap();
2835 assert_eq!(config.version, PythonVersion { major: 3, minor: 7 });
2836 }
2837
2838 #[test]
2839 fn abi3_version_cannot_be_higher_than_interpreter() {
2840 let mut config = InterpreterConfig {
2841 abi3: true,
2842 build_flags: BuildFlags::new(),
2843 pointer_width: None,
2844 executable: None,
2845 implementation: PythonImplementation::CPython,
2846 lib_dir: None,
2847 lib_name: None,
2848 shared: true,
2849 version: PythonVersion { major: 3, minor: 7 },
2850 suppress_build_script_link_lines: false,
2851 extra_build_script_lines: vec![],
2852 python_framework_prefix: None,
2853 };
2854
2855 assert!(config
2856 .fixup_for_abi3_version(Some(PythonVersion { major: 3, minor: 8 }))
2857 .unwrap_err()
2858 .to_string()
2859 .contains(
2860 "cannot set a minimum Python version 3.8 higher than the interpreter version 3.7"
2861 ));
2862 }
2863
2864 #[test]
2865 #[cfg(all(
2866 target_os = "linux",
2867 target_arch = "x86_64",
2868 feature = "resolve-config"
2869 ))]
2870 fn parse_sysconfigdata() {
2871 let interpreter_config = crate::get();
2876
2877 let lib_dir = match &interpreter_config.lib_dir {
2878 Some(lib_dir) => Path::new(lib_dir),
2879 None => return,
2881 };
2882
2883 let cross = CrossCompileConfig {
2884 lib_dir: Some(lib_dir.into()),
2885 version: Some(interpreter_config.version),
2886 implementation: Some(interpreter_config.implementation),
2887 target: triple!("x86_64-unknown-linux-gnu"),
2888 abiflags: if interpreter_config.is_free_threaded() {
2889 Some("t".into())
2890 } else {
2891 None
2892 },
2893 };
2894
2895 let sysconfigdata_path = match find_sysconfigdata(&cross) {
2896 Ok(Some(path)) => path,
2897 _ => return,
2899 };
2900 let sysconfigdata = super::parse_sysconfigdata(sysconfigdata_path).unwrap();
2901 let parsed_config = InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap();
2902
2903 assert_eq!(
2904 parsed_config,
2905 InterpreterConfig {
2906 abi3: false,
2907 build_flags: BuildFlags(interpreter_config.build_flags.0.clone()),
2908 pointer_width: Some(64),
2909 executable: None,
2910 implementation: PythonImplementation::CPython,
2911 lib_dir: interpreter_config.lib_dir.to_owned(),
2912 lib_name: interpreter_config.lib_name.to_owned(),
2913 shared: true,
2914 version: interpreter_config.version,
2915 suppress_build_script_link_lines: false,
2916 extra_build_script_lines: vec![],
2917 python_framework_prefix: None,
2918 }
2919 )
2920 }
2921
2922 #[test]
2923 fn test_venv_interpreter() {
2924 let base = OsStr::new("base");
2925 assert_eq!(
2926 venv_interpreter(base, true),
2927 PathBuf::from_iter(&["base", "Scripts", "python.exe"])
2928 );
2929 assert_eq!(
2930 venv_interpreter(base, false),
2931 PathBuf::from_iter(&["base", "bin", "python"])
2932 );
2933 }
2934
2935 #[test]
2936 fn test_conda_env_interpreter() {
2937 let base = OsStr::new("base");
2938 assert_eq!(
2939 conda_env_interpreter(base, true),
2940 PathBuf::from_iter(&["base", "python.exe"])
2941 );
2942 assert_eq!(
2943 conda_env_interpreter(base, false),
2944 PathBuf::from_iter(&["base", "bin", "python"])
2945 );
2946 }
2947
2948 #[test]
2949 fn test_not_cross_compiling_from_to() {
2950 assert!(cross_compiling_from_to(
2951 &triple!("x86_64-unknown-linux-gnu"),
2952 &triple!("x86_64-unknown-linux-gnu"),
2953 )
2954 .unwrap()
2955 .is_none());
2956
2957 assert!(cross_compiling_from_to(
2958 &triple!("x86_64-apple-darwin"),
2959 &triple!("x86_64-apple-darwin")
2960 )
2961 .unwrap()
2962 .is_none());
2963
2964 assert!(cross_compiling_from_to(
2965 &triple!("aarch64-apple-darwin"),
2966 &triple!("x86_64-apple-darwin")
2967 )
2968 .unwrap()
2969 .is_none());
2970
2971 assert!(cross_compiling_from_to(
2972 &triple!("x86_64-apple-darwin"),
2973 &triple!("aarch64-apple-darwin")
2974 )
2975 .unwrap()
2976 .is_none());
2977
2978 assert!(cross_compiling_from_to(
2979 &triple!("x86_64-pc-windows-msvc"),
2980 &triple!("i686-pc-windows-msvc")
2981 )
2982 .unwrap()
2983 .is_none());
2984
2985 assert!(cross_compiling_from_to(
2986 &triple!("x86_64-unknown-linux-gnu"),
2987 &triple!("x86_64-unknown-linux-musl")
2988 )
2989 .unwrap()
2990 .is_none());
2991 }
2992
2993 #[test]
2994 fn test_is_cross_compiling_from_to() {
2995 assert!(cross_compiling_from_to(
2996 &triple!("x86_64-pc-windows-msvc"),
2997 &triple!("aarch64-pc-windows-msvc")
2998 )
2999 .unwrap()
3000 .is_some());
3001 }
3002
3003 #[test]
3004 fn test_run_python_script() {
3005 let interpreter = make_interpreter_config()
3007 .expect("could not get InterpreterConfig from installed interpreter");
3008 let out = interpreter
3009 .run_python_script("print(2 + 2)")
3010 .expect("failed to run Python script");
3011 assert_eq!(out.trim_end(), "4");
3012 }
3013
3014 #[test]
3015 fn test_run_python_script_with_envs() {
3016 let interpreter = make_interpreter_config()
3018 .expect("could not get InterpreterConfig from installed interpreter");
3019 let out = interpreter
3020 .run_python_script_with_envs(
3021 "import os; print(os.getenv('PYO3_TEST'))",
3022 vec![("PYO3_TEST", "42")],
3023 )
3024 .expect("failed to run Python script");
3025 assert_eq!(out.trim_end(), "42");
3026 }
3027
3028 #[test]
3029 fn test_build_script_outputs_base() {
3030 let interpreter_config = InterpreterConfig {
3031 implementation: PythonImplementation::CPython,
3032 version: PythonVersion { major: 3, minor: 9 },
3033 shared: true,
3034 abi3: false,
3035 lib_name: Some("python3".into()),
3036 lib_dir: None,
3037 executable: None,
3038 pointer_width: None,
3039 build_flags: BuildFlags::default(),
3040 suppress_build_script_link_lines: false,
3041 extra_build_script_lines: vec![],
3042 python_framework_prefix: None,
3043 };
3044 assert_eq!(
3045 interpreter_config.build_script_outputs(),
3046 [
3047 "cargo:rustc-cfg=Py_3_7".to_owned(),
3048 "cargo:rustc-cfg=Py_3_8".to_owned(),
3049 "cargo:rustc-cfg=Py_3_9".to_owned(),
3050 ]
3051 );
3052
3053 let interpreter_config = InterpreterConfig {
3054 implementation: PythonImplementation::PyPy,
3055 ..interpreter_config
3056 };
3057 assert_eq!(
3058 interpreter_config.build_script_outputs(),
3059 [
3060 "cargo:rustc-cfg=Py_3_7".to_owned(),
3061 "cargo:rustc-cfg=Py_3_8".to_owned(),
3062 "cargo:rustc-cfg=Py_3_9".to_owned(),
3063 "cargo:rustc-cfg=PyPy".to_owned(),
3064 ]
3065 );
3066 }
3067
3068 #[test]
3069 fn test_build_script_outputs_abi3() {
3070 let interpreter_config = InterpreterConfig {
3071 implementation: PythonImplementation::CPython,
3072 version: PythonVersion { major: 3, minor: 9 },
3073 shared: true,
3074 abi3: true,
3075 lib_name: Some("python3".into()),
3076 lib_dir: None,
3077 executable: None,
3078 pointer_width: None,
3079 build_flags: BuildFlags::default(),
3080 suppress_build_script_link_lines: false,
3081 extra_build_script_lines: vec![],
3082 python_framework_prefix: None,
3083 };
3084
3085 assert_eq!(
3086 interpreter_config.build_script_outputs(),
3087 [
3088 "cargo:rustc-cfg=Py_3_7".to_owned(),
3089 "cargo:rustc-cfg=Py_3_8".to_owned(),
3090 "cargo:rustc-cfg=Py_3_9".to_owned(),
3091 "cargo:rustc-cfg=Py_LIMITED_API".to_owned(),
3092 ]
3093 );
3094
3095 let interpreter_config = InterpreterConfig {
3096 implementation: PythonImplementation::PyPy,
3097 ..interpreter_config
3098 };
3099 assert_eq!(
3100 interpreter_config.build_script_outputs(),
3101 [
3102 "cargo:rustc-cfg=Py_3_7".to_owned(),
3103 "cargo:rustc-cfg=Py_3_8".to_owned(),
3104 "cargo:rustc-cfg=Py_3_9".to_owned(),
3105 "cargo:rustc-cfg=PyPy".to_owned(),
3106 "cargo:rustc-cfg=Py_LIMITED_API".to_owned(),
3107 ]
3108 );
3109 }
3110
3111 #[test]
3112 fn test_build_script_outputs_gil_disabled() {
3113 let mut build_flags = BuildFlags::default();
3114 build_flags.0.insert(BuildFlag::Py_GIL_DISABLED);
3115 let interpreter_config = InterpreterConfig {
3116 implementation: PythonImplementation::CPython,
3117 version: PythonVersion {
3118 major: 3,
3119 minor: 13,
3120 },
3121 shared: true,
3122 abi3: false,
3123 lib_name: Some("python3".into()),
3124 lib_dir: None,
3125 executable: None,
3126 pointer_width: None,
3127 build_flags,
3128 suppress_build_script_link_lines: false,
3129 extra_build_script_lines: vec![],
3130 python_framework_prefix: None,
3131 };
3132
3133 assert_eq!(
3134 interpreter_config.build_script_outputs(),
3135 [
3136 "cargo:rustc-cfg=Py_3_7".to_owned(),
3137 "cargo:rustc-cfg=Py_3_8".to_owned(),
3138 "cargo:rustc-cfg=Py_3_9".to_owned(),
3139 "cargo:rustc-cfg=Py_3_10".to_owned(),
3140 "cargo:rustc-cfg=Py_3_11".to_owned(),
3141 "cargo:rustc-cfg=Py_3_12".to_owned(),
3142 "cargo:rustc-cfg=Py_3_13".to_owned(),
3143 "cargo:rustc-cfg=Py_GIL_DISABLED".to_owned(),
3144 ]
3145 );
3146 }
3147
3148 #[test]
3149 fn test_build_script_outputs_debug() {
3150 let mut build_flags = BuildFlags::default();
3151 build_flags.0.insert(BuildFlag::Py_DEBUG);
3152 let interpreter_config = InterpreterConfig {
3153 implementation: PythonImplementation::CPython,
3154 version: PythonVersion { major: 3, minor: 7 },
3155 shared: true,
3156 abi3: false,
3157 lib_name: Some("python3".into()),
3158 lib_dir: None,
3159 executable: None,
3160 pointer_width: None,
3161 build_flags,
3162 suppress_build_script_link_lines: false,
3163 extra_build_script_lines: vec![],
3164 python_framework_prefix: None,
3165 };
3166
3167 assert_eq!(
3168 interpreter_config.build_script_outputs(),
3169 [
3170 "cargo:rustc-cfg=Py_3_7".to_owned(),
3171 "cargo:rustc-cfg=py_sys_config=\"Py_DEBUG\"".to_owned(),
3172 ]
3173 );
3174 }
3175
3176 #[test]
3177 fn test_find_sysconfigdata_in_invalid_lib_dir() {
3178 let e = find_all_sysconfigdata(&CrossCompileConfig {
3179 lib_dir: Some(PathBuf::from("/abc/123/not/a/real/path")),
3180 version: None,
3181 implementation: None,
3182 target: triple!("x86_64-unknown-linux-gnu"),
3183 abiflags: None,
3184 })
3185 .unwrap_err();
3186
3187 assert!(e.report().to_string().starts_with(
3189 "failed to search the lib dir at 'PYO3_CROSS_LIB_DIR=/abc/123/not/a/real/path'\n\
3190 caused by:\n \
3191 - 0: failed to list the entries in '/abc/123/not/a/real/path'\n \
3192 - 1: \
3193 "
3194 ));
3195 }
3196
3197 #[test]
3198 fn test_from_pyo3_config_file_env_rebuild() {
3199 READ_ENV_VARS.with(|vars| vars.borrow_mut().clear());
3200 let _ = InterpreterConfig::from_pyo3_config_file_env();
3201 READ_ENV_VARS.with(|vars| assert!(vars.borrow().contains(&"PYO3_CONFIG_FILE".to_string())));
3203 }
3204}