Skip to content

Commit

Permalink
build: make include dir optional when targeting Windows
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhewitt committed Dec 31, 2020
1 parent cc6fc48 commit a350dd2
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 23 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### 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 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
- 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)
Expand Down
90 changes: 69 additions & 21 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,7 @@ struct CrossCompileConfig {
impl CrossCompileConfig {
fn both() -> Result<Self> {
Ok(CrossCompileConfig {
include_dir: Some(CrossCompileConfig::validate_variable(
"PYO3_CROSS_INCLUDE_DIR",
)?),
include_dir: env::var_os("PYO3_CROSS_INCLUDE_DIR").map(Into::into),
..CrossCompileConfig::lib_only()?
})
}
Expand Down Expand Up @@ -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
fn load_cross_compile_from_sysconfigdata(
python_paths: CrossCompileConfig,
cross_compile_config: CrossCompileConfig,
) -> 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 major = sysconfig_data.get_numeric("version_major")?;
Expand All @@ -393,7 +391,7 @@ fn load_cross_compile_from_sysconfigdata(

let interpreter_config = InterpreterConfig {
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")?,
ld_version,
base_prefix: "".to_string(),
Expand All @@ -407,9 +405,9 @@ fn load_cross_compile_from_sysconfigdata(
}

fn load_cross_compile_from_headers(
python_paths: CrossCompileConfig,
cross_compile_config: CrossCompileConfig,
) -> 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 patchlevel_defines = parse_header_defines(python_include_dir.join("patchlevel.h"))?;

Expand All @@ -426,7 +424,7 @@ fn load_cross_compile_from_headers(

let interpreter_config = InterpreterConfig {
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")?,
ld_version: format!("{}.{}", major, minor),
base_prefix: "".to_string(),
Expand All @@ -439,17 +437,60 @@ fn load_cross_compile_from_headers(
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(
python_paths: CrossCompileConfig,
cross_compile_config: CrossCompileConfig,
) -> Result<(InterpreterConfig, HashSet<BuildFlag>)> {
let target_family = env::var("CARGO_CFG_TARGET_FAMILY")?;
// Because compiling for windows on linux still includes the unix target family
if target_family == "unix" {
// Configure for unix platforms using the sysconfigdata file
load_cross_compile_from_sysconfigdata(python_paths)
} else {
load_cross_compile_from_sysconfigdata(cross_compile_config)
} else if cross_compile_config.include_dir.is_some() {
// 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)
}
}

Expand All @@ -458,7 +499,7 @@ fn load_cross_compile_info(
/// sysconfig.get_config_vars.
fn get_build_flags(python_path: &Path) -> Result<HashSet<BuildFlag>> {
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; \
Expand Down Expand Up @@ -487,7 +528,7 @@ fn get_build_flags(python_path: &Path) -> Result<HashSet<BuildFlag>> {
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
// query the interpreter directly for its build flags.
//
Expand Down Expand Up @@ -547,9 +588,12 @@ fn get_rustc_link_lib(config: &InterpreterConfig) -> String {
//
// This contains only the limited ABI symbols.
if env::var_os("CARGO_FEATURE_ABI3").is_some() {
format!("python3")
"pythonXY:python3".to_owned()
} else {
format!("python{}{}", config.version.major, config.version.minor)
format!(
"pythonXY:python{}{}",
config.version.major, config.version.minor
)
}
} else {
match config.version.implementation {
Expand Down Expand Up @@ -705,10 +749,8 @@ fn configure(interpreter_config: &InterpreterConfig) -> Result<()> {
let minor = if env::var_os("CARGO_FEATURE_ABI3").is_some() {
println!("cargo:rustc-cfg=Py_LIMITED_API");
// 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!(
"You cannot set a mininimum Python version 3.{} higher than the interpreter version 3.{}",
minor,
Expand Down Expand Up @@ -763,10 +805,16 @@ fn check_target_architecture(interpreter_config: &InterpreterConfig) -> Result<(
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<()> {
println!("cargo:rustc-cfg=Py_LIMITED_API");
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);
flags += &format!(",CFG_Py_3_{}", minor);
}
Expand Down
6 changes: 4 additions & 2 deletions guide/src/building_and_distribution.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

* `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_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`):

Expand Down

0 comments on commit a350dd2

Please sign in to comment.