pyo3-build-config: improve config file format

This commit is contained in:
David Hewitt 2021-08-03 21:17:17 +01:00
parent fcd35c1c3b
commit ed994ca44f
3 changed files with 333 additions and 242 deletions

183
build.rs
View File

@ -1,9 +1,9 @@
use std::{env, process::Command};
use std::{env, ffi::OsString, path::Path, process::Command};
use pyo3_build_config::{
bail, cargo_env_var, ensure, env_var,
errors::{Context, Result},
InterpreterConfig, PythonImplementation, PythonVersion,
InterpreterConfig, PythonVersion,
};
/// Minimum Python version PyO3 supports.
@ -20,80 +20,27 @@ fn ensure_python_version(interpreter_config: &InterpreterConfig) -> Result<()> {
Ok(())
}
fn ensure_target_architecture(interpreter_config: &InterpreterConfig) -> Result<()> {
fn ensure_target_pointer_width(pointer_width: u32) -> Result<()> {
// Try to check whether the target architecture matches the python library
let rust_target = match cargo_env_var("CARGO_CFG_TARGET_POINTER_WIDTH")
.unwrap()
.as_str()
{
"64" => "64-bit",
"32" => "32-bit",
"64" => 64,
"32" => 32,
x => bail!("unexpected Rust target pointer width: {}", x),
};
// The reason we don't use platform.architecture() here is that it's not
// reliable on macOS. See https://stackoverflow.com/a/1405971/823869.
// Similarly, sys.maxsize is not reliable on Windows. See
// https://stackoverflow.com/questions/1405913/how-do-i-determine-if-my-python-shell-is-executing-in-32bit-or-64bit-mode-on-os/1405971#comment6209952_1405971
// and https://stackoverflow.com/a/3411134/823869.
let python_target = match interpreter_config.calcsize_pointer {
Some(8) => "64-bit",
Some(4) => "32-bit",
None => {
// Unset, e.g. because we're cross-compiling. Don't check anything
// in this case.
return Ok(());
}
Some(n) => bail!("unexpected Python calcsize_pointer value: {}", n),
};
ensure!(
rust_target == python_target,
"Your Rust target architecture ({}) does not match your python interpreter ({})",
rust_target == pointer_width,
"your Rust target architecture ({}-bit) does not match your python interpreter ({}-bit)",
rust_target,
python_target
pointer_width
);
Ok(())
}
fn get_rustc_link_lib(config: &InterpreterConfig) -> Result<String> {
let link_name = if cargo_env_var("CARGO_CFG_TARGET_OS").unwrap() == "windows" {
if config.abi3 {
// Link against python3.lib for the stable ABI on Windows.
// See https://www.python.org/dev/peps/pep-0384/#linkage
//
// This contains only the limited ABI symbols.
"pythonXY:python3".to_owned()
} else if cargo_env_var("CARGO_CFG_TARGET_ENV").unwrap() == "gnu" {
// https://packages.msys2.org/base/mingw-w64-python
format!(
"pythonXY:python{}.{}",
config.version.major, config.version.minor
)
} else {
format!(
"pythonXY:python{}{}",
config.version.major, config.version.minor
)
}
} else {
match config.implementation {
PythonImplementation::CPython => match &config.ld_version {
Some(ld_version) => format!("python{}", ld_version),
None => bail!("failed to configure `ld_version` when compiling for unix"),
},
PythonImplementation::PyPy => format!("pypy{}-c", config.version.major),
}
};
Ok(format!(
"cargo:rustc-link-lib={link_model}{link_name}",
link_model = if config.shared { "" } else { "static=" },
link_name = link_name
))
}
fn rustc_minor_version() -> Option<u32> {
let rustc = env::var_os("RUSTC")?;
let output = Command::new(rustc).arg("--version").output().ok()?;
@ -108,28 +55,29 @@ fn rustc_minor_version() -> Option<u32> {
fn emit_cargo_configuration(interpreter_config: &InterpreterConfig) -> Result<()> {
let target_os = cargo_env_var("CARGO_CFG_TARGET_OS").unwrap();
let is_extension_module = cargo_env_var("CARGO_FEATURE_EXTENSION_MODULE").is_some();
match (is_extension_module, target_os.as_str()) {
(_, "windows") => {
// always link on windows, even with extension module
println!("{}", get_rustc_link_lib(interpreter_config)?);
// Set during cross-compiling.
if let Some(libdir) = &interpreter_config.libdir {
println!("cargo:rustc-link-search=native={}", libdir);
if target_os == "windows" || target_os == "android" || !is_extension_module {
// windows and android - always link
// other systems - only link if not extension module
println!(
"cargo:rustc-link-lib={link_model}{alias}{lib_name}",
link_model = if interpreter_config.shared {
""
} else {
"static="
},
alias = if target_os == "windows" {
"pythonXY:"
} else {
""
},
lib_name = interpreter_config
.lib_name
.as_ref()
.ok_or("config does not contain lib_name")?,
);
if let Some(lib_dir) = &interpreter_config.lib_dir {
println!("cargo:rustc-link-search=native={}", lib_dir);
}
// Set if we have an interpreter to use.
if let Some(base_prefix) = &interpreter_config.base_prefix {
println!("cargo:rustc-link-search=native={}\\libs", base_prefix);
}
}
(false, _) | (_, "android") => {
// other systems, only link libs if not extension module
// android always link.
println!("{}", get_rustc_link_lib(interpreter_config)?);
if let Some(libdir) = &interpreter_config.libdir {
println!("cargo:rustc-link-search=native={}", libdir);
}
}
_ => {}
}
if cargo_env_var("CARGO_FEATURE_AUTO_INITIALIZE").is_some() {
@ -152,9 +100,9 @@ fn emit_cargo_configuration(interpreter_config: &InterpreterConfig) -> Result<()
// TODO: PYO3_CI env is a hack to workaround CI with PyPy, where the `dev-dependencies`
// currently cause `auto-initialize` to be enabled in CI.
// Once cargo's `resolver = "2"` is stable (~ MSRV Rust 1.52), remove this.
// Once MSRV is 1.51 or higher, use cargo's `resolver = "2"` instead.
if interpreter_config.is_pypy() && env::var_os("PYO3_CI").is_none() {
bail!("The `auto-initialize` feature is not supported with PyPy.");
bail!("the `auto-initialize` feature is not supported with PyPy");
}
}
@ -166,21 +114,56 @@ fn emit_cargo_configuration(interpreter_config: &InterpreterConfig) -> Result<()
/// The result is written to pyo3_build_config::PATH, which downstream scripts can read from
/// (including `pyo3-macros-backend` during macro expansion).
fn configure_pyo3() -> Result<()> {
let interpreter_config = pyo3_build_config::make_interpreter_config()?;
let write_config_file = env_var("PYO3_WRITE_CONFIG_FILE").map_or(false, |os_str| os_str == "1");
let custom_config_file_path = env_var("PYO3_CONFIG_FILE");
if let Some(path) = &custom_config_file_path {
ensure!(
Path::new(path).is_absolute(),
"PYO3_CONFIG_FILE must be absolute"
);
}
let (interpreter_config, path_to_write) = match (write_config_file, custom_config_file_path) {
(true, Some(path)) => {
// Create new interpreter config and write it to config file
(pyo3_build_config::make_interpreter_config()?, Some(path))
}
(true, None) => bail!("PYO3_CONFIG_FILE must be set when PYO3_WRITE_CONFIG_FILE is set"),
(false, Some(path)) => {
// Read custom config file
let path = Path::new(&path);
println!("cargo:rerun-if-changed={}", path.display());
let config_file = std::fs::File::open(path)
.with_context(|| format!("failed to read config file at {}", path.display()))?;
let reader = std::io::BufReader::new(config_file);
(
pyo3_build_config::InterpreterConfig::from_reader(reader)?,
None,
)
}
(false, None) => (
// Create new interpreter config and write it to the default location
pyo3_build_config::make_interpreter_config()?,
Some(OsString::from(pyo3_build_config::DEFAULT_CONFIG_PATH)),
),
};
if let Some(path) = path_to_write {
interpreter_config.to_writer(&mut std::fs::File::create(&path).with_context(|| {
format!(
"failed to create config file at {}",
Path::new(&path).display()
)
})?)?;
}
if env_var("PYO3_PRINT_CONFIG").map_or(false, |os_str| os_str == "1") {
print_config_and_exit(&interpreter_config);
}
ensure_python_version(&interpreter_config)?;
ensure_target_architecture(&interpreter_config)?;
if let Some(pointer_width) = interpreter_config.pointer_width {
ensure_target_pointer_width(pointer_width)?;
}
emit_cargo_configuration(&interpreter_config)?;
interpreter_config.to_writer(
&mut std::fs::File::create(pyo3_build_config::PATH).with_context(|| {
format!(
"failed to create config file at {}",
pyo3_build_config::PATH
)
})?,
)?;
interpreter_config.emit_pyo3_cfgs();
let rustc_minor_version = rustc_minor_version().unwrap_or(0);
@ -200,15 +183,9 @@ fn configure_pyo3() -> Result<()> {
fn print_config_and_exit(config: &InterpreterConfig) {
println!("\n-- PYO3_PRINT_CONFIG=1 is set, printing configuration and halting compile --");
println!("implementation: {}", config.implementation);
println!("interpreter version: {}", config.version);
println!("interpreter path: {:?}", config.executable);
println!("libdir: {:?}", config.libdir);
println!("shared: {}", config.shared);
println!("base prefix: {:?}", config.base_prefix);
println!("ld_version: {:?}", config.ld_version);
println!("pointer width: {:?}", config.calcsize_pointer);
config
.to_writer(&mut std::io::stdout())
.expect("failed to print config to stdout");
std::process::exit(101);
}

View File

@ -43,15 +43,14 @@ pub fn env_var(var: &str) -> Option<OsString> {
/// this type.
#[cfg_attr(test, derive(Debug, PartialEq))]
pub struct InterpreterConfig {
pub implementation: PythonImplementation,
pub version: PythonVersion,
pub libdir: Option<String>,
pub shared: bool,
pub abi3: bool,
pub ld_version: Option<String>,
pub base_prefix: Option<String>,
pub lib_name: Option<String>,
pub lib_dir: Option<String>,
pub executable: Option<String>,
pub calcsize_pointer: Option<u32>,
pub implementation: PythonImplementation,
pub pointer_width: Option<u32>,
pub build_flags: BuildFlags,
}
@ -89,66 +88,85 @@ impl InterpreterConfig {
#[doc(hidden)]
pub fn from_reader(reader: impl Read) -> Result<Self> {
let reader = BufReader::new(reader);
let mut lines = reader.lines();
let lines = reader.lines();
macro_rules! parse_line {
($value:literal) => {
lines
.next()
.ok_or(concat!("reached end of config when reading ", $value))?
.context(concat!("failed to read ", $value, " from config"))?
.parse()
.context(concat!("failed to parse ", $value, " from config"))
macro_rules! parse_value {
($variable:ident, $value:ident) => {
$variable = Some($value.parse().context(format!(
concat!(
"failed to parse ",
stringify!($variable),
" from config value '{}'"
),
$value
))?)
};
}
macro_rules! parse_option_line {
($value:literal) => {
parse_option_string(
lines
let mut implementation = None;
let mut version = None;
let mut shared = None;
let mut abi3 = None;
let mut lib_name = None;
let mut lib_dir = None;
let mut executable = None;
let mut pointer_width = None;
let mut build_flags = None;
for (i, line) in lines.enumerate() {
let line = line.context("failed to read line from config")?;
let mut split = line.splitn(2, '=');
let (key, value) = (
split
.next()
.ok_or(concat!("reached end of config when reading ", $value))?
.context(concat!("failed to read ", $value, " from config"))?,
)
.context(concat!("failed to parse ", $value, "from config"))
};
.expect("first splitn value should always be present"),
split
.next()
.ok_or_else(|| format!("expected key=value pair on line {}", i + 1))?,
);
match key {
"implementation" => parse_value!(implementation, value),
"version" => parse_value!(version, value),
"shared" => parse_value!(shared, value),
"abi3" => parse_value!(abi3, value),
"lib_name" => parse_value!(lib_name, value),
"lib_dir" => parse_value!(lib_dir, value),
"executable" => parse_value!(executable, value),
"pointer_width" => parse_value!(pointer_width, value),
"build_flags" => parse_value!(build_flags, value),
unknown => bail!("unknown config key `{}`", unknown),
}
}
let major = parse_line!("major version")?;
let minor = parse_line!("minor version")?;
let libdir = parse_option_line!("libdir")?;
let shared = parse_line!("shared")?;
let abi3 = parse_line!("abi3")?;
let ld_version = parse_option_line!("ld_version")?;
let base_prefix = parse_option_line!("base_prefix")?;
let executable = parse_option_line!("executable")?;
let calcsize_pointer = parse_option_line!("calcsize_pointer")?;
let implementation = parse_line!("implementation")?;
let mut build_flags = BuildFlags(HashSet::new());
for line in lines {
build_flags
.0
.insert(line.context("failed to read flag from config")?.parse()?);
}
let version = version.ok_or("missing value for version")?;
let implementation = implementation.unwrap_or(PythonImplementation::CPython);
let abi3 = abi3.unwrap_or(false);
Ok(InterpreterConfig {
version: PythonVersion { major, minor },
libdir,
shared,
abi3,
ld_version,
base_prefix,
executable,
calcsize_pointer,
implementation,
build_flags,
version,
shared: shared.unwrap_or(true),
abi3,
lib_name,
lib_dir,
executable,
pointer_width,
build_flags: build_flags.unwrap_or_else(|| {
if abi3 {
BuildFlags::abi3()
} else {
BuildFlags(HashSet::new())
}
.fixup(version, implementation)
}),
})
}
#[doc(hidden)]
pub fn to_writer(&self, mut writer: impl Write) -> Result<()> {
macro_rules! write_line {
($value:expr) => {
writeln!(writer, "{}", $value).context(concat!(
($value:ident) => {
writeln!(writer, "{}={}", stringify!($value), self.$value).context(concat!(
"failed to write ",
stringify!($value),
" to config"
@ -157,44 +175,32 @@ impl InterpreterConfig {
}
macro_rules! write_option_line {
($opt:expr) => {
match &$opt {
Some(value) => writeln!(writer, "{}", value),
None => writeln!(writer, "null"),
}
.context(concat!(
($value:ident) => {
if let Some(value) = &self.$value {
writeln!(writer, "{}={}", stringify!($value), value).context(concat!(
"failed to write ",
stringify!($value),
" to config"
))
} else {
Ok(())
}
};
}
write_line!(self.version.major)?;
write_line!(self.version.minor)?;
write_option_line!(self.libdir)?;
write_line!(self.shared)?;
write_line!(self.abi3)?;
write_option_line!(self.ld_version)?;
write_option_line!(self.base_prefix)?;
write_option_line!(self.executable)?;
write_option_line!(self.calcsize_pointer)?;
write_line!(self.implementation)?;
for flag in &self.build_flags.0 {
write_line!(flag)?;
}
write_line!(implementation)?;
write_line!(version)?;
write_line!(shared)?;
write_line!(abi3)?;
write_option_line!(lib_name)?;
write_option_line!(lib_dir)?;
write_option_line!(executable)?;
write_option_line!(pointer_width)?;
write_line!(build_flags)?;
Ok(())
}
}
fn parse_option_string<T: FromStr>(string: String) -> Result<Option<T>, <T as FromStr>::Err> {
if string == "null" {
Ok(None)
} else {
string.parse().map(Some)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct PythonVersion {
pub major: u8,
@ -211,6 +217,24 @@ impl Display for PythonVersion {
}
}
impl FromStr for PythonVersion {
type Err = crate::errors::Error;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let mut split = value.splitn(2, '.');
let (major, minor) = (
split
.next()
.expect("first splitn value should always be present"),
split.next().ok_or("expected major.minor version")?,
);
Ok(Self {
major: major.parse().context("failed to parse major version")?,
minor: minor.parse().context("failed to parse minor version")?,
})
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum PythonImplementation {
CPython,
@ -453,14 +477,6 @@ impl BuildFlags {
// query the interpreter directly for its build flags.
let mut flags = HashSet::new();
flags.insert(BuildFlag::WITH_THREAD);
// Uncomment these manually if your python was built with these and you want
// the cfg flags to be set in rust.
//
// flags.insert(BuildFlag::Py_DEBUG);
// flags.insert(BuildFlag::Py_REF_DEBUG);
// flags.insert(BuildFlag::Py_TRACE_REFS);
// flags.insert(BuildFlag::COUNT_ALLOCS;
Self(flags)
}
@ -488,6 +504,33 @@ impl BuildFlags {
}
}
impl Display for BuildFlags {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut first = true;
for flag in &self.0 {
if !first {
write!(f, ",")?;
} else {
first = false;
}
write!(f, "{}", flag)?;
}
Ok(())
}
}
impl FromStr for BuildFlags {
type Err = std::convert::Infallible;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let mut flags = HashSet::new();
for flag in value.split(',') {
flags.insert(flag.parse().unwrap());
}
Ok(BuildFlags(flags))
}
}
fn parse_script_output(output: &str) -> HashMap<String, String> {
output
.lines()
@ -675,11 +718,10 @@ fn load_cross_compile_from_sysconfigdata(
let major = sysconfig_data.get_numeric("version_major")?;
let minor = sysconfig_data.get_numeric("version_minor")?;
let ld_version = match sysconfig_data.get("LDVERSION") {
Some(s) => s.clone(),
None => format!("{}.{}", major, minor),
};
let calcsize_pointer = sysconfig_data.get_numeric("SIZEOF_VOID_P").ok();
let pointer_width = sysconfig_data
.get_numeric("SIZEOF_VOID_P")
.map(|bytes_width: u32| bytes_width * 8)
.ok();
let soabi = match sysconfig_data.get("SOABI") {
Some(s) => s,
None => bail!("sysconfigdata did not define SOABI"),
@ -695,15 +737,18 @@ fn load_cross_compile_from_sysconfigdata(
};
Ok(InterpreterConfig {
implementation,
version,
libdir: cross_compile_config.lib_dir.to_str().map(String::from),
shared: sysconfig_data.get_bool("Py_ENABLE_SHARED")?,
abi3: is_abi3(),
ld_version: Some(ld_version),
base_prefix: None,
executable: None,
calcsize_pointer,
lib_dir: cross_compile_config.lib_dir.to_str().map(String::from),
lib_name: Some(default_lib_name_unix(
&version,
implementation,
sysconfig_data.get("LDVERSION").map(String::as_str),
)?),
executable: None,
pointer_width,
build_flags: BuildFlags::from_config_map(&sysconfig_data).fixup(version, implementation),
})
}
@ -730,16 +775,17 @@ fn windows_hardcoded_cross_compile(
bail!("PYO3_CROSS_PYTHON_VERSION or an abi3-py3* feature must be specified when cross-compiling for Windows.")
};
let version = PythonVersion { major, minor };
Ok(InterpreterConfig {
version: PythonVersion { major, minor },
libdir: cross_compile_config.lib_dir.to_str().map(String::from),
implementation: PythonImplementation::CPython,
version,
shared: true,
abi3: is_abi3(),
ld_version: None,
base_prefix: None,
lib_name: Some(default_lib_name_windows(&version, false, "msvc")),
lib_dir: cross_compile_config.lib_dir.to_str().map(String::from),
executable: None,
calcsize_pointer: None,
implementation: PythonImplementation::CPython,
pointer_width: None,
build_flags: BuildFlags::windows_hardcoded(),
})
}
@ -762,6 +808,37 @@ fn load_cross_compile_info(cross_compile_config: CrossCompileConfig) -> Result<I
}
}
// Link against python3.lib for the stable ABI on Windows.
// See https://www.python.org/dev/peps/pep-0384/#linkage
//
// This contains only the limited ABI symbols.
const WINDOWS_ABI3_LIB_NAME: &str = "python3";
fn default_lib_name_windows(version: &PythonVersion, abi3: bool, target_env: &str) -> String {
if abi3 {
WINDOWS_ABI3_LIB_NAME.to_owned()
} else if target_env == "gnu" {
// https://packages.msys2.org/base/mingw-w64-python
format!("python{}.{}", version.major, version.minor)
} else {
format!("python{}{}", version.major, version.minor)
}
}
fn default_lib_name_unix(
version: &PythonVersion,
implementation: PythonImplementation,
ld_version: Option<&str>,
) -> Result<String> {
match implementation {
PythonImplementation::CPython => match &ld_version {
Some(ld_version) => Ok(format!("python{}", ld_version)),
None => bail!("failed to configure `ld_version` when compiling for unix"),
},
PythonImplementation::PyPy => Ok(format!("pypy{}-c", version.major)),
}
}
/// Run a python script using the specified interpreter binary.
fn run_python_script(interpreter: &Path, script: &str) -> Result<String> {
let out = Command::new(interpreter)
@ -878,13 +955,13 @@ FRAMEWORK = bool(get_config_var("PYTHONFRAMEWORK"))
# unix-style shared library enabled
SHARED = bool(get_config_var("Py_ENABLE_SHARED"))
print("implementation", platform.python_implementation())
print("version_major", sys.version_info[0])
print("version_minor", sys.version_info[1])
print("implementation", platform.python_implementation())
print_if_set("libdir", get_config_var("LIBDIR"))
print_if_set("ld_version", get_config_var("LDVERSION"))
print_if_set("base_prefix", base_prefix)
print("shared", PYPY or ANACONDA or WINDOWS or FRAMEWORK or SHARED)
print_if_set("ld_version", get_config_var("LDVERSION"))
print_if_set("libdir", get_config_var("LIBDIR"))
print_if_set("base_prefix", base_prefix)
print("executable", sys.executable)
print("calcsize_pointer", struct.calcsize("P"))
"#;
@ -901,22 +978,48 @@ print("calcsize_pointer", struct.calcsize("P"))
.context("failed to parse minor version")?,
};
let abi3 = is_abi3();
let implementation = map["implementation"].parse()?;
let lib_name = if cfg!(windows) {
default_lib_name_windows(
&version,
abi3,
&cargo_env_var("CARGO_CFG_TARGET_ENV").unwrap(),
)
} else {
default_lib_name_unix(
&version,
implementation,
map.get("ld_version").map(String::as_str),
)?
};
let lib_dir = if cfg!(windows) {
map.get("base_prefix")
.map(|base_prefix| format!("cargo:rustc-link-search=native={}\\libs", base_prefix))
} else {
map.get("libdir").cloned()
};
// The reason we don't use platform.architecture() here is that it's not
// reliable on macOS. See https://stackoverflow.com/a/1405971/823869.
// Similarly, sys.maxsize is not reliable on Windows. See
// https://stackoverflow.com/questions/1405913/how-do-i-determine-if-my-python-shell-is-executing-in-32bit-or-64bit-mode-on-os/1405971#comment6209952_1405971
// and https://stackoverflow.com/a/3411134/823869.
let calcsize_pointer: u32 = map["calcsize_pointer"]
.parse()
.context("failed to parse calcsize_pointer")?;
Ok(InterpreterConfig {
version,
implementation,
libdir: map.get("libdir").cloned(),
shared,
abi3: is_abi3(),
ld_version: map.get("ld_version").cloned(),
base_prefix: map.get("base_prefix").cloned(),
abi3,
lib_name: Some(lib_name),
lib_dir,
executable: map.get("executable").cloned(),
calcsize_pointer: Some(
map["calcsize_pointer"]
.parse()
.context("failed to parse calcsize_pointer")?,
),
pointer_width: Some(calcsize_pointer * 8),
build_flags: BuildFlags::from_interpreter(interpreter)?.fixup(version, implementation),
})
}
@ -932,19 +1035,25 @@ pub fn make_interpreter_config() -> Result<InterpreterConfig> {
// If PYO3_NO_PYTHON is set with abi3, we can build PyO3 without calling Python.
if let Some(abi3_minor_version) = abi3_version {
if env_var("PYO3_NO_PYTHON").is_some() {
return Ok(InterpreterConfig {
version: PythonVersion {
let version = PythonVersion {
major: 3,
minor: abi3_minor_version,
},
implementation: PythonImplementation::CPython,
};
let implementation = PythonImplementation::CPython;
let lib_name = if cfg!(windows) {
Some(WINDOWS_ABI3_LIB_NAME.to_owned())
} else {
None
};
return Ok(InterpreterConfig {
version,
implementation,
abi3: true,
libdir: None,
lib_name,
lib_dir: None,
build_flags: BuildFlags::abi3(),
base_prefix: None,
calcsize_pointer: None,
pointer_width: None,
executable: None,
ld_version: None,
shared: true,
});
}
@ -981,13 +1090,12 @@ mod tests {
fn test_read_write_roundtrip() {
let config = InterpreterConfig {
abi3: true,
base_prefix: Some("base_prefix".into()),
build_flags: BuildFlags::abi3(),
calcsize_pointer: Some(32),
pointer_width: Some(32),
executable: Some("executable".into()),
implementation: PythonImplementation::CPython,
ld_version: Some("ld_version".into()),
libdir: Some("libdir".into()),
lib_name: Some("lib_name".into()),
lib_dir: Some("lib_dir".into()),
shared: true,
version: MINIMUM_SUPPORTED_VERSION,
};
@ -1003,18 +1111,17 @@ mod tests {
let config = InterpreterConfig {
abi3: false,
base_prefix: None,
build_flags: {
let mut flags = HashSet::new();
flags.insert(BuildFlag::Py_DEBUG);
flags.insert(BuildFlag::Other(String::from("Py_SOME_FLAG")));
BuildFlags(flags)
},
calcsize_pointer: None,
pointer_width: None,
executable: None,
implementation: PythonImplementation::PyPy,
ld_version: None,
libdir: None,
lib_dir: None,
lib_name: None,
shared: true,
version: PythonVersion {
major: 3,

View File

@ -7,6 +7,8 @@
pub mod errors;
mod impl_;
use std::{ffi::OsString, path::Path};
use once_cell::sync::OnceCell;
// Used in PyO3's build.rs
@ -23,15 +25,20 @@ pub use impl_::{
pub fn get() -> &'static InterpreterConfig {
static CONFIG: OnceCell<InterpreterConfig> = OnceCell::new();
CONFIG.get_or_init(|| {
let config_file = std::fs::File::open(PATH).expect("config file missing");
let config_path = std::env::var_os("PYO3_CONFIG_FILE")
.unwrap_or_else(|| OsString::from(DEFAULT_CONFIG_PATH));
let config_file = std::fs::File::open(DEFAULT_CONFIG_PATH).expect(&format!(
"failed to open PyO3 config file at {}",
Path::new(&config_path).display()
));
let reader = std::io::BufReader::new(config_file);
InterpreterConfig::from_reader(reader).expect("failed to parse config file")
})
}
/// Path where PyO3's build.rs will write configuration.
/// Path where PyO3's build.rs will write configuration by default.
#[doc(hidden)]
pub const PATH: &str = concat!(env!("OUT_DIR"), "/pyo3-build-config.txt");
pub const DEFAULT_CONFIG_PATH: &str = concat!(env!("OUT_DIR"), "/pyo3-build-config.txt");
/// Adds all the [`#[cfg]` flags](index.html) to the current compilation.
///