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 = 13;
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 |= matches!(target.operating_system, OperatingSystem::Darwin(_))
967 && matches!(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 # (ana)conda(-forge) built Pythons are statically linked but ship the shared library with them.
1310 # We detect them based on the magic prefix directory they have encoded in their builds.
1311 if key == "Py_ENABLE_SHARED" and "_h_env_placehold" in build_time_vars.get("prefix"):
1312 val = 1
1313 print(key, val)
1314"#;
1315
1316 let output = run_python_script(&find_interpreter()?, &script)?;
1317
1318 Ok(Sysconfigdata(parse_script_output(&output)))
1319}
1320
1321fn starts_with(entry: &DirEntry, pat: &str) -> bool {
1322 let name = entry.file_name();
1323 name.to_string_lossy().starts_with(pat)
1324}
1325fn ends_with(entry: &DirEntry, pat: &str) -> bool {
1326 let name = entry.file_name();
1327 name.to_string_lossy().ends_with(pat)
1328}
1329
1330fn find_sysconfigdata(cross: &CrossCompileConfig) -> Result<Option<PathBuf>> {
1335 let mut sysconfig_paths = find_all_sysconfigdata(cross)?;
1336 if sysconfig_paths.is_empty() {
1337 if let Some(lib_dir) = cross.lib_dir.as_ref() {
1338 bail!("Could not find _sysconfigdata*.py in {}", lib_dir.display());
1339 } else {
1340 return Ok(None);
1342 }
1343 } else if sysconfig_paths.len() > 1 {
1344 let mut error_msg = String::from(
1345 "Detected multiple possible Python versions. Please set either the \
1346 PYO3_CROSS_PYTHON_VERSION variable to the wanted version or the \
1347 _PYTHON_SYSCONFIGDATA_NAME variable to the wanted sysconfigdata file name.\n\n\
1348 sysconfigdata files found:",
1349 );
1350 for path in sysconfig_paths {
1351 use std::fmt::Write;
1352 write!(&mut error_msg, "\n\t{}", path.display()).unwrap();
1353 }
1354 bail!("{}\n", error_msg);
1355 }
1356
1357 Ok(Some(sysconfig_paths.remove(0)))
1358}
1359
1360pub fn find_all_sysconfigdata(cross: &CrossCompileConfig) -> Result<Vec<PathBuf>> {
1399 let sysconfig_paths = if let Some(lib_dir) = cross.lib_dir.as_ref() {
1400 search_lib_dir(lib_dir, cross).with_context(|| {
1401 format!(
1402 "failed to search the lib dir at 'PYO3_CROSS_LIB_DIR={}'",
1403 lib_dir.display()
1404 )
1405 })?
1406 } else {
1407 return Ok(Vec::new());
1408 };
1409
1410 let sysconfig_name = env_var("_PYTHON_SYSCONFIGDATA_NAME");
1411 let mut sysconfig_paths = sysconfig_paths
1412 .iter()
1413 .filter_map(|p| {
1414 let canonical = fs::canonicalize(p).ok();
1415 match &sysconfig_name {
1416 Some(_) => canonical.filter(|p| p.file_stem() == sysconfig_name.as_deref()),
1417 None => canonical,
1418 }
1419 })
1420 .collect::<Vec<PathBuf>>();
1421
1422 sysconfig_paths.sort();
1423 sysconfig_paths.dedup();
1424
1425 Ok(sysconfig_paths)
1426}
1427
1428fn is_pypy_lib_dir(path: &str, v: &Option<PythonVersion>) -> bool {
1429 let pypy_version_pat = if let Some(v) = v {
1430 format!("pypy{}", v)
1431 } else {
1432 "pypy3.".into()
1433 };
1434 path == "lib_pypy" || path.starts_with(&pypy_version_pat)
1435}
1436
1437fn is_graalpy_lib_dir(path: &str, v: &Option<PythonVersion>) -> bool {
1438 let graalpy_version_pat = if let Some(v) = v {
1439 format!("graalpy{}", v)
1440 } else {
1441 "graalpy2".into()
1442 };
1443 path == "lib_graalpython" || path.starts_with(&graalpy_version_pat)
1444}
1445
1446fn is_cpython_lib_dir(path: &str, v: &Option<PythonVersion>) -> bool {
1447 let cpython_version_pat = if let Some(v) = v {
1448 format!("python{}", v)
1449 } else {
1450 "python3.".into()
1451 };
1452 path.starts_with(&cpython_version_pat)
1453}
1454
1455fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Result<Vec<PathBuf>> {
1457 let mut sysconfig_paths = vec![];
1458 for f in fs::read_dir(path.as_ref()).with_context(|| {
1459 format!(
1460 "failed to list the entries in '{}'",
1461 path.as_ref().display()
1462 )
1463 })? {
1464 sysconfig_paths.extend(match &f {
1465 Ok(f) if starts_with(f, "_sysconfigdata_") && ends_with(f, "py") => vec![f.path()],
1467 Ok(f) if f.metadata().map_or(false, |metadata| metadata.is_dir()) => {
1468 let file_name = f.file_name();
1469 let file_name = file_name.to_string_lossy();
1470 if file_name == "build" || file_name == "lib" {
1471 search_lib_dir(f.path(), cross)?
1472 } else if file_name.starts_with("lib.") {
1473 if !file_name.contains(&cross.target.operating_system.to_string()) {
1475 continue;
1476 }
1477 if !file_name.contains(&cross.target.architecture.to_string()) {
1479 continue;
1480 }
1481 search_lib_dir(f.path(), cross)?
1482 } else if is_cpython_lib_dir(&file_name, &cross.version)
1483 || is_pypy_lib_dir(&file_name, &cross.version)
1484 || is_graalpy_lib_dir(&file_name, &cross.version)
1485 {
1486 search_lib_dir(f.path(), cross)?
1487 } else {
1488 continue;
1489 }
1490 }
1491 _ => continue,
1492 });
1493 }
1494 if sysconfig_paths.len() > 1 {
1502 let temp = sysconfig_paths
1503 .iter()
1504 .filter(|p| {
1505 p.to_string_lossy()
1506 .contains(&cross.target.architecture.to_string())
1507 })
1508 .cloned()
1509 .collect::<Vec<PathBuf>>();
1510 if !temp.is_empty() {
1511 sysconfig_paths = temp;
1512 }
1513 }
1514
1515 Ok(sysconfig_paths)
1516}
1517
1518fn cross_compile_from_sysconfigdata(
1526 cross_compile_config: &CrossCompileConfig,
1527) -> Result<Option<InterpreterConfig>> {
1528 if let Some(path) = find_sysconfigdata(cross_compile_config)? {
1529 let data = parse_sysconfigdata(path)?;
1530 let mut config = InterpreterConfig::from_sysconfigdata(&data)?;
1531 if let Some(cross_lib_dir) = cross_compile_config.lib_dir_string() {
1532 config.lib_dir = Some(cross_lib_dir)
1533 }
1534
1535 Ok(Some(config))
1536 } else {
1537 Ok(None)
1538 }
1539}
1540
1541#[allow(unused_mut)]
1548fn default_cross_compile(cross_compile_config: &CrossCompileConfig) -> Result<InterpreterConfig> {
1549 let version = cross_compile_config
1550 .version
1551 .or_else(get_abi3_version)
1552 .ok_or_else(||
1553 format!(
1554 "PYO3_CROSS_PYTHON_VERSION or an abi3-py3* feature must be specified \
1555 when cross-compiling and PYO3_CROSS_LIB_DIR is not set.\n\
1556 = help: see the PyO3 user guide for more information: https://pyo3.rs/v{}/building-and-distribution.html#cross-compiling",
1557 env!("CARGO_PKG_VERSION")
1558 )
1559 )?;
1560
1561 let abi3 = is_abi3();
1562 let implementation = cross_compile_config
1563 .implementation
1564 .unwrap_or(PythonImplementation::CPython);
1565 let gil_disabled = cross_compile_config.abiflags.as_deref() == Some("t");
1566
1567 let lib_name = default_lib_name_for_target(
1568 version,
1569 implementation,
1570 abi3,
1571 gil_disabled,
1572 &cross_compile_config.target,
1573 );
1574
1575 let mut lib_dir = cross_compile_config.lib_dir_string();
1576
1577 #[cfg(feature = "python3-dll-a")]
1579 if lib_dir.is_none() {
1580 let py_version = if implementation == PythonImplementation::CPython && abi3 && !gil_disabled
1581 {
1582 None
1583 } else {
1584 Some(version)
1585 };
1586 lib_dir = self::import_lib::generate_import_lib(
1587 &cross_compile_config.target,
1588 cross_compile_config
1589 .implementation
1590 .unwrap_or(PythonImplementation::CPython),
1591 py_version,
1592 None,
1593 )?;
1594 }
1595
1596 Ok(InterpreterConfig {
1597 implementation,
1598 version,
1599 shared: true,
1600 abi3,
1601 lib_name,
1602 lib_dir,
1603 executable: None,
1604 pointer_width: None,
1605 build_flags: BuildFlags::default(),
1606 suppress_build_script_link_lines: false,
1607 extra_build_script_lines: vec![],
1608 python_framework_prefix: None,
1609 })
1610}
1611
1612fn default_abi3_config(host: &Triple, version: PythonVersion) -> Result<InterpreterConfig> {
1622 let implementation = PythonImplementation::CPython;
1624 let abi3 = true;
1625
1626 let lib_name = if host.operating_system == OperatingSystem::Windows {
1627 Some(default_lib_name_windows(
1628 version,
1629 implementation,
1630 abi3,
1631 false,
1632 false,
1633 false,
1634 )?)
1635 } else {
1636 None
1637 };
1638
1639 Ok(InterpreterConfig {
1640 implementation,
1641 version,
1642 shared: true,
1643 abi3,
1644 lib_name,
1645 lib_dir: None,
1646 executable: None,
1647 pointer_width: None,
1648 build_flags: BuildFlags::default(),
1649 suppress_build_script_link_lines: false,
1650 extra_build_script_lines: vec![],
1651 python_framework_prefix: None,
1652 })
1653}
1654
1655fn load_cross_compile_config(
1663 cross_compile_config: CrossCompileConfig,
1664) -> Result<InterpreterConfig> {
1665 let windows = cross_compile_config.target.operating_system == OperatingSystem::Windows;
1666
1667 let config = if windows || !have_python_interpreter() {
1668 default_cross_compile(&cross_compile_config)?
1672 } else if let Some(config) = cross_compile_from_sysconfigdata(&cross_compile_config)? {
1673 config
1675 } else {
1676 default_cross_compile(&cross_compile_config)?
1678 };
1679
1680 if config.lib_name.is_some() && config.lib_dir.is_none() {
1681 warn!(
1682 "The output binary will link to libpython, \
1683 but PYO3_CROSS_LIB_DIR environment variable is not set. \
1684 Ensure that the target Python library directory is \
1685 in the rustc native library search path."
1686 );
1687 }
1688
1689 Ok(config)
1690}
1691
1692const WINDOWS_ABI3_LIB_NAME: &str = "python3";
1694const WINDOWS_ABI3_DEBUG_LIB_NAME: &str = "python3_d";
1695
1696fn default_lib_name_for_target(
1697 version: PythonVersion,
1698 implementation: PythonImplementation,
1699 abi3: bool,
1700 gil_disabled: bool,
1701 target: &Triple,
1702) -> Option<String> {
1703 if target.operating_system == OperatingSystem::Windows {
1704 Some(
1705 default_lib_name_windows(version, implementation, abi3, false, false, gil_disabled)
1706 .unwrap(),
1707 )
1708 } else if is_linking_libpython_for_target(target) {
1709 Some(default_lib_name_unix(version, implementation, None, gil_disabled).unwrap())
1710 } else {
1711 None
1712 }
1713}
1714
1715fn default_lib_name_windows(
1716 version: PythonVersion,
1717 implementation: PythonImplementation,
1718 abi3: bool,
1719 mingw: bool,
1720 debug: bool,
1721 gil_disabled: bool,
1722) -> Result<String> {
1723 if debug && version < PythonVersion::PY310 {
1724 Ok(format!("python{}{}_d", version.major, version.minor))
1727 } else if abi3 && !(gil_disabled || implementation.is_pypy() || implementation.is_graalpy()) {
1728 if debug {
1729 Ok(WINDOWS_ABI3_DEBUG_LIB_NAME.to_owned())
1730 } else {
1731 Ok(WINDOWS_ABI3_LIB_NAME.to_owned())
1732 }
1733 } else if mingw {
1734 ensure!(
1735 !gil_disabled,
1736 "MinGW free-threaded builds are not currently tested or supported"
1737 );
1738 Ok(format!("python{}.{}", version.major, version.minor))
1740 } else if gil_disabled {
1741 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);
1742 if debug {
1743 Ok(format!("python{}{}t_d", version.major, version.minor))
1744 } else {
1745 Ok(format!("python{}{}t", version.major, version.minor))
1746 }
1747 } else if debug {
1748 Ok(format!("python{}{}_d", version.major, version.minor))
1749 } else {
1750 Ok(format!("python{}{}", version.major, version.minor))
1751 }
1752}
1753
1754fn default_lib_name_unix(
1755 version: PythonVersion,
1756 implementation: PythonImplementation,
1757 ld_version: Option<&str>,
1758 gil_disabled: bool,
1759) -> Result<String> {
1760 match implementation {
1761 PythonImplementation::CPython => match ld_version {
1762 Some(ld_version) => Ok(format!("python{}", ld_version)),
1763 None => {
1764 if version > PythonVersion::PY37 {
1765 if gil_disabled {
1767 ensure!(version >= PythonVersion::PY313, "Cannot compile C extensions for the free-threaded build on Python versions earlier than 3.13, found {}.{}", version.major, version.minor);
1768 Ok(format!("python{}.{}t", version.major, version.minor))
1769 } else {
1770 Ok(format!("python{}.{}", version.major, version.minor))
1771 }
1772 } else {
1773 Ok(format!("python{}.{}m", version.major, version.minor))
1775 }
1776 }
1777 },
1778 PythonImplementation::PyPy => match ld_version {
1779 Some(ld_version) => Ok(format!("pypy{}-c", ld_version)),
1780 None => Ok(format!("pypy{}.{}-c", version.major, version.minor)),
1781 },
1782
1783 PythonImplementation::GraalPy => Ok("python-native".to_string()),
1784 }
1785}
1786
1787fn run_python_script(interpreter: &Path, script: &str) -> Result<String> {
1789 run_python_script_with_envs(interpreter, script, std::iter::empty::<(&str, &str)>())
1790}
1791
1792fn run_python_script_with_envs<I, K, V>(interpreter: &Path, script: &str, envs: I) -> Result<String>
1795where
1796 I: IntoIterator<Item = (K, V)>,
1797 K: AsRef<OsStr>,
1798 V: AsRef<OsStr>,
1799{
1800 let out = Command::new(interpreter)
1801 .env("PYTHONIOENCODING", "utf-8")
1802 .envs(envs)
1803 .stdin(Stdio::piped())
1804 .stdout(Stdio::piped())
1805 .stderr(Stdio::inherit())
1806 .spawn()
1807 .and_then(|mut child| {
1808 child
1809 .stdin
1810 .as_mut()
1811 .expect("piped stdin")
1812 .write_all(script.as_bytes())?;
1813 child.wait_with_output()
1814 });
1815
1816 match out {
1817 Err(err) => bail!(
1818 "failed to run the Python interpreter at {}: {}",
1819 interpreter.display(),
1820 err
1821 ),
1822 Ok(ok) if !ok.status.success() => bail!("Python script failed"),
1823 Ok(ok) => Ok(String::from_utf8(ok.stdout)
1824 .context("failed to parse Python script output as utf-8")?),
1825 }
1826}
1827
1828fn venv_interpreter(virtual_env: &OsStr, windows: bool) -> PathBuf {
1829 if windows {
1830 Path::new(virtual_env).join("Scripts").join("python.exe")
1831 } else {
1832 Path::new(virtual_env).join("bin").join("python")
1833 }
1834}
1835
1836fn conda_env_interpreter(conda_prefix: &OsStr, windows: bool) -> PathBuf {
1837 if windows {
1838 Path::new(conda_prefix).join("python.exe")
1839 } else {
1840 Path::new(conda_prefix).join("bin").join("python")
1841 }
1842}
1843
1844fn get_env_interpreter() -> Option<PathBuf> {
1845 match (env_var("VIRTUAL_ENV"), env_var("CONDA_PREFIX")) {
1846 (Some(dir), None) => Some(venv_interpreter(&dir, cfg!(windows))),
1849 (None, Some(dir)) => Some(conda_env_interpreter(&dir, cfg!(windows))),
1850 (Some(_), Some(_)) => {
1851 warn!(
1852 "Both VIRTUAL_ENV and CONDA_PREFIX are set. PyO3 will ignore both of these for \
1853 locating the Python interpreter until you unset one of them."
1854 );
1855 None
1856 }
1857 (None, None) => None,
1858 }
1859}
1860
1861pub fn find_interpreter() -> Result<PathBuf> {
1869 println!("cargo:rerun-if-env-changed=PYO3_ENVIRONMENT_SIGNATURE");
1872
1873 if let Some(exe) = env_var("PYO3_PYTHON") {
1874 Ok(exe.into())
1875 } else if let Some(env_interpreter) = get_env_interpreter() {
1876 Ok(env_interpreter)
1877 } else {
1878 println!("cargo:rerun-if-env-changed=PATH");
1879 ["python", "python3"]
1880 .iter()
1881 .find(|bin| {
1882 if let Ok(out) = Command::new(bin).arg("--version").output() {
1883 out.stdout.starts_with(b"Python 3")
1885 || out.stderr.starts_with(b"Python 3")
1886 || out.stdout.starts_with(b"GraalPy 3")
1887 } else {
1888 false
1889 }
1890 })
1891 .map(PathBuf::from)
1892 .ok_or_else(|| "no Python 3.x interpreter found".into())
1893 }
1894}
1895
1896fn get_host_interpreter(abi3_version: Option<PythonVersion>) -> Result<InterpreterConfig> {
1900 let interpreter_path = find_interpreter()?;
1901
1902 let mut interpreter_config = InterpreterConfig::from_interpreter(interpreter_path)?;
1903 interpreter_config.fixup_for_abi3_version(abi3_version)?;
1904
1905 Ok(interpreter_config)
1906}
1907
1908pub fn make_cross_compile_config() -> Result<Option<InterpreterConfig>> {
1913 let interpreter_config = if let Some(cross_config) = cross_compiling_from_cargo_env()? {
1914 let mut interpreter_config = load_cross_compile_config(cross_config)?;
1915 interpreter_config.fixup_for_abi3_version(get_abi3_version())?;
1916 Some(interpreter_config)
1917 } else {
1918 None
1919 };
1920
1921 Ok(interpreter_config)
1922}
1923
1924#[allow(dead_code, unused_mut)]
1927pub fn make_interpreter_config() -> Result<InterpreterConfig> {
1928 let host = Triple::host();
1929 let abi3_version = get_abi3_version();
1930
1931 let need_interpreter = abi3_version.is_none() || require_libdir_for_target(&host);
1934
1935 if have_python_interpreter() {
1936 match get_host_interpreter(abi3_version) {
1937 Ok(interpreter_config) => return Ok(interpreter_config),
1938 Err(e) if need_interpreter => return Err(e),
1940 _ => {
1941 warn!("Compiling without a working Python interpreter.");
1944 }
1945 }
1946 } else {
1947 ensure!(
1948 abi3_version.is_some(),
1949 "An abi3-py3* feature must be specified when compiling without a Python interpreter."
1950 );
1951 };
1952
1953 let mut interpreter_config = default_abi3_config(&host, abi3_version.unwrap())?;
1954
1955 #[cfg(feature = "python3-dll-a")]
1957 {
1958 let gil_disabled = interpreter_config
1959 .build_flags
1960 .0
1961 .contains(&BuildFlag::Py_GIL_DISABLED);
1962 let py_version = if interpreter_config.implementation == PythonImplementation::CPython
1963 && interpreter_config.abi3
1964 && !gil_disabled
1965 {
1966 None
1967 } else {
1968 Some(interpreter_config.version)
1969 };
1970 interpreter_config.lib_dir = self::import_lib::generate_import_lib(
1971 &host,
1972 interpreter_config.implementation,
1973 py_version,
1974 None,
1975 )?;
1976 }
1977
1978 Ok(interpreter_config)
1979}
1980
1981fn escape(bytes: &[u8]) -> String {
1982 let mut escaped = String::with_capacity(2 * bytes.len());
1983
1984 for byte in bytes {
1985 const LUT: &[u8; 16] = b"0123456789abcdef";
1986
1987 escaped.push(LUT[(byte >> 4) as usize] as char);
1988 escaped.push(LUT[(byte & 0x0F) as usize] as char);
1989 }
1990
1991 escaped
1992}
1993
1994fn unescape(escaped: &str) -> Vec<u8> {
1995 assert!(escaped.len() % 2 == 0, "invalid hex encoding");
1996
1997 let mut bytes = Vec::with_capacity(escaped.len() / 2);
1998
1999 for chunk in escaped.as_bytes().chunks_exact(2) {
2000 fn unhex(hex: u8) -> u8 {
2001 match hex {
2002 b'a'..=b'f' => hex - b'a' + 10,
2003 b'0'..=b'9' => hex - b'0',
2004 _ => panic!("invalid hex encoding"),
2005 }
2006 }
2007
2008 bytes.push((unhex(chunk[0]) << 4) | unhex(chunk[1]));
2009 }
2010
2011 bytes
2012}
2013
2014#[cfg(test)]
2015mod tests {
2016 use target_lexicon::triple;
2017
2018 use super::*;
2019
2020 #[test]
2021 fn test_config_file_roundtrip() {
2022 let config = InterpreterConfig {
2023 abi3: true,
2024 build_flags: BuildFlags::default(),
2025 pointer_width: Some(32),
2026 executable: Some("executable".into()),
2027 implementation: PythonImplementation::CPython,
2028 lib_name: Some("lib_name".into()),
2029 lib_dir: Some("lib_dir".into()),
2030 shared: true,
2031 version: MINIMUM_SUPPORTED_VERSION,
2032 suppress_build_script_link_lines: true,
2033 extra_build_script_lines: vec!["cargo:test1".to_string(), "cargo:test2".to_string()],
2034 python_framework_prefix: None,
2035 };
2036 let mut buf: Vec<u8> = Vec::new();
2037 config.to_writer(&mut buf).unwrap();
2038
2039 assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap());
2040
2041 let config = InterpreterConfig {
2044 abi3: false,
2045 build_flags: {
2046 let mut flags = HashSet::new();
2047 flags.insert(BuildFlag::Py_DEBUG);
2048 flags.insert(BuildFlag::Other(String::from("Py_SOME_FLAG")));
2049 BuildFlags(flags)
2050 },
2051 pointer_width: None,
2052 executable: None,
2053 implementation: PythonImplementation::PyPy,
2054 lib_dir: None,
2055 lib_name: None,
2056 shared: true,
2057 version: PythonVersion {
2058 major: 3,
2059 minor: 10,
2060 },
2061 suppress_build_script_link_lines: false,
2062 extra_build_script_lines: vec![],
2063 python_framework_prefix: None,
2064 };
2065 let mut buf: Vec<u8> = Vec::new();
2066 config.to_writer(&mut buf).unwrap();
2067
2068 assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap());
2069 }
2070
2071 #[test]
2072 fn test_config_file_roundtrip_with_escaping() {
2073 let config = InterpreterConfig {
2074 abi3: true,
2075 build_flags: BuildFlags::default(),
2076 pointer_width: Some(32),
2077 executable: Some("executable".into()),
2078 implementation: PythonImplementation::CPython,
2079 lib_name: Some("lib_name".into()),
2080 lib_dir: Some("lib_dir\\n".into()),
2081 shared: true,
2082 version: MINIMUM_SUPPORTED_VERSION,
2083 suppress_build_script_link_lines: true,
2084 extra_build_script_lines: vec!["cargo:test1".to_string(), "cargo:test2".to_string()],
2085 python_framework_prefix: None,
2086 };
2087 let mut buf: Vec<u8> = Vec::new();
2088 config.to_writer(&mut buf).unwrap();
2089
2090 let buf = unescape(&escape(&buf));
2091
2092 assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap());
2093 }
2094
2095 #[test]
2096 fn test_config_file_defaults() {
2097 assert_eq!(
2099 InterpreterConfig::from_reader("version=3.7".as_bytes()).unwrap(),
2100 InterpreterConfig {
2101 version: PythonVersion { major: 3, minor: 7 },
2102 implementation: PythonImplementation::CPython,
2103 shared: true,
2104 abi3: false,
2105 lib_name: None,
2106 lib_dir: None,
2107 executable: None,
2108 pointer_width: None,
2109 build_flags: BuildFlags::default(),
2110 suppress_build_script_link_lines: false,
2111 extra_build_script_lines: vec![],
2112 python_framework_prefix: None,
2113 }
2114 )
2115 }
2116
2117 #[test]
2118 fn test_config_file_unknown_keys() {
2119 assert_eq!(
2121 InterpreterConfig::from_reader("version=3.7\next_suffix=.python37.so".as_bytes())
2122 .unwrap(),
2123 InterpreterConfig {
2124 version: PythonVersion { major: 3, minor: 7 },
2125 implementation: PythonImplementation::CPython,
2126 shared: true,
2127 abi3: false,
2128 lib_name: None,
2129 lib_dir: None,
2130 executable: None,
2131 pointer_width: None,
2132 build_flags: BuildFlags::default(),
2133 suppress_build_script_link_lines: false,
2134 extra_build_script_lines: vec![],
2135 python_framework_prefix: None,
2136 }
2137 )
2138 }
2139
2140 #[test]
2141 fn build_flags_default() {
2142 assert_eq!(BuildFlags::default(), BuildFlags::new());
2143 }
2144
2145 #[test]
2146 fn build_flags_from_sysconfigdata() {
2147 let mut sysconfigdata = Sysconfigdata::new();
2148
2149 assert_eq!(
2150 BuildFlags::from_sysconfigdata(&sysconfigdata).0,
2151 HashSet::new()
2152 );
2153
2154 for flag in &BuildFlags::ALL {
2155 sysconfigdata.insert(flag.to_string(), "0".into());
2156 }
2157
2158 assert_eq!(
2159 BuildFlags::from_sysconfigdata(&sysconfigdata).0,
2160 HashSet::new()
2161 );
2162
2163 let mut expected_flags = HashSet::new();
2164 for flag in &BuildFlags::ALL {
2165 sysconfigdata.insert(flag.to_string(), "1".into());
2166 expected_flags.insert(flag.clone());
2167 }
2168
2169 assert_eq!(
2170 BuildFlags::from_sysconfigdata(&sysconfigdata).0,
2171 expected_flags
2172 );
2173 }
2174
2175 #[test]
2176 fn build_flags_fixup() {
2177 let mut build_flags = BuildFlags::new();
2178
2179 build_flags = build_flags.fixup();
2180 assert!(build_flags.0.is_empty());
2181
2182 build_flags.0.insert(BuildFlag::Py_DEBUG);
2183
2184 build_flags = build_flags.fixup();
2185
2186 assert!(build_flags.0.contains(&BuildFlag::Py_REF_DEBUG));
2188 }
2189
2190 #[test]
2191 fn parse_script_output() {
2192 let output = "foo bar\nbar foobar\n\n";
2193 let map = super::parse_script_output(output);
2194 assert_eq!(map.len(), 2);
2195 assert_eq!(map["foo"], "bar");
2196 assert_eq!(map["bar"], "foobar");
2197 }
2198
2199 #[test]
2200 fn config_from_interpreter() {
2201 assert!(make_interpreter_config().is_ok())
2205 }
2206
2207 #[test]
2208 fn config_from_empty_sysconfigdata() {
2209 let sysconfigdata = Sysconfigdata::new();
2210 assert!(InterpreterConfig::from_sysconfigdata(&sysconfigdata).is_err());
2211 }
2212
2213 #[test]
2214 fn config_from_sysconfigdata() {
2215 let mut sysconfigdata = Sysconfigdata::new();
2216 sysconfigdata.insert("SOABI", "cpython-37m-x86_64-linux-gnu");
2219 sysconfigdata.insert("VERSION", "3.7");
2220 sysconfigdata.insert("Py_ENABLE_SHARED", "1");
2221 sysconfigdata.insert("LIBDIR", "/usr/lib");
2222 sysconfigdata.insert("LDVERSION", "3.7m");
2223 sysconfigdata.insert("SIZEOF_VOID_P", "8");
2224 assert_eq!(
2225 InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(),
2226 InterpreterConfig {
2227 abi3: false,
2228 build_flags: BuildFlags::from_sysconfigdata(&sysconfigdata),
2229 pointer_width: Some(64),
2230 executable: None,
2231 implementation: PythonImplementation::CPython,
2232 lib_dir: Some("/usr/lib".into()),
2233 lib_name: Some("python3.7m".into()),
2234 shared: true,
2235 version: PythonVersion::PY37,
2236 suppress_build_script_link_lines: false,
2237 extra_build_script_lines: vec![],
2238 python_framework_prefix: None,
2239 }
2240 );
2241 }
2242
2243 #[test]
2244 fn config_from_sysconfigdata_framework() {
2245 let mut sysconfigdata = Sysconfigdata::new();
2246 sysconfigdata.insert("SOABI", "cpython-37m-x86_64-linux-gnu");
2247 sysconfigdata.insert("VERSION", "3.7");
2248 sysconfigdata.insert("Py_ENABLE_SHARED", "0");
2250 sysconfigdata.insert("PYTHONFRAMEWORK", "Python");
2251 sysconfigdata.insert("LIBDIR", "/usr/lib");
2252 sysconfigdata.insert("LDVERSION", "3.7m");
2253 sysconfigdata.insert("SIZEOF_VOID_P", "8");
2254 assert_eq!(
2255 InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(),
2256 InterpreterConfig {
2257 abi3: false,
2258 build_flags: BuildFlags::from_sysconfigdata(&sysconfigdata),
2259 pointer_width: Some(64),
2260 executable: None,
2261 implementation: PythonImplementation::CPython,
2262 lib_dir: Some("/usr/lib".into()),
2263 lib_name: Some("python3.7m".into()),
2264 shared: true,
2265 version: PythonVersion::PY37,
2266 suppress_build_script_link_lines: false,
2267 extra_build_script_lines: vec![],
2268 python_framework_prefix: None,
2269 }
2270 );
2271
2272 sysconfigdata = Sysconfigdata::new();
2273 sysconfigdata.insert("SOABI", "cpython-37m-x86_64-linux-gnu");
2274 sysconfigdata.insert("VERSION", "3.7");
2275 sysconfigdata.insert("Py_ENABLE_SHARED", "0");
2277 sysconfigdata.insert("PYTHONFRAMEWORK", "");
2278 sysconfigdata.insert("LIBDIR", "/usr/lib");
2279 sysconfigdata.insert("LDVERSION", "3.7m");
2280 sysconfigdata.insert("SIZEOF_VOID_P", "8");
2281 assert_eq!(
2282 InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(),
2283 InterpreterConfig {
2284 abi3: false,
2285 build_flags: BuildFlags::from_sysconfigdata(&sysconfigdata),
2286 pointer_width: Some(64),
2287 executable: None,
2288 implementation: PythonImplementation::CPython,
2289 lib_dir: Some("/usr/lib".into()),
2290 lib_name: Some("python3.7m".into()),
2291 shared: false,
2292 version: PythonVersion::PY37,
2293 suppress_build_script_link_lines: false,
2294 extra_build_script_lines: vec![],
2295 python_framework_prefix: None,
2296 }
2297 );
2298 }
2299
2300 #[test]
2301 fn windows_hardcoded_abi3_compile() {
2302 let host = triple!("x86_64-pc-windows-msvc");
2303 let min_version = "3.7".parse().unwrap();
2304
2305 assert_eq!(
2306 default_abi3_config(&host, min_version).unwrap(),
2307 InterpreterConfig {
2308 implementation: PythonImplementation::CPython,
2309 version: PythonVersion { major: 3, minor: 7 },
2310 shared: true,
2311 abi3: true,
2312 lib_name: Some("python3".into()),
2313 lib_dir: None,
2314 executable: None,
2315 pointer_width: None,
2316 build_flags: BuildFlags::default(),
2317 suppress_build_script_link_lines: false,
2318 extra_build_script_lines: vec![],
2319 python_framework_prefix: None,
2320 }
2321 );
2322 }
2323
2324 #[test]
2325 fn unix_hardcoded_abi3_compile() {
2326 let host = triple!("x86_64-unknown-linux-gnu");
2327 let min_version = "3.9".parse().unwrap();
2328
2329 assert_eq!(
2330 default_abi3_config(&host, min_version).unwrap(),
2331 InterpreterConfig {
2332 implementation: PythonImplementation::CPython,
2333 version: PythonVersion { major: 3, minor: 9 },
2334 shared: true,
2335 abi3: true,
2336 lib_name: None,
2337 lib_dir: None,
2338 executable: None,
2339 pointer_width: None,
2340 build_flags: BuildFlags::default(),
2341 suppress_build_script_link_lines: false,
2342 extra_build_script_lines: vec![],
2343 python_framework_prefix: None,
2344 }
2345 );
2346 }
2347
2348 #[test]
2349 fn windows_hardcoded_cross_compile() {
2350 let env_vars = CrossCompileEnvVars {
2351 pyo3_cross: None,
2352 pyo3_cross_lib_dir: Some("C:\\some\\path".into()),
2353 pyo3_cross_python_implementation: None,
2354 pyo3_cross_python_version: Some("3.7".into()),
2355 };
2356
2357 let host = triple!("x86_64-unknown-linux-gnu");
2358 let target = triple!("i686-pc-windows-msvc");
2359 let cross_config =
2360 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
2361 .unwrap()
2362 .unwrap();
2363
2364 assert_eq!(
2365 default_cross_compile(&cross_config).unwrap(),
2366 InterpreterConfig {
2367 implementation: PythonImplementation::CPython,
2368 version: PythonVersion { major: 3, minor: 7 },
2369 shared: true,
2370 abi3: false,
2371 lib_name: Some("python37".into()),
2372 lib_dir: Some("C:\\some\\path".into()),
2373 executable: None,
2374 pointer_width: None,
2375 build_flags: BuildFlags::default(),
2376 suppress_build_script_link_lines: false,
2377 extra_build_script_lines: vec![],
2378 python_framework_prefix: None,
2379 }
2380 );
2381 }
2382
2383 #[test]
2384 fn mingw_hardcoded_cross_compile() {
2385 let env_vars = CrossCompileEnvVars {
2386 pyo3_cross: None,
2387 pyo3_cross_lib_dir: Some("/usr/lib/mingw".into()),
2388 pyo3_cross_python_implementation: None,
2389 pyo3_cross_python_version: Some("3.8".into()),
2390 };
2391
2392 let host = triple!("x86_64-unknown-linux-gnu");
2393 let target = triple!("i686-pc-windows-gnu");
2394 let cross_config =
2395 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
2396 .unwrap()
2397 .unwrap();
2398
2399 assert_eq!(
2400 default_cross_compile(&cross_config).unwrap(),
2401 InterpreterConfig {
2402 implementation: PythonImplementation::CPython,
2403 version: PythonVersion { major: 3, minor: 8 },
2404 shared: true,
2405 abi3: false,
2406 lib_name: Some("python38".into()),
2407 lib_dir: Some("/usr/lib/mingw".into()),
2408 executable: None,
2409 pointer_width: None,
2410 build_flags: BuildFlags::default(),
2411 suppress_build_script_link_lines: false,
2412 extra_build_script_lines: vec![],
2413 python_framework_prefix: None,
2414 }
2415 );
2416 }
2417
2418 #[test]
2419 fn unix_hardcoded_cross_compile() {
2420 let env_vars = CrossCompileEnvVars {
2421 pyo3_cross: None,
2422 pyo3_cross_lib_dir: Some("/usr/arm64/lib".into()),
2423 pyo3_cross_python_implementation: None,
2424 pyo3_cross_python_version: Some("3.9".into()),
2425 };
2426
2427 let host = triple!("x86_64-unknown-linux-gnu");
2428 let target = triple!("aarch64-unknown-linux-gnu");
2429 let cross_config =
2430 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target)
2431 .unwrap()
2432 .unwrap();
2433
2434 assert_eq!(
2435 default_cross_compile(&cross_config).unwrap(),
2436 InterpreterConfig {
2437 implementation: PythonImplementation::CPython,
2438 version: PythonVersion { major: 3, minor: 9 },
2439 shared: true,
2440 abi3: false,
2441 lib_name: Some("python3.9".into()),
2442 lib_dir: Some("/usr/arm64/lib".into()),
2443 executable: None,
2444 pointer_width: None,
2445 build_flags: BuildFlags::default(),
2446 suppress_build_script_link_lines: false,
2447 extra_build_script_lines: vec![],
2448 python_framework_prefix: None,
2449 }
2450 );
2451 }
2452
2453 #[test]
2454 fn pypy_hardcoded_cross_compile() {
2455 let env_vars = CrossCompileEnvVars {
2456 pyo3_cross: None,
2457 pyo3_cross_lib_dir: None,
2458 pyo3_cross_python_implementation: Some("PyPy".into()),
2459 pyo3_cross_python_version: Some("3.10".into()),
2460 };
2461
2462 let triple = triple!("x86_64-unknown-linux-gnu");
2463 let cross_config =
2464 CrossCompileConfig::try_from_env_vars_host_target(env_vars, &triple, &triple)
2465 .unwrap()
2466 .unwrap();
2467
2468 assert_eq!(
2469 default_cross_compile(&cross_config).unwrap(),
2470 InterpreterConfig {
2471 implementation: PythonImplementation::PyPy,
2472 version: PythonVersion {
2473 major: 3,
2474 minor: 10
2475 },
2476 shared: true,
2477 abi3: false,
2478 lib_name: Some("pypy3.10-c".into()),
2479 lib_dir: None,
2480 executable: None,
2481 pointer_width: None,
2482 build_flags: BuildFlags::default(),
2483 suppress_build_script_link_lines: false,
2484 extra_build_script_lines: vec![],
2485 python_framework_prefix: None,
2486 }
2487 );
2488 }
2489
2490 #[test]
2491 fn default_lib_name_windows() {
2492 use PythonImplementation::*;
2493 assert_eq!(
2494 super::default_lib_name_windows(
2495 PythonVersion { major: 3, minor: 9 },
2496 CPython,
2497 false,
2498 false,
2499 false,
2500 false,
2501 )
2502 .unwrap(),
2503 "python39",
2504 );
2505 assert!(super::default_lib_name_windows(
2506 PythonVersion { major: 3, minor: 9 },
2507 CPython,
2508 false,
2509 false,
2510 false,
2511 true,
2512 )
2513 .is_err());
2514 assert_eq!(
2515 super::default_lib_name_windows(
2516 PythonVersion { major: 3, minor: 9 },
2517 CPython,
2518 true,
2519 false,
2520 false,
2521 false,
2522 )
2523 .unwrap(),
2524 "python3",
2525 );
2526 assert_eq!(
2527 super::default_lib_name_windows(
2528 PythonVersion { major: 3, minor: 9 },
2529 CPython,
2530 false,
2531 true,
2532 false,
2533 false,
2534 )
2535 .unwrap(),
2536 "python3.9",
2537 );
2538 assert_eq!(
2539 super::default_lib_name_windows(
2540 PythonVersion { major: 3, minor: 9 },
2541 CPython,
2542 true,
2543 true,
2544 false,
2545 false,
2546 )
2547 .unwrap(),
2548 "python3",
2549 );
2550 assert_eq!(
2551 super::default_lib_name_windows(
2552 PythonVersion { major: 3, minor: 9 },
2553 PyPy,
2554 true,
2555 false,
2556 false,
2557 false,
2558 )
2559 .unwrap(),
2560 "python39",
2561 );
2562 assert_eq!(
2563 super::default_lib_name_windows(
2564 PythonVersion { major: 3, minor: 9 },
2565 CPython,
2566 false,
2567 false,
2568 true,
2569 false,
2570 )
2571 .unwrap(),
2572 "python39_d",
2573 );
2574 assert_eq!(
2577 super::default_lib_name_windows(
2578 PythonVersion { major: 3, minor: 9 },
2579 CPython,
2580 true,
2581 false,
2582 true,
2583 false,
2584 )
2585 .unwrap(),
2586 "python39_d",
2587 );
2588 assert_eq!(
2589 super::default_lib_name_windows(
2590 PythonVersion {
2591 major: 3,
2592 minor: 10
2593 },
2594 CPython,
2595 true,
2596 false,
2597 true,
2598 false,
2599 )
2600 .unwrap(),
2601 "python3_d",
2602 );
2603 assert!(super::default_lib_name_windows(
2605 PythonVersion {
2606 major: 3,
2607 minor: 12,
2608 },
2609 CPython,
2610 false,
2611 false,
2612 false,
2613 true,
2614 )
2615 .is_err());
2616 assert!(super::default_lib_name_windows(
2618 PythonVersion {
2619 major: 3,
2620 minor: 12,
2621 },
2622 CPython,
2623 false,
2624 true,
2625 false,
2626 true,
2627 )
2628 .is_err());
2629 assert_eq!(
2630 super::default_lib_name_windows(
2631 PythonVersion {
2632 major: 3,
2633 minor: 13
2634 },
2635 CPython,
2636 false,
2637 false,
2638 false,
2639 true,
2640 )
2641 .unwrap(),
2642 "python313t",
2643 );
2644 assert_eq!(
2645 super::default_lib_name_windows(
2646 PythonVersion {
2647 major: 3,
2648 minor: 13
2649 },
2650 CPython,
2651 true, false,
2653 false,
2654 true,
2655 )
2656 .unwrap(),
2657 "python313t",
2658 );
2659 assert_eq!(
2660 super::default_lib_name_windows(
2661 PythonVersion {
2662 major: 3,
2663 minor: 13
2664 },
2665 CPython,
2666 false,
2667 false,
2668 true,
2669 true,
2670 )
2671 .unwrap(),
2672 "python313t_d",
2673 );
2674 }
2675
2676 #[test]
2677 fn default_lib_name_unix() {
2678 use PythonImplementation::*;
2679 assert_eq!(
2681 super::default_lib_name_unix(
2682 PythonVersion { major: 3, minor: 7 },
2683 CPython,
2684 None,
2685 false
2686 )
2687 .unwrap(),
2688 "python3.7m",
2689 );
2690 assert_eq!(
2692 super::default_lib_name_unix(
2693 PythonVersion { major: 3, minor: 8 },
2694 CPython,
2695 None,
2696 false
2697 )
2698 .unwrap(),
2699 "python3.8",
2700 );
2701 assert_eq!(
2702 super::default_lib_name_unix(
2703 PythonVersion { major: 3, minor: 9 },
2704 CPython,
2705 None,
2706 false
2707 )
2708 .unwrap(),
2709 "python3.9",
2710 );
2711 assert_eq!(
2713 super::default_lib_name_unix(
2714 PythonVersion { major: 3, minor: 9 },
2715 CPython,
2716 Some("3.7md"),
2717 false
2718 )
2719 .unwrap(),
2720 "python3.7md",
2721 );
2722
2723 assert_eq!(
2725 super::default_lib_name_unix(PythonVersion { major: 3, minor: 9 }, PyPy, None, false)
2726 .unwrap(),
2727 "pypy3.9-c",
2728 );
2729
2730 assert_eq!(
2731 super::default_lib_name_unix(
2732 PythonVersion { major: 3, minor: 9 },
2733 PyPy,
2734 Some("3.9d"),
2735 false
2736 )
2737 .unwrap(),
2738 "pypy3.9d-c",
2739 );
2740
2741 assert_eq!(
2743 super::default_lib_name_unix(
2744 PythonVersion {
2745 major: 3,
2746 minor: 13
2747 },
2748 CPython,
2749 None,
2750 true
2751 )
2752 .unwrap(),
2753 "python3.13t",
2754 );
2755 assert!(super::default_lib_name_unix(
2757 PythonVersion {
2758 major: 3,
2759 minor: 12,
2760 },
2761 CPython,
2762 None,
2763 true,
2764 )
2765 .is_err());
2766 }
2767
2768 #[test]
2769 fn parse_cross_python_version() {
2770 let env_vars = CrossCompileEnvVars {
2771 pyo3_cross: None,
2772 pyo3_cross_lib_dir: None,
2773 pyo3_cross_python_version: Some("3.9".into()),
2774 pyo3_cross_python_implementation: None,
2775 };
2776
2777 assert_eq!(
2778 env_vars.parse_version().unwrap(),
2779 (Some(PythonVersion { major: 3, minor: 9 }), None),
2780 );
2781
2782 let env_vars = CrossCompileEnvVars {
2783 pyo3_cross: None,
2784 pyo3_cross_lib_dir: None,
2785 pyo3_cross_python_version: None,
2786 pyo3_cross_python_implementation: None,
2787 };
2788
2789 assert_eq!(env_vars.parse_version().unwrap(), (None, None));
2790
2791 let env_vars = CrossCompileEnvVars {
2792 pyo3_cross: None,
2793 pyo3_cross_lib_dir: None,
2794 pyo3_cross_python_version: Some("3.13t".into()),
2795 pyo3_cross_python_implementation: None,
2796 };
2797
2798 assert_eq!(
2799 env_vars.parse_version().unwrap(),
2800 (
2801 Some(PythonVersion {
2802 major: 3,
2803 minor: 13
2804 }),
2805 Some("t".into())
2806 ),
2807 );
2808
2809 let env_vars = CrossCompileEnvVars {
2810 pyo3_cross: None,
2811 pyo3_cross_lib_dir: None,
2812 pyo3_cross_python_version: Some("100".into()),
2813 pyo3_cross_python_implementation: None,
2814 };
2815
2816 assert!(env_vars.parse_version().is_err());
2817 }
2818
2819 #[test]
2820 fn interpreter_version_reduced_to_abi3() {
2821 let mut config = InterpreterConfig {
2822 abi3: true,
2823 build_flags: BuildFlags::default(),
2824 pointer_width: None,
2825 executable: None,
2826 implementation: PythonImplementation::CPython,
2827 lib_dir: None,
2828 lib_name: None,
2829 shared: true,
2830 version: PythonVersion { major: 3, minor: 7 },
2831 suppress_build_script_link_lines: false,
2832 extra_build_script_lines: vec![],
2833 python_framework_prefix: None,
2834 };
2835
2836 config
2837 .fixup_for_abi3_version(Some(PythonVersion { major: 3, minor: 7 }))
2838 .unwrap();
2839 assert_eq!(config.version, PythonVersion { major: 3, minor: 7 });
2840 }
2841
2842 #[test]
2843 fn abi3_version_cannot_be_higher_than_interpreter() {
2844 let mut config = InterpreterConfig {
2845 abi3: true,
2846 build_flags: BuildFlags::new(),
2847 pointer_width: None,
2848 executable: None,
2849 implementation: PythonImplementation::CPython,
2850 lib_dir: None,
2851 lib_name: None,
2852 shared: true,
2853 version: PythonVersion { major: 3, minor: 7 },
2854 suppress_build_script_link_lines: false,
2855 extra_build_script_lines: vec![],
2856 python_framework_prefix: None,
2857 };
2858
2859 assert!(config
2860 .fixup_for_abi3_version(Some(PythonVersion { major: 3, minor: 8 }))
2861 .unwrap_err()
2862 .to_string()
2863 .contains(
2864 "cannot set a minimum Python version 3.8 higher than the interpreter version 3.7"
2865 ));
2866 }
2867
2868 #[test]
2869 #[cfg(all(
2870 target_os = "linux",
2871 target_arch = "x86_64",
2872 feature = "resolve-config"
2873 ))]
2874 fn parse_sysconfigdata() {
2875 let interpreter_config = crate::get();
2880
2881 let lib_dir = match &interpreter_config.lib_dir {
2882 Some(lib_dir) => Path::new(lib_dir),
2883 None => return,
2885 };
2886
2887 let cross = CrossCompileConfig {
2888 lib_dir: Some(lib_dir.into()),
2889 version: Some(interpreter_config.version),
2890 implementation: Some(interpreter_config.implementation),
2891 target: triple!("x86_64-unknown-linux-gnu"),
2892 abiflags: if interpreter_config.is_free_threaded() {
2893 Some("t".into())
2894 } else {
2895 None
2896 },
2897 };
2898
2899 let sysconfigdata_path = match find_sysconfigdata(&cross) {
2900 Ok(Some(path)) => path,
2901 _ => return,
2903 };
2904 let sysconfigdata = super::parse_sysconfigdata(sysconfigdata_path).unwrap();
2905 let parsed_config = InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap();
2906
2907 assert_eq!(
2908 parsed_config,
2909 InterpreterConfig {
2910 abi3: false,
2911 build_flags: BuildFlags(interpreter_config.build_flags.0.clone()),
2912 pointer_width: Some(64),
2913 executable: None,
2914 implementation: PythonImplementation::CPython,
2915 lib_dir: interpreter_config.lib_dir.to_owned(),
2916 lib_name: interpreter_config.lib_name.to_owned(),
2917 shared: true,
2918 version: interpreter_config.version,
2919 suppress_build_script_link_lines: false,
2920 extra_build_script_lines: vec![],
2921 python_framework_prefix: None,
2922 }
2923 )
2924 }
2925
2926 #[test]
2927 fn test_venv_interpreter() {
2928 let base = OsStr::new("base");
2929 assert_eq!(
2930 venv_interpreter(base, true),
2931 PathBuf::from_iter(&["base", "Scripts", "python.exe"])
2932 );
2933 assert_eq!(
2934 venv_interpreter(base, false),
2935 PathBuf::from_iter(&["base", "bin", "python"])
2936 );
2937 }
2938
2939 #[test]
2940 fn test_conda_env_interpreter() {
2941 let base = OsStr::new("base");
2942 assert_eq!(
2943 conda_env_interpreter(base, true),
2944 PathBuf::from_iter(&["base", "python.exe"])
2945 );
2946 assert_eq!(
2947 conda_env_interpreter(base, false),
2948 PathBuf::from_iter(&["base", "bin", "python"])
2949 );
2950 }
2951
2952 #[test]
2953 fn test_not_cross_compiling_from_to() {
2954 assert!(cross_compiling_from_to(
2955 &triple!("x86_64-unknown-linux-gnu"),
2956 &triple!("x86_64-unknown-linux-gnu"),
2957 )
2958 .unwrap()
2959 .is_none());
2960
2961 assert!(cross_compiling_from_to(
2962 &triple!("x86_64-apple-darwin"),
2963 &triple!("x86_64-apple-darwin")
2964 )
2965 .unwrap()
2966 .is_none());
2967
2968 assert!(cross_compiling_from_to(
2969 &triple!("aarch64-apple-darwin"),
2970 &triple!("x86_64-apple-darwin")
2971 )
2972 .unwrap()
2973 .is_none());
2974
2975 assert!(cross_compiling_from_to(
2976 &triple!("x86_64-apple-darwin"),
2977 &triple!("aarch64-apple-darwin")
2978 )
2979 .unwrap()
2980 .is_none());
2981
2982 assert!(cross_compiling_from_to(
2983 &triple!("x86_64-pc-windows-msvc"),
2984 &triple!("i686-pc-windows-msvc")
2985 )
2986 .unwrap()
2987 .is_none());
2988
2989 assert!(cross_compiling_from_to(
2990 &triple!("x86_64-unknown-linux-gnu"),
2991 &triple!("x86_64-unknown-linux-musl")
2992 )
2993 .unwrap()
2994 .is_none());
2995 }
2996
2997 #[test]
2998 fn test_is_cross_compiling_from_to() {
2999 assert!(cross_compiling_from_to(
3000 &triple!("x86_64-pc-windows-msvc"),
3001 &triple!("aarch64-pc-windows-msvc")
3002 )
3003 .unwrap()
3004 .is_some());
3005 }
3006
3007 #[test]
3008 fn test_run_python_script() {
3009 let interpreter = make_interpreter_config()
3011 .expect("could not get InterpreterConfig from installed interpreter");
3012 let out = interpreter
3013 .run_python_script("print(2 + 2)")
3014 .expect("failed to run Python script");
3015 assert_eq!(out.trim_end(), "4");
3016 }
3017
3018 #[test]
3019 fn test_run_python_script_with_envs() {
3020 let interpreter = make_interpreter_config()
3022 .expect("could not get InterpreterConfig from installed interpreter");
3023 let out = interpreter
3024 .run_python_script_with_envs(
3025 "import os; print(os.getenv('PYO3_TEST'))",
3026 vec![("PYO3_TEST", "42")],
3027 )
3028 .expect("failed to run Python script");
3029 assert_eq!(out.trim_end(), "42");
3030 }
3031
3032 #[test]
3033 fn test_build_script_outputs_base() {
3034 let interpreter_config = InterpreterConfig {
3035 implementation: PythonImplementation::CPython,
3036 version: PythonVersion { major: 3, minor: 9 },
3037 shared: true,
3038 abi3: false,
3039 lib_name: Some("python3".into()),
3040 lib_dir: None,
3041 executable: None,
3042 pointer_width: None,
3043 build_flags: BuildFlags::default(),
3044 suppress_build_script_link_lines: false,
3045 extra_build_script_lines: vec![],
3046 python_framework_prefix: None,
3047 };
3048 assert_eq!(
3049 interpreter_config.build_script_outputs(),
3050 [
3051 "cargo:rustc-cfg=Py_3_7".to_owned(),
3052 "cargo:rustc-cfg=Py_3_8".to_owned(),
3053 "cargo:rustc-cfg=Py_3_9".to_owned(),
3054 ]
3055 );
3056
3057 let interpreter_config = InterpreterConfig {
3058 implementation: PythonImplementation::PyPy,
3059 ..interpreter_config
3060 };
3061 assert_eq!(
3062 interpreter_config.build_script_outputs(),
3063 [
3064 "cargo:rustc-cfg=Py_3_7".to_owned(),
3065 "cargo:rustc-cfg=Py_3_8".to_owned(),
3066 "cargo:rustc-cfg=Py_3_9".to_owned(),
3067 "cargo:rustc-cfg=PyPy".to_owned(),
3068 ]
3069 );
3070 }
3071
3072 #[test]
3073 fn test_build_script_outputs_abi3() {
3074 let interpreter_config = InterpreterConfig {
3075 implementation: PythonImplementation::CPython,
3076 version: PythonVersion { major: 3, minor: 9 },
3077 shared: true,
3078 abi3: true,
3079 lib_name: Some("python3".into()),
3080 lib_dir: None,
3081 executable: None,
3082 pointer_width: None,
3083 build_flags: BuildFlags::default(),
3084 suppress_build_script_link_lines: false,
3085 extra_build_script_lines: vec![],
3086 python_framework_prefix: None,
3087 };
3088
3089 assert_eq!(
3090 interpreter_config.build_script_outputs(),
3091 [
3092 "cargo:rustc-cfg=Py_3_7".to_owned(),
3093 "cargo:rustc-cfg=Py_3_8".to_owned(),
3094 "cargo:rustc-cfg=Py_3_9".to_owned(),
3095 "cargo:rustc-cfg=Py_LIMITED_API".to_owned(),
3096 ]
3097 );
3098
3099 let interpreter_config = InterpreterConfig {
3100 implementation: PythonImplementation::PyPy,
3101 ..interpreter_config
3102 };
3103 assert_eq!(
3104 interpreter_config.build_script_outputs(),
3105 [
3106 "cargo:rustc-cfg=Py_3_7".to_owned(),
3107 "cargo:rustc-cfg=Py_3_8".to_owned(),
3108 "cargo:rustc-cfg=Py_3_9".to_owned(),
3109 "cargo:rustc-cfg=PyPy".to_owned(),
3110 "cargo:rustc-cfg=Py_LIMITED_API".to_owned(),
3111 ]
3112 );
3113 }
3114
3115 #[test]
3116 fn test_build_script_outputs_gil_disabled() {
3117 let mut build_flags = BuildFlags::default();
3118 build_flags.0.insert(BuildFlag::Py_GIL_DISABLED);
3119 let interpreter_config = InterpreterConfig {
3120 implementation: PythonImplementation::CPython,
3121 version: PythonVersion {
3122 major: 3,
3123 minor: 13,
3124 },
3125 shared: true,
3126 abi3: false,
3127 lib_name: Some("python3".into()),
3128 lib_dir: None,
3129 executable: None,
3130 pointer_width: None,
3131 build_flags,
3132 suppress_build_script_link_lines: false,
3133 extra_build_script_lines: vec![],
3134 python_framework_prefix: None,
3135 };
3136
3137 assert_eq!(
3138 interpreter_config.build_script_outputs(),
3139 [
3140 "cargo:rustc-cfg=Py_3_7".to_owned(),
3141 "cargo:rustc-cfg=Py_3_8".to_owned(),
3142 "cargo:rustc-cfg=Py_3_9".to_owned(),
3143 "cargo:rustc-cfg=Py_3_10".to_owned(),
3144 "cargo:rustc-cfg=Py_3_11".to_owned(),
3145 "cargo:rustc-cfg=Py_3_12".to_owned(),
3146 "cargo:rustc-cfg=Py_3_13".to_owned(),
3147 "cargo:rustc-cfg=Py_GIL_DISABLED".to_owned(),
3148 ]
3149 );
3150 }
3151
3152 #[test]
3153 fn test_build_script_outputs_debug() {
3154 let mut build_flags = BuildFlags::default();
3155 build_flags.0.insert(BuildFlag::Py_DEBUG);
3156 let interpreter_config = InterpreterConfig {
3157 implementation: PythonImplementation::CPython,
3158 version: PythonVersion { major: 3, minor: 7 },
3159 shared: true,
3160 abi3: false,
3161 lib_name: Some("python3".into()),
3162 lib_dir: None,
3163 executable: None,
3164 pointer_width: None,
3165 build_flags,
3166 suppress_build_script_link_lines: false,
3167 extra_build_script_lines: vec![],
3168 python_framework_prefix: None,
3169 };
3170
3171 assert_eq!(
3172 interpreter_config.build_script_outputs(),
3173 [
3174 "cargo:rustc-cfg=Py_3_7".to_owned(),
3175 "cargo:rustc-cfg=py_sys_config=\"Py_DEBUG\"".to_owned(),
3176 ]
3177 );
3178 }
3179
3180 #[test]
3181 fn test_find_sysconfigdata_in_invalid_lib_dir() {
3182 let e = find_all_sysconfigdata(&CrossCompileConfig {
3183 lib_dir: Some(PathBuf::from("/abc/123/not/a/real/path")),
3184 version: None,
3185 implementation: None,
3186 target: triple!("x86_64-unknown-linux-gnu"),
3187 abiflags: None,
3188 })
3189 .unwrap_err();
3190
3191 assert!(e.report().to_string().starts_with(
3193 "failed to search the lib dir at 'PYO3_CROSS_LIB_DIR=/abc/123/not/a/real/path'\n\
3194 caused by:\n \
3195 - 0: failed to list the entries in '/abc/123/not/a/real/path'\n \
3196 - 1: \
3197 "
3198 ));
3199 }
3200
3201 #[test]
3202 fn test_from_pyo3_config_file_env_rebuild() {
3203 READ_ENV_VARS.with(|vars| vars.borrow_mut().clear());
3204 let _ = InterpreterConfig::from_pyo3_config_file_env();
3205 READ_ENV_VARS.with(|vars| assert!(vars.borrow().contains(&"PYO3_CONFIG_FILE".to_string())));
3207 }
3208}