diff --git a/build.rs b/build.rs index aff03653..e2020631 100644 --- a/build.rs +++ b/build.rs @@ -33,10 +33,12 @@ fn ensure_auto_initialize_ok(interpreter_config: &InterpreterConfig) -> Result<( fn configure_pyo3() -> Result<()> { let interpreter_config = pyo3_build_config::get(); - interpreter_config.emit_pyo3_cfgs(); - ensure_auto_initialize_ok(interpreter_config)?; + for cfg in interpreter_config.build_script_outputs() { + println!("{}", cfg) + } + // Emit cfgs like `thread_local_const_init` print_feature_cfgs(); diff --git a/pyo3-build-config/src/errors.rs b/pyo3-build-config/src/errors.rs index 8670b355..87c59a99 100644 --- a/pyo3-build-config/src/errors.rs +++ b/pyo3-build-config/src/errors.rs @@ -12,15 +12,21 @@ macro_rules! ensure { ($condition:expr, $($args: tt)+) => { if !($condition) { bail!($($args)+) } }; } -/// Show warning. If needed, please extend this macro to support arguments. +/// Show warning. #[macro_export] #[doc(hidden)] macro_rules! warn { - ($msg: literal) => { - println!(concat!("cargo:warning=", $msg)) + ($($args: tt)+) => { + println!("{}", $crate::format_warn!($($args)+)) }; - ($fmt: expr, $($args: tt)+) => { - println!("cargo:warning={}", format_args!($fmt, $($args)+)) +} + +/// Format warning into string. +#[macro_export] +#[doc(hidden)] +macro_rules! format_warn { + ($($args: tt)+) => { + format!("cargo:warning={}", format_args!($($args)+)) }; } diff --git a/pyo3-build-config/src/impl_.rs b/pyo3-build-config/src/impl_.rs index e9c9f5a8..8ef14d15 100644 --- a/pyo3-build-config/src/impl_.rs +++ b/pyo3-build-config/src/impl_.rs @@ -27,7 +27,7 @@ use target_lexicon::{Environment, OperatingSystem}; use crate::{ bail, ensure, errors::{Context, Error, Result}, - warn, + format_warn, warn, }; /// Minimum Python version PyO3 supports. @@ -154,31 +154,35 @@ pub struct InterpreterConfig { impl InterpreterConfig { #[doc(hidden)] - pub fn emit_pyo3_cfgs(&self) { + pub fn build_script_outputs(&self) -> Vec { // This should have been checked during pyo3-build-config build time. assert!(self.version >= MINIMUM_SUPPORTED_VERSION); + let mut out = vec![]; + // pyo3-build-config was released when Python 3.6 was supported, so minimum flag to emit is // Py_3_6 (to avoid silently breaking users who depend on this cfg). for i in 6..=self.version.minor { - println!("cargo:rustc-cfg=Py_3_{}", i); + out.push(format!("cargo:rustc-cfg=Py_3_{}", i)); } if self.implementation.is_pypy() { - println!("cargo:rustc-cfg=PyPy"); + out.push("cargo:rustc-cfg=PyPy".to_owned()); if self.abi3 { - warn!( + out.push(format_warn!( "PyPy does not yet support abi3 so the build artifacts will be version-specific. \ See https://foss.heptapod.net/pypy/pypy/-/issues/3397 for more information." - ); + )); } } else if self.abi3 { - println!("cargo:rustc-cfg=Py_LIMITED_API"); + out.push("cargo:rustc-cfg=Py_LIMITED_API".to_owned()); } for flag in &self.build_flags.0 { - println!("cargo:rustc-cfg=py_sys_config=\"{}\"", flag); + out.push(format!("cargo:rustc-cfg=py_sys_config=\"{}\"", flag)); } + + out } #[doc(hidden)] @@ -1011,12 +1015,12 @@ impl BuildFlags { Self( BuildFlags::ALL .iter() - .cloned() .filter(|flag| { config_map .get_value(&flag.to_string()) .map_or(false, |value| value == "1") }) + .cloned() .collect(), ) .fixup() @@ -2581,4 +2585,114 @@ mod tests { .expect("failed to run Python script"); assert_eq!(out.trim_end(), "42"); } + + #[test] + fn test_build_script_outputs_base() { + let interpreter_config = InterpreterConfig { + implementation: PythonImplementation::CPython, + version: PythonVersion { major: 3, minor: 8 }, + shared: true, + abi3: false, + 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![], + }; + assert_eq!( + interpreter_config.build_script_outputs(), + [ + "cargo:rustc-cfg=Py_3_6".to_owned(), + "cargo:rustc-cfg=Py_3_7".to_owned(), + "cargo:rustc-cfg=Py_3_8".to_owned(), + ] + ); + + let interpreter_config = InterpreterConfig { + implementation: PythonImplementation::PyPy, + ..interpreter_config + }; + assert_eq!( + interpreter_config.build_script_outputs(), + [ + "cargo:rustc-cfg=Py_3_6".to_owned(), + "cargo:rustc-cfg=Py_3_7".to_owned(), + "cargo:rustc-cfg=Py_3_8".to_owned(), + "cargo:rustc-cfg=PyPy".to_owned(), + ] + ); + } + + #[test] + fn test_build_script_outputs_abi3() { + let interpreter_config = 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![], + }; + + assert_eq!( + interpreter_config.build_script_outputs(), + [ + "cargo:rustc-cfg=Py_3_6".to_owned(), + "cargo:rustc-cfg=Py_3_7".to_owned(), + "cargo:rustc-cfg=Py_LIMITED_API".to_owned(), + ] + ); + + let interpreter_config = InterpreterConfig { + implementation: PythonImplementation::PyPy, + ..interpreter_config + }; + assert_eq!( + interpreter_config.build_script_outputs(), + [ + "cargo:rustc-cfg=Py_3_6".to_owned(), + "cargo:rustc-cfg=Py_3_7".to_owned(), + "cargo:rustc-cfg=PyPy".to_owned(), + "cargo:warning=PyPy does not yet support abi3 so the build artifacts \ + will be version-specific. See https://foss.heptapod.net/pypy/pypy/-/issues/3397 \ + for more information." + .to_owned(), + ] + ); + } + + #[test] + fn test_build_script_outputs_debug() { + let mut build_flags = BuildFlags::default(); + build_flags.0.insert(BuildFlag::Py_DEBUG); + let interpreter_config = InterpreterConfig { + implementation: PythonImplementation::CPython, + version: PythonVersion { major: 3, minor: 7 }, + shared: true, + abi3: false, + lib_name: Some("python3".into()), + lib_dir: None, + executable: None, + pointer_width: None, + build_flags, + suppress_build_script_link_lines: false, + extra_build_script_lines: vec![], + }; + + assert_eq!( + interpreter_config.build_script_outputs(), + [ + "cargo:rustc-cfg=Py_3_6".to_owned(), + "cargo:rustc-cfg=Py_3_7".to_owned(), + "cargo:rustc-cfg=py_sys_config=\"Py_DEBUG\"".to_owned(), + ] + ); + } } diff --git a/pyo3-build-config/src/lib.rs b/pyo3-build-config/src/lib.rs index e4655cd7..576dd370 100644 --- a/pyo3-build-config/src/lib.rs +++ b/pyo3-build-config/src/lib.rs @@ -42,7 +42,9 @@ use target_lexicon::OperatingSystem; /// For examples of how to use these attributes, [see PyO3's guide](https://pyo3.rs/latest/building_and_distribution/multiple_python_versions.html). #[cfg(feature = "resolve-config")] pub fn use_pyo3_cfgs() { - get().emit_pyo3_cfgs(); + for cargo_command in get().build_script_outputs() { + println!("{}", cargo_command) + } } /// Adds linker arguments suitable for PyO3's `extension-module` feature. diff --git a/pyo3-ffi/build.rs b/pyo3-ffi/build.rs index c472003d..940d9808 100644 --- a/pyo3-ffi/build.rs +++ b/pyo3-ffi/build.rs @@ -93,7 +93,9 @@ fn configure_pyo3() -> Result<()> { emit_link_config(&interpreter_config)?; } - interpreter_config.emit_pyo3_cfgs(); + for cfg in interpreter_config.build_script_outputs() { + println!("{}", cfg) + } // Extra lines come last, to support last write wins. for line in &interpreter_config.extra_build_script_lines { @@ -109,7 +111,7 @@ fn configure_pyo3() -> Result<()> { fn print_config_and_exit(config: &InterpreterConfig) { println!("\n-- PYO3_PRINT_CONFIG=1 is set, printing configuration and halting compile --"); config - .to_writer(&mut std::io::stdout()) + .to_writer(std::io::stdout()) .expect("failed to print config to stdout"); println!("\nnote: unset the PYO3_PRINT_CONFIG environment variable and retry to compile with the above config"); std::process::exit(101);