From 80675361f40947ab9a2024cfd476b38b7938ea41 Mon Sep 17 00:00:00 2001 From: Sergey Kvachonok Date: Mon, 4 Apr 2022 14:51:26 +0300 Subject: [PATCH] pyo3-build-config: Add `PYO3_CROSS_PYTHON_IMPLEMENTATION` env var Adds a new cross-compile target interpreter configuration environment variable. This feature allows PyO3 to target PyPy on both Windows and Unix cross compile targets. --- CHANGELOG.md | 1 + guide/src/building_and_distribution.md | 1 + pyo3-build-config/src/impl_.rs | 154 ++++++++++++++++++++----- 3 files changed, 130 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a95190576a..d0a6873754e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Add `PYO3_CROSS_PYTHON_IMPLEMENTATION` environment variable for selecting the default cross Python implementation. [#2272](https://github.com/PyO3/pyo3/pull/2272) - Add new public `pyo3-build-config` API using the types from `target_lexicon` crate. Deprecate `cross_compiling()`. [#2253](https://github.com/PyO3/pyo3/pull/2253) - Allow dependent crates to access config values from `pyo3-build-config` via cargo link dep env vars. [#2092](https://github.com/PyO3/pyo3/pull/2092) - Added methods on `InterpreterConfig` to run Python scripts using the configured executable. [#2092](https://github.com/PyO3/pyo3/pull/2092) diff --git a/guide/src/building_and_distribution.md b/guide/src/building_and_distribution.md index 905b317c75b..c4113ad15ac 100644 --- a/guide/src/building_and_distribution.md +++ b/guide/src/building_and_distribution.md @@ -228,6 +228,7 @@ When cross-compiling, PyO3's build script cannot execute the target Python inter * `PYO3_CROSS`: If present this variable forces PyO3 to configure as a cross-compilation. * `PYO3_CROSS_LIB_DIR`: This variable can 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. This variable is only needed when the output binary must link to libpython explicitly (e.g. when targeting Windows and Android or embedding a Python interpreter), or when it is absolutely required to get the interpreter configuration from `_sysconfigdata*.py`. * `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 `PYO3_CROSS_LIB_DIR` is not set, or if there are multiple versions of Python present in `PYO3_CROSS_LIB_DIR`. +* `PYO3_CROSS_PYTHON_IMPLEMENTATION`: Python implementation name ("CPython" or "PyPy") of the target Python installation. CPython is assumed by default when this variable is not set, unless `PYO3_CROSS_LIB_DIR` is set for a Unix-like target and PyO3 can get the interpreter configuration from `_sysconfigdata*.py`. An example might look like the following (assuming your target's sysroot is at `/home/pyo3/cross/sysroot` and that your target is `armv7`): diff --git a/pyo3-build-config/src/impl_.rs b/pyo3-build-config/src/impl_.rs index b74314d5ea3..84c69882963 100644 --- a/pyo3-build-config/src/impl_.rs +++ b/pyo3-build-config/src/impl_.rs @@ -690,6 +690,9 @@ pub struct CrossCompileConfig { /// The version of the Python library to link against. version: Option, + /// The target Python implementation hint (CPython or PyPy) + implementation: Option, + /// The compile target triple (e.g. aarch64-unknown-linux-gnu) target: Triple, } @@ -707,12 +710,14 @@ impl CrossCompileConfig { if env_vars.any() || Self::is_cross_compiling_from_to(host, target) { let lib_dir = env_vars.lib_dir_path()?; let version = env_vars.parse_version()?; + let implementation = env_vars.parse_implementation()?; let target = target.clone(); Ok(Some(CrossCompileConfig { lib_dir, - target, version, + implementation, + target, })) } else { Ok(None) @@ -752,17 +757,37 @@ impl CrossCompileConfig { } } -pub(crate) struct CrossCompileEnvVars { +/// PyO3-specific cross compile environment variable values +struct CrossCompileEnvVars { + /// `PYO3_CROSS` pyo3_cross: Option, + /// `PYO3_CROSS_LIB_DIR` pyo3_cross_lib_dir: Option, + /// `PYO3_CROSS_PYTHON_VERSION` pyo3_cross_python_version: Option, + /// `PYO3_CROSS_PYTHON_IMPLEMENTATION` + pyo3_cross_python_implementation: Option, } impl CrossCompileEnvVars { + /// Grabs the PyO3 cross-compile variables from the environment. + /// + /// Registers the build script to rerun if any of the variables changes. + fn from_env() -> Self { + CrossCompileEnvVars { + pyo3_cross: env_var("PYO3_CROSS"), + pyo3_cross_lib_dir: env_var("PYO3_CROSS_LIB_DIR"), + pyo3_cross_python_version: env_var("PYO3_CROSS_PYTHON_VERSION"), + pyo3_cross_python_implementation: env_var("PYO3_CROSS_PYTHON_IMPLEMENTATION"), + } + } + + /// Checks if any of the variables is set. fn any(&self) -> bool { self.pyo3_cross.is_some() || self.pyo3_cross_lib_dir.is_some() || self.pyo3_cross_python_version.is_some() + || self.pyo3_cross_python_implementation.is_some() } /// Parses `PYO3_CROSS_PYTHON_VERSION` environment variable value @@ -784,6 +809,25 @@ impl CrossCompileEnvVars { Ok(version) } + /// Parses `PYO3_CROSS_PYTHON_IMPLEMENTATION` environment variable value + /// into `PythonImplementation`. + fn parse_implementation(&self) -> Result> { + let implementation = self + .pyo3_cross_python_implementation + .as_ref() + .map(|os_string| { + let utf8_str = os_string + .to_str() + .ok_or("PYO3_CROSS_PYTHON_IMPLEMENTATION is not valid a UTF-8 string")?; + utf8_str + .parse() + .context("failed to parse PYO3_CROSS_PYTHON_IMPLEMENTATION") + }) + .transpose()?; + + Ok(implementation) + } + /// Converts the stored `PYO3_CROSS_LIB_DIR` variable value (if any) /// into a `PathBuf` instance. /// @@ -802,14 +846,6 @@ impl CrossCompileEnvVars { } } -pub(crate) fn cross_compile_env_vars() -> CrossCompileEnvVars { - CrossCompileEnvVars { - pyo3_cross: env::var_os("PYO3_CROSS"), - pyo3_cross_lib_dir: env::var_os("PYO3_CROSS_LIB_DIR"), - pyo3_cross_python_version: env::var_os("PYO3_CROSS_PYTHON_VERSION"), - } -} - /// Detect whether we are cross compiling and return an assembled CrossCompileConfig if so. /// /// This function relies on PyO3 cross-compiling environment variables: @@ -879,7 +915,7 @@ pub fn cross_compiling_from_to( host: &Triple, target: &Triple, ) -> Result> { - let env_vars = cross_compile_env_vars(); + let env_vars = CrossCompileEnvVars::from_env(); CrossCompileConfig::try_from_env_vars_host_target(env_vars, host, target) } @@ -889,7 +925,7 @@ pub fn cross_compiling_from_to( /// This must be called from PyO3's build script, because it relies on environment /// variables such as `CARGO_CFG_TARGET_OS` which aren't available at any other time. pub fn cross_compiling_from_cargo_env() -> Result> { - let env_vars = cross_compile_env_vars(); + let env_vars = CrossCompileEnvVars::from_env(); let host = Triple::host(); let target = target_triple_from_env(); @@ -1302,7 +1338,7 @@ fn cross_compile_from_sysconfigdata( /// Generates "default" cross compilation information for the target. /// /// This should work for most CPython extension modules when targeting -/// Windows, MacOS and Linux. +/// Windows, macOS and Linux. /// /// Must be called from a PyO3 crate build script. fn default_cross_compile(cross_compile_config: &CrossCompileConfig) -> Result { @@ -1315,7 +1351,9 @@ fn default_cross_compile(cross_compile_config: &CrossCompileConfig) -> Result