can build with only a major python 3 version

This commit is contained in:
James Salter 2015-05-25 14:41:26 +01:00
parent 7931e8f1f6
commit 124a85d749
4 changed files with 85 additions and 24 deletions

View File

@ -40,3 +40,9 @@ optional = true
[features]
# Maybe one day python 3 should be the default. But not this day.
default = ["python27-sys"]
# Optional features to support explicitly specifying python minor version.
# If you don't care which minor version, just specify python3-sys as a
# feature.
python-3-5 = ["python3-sys/python-3-5"]
python-3-4 = ["python3-sys/python-3-4"]

View File

@ -41,5 +41,5 @@ git = "https://github.com/alexcrichton/pkg-config-rs.git"
#
# Similarly functionality is duplicated in python3-sys/Cargo.toml
# where supporting multiple 3.x's is more important.
default = ["python_2_7"]
python_2_7 = []
default = ["python-2-7"]
python-2-7 = []

View File

@ -35,6 +35,11 @@ git = "https://github.com/alexcrichton/pkg-config-rs.git"
[features]
# This is examined by ./build.rs to determine which python version
# to try to bind to.
default = ["python_3_4"]
python_3_4 = []
python_3_5 = []
default = ["python-3"]
# Bind to any python 3.x.
python-3 = []
# Or, bind to a particular minor version.
python-3-4 = []
python-3-5 = []

View File

@ -9,7 +9,8 @@ use std::fs;
struct PythonVersion {
major: u8,
minor: u8
// minor == None means any minor version will do
minor: Option<u8>
}
const CFG_KEY: &'static str = "py_sys_config";
@ -148,7 +149,7 @@ fn get_macos_linkmodel() -> Result<String, String> {
fn get_rustc_link_lib(version: &PythonVersion, _: bool) -> Result<String, String> {
// os x can be linked to a framework or static or dynamic, and
// Py_ENABLE_SHARED is wrong; framework means shared library
let dotted_version = format!("{}.{}", version.major, version.minor);
let dotted_version = format!("{}.{}", version.major, version.minor.unwrap());
match get_macos_linkmodel().unwrap().as_ref() {
"static" => Ok(format!("cargo:rustc-link-lib=static=python{}",
dotted_version)),
@ -160,10 +161,40 @@ fn get_rustc_link_lib(version: &PythonVersion, _: bool) -> Result<String, String
}
}
/// Check that the interpreter's reported version matches expected_version;
/// if it does, return the specific version the interpreter reports.
fn get_interpreter_version(line: &str, expected_version: &PythonVersion)
-> Result<PythonVersion, String> {
let version_re = Regex::new(r"\((\d+), (\d+)\)").unwrap();
let interpreter_version = match version_re.captures(&line) {
Some(cap) => PythonVersion {
major: cap.at(1).unwrap().parse().unwrap(),
minor: Some(cap.at(2).unwrap().parse().unwrap())
},
None => return Err(
format!("Unexpected response to version query {}", line))
};
if interpreter_version.major != expected_version.major ||
(expected_version.minor.is_some() &&
interpreter_version.minor != expected_version.minor)
{
Err(format!("'python' is not version {}.{} (is {})",
expected_version.major,
match expected_version.minor {
Some(v) => v.to_string(),
None => "*".to_owned()
},
line))
} else {
Ok(interpreter_version)
}
}
/// Deduce configuration from the 'python' in the current PATH and print
/// cargo vars to stdout.
///
/// Note that if that python isn't version 2.7, this will error.
/// Note that if the python doesn't satisfy expected_version, this will error.
fn configure_from_path(expected_version: &PythonVersion) -> Result<String, String> {
let script = "import sys; import sysconfig; print(sys.version_info[0:2]); \
print(sysconfig.get_config_var('LIBDIR')); \
@ -174,18 +205,12 @@ print(sys.exec_prefix);";
let version: &str = lines[0];
let libpath: &str = lines[1];
let enable_shared: &str = lines[2];
let exec_prefix: &str = lines[3];
if version != format!("({}, {})",
expected_version.major,
expected_version.minor)
{
return Err(format!("'python' is not version {}.{} (is {})",
expected_version.major, expected_version.minor, version));
}
let interpreter_version = try!(get_interpreter_version(version,
expected_version));
println!("{}", get_rustc_link_lib(expected_version,
println!("{}", get_rustc_link_lib(&interpreter_version,
enable_shared == "1").unwrap());
println!("cargo:rustc-link-search=native={}", libpath);
return Ok(format!("{}/bin/python", exec_prefix));
@ -208,12 +233,22 @@ fn configure_from_pkgconfig(version: &PythonVersion, pkg_name: &str)
// try to find the python interpreter in the exec_prefix somewhere.
// the .pc doesn't tell us :(
let attempts = [
format!("/bin/python{}_{}", version.major, version.minor),
let mut attempts = vec![
format!("/bin/python{}", version.major),
"/bin/python".to_owned()
];
// Try to seek python(major).(minor) if the user specified a minor.
//
// Ideally, we'd still do this even if they didn't based off the
// specific version of the package located by pkg_config above,
// but it's not obvious how to reliably extract that out of the .pc.
if version.minor.is_some() {
attempts.insert(0, format!("/bin/python{}_{}", version.major,
version.minor.unwrap()));
}
for attempt in attempts.iter() {
let possible_exec_name = format!("{}{}", exec_prefix,
attempt);
@ -227,13 +262,24 @@ fn configure_from_pkgconfig(version: &PythonVersion, pkg_name: &str)
/// Determine the python version we're supposed to be building
/// from the features passed via the environment.
///
/// The environment variable can choose to omit a minor
/// version if the user doesn't care.
fn version_from_env() -> Result<PythonVersion, String> {
let re = Regex::new(r"CARGO_FEATURE_PYTHON_(\d+)_(\d+)").unwrap();
for (key, _) in env::vars() {
let re = Regex::new(r"CARGO_FEATURE_PYTHON_(\d+)(_(\d+))?").unwrap();
// sort env::vars so we get more explicit version specifiers first
// so if the user passes e.g. the python-3 feature and the python-3-5
// feature, python-3-5 takes priority.
let mut vars = env::vars().collect::<Vec<_>>();
vars.sort_by(|a, b| b.cmp(a));
for (key, _) in vars {
match re.captures(&key) {
Some(cap) => return Ok(PythonVersion {
major: cap.at(1).unwrap().parse().unwrap(),
minor: cap.at(2).unwrap().parse().unwrap()
minor: match cap.at(3) {
Some(s) => Some(s.parse().unwrap()),
None => None
}
}),
None => ()
}
@ -259,7 +305,11 @@ fn main() {
// try using 'env' (sorry but this isn't our fault - it just has to
// match the pkg-config package name, which is going to have a . in it).
let version = version_from_env().unwrap();
let pkg_name = format!("python-{}.{}", version.major, version.minor);
let pkg_name = match version.minor {
Some(minor) => format!("python-{}.{}", version.major, minor),
None => format!("python{}", version.major)
};
let python_interpreter_path = match configure_from_pkgconfig(&version, &pkg_name) {
Ok(p) => p,
// no pkgconfig - either it failed or user set the environment