pyo3-build-config: Inline the `PYO3_NO_PYTHON` switch

This patch folds the `PYO3_NO_PYTHON` + `abi3` special case into
the existing native and cross compilation code paths.

The cross compilation route is now guaranteed to behave the same
whether `PYO3_NO_PYTHON` is set or not (except for sysconfigdata
discovery for the Unix targets).

The native compilation route now stores the hardcoded abi3 interpreter
configuration in place of the discovered configuration blob.
This commit is contained in:
Sergey Kvachonok 2022-04-05 16:51:24 +03:00
parent 0f868e7fa5
commit 29476b0d0d
3 changed files with 134 additions and 79 deletions

View File

@ -12,10 +12,7 @@ mod errors;
use std::{env, path::Path};
use errors::{Context, Result};
use impl_::{
env_var, get_abi3_version, make_interpreter_config, BuildFlags, InterpreterConfig,
PythonImplementation,
};
use impl_::{env_var, make_interpreter_config, InterpreterConfig};
fn configure(interpreter_config: Option<InterpreterConfig>, name: &str) -> Result<bool> {
let target = Path::new(&env::var_os("OUT_DIR").unwrap()).join(name);
@ -52,37 +49,12 @@ fn config_file() -> Result<Option<InterpreterConfig>> {
}
}
/// If PYO3_NO_PYTHON is set with abi3, use standard abi3 settings.
pub fn abi3_config() -> Option<InterpreterConfig> {
if let Some(version) = get_abi3_version() {
if env_var("PYO3_NO_PYTHON").is_some() {
return Some(InterpreterConfig {
version,
// NB PyPy doesn't support abi3 yet
implementation: PythonImplementation::CPython,
abi3: true,
lib_name: None,
lib_dir: None,
build_flags: BuildFlags::default(),
pointer_width: None,
executable: None,
shared: true,
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
});
}
}
None
}
fn generate_build_configs() -> Result<()> {
let mut configured = false;
configured |= configure(config_file()?, "pyo3-build-config-file.txt")?;
configured |= configure(abi3_config(), "pyo3-build-config-abi3.txt")?;
let configured = configure(config_file()?, "pyo3-build-config-file.txt")?;
if configured {
// Don't bother trying to find an interpreter on the host system if at least one of the
// config file or abi3 settings are present
// Don't bother trying to find an interpreter on the host system
// if the user-provided config file is present.
configure(None, "pyo3-build-config.txt")?;
} else {
configure(Some(make_interpreter_config()?), "pyo3-build-config.txt")?;

View File

@ -641,6 +641,17 @@ impl FromStr for PythonImplementation {
}
}
/// Checks if we should look for a Python interpreter installation
/// to get the target interpreter configuration.
///
/// Returns `false` if `PYO3_NO_PYTHON` environment variable is set.
fn have_python_interpreter() -> bool {
env_var("PYO3_NO_PYTHON").is_none()
}
/// Checks if `abi3` or any of the `abi3-py3*` features is enabled for the PyO3 crate.
///
/// Must be called from a PyO3 crate build script.
fn is_abi3() -> bool {
cargo_env_var("CARGO_FEATURE_ABI3").is_some()
}
@ -1387,6 +1398,46 @@ fn default_cross_compile(cross_compile_config: &CrossCompileConfig) -> Result<In
})
}
/// Generates "default" interpreter configuration when compiling "abi3" extensions
/// without a working Python interpreter.
///
/// `version` specifies the minimum supported Stable ABI CPython version.
///
/// This should work for most CPython extension modules when compiling on
/// Windows, macOS and Linux.
///
/// Must be called from a PyO3 crate build script.
fn default_abi3_config(host: &Triple, version: PythonVersion) -> InterpreterConfig {
// FIXME: PyPy does not support the Stable ABI yet.
let implementation = PythonImplementation::CPython;
let abi3 = true;
let lib_name = if host.operating_system == OperatingSystem::Windows {
Some(default_lib_name_windows(
version,
implementation,
abi3,
false,
))
} else {
None
};
InterpreterConfig {
implementation,
version,
shared: true,
abi3,
lib_name,
lib_dir: None,
executable: None,
pointer_width: None,
build_flags: BuildFlags::default(),
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
}
}
/// Detects the cross compilation target interpreter configuration from all
/// available sources (PyO3 environment variables, Python sysconfigdata, etc.).
///
@ -1397,30 +1448,31 @@ fn default_cross_compile(cross_compile_config: &CrossCompileConfig) -> Result<In
fn load_cross_compile_config(
cross_compile_config: CrossCompileConfig,
) -> Result<InterpreterConfig> {
// Load the defaults for Windows even when `PYO3_CROSS_LIB_DIR` is set
// since it has no sysconfigdata files in it.
if cross_compile_config.target.operating_system == OperatingSystem::Windows {
return default_cross_compile(&cross_compile_config);
}
let windows = cross_compile_config.target.operating_system == OperatingSystem::Windows;
// Try to find and parse sysconfigdata files on other targets
// and fall back to the defaults when none are found.
if let Some(config) = cross_compile_from_sysconfigdata(&cross_compile_config)? {
Ok(config)
let config = if windows || !have_python_interpreter() {
// Load the defaults for Windows even when `PYO3_CROSS_LIB_DIR` is set
// since it has no sysconfigdata files in it.
// Also, do not try to look for sysconfigdata when `PYO3_NO_PYTHON` variable is set.
default_cross_compile(&cross_compile_config)?
} else if let Some(config) = cross_compile_from_sysconfigdata(&cross_compile_config)? {
// Try to find and parse sysconfigdata files on other targets.
config
} else {
let config = default_cross_compile(&cross_compile_config)?;
// Fall back to the defaults when nothing else can be done.
default_cross_compile(&cross_compile_config)?
};
if config.lib_name.is_some() && config.lib_dir.is_none() {
warn!(
"The output binary will link to libpython, \
but PYO3_CROSS_LIB_DIR environment variable is not set. \
Ensure that the target Python library directory is \
in the rustc native library search path."
);
}
Ok(config)
if config.lib_name.is_some() && config.lib_dir.is_none() {
warn!(
"The output binary will link to libpython, \
but PYO3_CROSS_LIB_DIR environment variable is not set. \
Ensure that the target Python library directory is \
in the rustc native library search path."
);
}
Ok(config)
}
// Link against python3.lib for the stable ABI on Windows.
@ -1591,9 +1643,18 @@ pub fn make_cross_compile_config() -> Result<Option<InterpreterConfig>> {
/// Only used by `pyo3-build-config` build script.
#[allow(dead_code)]
pub fn make_interpreter_config() -> Result<InterpreterConfig> {
let mut interpreter_config = InterpreterConfig::from_interpreter(find_interpreter()?)?;
interpreter_config.fixup_for_abi3_version(get_abi3_version())?;
Ok(interpreter_config)
let abi3_version = get_abi3_version();
if have_python_interpreter() {
let mut interpreter_config = InterpreterConfig::from_interpreter(find_interpreter()?)?;
interpreter_config.fixup_for_abi3_version(abi3_version)?;
Ok(interpreter_config)
} else if let Some(version) = abi3_version {
let host = Triple::host();
Ok(default_abi3_config(&host, version))
} else {
bail!("An abi3-py3* feature must be specified when compiling without a Python interpreter.")
}
}
#[cfg(test)]
@ -1844,6 +1905,52 @@ mod tests {
);
}
#[test]
fn windows_hardcoded_abi3_compile() {
let host = triple!("x86_64-pc-windows-msvc");
let min_version = "3.7".parse().unwrap();
assert_eq!(
default_abi3_config(&host, min_version),
InterpreterConfig {
implementation: PythonImplementation::CPython,
version: PythonVersion { major: 3, minor: 7 },
shared: true,
abi3: true,
lib_name: Some("python3".into()),
lib_dir: None,
executable: None,
pointer_width: None,
build_flags: BuildFlags::default(),
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
}
);
}
#[test]
fn unix_hardcoded_abi3_compile() {
let host = triple!("x86_64-unknown-linux-gnu");
let min_version = "3.9".parse().unwrap();
assert_eq!(
default_abi3_config(&host, min_version),
InterpreterConfig {
implementation: PythonImplementation::CPython,
version: PythonVersion { major: 3, minor: 9 },
shared: true,
abi3: true,
lib_name: None,
lib_dir: None,
executable: None,
pointer_width: None,
build_flags: BuildFlags::default(),
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
}
);
}
#[test]
fn windows_hardcoded_cross_compile() {
let env_vars = CrossCompileEnvVars {

View File

@ -84,8 +84,6 @@ pub fn get() -> &'static InterpreterConfig {
interpreter_config
} else if !CONFIG_FILE.is_empty() {
InterpreterConfig::from_reader(Cursor::new(CONFIG_FILE))
} else if !ABI3_CONFIG.is_empty() {
Ok(abi3_config())
} else if cross_compiling {
InterpreterConfig::from_path(cross_compile_config_path.as_ref().unwrap())
} else {
@ -100,12 +98,6 @@ pub fn get() -> &'static InterpreterConfig {
#[cfg(feature = "resolve-config")]
const CONFIG_FILE: &str = include_str!(concat!(env!("OUT_DIR"), "/pyo3-build-config-file.txt"));
/// Build configuration set if abi3 features enabled and `PYO3_NO_PYTHON` env var present. Empty if
/// not both present.
#[doc(hidden)]
#[cfg(feature = "resolve-config")]
const ABI3_CONFIG: &str = include_str!(concat!(env!("OUT_DIR"), "/pyo3-build-config-abi3.txt"));
/// Build configuration discovered by `pyo3-build-config` build script. Not aware of
/// cross-compilation settings.
#[doc(hidden)]
@ -128,20 +120,6 @@ fn resolve_cross_compile_config_path() -> Option<PathBuf> {
})
}
#[cfg(feature = "resolve-config")]
fn abi3_config() -> InterpreterConfig {
let mut interpreter_config = InterpreterConfig::from_reader(Cursor::new(ABI3_CONFIG))
.expect("failed to parse hardcoded PyO3 abi3 config");
// If running from a build script on Windows, tweak the hardcoded abi3 config to contain
// the standard lib name (this is necessary so that abi3 extension modules using
// PYO3_NO_PYTHON on Windows can link)
if std::env::var("CARGO_CFG_TARGET_OS").map_or(false, |target_os| target_os == "windows") {
assert_eq!(interpreter_config.lib_name, None);
interpreter_config.lib_name = Some("python3".to_owned())
}
interpreter_config
}
/// Use certain features if we detect the compiler being used supports them.
///
/// Features may be removed or added as MSRV gets bumped or new features become available,
@ -200,8 +178,6 @@ pub mod pyo3_build_script_impl {
pub fn resolve_interpreter_config() -> Result<InterpreterConfig> {
if !CONFIG_FILE.is_empty() {
InterpreterConfig::from_reader(Cursor::new(CONFIG_FILE))
} else if !ABI3_CONFIG.is_empty() {
Ok(abi3_config())
} else if let Some(interpreter_config) = make_cross_compile_config()? {
// This is a cross compile and need to write the config file.
let path = resolve_cross_compile_config_path()