pyo3-build-config: conditionalize symbols on resolve-config feature

PR #1856 was buggy in that the `pyo3-build-config` crate didn't actually
work in library mode because `include_str!()` was attempting to resolve
missing files as part of populating some `const` values.

We could change the logic of these constants to make them lazy if
we wanted to support possibly getting access to the value. But the
simple solution is to conditionalize their presence on the crate
feature.

Test coverage for building and testing the crate in insolation with the
feature disabled has been added.

Various code has been conditionalized to avoid compiler warnings.

Also, it appears `cargo build|test -p pyo3-build-config
--no-default-features` still passes default features. This seems wrong
to me. But it is how my system behaves. Maybe it is an sccache bug?
I coded the new tests to `cd pyo3-build-config` first to work around.
This commit is contained in:
Gregory Szorc 2021-09-02 17:01:08 -07:00
parent afd4d46bdb
commit 3957afc9c5
3 changed files with 31 additions and 1 deletions

View File

@ -176,11 +176,23 @@ jobs:
- name: Build (no features) - name: Build (no features)
run: cargo build --lib --tests --no-default-features run: cargo build --lib --tests --no-default-features
# --no-default-features when used with `cargo build/test -p` doesn't seem to work!
- name: Build pyo3-build-config (no features)
run: |
cd pyo3-build-config
cargo build --no-default-features
# Run tests (except on PyPy, because no embedding API). # Run tests (except on PyPy, because no embedding API).
- if: ${{ !startsWith(matrix.python-version, 'pypy') }} - if: ${{ !startsWith(matrix.python-version, 'pypy') }}
name: Test (no features) name: Test (no features)
run: cargo test --no-default-features run: cargo test --no-default-features
# --no-default-features when used with `cargo build/test -p` doesn't seem to work!
- name: Test pyo3-build-config (no features)
run: |
cd pyo3-build-config
cargo test --no-default-features
- name: Build (all additive features) - name: Build (all additive features)
run: cargo build --lib --tests --no-default-features --features "${{ steps.settings.outputs.all_additive_features }}" run: cargo build --lib --tests --no-default-features --features "${{ steps.settings.outputs.all_additive_features }}"

View File

