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

Add export-config feature to pyo3-build-config #2092

Merged
merged 12 commits into from Mar 23, 2022
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Allow to allow dependent crates to access values from `pyo3-build-config` via cargo link env vars. [#2092](https://github.com/PyO3/pyo3/pull/2092)

### Fixed

- Considered `PYTHONFRAMEWORK` when cross compiling in order that on macos cross compiling against a [Framework bundle](https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/FrameworkAnatomy.html) is considered shared. [#2233](https://github.com/PyO3/pyo3/pull/2233)
Expand Down
13 changes: 12 additions & 1 deletion Cargo.toml
Expand Up @@ -87,7 +87,18 @@ nightly = []

# Activates all additional features
# This is mostly intended for testing purposes - activating *all* of these isn't particularly useful.
full = ["macros", "pyproto", "multiple-pymethods", "num-bigint", "num-complex", "hashbrown", "serde", "indexmap", "eyre", "anyhow"]
full = [
"macros",
"pyproto",
"multiple-pymethods",
"num-bigint",
"num-complex",
"hashbrown",
"serde",
"indexmap",
"eyre",
"anyhow",
]

[[bench]]
name = "bench_call"
Expand Down
91 changes: 90 additions & 1 deletion pyo3-build-config/src/impl_.rs
Expand Up @@ -8,6 +8,7 @@ use std::{
io::{BufRead, BufReader, Read, Write},
path::{Path, PathBuf},
process::{Command, Stdio},
str,
str::FromStr,
};

Expand Down Expand Up @@ -153,7 +154,7 @@ impl InterpreterConfig {
}

for flag in &self.build_flags.0 {
println!("cargo:rustc-cfg=py_sys_config=\"{}\"", flag)
println!("cargo:rustc-cfg=py_sys_config=\"{}\"", flag);
}
}

Expand Down Expand Up @@ -339,6 +340,15 @@ print("mingw", get_platform().startswith("mingw"))
InterpreterConfig::from_reader(reader)
}

#[doc(hidden)]
pub fn from_cargo_link_env() -> Result<Self> {
// un-escape any newlines in the exported config
let buf = cargo_env_var("DEP_PYTHON_PYO3_CONFIG")
.unwrap()
.replace("\\n", "\n");
InterpreterConfig::from_reader(buf.as_bytes())
}

#[doc(hidden)]
pub fn from_reader(reader: impl Read) -> Result<Self> {
let reader = BufReader::new(reader);
Expand Down Expand Up @@ -419,6 +429,19 @@ print("mingw", get_platform().startswith("mingw"))
})
}

#[doc(hidden)]
pub fn to_cargo_link_env(&self) -> Result<()> {
let mut buf = Vec::new();
self.to_writer(&mut buf)?;
// escape newlines in env var
if let Ok(config) = str::from_utf8(&buf) {
println!("cargo:PYO3_CONFIG={}", config.replace('\n', "\\n"));
} else {
bail!("unable to emit interpreter config to link env for downstream use");
}
Ok(())
}

#[doc(hidden)]
pub fn to_writer(&self, mut writer: impl Write) -> Result<()> {
macro_rules! write_line {
Expand Down Expand Up @@ -461,6 +484,30 @@ print("mingw", get_platform().startswith("mingw"))
}
Ok(())
}

/// Run a python script using the executable of this InterpreterConfig with additional
/// environment variables (e.g. PYTHONPATH) set.
pub fn run_python_script_with_envs<I, K, V>(&self, script: &str, envs: I) -> Result<String>
aganders3 marked this conversation as resolved.
Show resolved Hide resolved
where
I: IntoIterator<Item = (K, V)>,
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
run_python_script_with_envs(
Path::new(self.executable.as_ref().expect("no interpreter executable")),
script,
envs,
)
}

/// Run a python script using the executable of this InterpreterConfig.
pub fn run_python_script(&self, script: &str) -> Result<String> {
run_python_script_with_envs(
Path::new(self.executable.as_ref().expect("no interpreter executable")),
script,
std::iter::empty::<(&str, &str)>(),
)
}
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
Expand Down Expand Up @@ -545,6 +592,11 @@ fn is_abi3() -> bool {
cargo_env_var("CARGO_FEATURE_ABI3").is_some()
}

#[allow(unused)]
pub fn link_env_var_set() -> bool {
cargo_env_var("DEP_PYTHON_PYO3_CONFIG").is_some()
}

#[derive(Debug, PartialEq)]
struct TargetInfo {
/// The `arch` component of the compilation target triple.
Expand Down Expand Up @@ -1183,8 +1235,20 @@ fn default_lib_name_unix(

/// Run a python script using the specified interpreter binary.
fn run_python_script(interpreter: &Path, script: &str) -> Result<String> {
run_python_script_with_envs(interpreter, script, std::iter::empty::<(&str, &str)>())
}

/// Run a python script using the specified interpreter binary with additional environment
/// variables (e.g. PYTHONPATH) set.
fn run_python_script_with_envs<I, K, V>(interpreter: &Path, script: &str, envs: I) -> Result<String>
where
I: IntoIterator<Item = (K, V)>,
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
let out = Command::new(interpreter)
.env("PYTHONIOENCODING", "utf-8")
.envs(envs)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
Expand Down Expand Up @@ -1848,4 +1912,29 @@ mod tests {
.is_none()
);
}

#[test]
fn test_run_python_script() {
// as above, this should be okay in CI where Python is presumed installed
let interpreter = make_interpreter_config()
.expect("could not get InterpreterConfig from installed interpreter");
let out = interpreter
.run_python_script("print(2 + 2)")
.expect("failed to run Python script");
assert_eq!(out.trim_end(), "4");
}

#[test]
fn test_run_python_script_with_envs() {
// as above, this should be okay in CI where Python is presumed installed
let interpreter = make_interpreter_config()
.expect("could not get InterpreterConfig from installed interpreter");
let out = interpreter
.run_python_script_with_envs(
"import os; print(os.getenv('PYO3_TEST'))",
vec![("PYO3_TEST", "42")],
)
.expect("failed to run Python script");
assert_eq!(out.trim_end(), "42");
}
}
3 changes: 2 additions & 1 deletion pyo3-build-config/src/lib.rs
Expand Up @@ -61,7 +61,6 @@ fn _add_extension_module_link_args(target_os: &str, mut writer: impl std::io::Wr
/// Loads the configuration determined from the build environment.
///
/// Because this will never change in a given compilation run, this is cached in a `once_cell`.
#[doc(hidden)]
#[cfg(feature = "resolve-config")]
pub fn get() -> &'static InterpreterConfig {
static CONFIG: OnceCell<InterpreterConfig> = OnceCell::new();
Expand All @@ -72,6 +71,8 @@ pub fn get() -> &'static InterpreterConfig {
Ok(abi3_config())
} else if impl_::cross_compile_env_vars().any() {
InterpreterConfig::from_path(DEFAULT_CROSS_COMPILE_CONFIG_PATH)
} else if impl_::link_env_var_set() {
aganders3 marked this conversation as resolved.
Show resolved Hide resolved
InterpreterConfig::from_cargo_link_env()
} else {
InterpreterConfig::from_reader(Cursor::new(HOST_CONFIG))
}
Expand Down
3 changes: 3 additions & 0 deletions pyo3-ffi/build.rs
Expand Up @@ -69,6 +69,9 @@ fn emit_link_config(interpreter_config: &InterpreterConfig) -> Result<()> {
}
}

// serialize the whole interpreter config in DEP_PYTHON_PYO3_CONFIG
interpreter_config.to_cargo_link_env()?;

Ok(())
}

Expand Down