build: make include dir optional when targeting Windows

This commit is contained in:
David Hewitt 2020-12-31 00:55:03 +00:00
parent cc6fc483a6
commit a350dd2c20
3 changed files with 74 additions and 23 deletions

View File

@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Added ### Added
- Add support for `#[pyclass(dict)]` and `#[pyclass(weakref)]` with the `abi3` feature on Python 3.9 and up. [#1342](https://github.com/PyO3/pyo3/pull/1342) - Add support for `#[pyclass(dict)]` and `#[pyclass(weakref)]` with the `abi3` feature on Python 3.9 and up. [#1342](https://github.com/PyO3/pyo3/pull/1342)
- Add FFI definitions `PyOS_BeforeFork`, `PyOS_AfterFork_Parent`, `PyOS_AfterFork_Child` for Python 3.7 and up. [#1348](https://github.com/PyO3/pyo3/pull/1348) - Add FFI definitions `PyOS_BeforeFork`, `PyOS_AfterFork_Parent`, `PyOS_AfterFork_Child` for Python 3.7 and up. [#1348](https://github.com/PyO3/pyo3/pull/1348)
- Add support for cross-compiling to Windows without needing `PYO3_CROSS_INCLUDE_DIR`. [#1350](https://github.com/PyO3/pyo3/pull/1350)
### Changed ### Changed
- Deprecate FFI definitions `PyEval_CallObjectWithKeywords`, `PyEval_CallObject`, `PyEval_CallFunction`, `PyEval_CallMethod` when building for Python 3.9. [#1338](https://github.com/PyO3/pyo3/pull/1338) - Deprecate FFI definitions `PyEval_CallObjectWithKeywords`, `PyEval_CallObject`, `PyEval_CallFunction`, `PyEval_CallMethod` when building for Python 3.9. [#1338](https://github.com/PyO3/pyo3/pull/1338)

View File

@ -97,9 +97,7 @@ struct CrossCompileConfig {
impl CrossCompileConfig { impl CrossCompileConfig {
fn both() -> Result<Self> { fn both() -> Result<Self> {
Ok(CrossCompileConfig { Ok(CrossCompileConfig {
include_dir: Some(CrossCompileConfig::validate_variable( include_dir: env::var_os("PYO3_CROSS_INCLUDE_DIR").map(Into::into),
"PYO3_CROSS_INCLUDE_DIR",
)?),
..CrossCompileConfig::lib_only()? ..CrossCompileConfig::lib_only()?
}) })
} }
@ -372,9 +370,9 @@ fn build_flags_from_config_map(config_map: &HashMap<String, String>) -> HashSet<
/// ///
/// [1]: https://github.com/python/cpython/blob/3.8/Lib/sysconfig.py#L348 /// [1]: https://github.com/python/cpython/blob/3.8/Lib/sysconfig.py#L348
fn load_cross_compile_from_sysconfigdata( fn load_cross_compile_from_sysconfigdata(
python_paths: CrossCompileConfig, cross_compile_config: CrossCompileConfig,
) -> Result<(InterpreterConfig, HashSet<BuildFlag>)> { ) -> Result<(InterpreterConfig, HashSet<BuildFlag>)> {
let sysconfig_path = find_sysconfigdata(&python_paths)?; let sysconfig_path = find_sysconfigdata(&cross_compile_config)?;
let sysconfig_data = parse_sysconfigdata(sysconfig_path)?; let sysconfig_data = parse_sysconfigdata(sysconfig_path)?;
let major = sysconfig_data.get_numeric("version_major")?; let major = sysconfig_data.get_numeric("version_major")?;
@ -393,7 +391,7 @@ fn load_cross_compile_from_sysconfigdata(
let interpreter_config = InterpreterConfig { let interpreter_config = InterpreterConfig {
version: python_version, version: python_version,
libdir: python_paths.lib_dir.to_str().map(String::from), libdir: cross_compile_config.lib_dir.to_str().map(String::from),
shared: sysconfig_data.get_bool("Py_ENABLE_SHARED")?, shared: sysconfig_data.get_bool("Py_ENABLE_SHARED")?,
ld_version, ld_version,
base_prefix: "".to_string(), base_prefix: "".to_string(),
@ -407,9 +405,9 @@ fn load_cross_compile_from_sysconfigdata(
} }
fn load_cross_compile_from_headers( fn load_cross_compile_from_headers(
python_paths: CrossCompileConfig, cross_compile_config: CrossCompileConfig,
) -> Result<(InterpreterConfig, HashSet<BuildFlag>)> { ) -> Result<(InterpreterConfig, HashSet<BuildFlag>)> {
let python_include_dir = python_paths.include_dir.unwrap(); let python_include_dir = cross_compile_config.include_dir.unwrap();
let python_include_dir = Path::new(&python_include_dir); let python_include_dir = Path::new(&python_include_dir);
let patchlevel_defines = parse_header_defines(python_include_dir.join("patchlevel.h"))?; let patchlevel_defines = parse_header_defines(python_include_dir.join("patchlevel.h"))?;
@ -426,7 +424,7 @@ fn load_cross_compile_from_headers(
let interpreter_config = InterpreterConfig { let interpreter_config = InterpreterConfig {
version: python_version, version: python_version,
libdir: python_paths.lib_dir.to_str().map(String::from), libdir: cross_compile_config.lib_dir.to_str().map(String::from),
shared: config_data.get_bool("Py_ENABLE_SHARED")?, shared: config_data.get_bool("Py_ENABLE_SHARED")?,
ld_version: format!("{}.{}", major, minor), ld_version: format!("{}.{}", major, minor),
base_prefix: "".to_string(), base_prefix: "".to_string(),
@ -439,17 +437,60 @@ fn load_cross_compile_from_headers(
Ok((interpreter_config, build_flags)) Ok((interpreter_config, build_flags))
} }
fn windows_hardcoded_cross_compile(
cross_compile_config: CrossCompileConfig,
) -> Result<(InterpreterConfig, HashSet<BuildFlag>)> {
let (major, minor) = if let Some(version) = cross_compile_config.version {
let mut parts = version.split('.');
match (
parts.next().and_then(|major| major.parse().ok()),
parts.next().and_then(|minor| minor.parse().ok()),
parts.next(),
) {
(Some(major), Some(minor), None) => (major, minor),
_ => bail!(
"Expected major.minor version (e.g. 3.9) for PYO3_CROSS_VERSION, got `{}`",
version
),
}
} else if let Some(minor_version) = get_abi3_minor_version() {
(3, minor_version)
} else {
bail!("One of PYO3_CROSS_INCLUDE_DIR, PYO3_CROSS_PYTHON_VERSION, or an abi3-py3* feature must be specified when cross-compiling for Windows.")
};
let python_version = PythonVersion {
major,
minor,
implementation: PythonInterpreterKind::CPython,
};
let interpreter_config = InterpreterConfig {
version: python_version,
libdir: cross_compile_config.lib_dir.to_str().map(String::from),
shared: true,
ld_version: format!("{}.{}", major, minor),
base_prefix: "".to_string(),
executable: PathBuf::new(),
calcsize_pointer: None,
};
Ok((interpreter_config, get_build_flags_windows()?))
}
fn load_cross_compile_info( fn load_cross_compile_info(
python_paths: CrossCompileConfig, cross_compile_config: CrossCompileConfig,
) -> Result<(InterpreterConfig, HashSet<BuildFlag>)> { ) -> Result<(InterpreterConfig, HashSet<BuildFlag>)> {
let target_family = env::var("CARGO_CFG_TARGET_FAMILY")?; let target_family = env::var("CARGO_CFG_TARGET_FAMILY")?;
// Because compiling for windows on linux still includes the unix target family // Because compiling for windows on linux still includes the unix target family
if target_family == "unix" { if target_family == "unix" {
// Configure for unix platforms using the sysconfigdata file // Configure for unix platforms using the sysconfigdata file
load_cross_compile_from_sysconfigdata(python_paths) load_cross_compile_from_sysconfigdata(cross_compile_config)
} else { } else if cross_compile_config.include_dir.is_some() {
// Must configure by headers on windows platform // Must configure by headers on windows platform
load_cross_compile_from_headers(python_paths) load_cross_compile_from_headers(cross_compile_config)
} else {
windows_hardcoded_cross_compile(cross_compile_config)
} }
} }
@ -458,7 +499,7 @@ fn load_cross_compile_info(
/// sysconfig.get_config_vars. /// sysconfig.get_config_vars.
fn get_build_flags(python_path: &Path) -> Result<HashSet<BuildFlag>> { fn get_build_flags(python_path: &Path) -> Result<HashSet<BuildFlag>> {
if env::var("CARGO_CFG_TARGET_OS").unwrap() == "windows" { if env::var("CARGO_CFG_TARGET_OS").unwrap() == "windows" {
return get_build_flags_windows(python_path); return get_build_flags_windows();
} }
let mut script = "import sysconfig; \ let mut script = "import sysconfig; \
@ -487,7 +528,7 @@ fn get_build_flags(python_path: &Path) -> Result<HashSet<BuildFlag>> {
Ok(flags) Ok(flags)
} }
fn get_build_flags_windows(_: &Path) -> Result<HashSet<BuildFlag>> { fn get_build_flags_windows() -> Result<HashSet<BuildFlag>> {
// sysconfig is missing all the flags on windows, so we can't actually // sysconfig is missing all the flags on windows, so we can't actually
// query the interpreter directly for its build flags. // query the interpreter directly for its build flags.
// //
@ -547,9 +588,12 @@ fn get_rustc_link_lib(config: &InterpreterConfig) -> String {
// //
// This contains only the limited ABI symbols. // This contains only the limited ABI symbols.
if env::var_os("CARGO_FEATURE_ABI3").is_some() { if env::var_os("CARGO_FEATURE_ABI3").is_some() {
format!("python3") "pythonXY:python3".to_owned()
} else { } else {
format!("python{}{}", config.version.major, config.version.minor) format!(
"pythonXY:python{}{}",
config.version.major, config.version.minor
)
} }
} else { } else {
match config.version.implementation { match config.version.implementation {
@ -705,10 +749,8 @@ fn configure(interpreter_config: &InterpreterConfig) -> Result<()> {
let minor = if env::var_os("CARGO_FEATURE_ABI3").is_some() { let minor = if env::var_os("CARGO_FEATURE_ABI3").is_some() {
println!("cargo:rustc-cfg=Py_LIMITED_API"); println!("cargo:rustc-cfg=Py_LIMITED_API");
// Check any `abi3-py3*` feature is set. If not, use the interpreter version. // Check any `abi3-py3*` feature is set. If not, use the interpreter version.
let abi3_minor = (PY3_MIN_MINOR..=ABI3_MAX_MINOR)
.find(|i| env::var_os(format!("CARGO_FEATURE_ABI3_PY3{}", i)).is_some());
match abi3_minor { match get_abi3_minor_version() {
Some(minor) if minor > interpreter_config.version.minor => bail!( Some(minor) if minor > interpreter_config.version.minor => bail!(
"You cannot set a mininimum Python version 3.{} higher than the interpreter version 3.{}", "You cannot set a mininimum Python version 3.{} higher than the interpreter version 3.{}",
minor, minor,
@ -763,10 +805,16 @@ fn check_target_architecture(interpreter_config: &InterpreterConfig) -> Result<(
Ok(()) Ok(())
} }
fn get_abi3_minor_version() -> Option<u8> {
(PY3_MIN_MINOR..=ABI3_MAX_MINOR)
.find(|i| env::var_os(format!("CARGO_FEATURE_ABI3_PY3{}", i)).is_some())
}
fn abi3_without_interpreter() -> Result<()> { fn abi3_without_interpreter() -> Result<()> {
println!("cargo:rustc-cfg=Py_LIMITED_API"); println!("cargo:rustc-cfg=Py_LIMITED_API");
let mut flags = "FLAG_WITH_THREAD=1".to_string(); let mut flags = "FLAG_WITH_THREAD=1".to_string();
for minor in PY3_MIN_MINOR..=ABI3_MAX_MINOR { let abi_version = get_abi3_minor_version().unwrap_or(ABI3_MAX_MINOR);
for minor in PY3_MIN_MINOR..=abi_version {
println!("cargo:rustc-cfg=Py_3_{}", minor); println!("cargo:rustc-cfg=Py_3_{}", minor);
flags += &format!(",CFG_Py_3_{}", minor); flags += &format!(",CFG_Py_3_{}", minor);
} }

View File

@ -91,9 +91,11 @@ See https://github.com/japaric/rust-cross for a primer on cross compiling Rust i
After you've obtained the above, you can build a cross compiled PyO3 module by setting a few extra environment variables: After you've obtained the above, you can build a cross compiled PyO3 module by setting a few extra environment variables:
* `PYO3_CROSS_INCLUDE_DIR`: This variable must be set to the directory containing the headers for the target's Python interpreter. **It is only necessary if targeting Windows platforms**
* `PYO3_CROSS_LIB_DIR`: This variable must be set to the directory containing the target's libpython DSO and the associated `_sysconfigdata*.py` file. * `PYO3_CROSS_LIB_DIR`: This variable must be set to the directory containing the target's libpython DSO and the associated `_sysconfigdata*.py` file.
* `PYO3_CROSS_PYTHON_VERSION`: This variable must be set if there are multiple versions of python compiled for a unix machine. * `PYO3_CROSS_PYTHON_VERSION`: Major and minor version (e.g. 3.9) of the target Python installation. This variable is only needed if pyo3 cannot determine the version to target by other means:
- From `PYO3_CROSS_INCLUDE_DIR` or abi3-py3* features when targeting Windows, or
- if there are multiple versions of python present in `PYO3_CROSS_LIB_DIR` when targeting unix.
* `PYO3_CROSS_INCLUDE_DIR`: This variable can optionally be set to the directory containing the headers for the target's Python interpreter when targeting Windows.
An example might look like the following (assuming your target's sysroot is at `/home/pyo3/cross/sysroot` and that your target is `armv7`): An example might look like the following (assuming your target's sysroot is at `/home/pyo3/cross/sysroot` and that your target is `armv7`):