Remove `pyconfig.h` header parsing

The config header parsing code was supposed to be only invoked when
cross-compiling for Windows, but in reality it fails to correctly parse
the config header files shipped with the upstream Python for Windows.

Given that there are now better options for reliable cross-compiling
for Windows such as `PYO3_CROSS_PYTHON_VERSION` or the `abi3-py3*` features,
it should be OK to remove this config for v0.14.

Update the cross-compilation instructions section of the user guide.

Fixes https://github.com/PyO3/pyo3/issues/1337
This commit is contained in:
Sergey Kvachonok 2021-03-26 09:15:21 +03:00
parent c4b7a44622
commit 80c747d2c0
3 changed files with 13 additions and 78 deletions

View File

@ -37,6 +37,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- `PyObject_Check`, `PySuper_Check`, and `FreeFunc` [#1438](https://github.com/PyO3/pyo3/pull/1438) - `PyObject_Check`, `PySuper_Check`, and `FreeFunc` [#1438](https://github.com/PyO3/pyo3/pull/1438)
- Remove pyclass implementation details `Type`, `DESCRIPTION`, and `FLAGS` from `PyTypeInfo`. [#1456](https://github.com/PyO3/pyo3/pull/1456) - Remove pyclass implementation details `Type`, `DESCRIPTION`, and `FLAGS` from `PyTypeInfo`. [#1456](https://github.com/PyO3/pyo3/pull/1456)
- Remove `__doc__` from module's `__all__`. [#1509](https://github.com/PyO3/pyo3/pull/1509) - Remove `__doc__` from module's `__all__`. [#1509](https://github.com/PyO3/pyo3/pull/1509)
- Remove `PYO3_CROSS_INCLUDE_DIR` environment variable and the associated C header parsing functionality.
### Fixed ### Fixed
- Remove FFI definition `PyCFunction_ClearFreeList` for Python 3.9 and later. [#1425](https://github.com/PyO3/pyo3/pull/1425) - Remove FFI definition `PyCFunction_ClearFreeList` for Python 3.9 and later. [#1425](https://github.com/PyO3/pyo3/pull/1425)

View File

@ -2,8 +2,8 @@ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
convert::AsRef, convert::AsRef,
env, env,
fs::{self, DirEntry, File}, fs::{self, DirEntry},
io::{self, BufRead, BufReader}, io,
path::{Path, PathBuf}, path::{Path, PathBuf},
process::{Command, Stdio}, process::{Command, Stdio},
str::FromStr, str::FromStr,
@ -109,24 +109,15 @@ impl GetPrimitive for HashMap<String, String> {
struct CrossCompileConfig { struct CrossCompileConfig {
lib_dir: PathBuf, lib_dir: PathBuf,
include_dir: Option<PathBuf>,
version: Option<String>, version: Option<String>,
os: String, os: String,
arch: String, arch: String,
} }
impl CrossCompileConfig { impl CrossCompileConfig {
fn both() -> Result<Self> { fn new() -> Result<Self> {
Ok(CrossCompileConfig {
include_dir: env::var_os("PYO3_CROSS_INCLUDE_DIR").map(Into::into),
..CrossCompileConfig::lib_only()?
})
}
fn lib_only() -> Result<Self> {
Ok(CrossCompileConfig { Ok(CrossCompileConfig {
lib_dir: CrossCompileConfig::validate_variable("PYO3_CROSS_LIB_DIR")?, lib_dir: CrossCompileConfig::validate_variable("PYO3_CROSS_LIB_DIR")?,
include_dir: None,
os: env::var("CARGO_CFG_TARGET_OS").unwrap(), os: env::var("CARGO_CFG_TARGET_OS").unwrap(),
arch: env::var("CARGO_CFG_TARGET_ARCH").unwrap(), arch: env::var("CARGO_CFG_TARGET_ARCH").unwrap(),
version: env::var_os("PYO3_CROSS_PYTHON_VERSION").map(|s| s.into_string().unwrap()), version: env::var_os("PYO3_CROSS_PYTHON_VERSION").map(|s| s.into_string().unwrap()),
@ -183,13 +174,8 @@ fn cross_compiling() -> Result<Option<CrossCompileConfig>> {
return Ok(None); return Ok(None);
} }
if env::var("CARGO_CFG_TARGET_FAMILY")? == "windows" {
// Windows cross-compile uses both header includes and sysconfig
return Ok(Some(CrossCompileConfig::both()?));
}
// Cross-compiling on any other platform // Cross-compiling on any other platform
Ok(Some(CrossCompileConfig::lib_only()?)) Ok(Some(CrossCompileConfig::new()?))
} }
/// A list of python interpreter compile-time preprocessor defines that /// A list of python interpreter compile-time preprocessor defines that
@ -300,23 +286,6 @@ impl BuildFlags {
} }
} }
/// Attempts to parse the header at the given path, returning a map of definitions to their values.
/// Each entry in the map directly corresponds to a `#define` in the given header.
fn parse_header_defines(header_path: impl AsRef<Path>) -> Result<HashMap<String, String>> {
let header_reader = BufReader::new(File::open(header_path.as_ref())?);
let mut definitions = HashMap::new();
for maybe_line in header_reader.lines() {
let line = maybe_line?;
let mut i = line.trim().split_whitespace();
if i.next() == Some("#define") {
if let (Some(key), Some(value), None) = (i.next(), i.next(), i.next()) {
definitions.insert(key.into(), value.into());
}
}
}
Ok(definitions)
}
fn parse_script_output(output: &str) -> HashMap<String, String> { fn parse_script_output(output: &str) -> HashMap<String, String> {
output output
.lines() .lines()
@ -500,36 +469,6 @@ fn load_cross_compile_from_sysconfigdata(
Ok((interpreter_config, build_flags)) Ok((interpreter_config, build_flags))
} }
fn load_cross_compile_from_headers(
cross_compile_config: CrossCompileConfig,
) -> Result<(InterpreterConfig, BuildFlags)> {
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"))?;
let major = patchlevel_defines.get_numeric("PY_MAJOR_VERSION")?;
let minor = patchlevel_defines.get_numeric("PY_MINOR_VERSION")?;
let python_version = PythonVersion { major, minor };
let config_data = parse_header_defines(python_include_dir.join("pyconfig.h"))?;
let interpreter_config = InterpreterConfig {
version: python_version,
libdir: cross_compile_config.lib_dir.to_str().map(String::from),
shared: config_data.get_bool("Py_ENABLE_SHARED").unwrap_or(false),
ld_version: format!("{}.{}", major, minor),
base_prefix: "".to_string(),
executable: PathBuf::new(),
calcsize_pointer: None,
implementation: PythonInterpreterKind::CPython,
};
let build_flags = BuildFlags::from_config_map(&config_data);
Ok((interpreter_config, build_flags))
}
fn windows_hardcoded_cross_compile( fn windows_hardcoded_cross_compile(
cross_compile_config: CrossCompileConfig, cross_compile_config: CrossCompileConfig,
) -> Result<(InterpreterConfig, BuildFlags)> { ) -> Result<(InterpreterConfig, BuildFlags)> {
@ -549,7 +488,7 @@ fn windows_hardcoded_cross_compile(
} else if let Some(minor_version) = get_abi3_minor_version() { } else if let Some(minor_version) = get_abi3_minor_version() {
(3, minor_version) (3, minor_version)
} else { } 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.") bail!("PYO3_CROSS_PYTHON_VERSION or an abi3-py3* feature must be specified when cross-compiling for Windows.")
}; };
let python_version = PythonVersion { major, minor }; let python_version = PythonVersion { major, minor };
@ -576,9 +515,6 @@ fn load_cross_compile_info(
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(cross_compile_config) 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(cross_compile_config)
} else { } else {
windows_hardcoded_cross_compile(cross_compile_config) windows_hardcoded_cross_compile(cross_compile_config)
} }

View File

@ -84,17 +84,13 @@ Cross compiling PyO3 modules is relatively straightforward and requires a few pi
* The appropriate options in your Cargo `.config` for the platform you're targeting and the toolchain you are using. * The appropriate options in your Cargo `.config` for the platform you're targeting and the toolchain you are using.
* A Python interpreter that's already been compiled for your target. * A Python interpreter that's already been compiled for your target.
* A Python interpreter that is built for your host and available through the `PATH` or setting the [`PYO3_PYTHON`](#python-version) variable. * A Python interpreter that is built for your host and available through the `PATH` or setting the [`PYO3_PYTHON`](#python-version) variable.
* The headers that match the above interpreter.
See https://github.com/japaric/rust-cross for a primer on cross compiling Rust in general. See [github.com/japaric/rust-cross](https://github.com/japaric/rust-cross) for a primer on cross compiling Rust in general.
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_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 for Unix-like targets, or the Python DLL import libraries for the Windows target.
* `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: * `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 from `abi3-py3*` features, or if there are multiple versions of Python present in `PYO3_CROSS_LIB_DIR`.
- 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`):
@ -112,14 +108,16 @@ export PYO3_CROSS_LIB_DIR="/home/pyo3/cross/sysroot/usr/lib"
cargo build --target armv7-unknown-linux-gnueabihf cargo build --target armv7-unknown-linux-gnueabihf
``` ```
Or another example with the same sys root but building for windows: Or another example with the same sys root but building for Windows:
```sh ```sh
export PYO3_CROSS_INCLUDE_DIR="/home/pyo3/cross/sysroot/usr/include" export PYO3_CROSS_PYTHON_VERSION=3.9
export PYO3_CROSS_LIB_DIR="/home/pyo3/cross/sysroot/usr/lib" export PYO3_CROSS_LIB_DIR="/home/pyo3/cross/sysroot/usr/lib"
cargo build --target x86_64-pc-windows-gnu cargo build --target x86_64-pc-windows-gnu
``` ```
Any of the `abi3-py3*` features can be enabled instead of setting `PYO3_CROSS_PYTHON_VERSION` in the above examples.
## Bazel ## Bazel
For an example of how to build python extensions using Bazel, see https://github.com/TheButlah/rules_pyo3 For an example of how to build python extensions using Bazel, see https://github.com/TheButlah/rules_pyo3