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