@ -479,6 +479,7 @@ struct CrossCompileConfig {
arch: String, arch: String,
} }
#[allow(unused)]
pub fn any_cross_compiling_env_vars_set() -> bool { pub fn any_cross_compiling_env_vars_set() -> bool {
env::var_os("PYO3_CROSS").is_some() env::var_os("PYO3_CROSS").is_some()
|| env::var_os("PYO3_CROSS_LIB_DIR").is_some() || env::var_os("PYO3_CROSS_LIB_DIR").is_some()
@ -1466,7 +1467,11 @@ mod tests {
} }
#[test] #[test]
#[cfg(all(target_os = "linux", target_arch = "x86_64"))] #[cfg(all(
target_os = "linux",
target_arch = "x86_64",
feature = "resolve-config"
))]
fn parse_sysconfigdata() { fn parse_sysconfigdata() {
// A best effort attempt to get test coverage for the sysconfigdata parsing. // A best effort attempt to get test coverage for the sysconfigdata parsing.
// Might not complete successfully depending on host installation; that's ok as long as // Might not complete successfully depending on host installation; that's ok as long as

View File

@ -9,8 +9,10 @@
mod errors; mod errors;
mod impl_; mod impl_;
#[cfg(feature = "resolve-config")]
use std::io::Cursor; use std::io::Cursor;
#[cfg(feature = "resolve-config")]
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
pub use impl_::{BuildFlag, BuildFlags, InterpreterConfig, PythonImplementation, PythonVersion}; pub use impl_::{BuildFlag, BuildFlags, InterpreterConfig, PythonImplementation, PythonVersion};
@ -28,6 +30,7 @@ pub use impl_::{BuildFlag, BuildFlags, InterpreterConfig, PythonImplementation,
/// | `#[cfg(PyPy)]` | This marks code which is run when compiling for PyPy. | /// | `#[cfg(PyPy)]` | This marks code which is run when compiling for PyPy. |
/// ///
/// For examples of how to use these attributes, [see PyO3's guide](https://pyo3.rs/latest/building_and_distribution/multiple_python_versions.html). /// 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() { pub fn use_pyo3_cfgs() {
get().emit_pyo3_cfgs(); get().emit_pyo3_cfgs();
} }
@ -56,6 +59,7 @@ fn _add_extension_module_link_args(target_os: &str, mut writer: impl std::io::Wr
/// ///
/// Because this will never change in a given compilation run, this is cached in a `once_cell`. /// Because this will never change in a given compilation run, this is cached in a `once_cell`.
#[doc(hidden)] #[doc(hidden)]
#[cfg(feature = "resolve-config")]
pub fn get() -> &'static InterpreterConfig { pub fn get() -> &'static InterpreterConfig {
static CONFIG: OnceCell<InterpreterConfig> = OnceCell::new(); static CONFIG: OnceCell<InterpreterConfig> = OnceCell::new();
CONFIG.get_or_init(|| { CONFIG.get_or_init(|| {
@ -74,23 +78,28 @@ pub fn get() -> &'static InterpreterConfig {
/// Path where PyO3's build.rs will write configuration by default. /// Path where PyO3's build.rs will write configuration by default.
#[doc(hidden)] #[doc(hidden)]
#[cfg(feature = "resolve-config")]
const DEFAULT_CROSS_COMPILE_CONFIG_PATH: &str = const DEFAULT_CROSS_COMPILE_CONFIG_PATH: &str =
concat!(env!("OUT_DIR"), "/pyo3-cross-compile-config.txt"); concat!(env!("OUT_DIR"), "/pyo3-cross-compile-config.txt");
/// Build configuration provided by `PYO3_CONFIG_FILE`. May be empty if env var not set. /// Build configuration provided by `PYO3_CONFIG_FILE`. May be empty if env var not set.
#[doc(hidden)] #[doc(hidden)]
#[cfg(feature = "resolve-config")]
const CONFIG_FILE: &str = include_str!(concat!(env!("OUT_DIR"), "/pyo3-build-config-file.txt")); 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 /// Build configuration set if abi3 features enabled and `PYO3_NO_PYTHON` env var present. Empty if
/// not both present. /// not both present.
#[doc(hidden)] #[doc(hidden)]
#[cfg(feature = "resolve-config")]
const ABI3_CONFIG: &str = include_str!(concat!(env!("OUT_DIR"), "/pyo3-build-config-abi3.txt")); 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 /// Build configuration discovered by `pyo3-build-config` build script. Not aware of
/// cross-compilation settings. /// cross-compilation settings.
#[doc(hidden)] #[doc(hidden)]
#[cfg(feature = "resolve-config")]
const HOST_CONFIG: &str = include_str!(concat!(env!("OUT_DIR"), "/pyo3-build-config.txt")); const HOST_CONFIG: &str = include_str!(concat!(env!("OUT_DIR"), "/pyo3-build-config.txt"));
#[cfg(feature = "resolve-config")]
fn abi3_config() -> InterpreterConfig { fn abi3_config() -> InterpreterConfig {
let mut interpreter_config = InterpreterConfig::from_reader(Cursor::new(ABI3_CONFIG)) let mut interpreter_config = InterpreterConfig::from_reader(Cursor::new(ABI3_CONFIG))
.expect("failed to parse hardcoded PyO3 abi3 config"); .expect("failed to parse hardcoded PyO3 abi3 config");
@ -109,9 +118,12 @@ fn abi3_config() -> InterpreterConfig {
/// Please don't use these - they could change at any time. /// Please don't use these - they could change at any time.
#[doc(hidden)] #[doc(hidden)]
pub mod pyo3_build_script_impl { pub mod pyo3_build_script_impl {
#[cfg(feature = "resolve-config")]
use crate::errors::{Context, Result}; use crate::errors::{Context, Result};
#[cfg(feature = "resolve-config")]
use std::path::Path; use std::path::Path;
#[cfg(feature = "resolve-config")]
use super::*; use super::*;
pub mod errors { pub mod errors {
@ -126,6 +138,7 @@ pub mod pyo3_build_script_impl {
/// Differs from .get() above only in the cross-compile case, where PyO3's build script is /// Differs from .get() above only in the cross-compile case, where PyO3's build script is
/// required to generate a new config (as it's the first build script which has access to the /// required to generate a new config (as it's the first build script which has access to the
/// correct value for CARGO_CFG_TARGET_OS). /// correct value for CARGO_CFG_TARGET_OS).
#[cfg(feature = "resolve-config")]
pub fn resolve_interpreter_config() -> Result<InterpreterConfig> { pub fn resolve_interpreter_config() -> Result<InterpreterConfig> {
if !CONFIG_FILE.is_empty() { if !CONFIG_FILE.is_empty() {
InterpreterConfig::from_reader(Cursor::new(CONFIG_FILE)) InterpreterConfig::from_reader(Cursor::new(CONFIG_FILE))