Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pyo3-build-config: Inline the PYO3_NO_PYTHON switch #2276

Merged
merged 1 commit into from
Apr 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 4 additions & 32 deletions pyo3-build-config/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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")?;
Expand Down
155 changes: 131 additions & 24 deletions pyo3-build-config/src/impl_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down Expand Up @@ -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.).
///
Expand All @@ -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);
}

// 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 windows = cross_compile_config.target.operating_system == OperatingSystem::Windows;

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)?;

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."
);
}
// Fall back to the defaults when nothing else can be done.
default_cross_compile(&cross_compile_config)?
};

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.
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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 {
Expand Down
24 changes: 0 additions & 24 deletions pyo3-build-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)]
Expand All @@ -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,
Expand Down Expand Up @@ -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()
Expand Down