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

Update build.rs and remove serde and regex dependencies. #896

Merged
merged 1 commit into from
May 4, 2020
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
3 changes: 0 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,7 @@ assert_approx_eq = "1.1.0"
trybuild = "1.0.23"

[build-dependencies]
regex = { version = "1", default_features = false, features = ["std"] }
version_check = "0.9.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

[features]
default = []
Expand Down
162 changes: 88 additions & 74 deletions build.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
use regex::Regex;
use serde::Deserialize;
use std::io::{self, BufRead, BufReader};
use std::process::{Command, Stdio};
use std::{collections::HashMap, convert::AsRef, env, fmt, fs::File, path::Path};
use std::{
collections::HashMap,
convert::AsRef,
env, fmt,
fs::File,
io::{self, BufRead, BufReader},
path::{Path, PathBuf},
process::{Command, Stdio},
str::FromStr,
};
use version_check::{Channel, Date, Version};

/// Specifies the minimum nightly version needed to compile pyo3.
Expand All @@ -23,25 +28,25 @@ macro_rules! bail {
}

/// Information returned from python interpreter
#[derive(Deserialize, Debug)]
#[derive(Debug)]
struct InterpreterConfig {
version: PythonVersion,
libdir: Option<String>,
shared: bool,
ld_version: String,
/// Prefix used for determining the directory of libpython
base_prefix: String,
executable: String,
executable: PathBuf,
calcsize_pointer: Option<u32>,
}

#[derive(Deserialize, Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq)]
pub enum PythonInterpreterKind {
CPython,
PyPy,
}

#[derive(Deserialize, Debug, Clone)]
#[derive(Debug, Clone)]
struct PythonVersion {
major: u8,
// minor == None means any minor version will do
Expand All @@ -67,6 +72,17 @@ impl fmt::Display for PythonVersion {
}
}

impl FromStr for PythonInterpreterKind {
type Err = Box<dyn std::error::Error>;
fn from_str(s: &str) -> Result<Self> {
match s {
"CPython" => Ok(Self::CPython),
"PyPy" => Ok(Self::PyPy),
_ => Err(format!("Invalid interpreter: {}", s).into()),
}
}
}

