Skip to content

Commit 480f280

Browse files
committed
pyo3-build-config: fix windows "cross-compile" panic
1 parent 9ef7987 commit 480f280

File tree

3 files changed

+114
-66
lines changed

3 files changed

+114
-66
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
## [Unreleased]
1010

11+
### Fixed
12+
13+
- Panic during compilation when `PYO3_CROSS_LIB_DIR` is set for some host/target combinations. [#2232](https://github.com/PyO3/pyo3/pull/2232)
14+
1115
## [0.16.2] - 2022-03-15
1216

1317
### Packaging

pyo3-build-config/src/impl_.rs

Lines changed: 109 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -568,35 +568,54 @@ pub struct CrossCompileConfig {
568568
os: String,
569569
}
570570

571-
#[allow(unused)]
572-
pub fn any_cross_compiling_env_vars_set() -> bool {
573-
env::var_os("PYO3_CROSS").is_some()
574-
|| env::var_os("PYO3_CROSS_LIB_DIR").is_some()
575-
|| env::var_os("PYO3_CROSS_PYTHON_VERSION").is_some()
571+
pub(crate) struct CrossCompileEnvVars {
572+
pyo3_cross: Option<OsString>,
573+
pyo3_cross_lib_dir: Option<OsString>,
574+
pyo3_cross_python_version: Option<OsString>,
576575
}
577576

578-
fn cross_compiling_from_cargo_env() -> Result<Option<CrossCompileConfig>> {
579-
let host = cargo_env_var("HOST").ok_or("expected HOST env var")?;
580-
let target = cargo_env_var("TARGET").ok_or("expected TARGET env var")?;
581-
582-
if host == target {
583-
// Definitely not cross compiling if the host matches the target
584-
return Ok(None);
577+
impl CrossCompileEnvVars {
578+
pub fn any(&self) -> bool {
579+
self.pyo3_cross.is_some()
580+
|| self.pyo3_cross_lib_dir.is_some()
581+
|| self.pyo3_cross_python_version.is_some()
585582
}
583+
}
586584

587-
if target == "i686-pc-windows-msvc" && host == "x86_64-pc-windows-msvc" {
588-
// Not cross-compiling to compile for 32-bit Python from windows 64-bit
589-
return Ok(None);
585+
pub(crate) fn cross_compile_env_vars() -> CrossCompileEnvVars {
586+
CrossCompileEnvVars {
587+
pyo3_cross: env::var_os("PYO3_CROSS"),
588+
pyo3_cross_lib_dir: env::var_os("PYO3_CROSS_LIB_DIR"),
589+
pyo3_cross_python_version: env::var_os("PYO3_CROSS_PYTHON_VERSION"),
590590
}
591+
}
591592

592-
let target_arch =
593-
cargo_env_var("CARGO_CFG_TARGET_ARCH").ok_or("expected CARGO_CFG_TARGET_ARCH env var")?;
594-
let target_vendor = cargo_env_var("CARGO_CFG_TARGET_VENDOR")
595-
.ok_or("expected CARGO_CFG_TARGET_VENDOR env var")?;
596-
let target_os =
597-
cargo_env_var("CARGO_CFG_TARGET_OS").ok_or("expected CARGO_CFG_TARGET_OS env var")?;
598-
599-
cross_compiling(&host, &target_arch, &target_vendor, &target_os)
593+
fn cross_compiling_from_vars(
594+
target_arch: &str,
595+
target_os: &str,
596+
target_vendor: &str,
597+
cross_compile_vars: CrossCompileEnvVars,
598+
) -> Result<CrossCompileConfig> {
599+
Ok(CrossCompileConfig {
600+
lib_dir: cross_compile_vars
601+
.pyo3_cross_lib_dir
602+
.ok_or("The PYO3_CROSS_LIB_DIR environment variable must be set when cross-compiling")?
603+
.into(),
604+
arch: target_arch.into(),
605+
vendor: target_vendor.into(),
606+
os: target_os.into(),
607+
version: cross_compile_vars
608+
.pyo3_cross_python_version
609+
.map(|os_string| {
610+
let utf8_str = os_string
611+
.to_str()
612+
.ok_or("PYO3_CROSS_PYTHON_VERSION is not valid utf-8.")?;
613+
utf8_str
614+
.parse()
615+
.context("failed to parse PYO3_CROSS_PYTHON_VERSION")
616+
})
617+
.transpose()?,
618+
})
600619
}
601620

602621
/// Detect whether we are cross compiling and return an assembled CrossCompileConfig if so.
@@ -619,52 +638,24 @@ pub fn cross_compiling(
619638
target_vendor: &str,
620639
target_os: &str,
621640
) -> Result<Option<CrossCompileConfig>> {
622-
let cross = env_var("PYO3_CROSS");
623-
let cross_lib_dir = env_var("PYO3_CROSS_LIB_DIR");
624-
let cross_python_version = env_var("PYO3_CROSS_PYTHON_VERSION");
625-
626-
let target_triple = format!(
627-
"{}-{}-{}",
628-
target_arch,
629-
target_vendor,
630-
if target_os == "macos" {
631-
"darwin"
632-
} else {
633-
target_os
634-
}
635-
);
636-
637-
if cross.is_none() && cross_lib_dir.is_none() && cross_python_version.is_none() {
638-
// No cross-compiling environment variables set; try to determine if this is a known case
639-
// which is not cross-compilation.
641+
let env_vars = cross_compile_env_vars();
640642

641-
if target_triple == "x86_64-apple-darwin" && host == "aarch64-apple-darwin" {
642-
// Not cross-compiling to compile for x86-64 Python from macOS arm64
643-
return Ok(None);
644-
}
645-
646-
if target_triple == "aarch64-apple-darwin" && host == "x86_64-apple-darwin" {
647-
// Not cross-compiling to compile for arm64 Python from macOS x86_64
648-
return Ok(None);
649-
}
650-
651-
if host.starts_with(&target_triple) {
652-
// Not cross-compiling if arch-vendor-os is all the same
653-
// e.g. x86_64-unknown-linux-musl on x86_64-unknown-linux-gnu host
654-
return Ok(None);
655-
}
643+
if !env_vars.any() && is_not_cross_compiling(host, target_arch, target_vendor, target_os) {
644+
return Ok(None);
656645
}
657646

658647
// At this point we assume that we are cross compiling.
659648

660649
Ok(Some(CrossCompileConfig {
661-
lib_dir: cross_lib_dir
650+
lib_dir: env_vars
651+
.pyo3_cross_lib_dir
662652
.ok_or("The PYO3_CROSS_LIB_DIR environment variable must be set when cross-compiling")?
663653
.into(),
664654
arch: target_arch.into(),
665655
vendor: target_vendor.into(),
666656
os: target_os.into(),
667-
version: cross_python_version
657+
version: env_vars
658+
.pyo3_cross_python_version
668659
.map(|os_string| {
669660
let utf8_str = os_string
670661
.to_str()
@@ -677,6 +668,39 @@ pub fn cross_compiling(
677668
}))
678669
}
679670

671+
fn is_not_cross_compiling(
672+
host: &str,
673+
target_arch: &str,
674+
target_vendor: &str,
675+
target_os: &str,
676+
) -> bool {
677+
let target_triple = format!(
678+
"{}-{}-{}",
679+
if target_arch == "x86" {
680+
"i686"
681+
} else {
682+
target_arch
683+
},
684+
target_vendor,
685+
if target_os == "macos" {
686+
"darwin"
687+
} else {
688+
target_os
689+
}
690+
);
691+
692+
// Not cross-compiling if arch-vendor-os is all the same
693+
// e.g. x86_64-unknown-linux-musl on x86_64-unknown-linux-gnu host
694+
// x86_64-pc-windows-gnu on x86_64-pc-windows-msvc host
695+
host.starts_with(&target_triple)
696+
// Not cross-compiling to compile for 32-bit Python from windows 64-bit
697+
|| (target_triple == "i686-pc-windows" && host.starts_with("x86_64-pc-windows"))
698+
// Not cross-compiling to compile for x86-64 Python from macOS arm64
699+
|| (target_triple == "x86_64-apple-darwin" && host == "aarch64-apple-darwin")
700+
// Not cross-compiling to compile for arm64 Python from macOS x86_64
701+
|| (target_triple == "aarch64-apple-darwin" && host == "x86_64-apple-darwin")
702+
}
703+
680704
#[allow(non_camel_case_types)]
681705
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
682706
pub enum BuildFlag {
@@ -1280,13 +1304,33 @@ fn fixup_config_for_abi3(
12801304
/// This must be called from PyO3's build script, because it relies on environment variables such as
12811305
/// CARGO_CFG_TARGET_OS which aren't available at any other time.
12821306
pub fn make_cross_compile_config() -> Result<Option<InterpreterConfig>> {
1283-
let mut interpreter_config = if let Some(paths) = cross_compiling_from_cargo_env()? {
1284-
load_cross_compile_config(paths)?
1285-
} else {
1286-
return Ok(None);
1287-
};
1288-
fixup_config_for_abi3(&mut interpreter_config, get_abi3_version())?;
1289-
Ok(Some(interpreter_config))
1307+
let env_vars = cross_compile_env_vars();
1308+
1309+
let host = cargo_env_var("HOST").ok_or("expected HOST env var")?;
1310+
let target = cargo_env_var("TARGET").ok_or("expected TARGET env var")?;
1311+
1312+
let target_arch =
1313+
cargo_env_var("CARGO_CFG_TARGET_ARCH").ok_or("expected CARGO_CFG_TARGET_ARCH env var")?;
1314+
let target_vendor = cargo_env_var("CARGO_CFG_TARGET_VENDOR")
1315+
.ok_or("expected CARGO_CFG_TARGET_VENDOR env var")?;
1316+
let target_os =
1317+
cargo_env_var("CARGO_CFG_TARGET_OS").ok_or("expected CARGO_CFG_TARGET_OS env var")?;
1318+
1319+
if env_vars.any() {
1320+
let cross_config =
1321+
cross_compiling_from_vars(&target_arch, &target_os, &target_vendor, env_vars)?;
1322+
let mut interpreter_config = load_cross_compile_config(cross_config)?;
1323+
fixup_config_for_abi3(&mut interpreter_config, get_abi3_version())?;
1324+
return Ok(Some(interpreter_config));
1325+
}
1326+
1327+
ensure!(
1328+
host == target || is_not_cross_compiling(&host, &target_arch, &target_os, &target_vendor),
1329+
"PyO3 detected compile host {host} and build target {target}, but none of PYO3_CROSS, PYO3_CROSS_LIB_DIR \
1330+
or PYO3_CROSS_PYTHON_VERSION environment variables are set."
1331+
);
1332+
1333+
Ok(None)
12901334
}
12911335

12921336
/// Generates an interpreter config which will be hard-coded into the pyo3-build-config crate.

pyo3-build-config/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ pub fn get() -> &'static InterpreterConfig {
7070
InterpreterConfig::from_reader(Cursor::new(CONFIG_FILE))
7171
} else if !ABI3_CONFIG.is_empty() {
7272
Ok(abi3_config())
73-
} else if impl_::any_cross_compiling_env_vars_set() {
73+
} else if impl_::cross_compile_env_vars().any() {
7474
InterpreterConfig::from_path(DEFAULT_CROSS_COMPILE_CONFIG_PATH)
7575
} else {
7676
InterpreterConfig::from_reader(Cursor::new(HOST_CONFIG))

0 commit comments

Comments
 (0)