/// A list of python interpreter compile-time preprocessor defines that
/// we will pick up and pass to rustc via --cfg=py_sys_config={varname};
/// this allows using them conditional cfg attributes in the .rs files, so
Expand Down Expand Up @@ -101,22 +117,15 @@ static SYSCONFIG_VALUES: [&str; 1] = [
/// Attempts to parse the header at the given path, returning a map of definitions to their values.
/// Each entry in the map directly corresponds to a `#define` in the given header.
fn parse_header_defines(header_path: impl AsRef<Path>) -> Result<HashMap<String, String>> {
// This regex picks apart a C style, single line `#define` statement into an identifier and a
// value. e.g. for the line `#define Py_DEBUG 1`, this regex will capture `Py_DEBUG` into
// `ident` and `1` into `value`.
let define_regex = Regex::new(r"^\s*#define\s+(?P<ident>[a-zA-Z0-9_]+)\s+(?P<value>.+)\s*$")?;

let header_file = File::open(header_path.as_ref())?;
let header_reader = BufReader::new(&header_file);

let header_reader = BufReader::new(File::open(header_path.as_ref())?);
let mut definitions = HashMap::new();
let tostr = |r: regex::Match<'_>| r.as_str().to_string();
for maybe_line in header_reader.lines() {
if let Some(captures) = define_regex.captures(&maybe_line?) {
match (captures.name("ident"), captures.name("value")) {
(Some(key), Some(val)) => definitions.insert(tostr(key), tostr(val)),
_ => None,
};
let line = maybe_line?;
let mut i = line.trim().split_whitespace();
if i.next() == Some("#define") {
if let (Some(key), Some(value), None) = (i.next(), i.next(), i.next()) {
definitions.insert(key.into(), value.into());
}
}
}
Ok(definitions)
Expand Down Expand Up @@ -179,7 +188,7 @@ fn load_cross_compile_info() -> Result<(InterpreterConfig, HashMap<String, Strin
shared,
ld_version: "".to_string(),
base_prefix: "".to_string(),
executable: "".to_string(),
executable: PathBuf::new(),
calcsize_pointer: None,
};

Expand All @@ -190,10 +199,7 @@ fn load_cross_compile_info() -> Result<(InterpreterConfig, HashMap<String, Strin
/// the interpreter and printing variables of interest from
/// sysconfig.get_config_vars.
#[cfg(not(target_os = "windows"))]
fn get_config_vars(python_path: &str) -> Result<HashMap<String, String>> {
// FIXME: We can do much better here using serde:
// import json, sysconfig; print(json.dumps({k:str(v) for k, v in sysconfig.get_config_vars().items()}))

fn get_config_vars(python_path: &Path) -> Result<HashMap<String, String>> {
let mut script = "import sysconfig; \
config = sysconfig.get_config_vars();"
.to_owned();
Expand Down Expand Up @@ -228,7 +234,7 @@ fn get_config_vars(python_path: &str) -> Result<HashMap<String, String>> {
}

#[cfg(target_os = "windows")]
fn get_config_vars(_: &str) -> Result<HashMap<String, String>> {
fn get_config_vars(_: &Path) -> Result<HashMap<String, String>> {
// sysconfig is missing all the flags on windows, so we can't actually
// query the interpreter directly for its build flags.
//
Expand Down Expand Up @@ -274,7 +280,7 @@ fn cfg_line_for_var(key: &str, val: &str) -> Option<String> {
}

/// Run a python script using the specified interpreter binary.
fn run_python_script(interpreter: &str, script: &str) -> Result<String> {
fn run_python_script(interpreter: &Path, script: &str) -> Result<String> {
let out = Command::new(interpreter)
.args(&["-c", script])
.stderr(Stdio::inherit())
Expand All @@ -286,12 +292,12 @@ fn run_python_script(interpreter: &str, script: &str) -> Result<String> {
bail!(
"Could not find any interpreter at {}, \
are you sure you have Python installed on your PATH?",
interpreter
interpreter.display()
);
} else {
bail!(
"Failed to run the Python interpreter at {}: {}",
interpreter,
interpreter.display(),
err
);
}
Expand Down Expand Up @@ -395,33 +401,24 @@ fn get_rustc_link_lib(config: &InterpreterConfig) -> Result<String> {
///
/// If none of the above works, an error is returned
fn find_interpreter_and_get_config() -> Result<(InterpreterConfig, HashMap<String, String>)> {
if let Some(sys_executable) = env::var_os("PYTHON_SYS_EXECUTABLE") {
let interpreter_path = sys_executable
.to_str()
.ok_or("Unable to get PYTHON_SYS_EXECUTABLE value")?;
let interpreter_config = get_config_from_interpreter(interpreter_path)?;

return Ok((interpreter_config, get_config_vars(interpreter_path)?));
let python_interpreter = if let Some(exe) = env::var_os("PYTHON_SYS_EXECUTABLE") {
exe.into()
} else {
PathBuf::from(
["python", "python3"]
.iter()
.find(|bin| {
if let Ok(out) = Command::new(bin).arg("--version").output() {
// begin with `Python 3.X.X :: additional info`
out.stdout.starts_with(b"Python 3") || out.stderr.starts_with(b"Python 3")
} else {
false
}
})
.ok_or("Python 3.x interpreter not found")?,
)
};

let python_interpreter = ["python", "python3"]
.iter()
.find(|bin| {
if let Ok(out) = Command::new(bin).arg("--version").output() {
// begin with `Python 3.X.X :: additional info`
out.stdout.starts_with(b"Python 3") || out.stderr.starts_with(b"Python 3")
} else {
false
}
})
.ok_or("Python 3.x interpreter not found")?;

// check default python
let interpreter_config = get_config_from_interpreter(&python_interpreter)?;
if interpreter_config.version.major == 3 {
return Ok((interpreter_config, get_config_vars(&python_interpreter)?));
}

let interpreter_config = get_config_from_interpreter(&python_interpreter)?;
if interpreter_config.version.major == 3 {
return Ok((interpreter_config, get_config_vars(&python_interpreter)?));
Expand All @@ -431,7 +428,7 @@ fn find_interpreter_and_get_config() -> Result<(InterpreterConfig, HashMap<Strin
}

/// Extract compilation vars from the specified interpreter.
fn get_config_from_interpreter(interpreter: &str) -> Result<InterpreterConfig> {
fn get_config_from_interpreter(interpreter: &Path) -> Result<InterpreterConfig> {
let script = r#"
import json
import platform
Expand All @@ -446,23 +443,40 @@ try:
except AttributeError:
base_prefix = sys.exec_prefix

print(json.dumps({
"version": {
"major": sys.version_info[0],
"minor": sys.version_info[1],
"implementation": platform.python_implementation()
},
"libdir": sysconfig.get_config_var('LIBDIR'),
"ld_version": sysconfig.get_config_var('LDVERSION') or sysconfig.get_config_var('py_version_short'),
"base_prefix": base_prefix,
"shared": PYPY or bool(sysconfig.get_config_var('Py_ENABLE_SHARED')),
"executable": sys.executable,
"calcsize_pointer": struct.calcsize("P"),
}))
libdir = sysconfig.get_config_var('LIBDIR')

print("version_major", sys.version_info[0])
print("version_minor", sys.version_info[1])
print("implementation", platform.python_implementation())
if libdir is not None:
print("libdir", libdir)
print("ld_version", sysconfig.get_config_var('LDVERSION') or sysconfig.get_config_var('py_version_short'))
print("base_prefix", base_prefix)
print("shared", PYPY or bool(sysconfig.get_config_var('Py_ENABLE_SHARED')))
print("executable", sys.executable)
print("calcsize_pointer", struct.calcsize("P"))
"#;
let json = run_python_script(interpreter, script)?;
Ok(serde_json::from_str(&json)
.map_err(|e| format!("Failed to get InterPreterConfig: {}", e))?)
let output = run_python_script(interpreter, script)?;
let map: HashMap<String, String> = output
.lines()
.filter_map(|line| {
let mut i = line.splitn(2, ' ');
Some((i.next()?.into(), i.next()?.into()))
})
.collect();
Ok(InterpreterConfig {
version: PythonVersion {
major: map["version_major"].parse()?,
minor: Some(map["version_minor"].parse()?),
implementation: map["implementation"].parse()?,
},
libdir: map.get("libdir").cloned(),
shared: map["shared"] == "True",
ld_version: map["ld_version"].clone(),
base_prefix: map["base_prefix"].clone(),
executable: map["executable"].clone().into(),
calcsize_pointer: Some(map["calcsize_pointer"].parse()?),
})
}

fn configure(interpreter_config: &InterpreterConfig) -> Result<String> {
Expand Down