From 93645484ed7fc52d3e50f1d837539cdbfdad0fb4 Mon Sep 17 00:00:00 2001 From: James Salter Date: Sun, 16 Aug 2015 11:45:14 +0100 Subject: [PATCH] Remove pkgconfig approach from -sys builds. * pkgconfig is frequently broken or missing, doesn't contain the explicit path to the interpreter (leading to bad guesses), and confuses users. Rely on PATH alone. * Fix bad detection of OSX shared library python * Fix get_config_from_interpreter reporting python interpreter path inconsistently with find_interpreter_and_get_config - fixes OSX python.org python 3, which doesn't have a 'python' in exec_prefix, just 'python3' --- python27-sys/build.rs | 121 +++++++++++------------------------------- python3-sys/build.rs | 121 +++++++++++------------------------------- 2 files changed, 60 insertions(+), 182 deletions(-) diff --git a/python27-sys/build.rs b/python27-sys/build.rs index 3996db64..a39e61ca 100644 --- a/python27-sys/build.rs +++ b/python27-sys/build.rs @@ -205,7 +205,7 @@ fn get_rustc_link_lib(version: &PythonVersion, _: bool) -> Result Ok(format!("cargo:rustc-link-lib=static=python{}", dotted_version)), - "dynamic" => Ok(format!("cargo:rustc-link-lib=python{}", + "shared" => Ok(format!("cargo:rustc-link-lib=python{}", dotted_version)), "framework" => Ok(format!("cargo:rustc-link-lib=python{}", dotted_version)), @@ -241,33 +241,42 @@ fn matching_version(expected_version: &PythonVersion, actual_version: &PythonVer actual_version.minor == expected_version.minor) } -fn find_interpreter_and_get_config(expected_version: &PythonVersion) -> Result<(PythonVersion, Vec), String> { - let (interpreter_version, lines) = try!(get_config_from_interpreter("python")); - if matching_version(expected_version, &interpreter_version) { - return Ok((interpreter_version, lines)); +/// Locate a suitable python interpreter and extract config from it. +/// Tries to execute the interpreter as "python", "python{major version}", +/// "python{major version}.{minor version}" in order until one +/// is of the version we are expecting. +fn find_interpreter_and_get_config(expected_version: &PythonVersion) -> + Result<(PythonVersion, String, Vec), String> { + { + let interpreter_path = "python"; + let (interpreter_version, lines) = + try!(get_config_from_interpreter(interpreter_path)); + if matching_version(expected_version, &interpreter_version) { + return Ok((interpreter_version, interpreter_path.to_owned(), lines)); + } } { + let major_interpreter_path = &format!("python{}", expected_version.major); let (interpreter_version, lines) = try!(get_config_from_interpreter( - &format!("python{}", expected_version.major))); + major_interpreter_path)); if matching_version(expected_version, &interpreter_version) { - return Ok((interpreter_version, lines)); + return Ok((interpreter_version, major_interpreter_path.to_owned(), lines)); } } if let Some(minor) = expected_version.minor { + let minor_interpreter_path = &format!("python{}.{}", + expected_version.major, minor); let (interpreter_version, lines) = try!(get_config_from_interpreter( - &format!("python{}.{}", expected_version.major, minor))); + minor_interpreter_path)); if matching_version(expected_version, &interpreter_version) { - return Ok((interpreter_version, lines)); + return Ok((interpreter_version, minor_interpreter_path.to_owned(), lines)); } } - Err(format!("'python' is not version {} (is {})", - expected_version, interpreter_version)) + Err(format!("No python interpreter found of version {}", + expected_version)) } -/// Deduce configuration from the 'python' in the current PATH and print -/// cargo vars to stdout. -/// -/// Note that if the python doesn't satisfy expected_version, this will error. +/// Extract compilation vars from the specified interpreter. fn get_config_from_interpreter(interpreter: &str) -> Result<(PythonVersion, Vec), String> { let script = "import sys; import sysconfig; print(sys.version_info[0:2]); \ print(sysconfig.get_config_var('LIBDIR')); \ @@ -284,7 +293,8 @@ print(sys.exec_prefix);"; /// /// Note that if the python doesn't satisfy expected_version, this will error. fn configure_from_path(expected_version: &PythonVersion) -> Result { - let (interpreter_version, lines) = try!(find_interpreter_and_get_config(expected_version)); + let (interpreter_version, interpreter_path, lines) = + try!(find_interpreter_and_get_config(expected_version)); let libpath: &str = &lines[1]; let enable_shared: &str = &lines[2]; let exec_prefix: &str = &lines[3]; @@ -298,61 +308,7 @@ fn configure_from_path(expected_version: &PythonVersion) -> Result Result { - if env::var("PYTHON_27_NO_PKG_CONFIG").is_ok() { - return Err("PYTHON_27_NO_PKG_CONFIG set".to_owned()); - } - - // this emits relevant build info to stdout, which is picked up by the - // build chain (funny name for something with side-effects!) - try!(pkg_config::find_library(pkg_name)); - - // This seems to be a convention - unfortunately pkg-config doesn't - // tell you the executable name, but I've noticed at least on - // OS X homebrew the python bin dir for 3.4 doesn't actually contain - // a 'python'. - let exec_prefix = pkg_config::Config::get_variable(pkg_name, - "exec_prefix").unwrap(); - - // try to find the python interpreter in the exec_prefix somewhere. - // the .pc doesn't tell us :( - - 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); - match fs::metadata(&possible_exec_name) { - Ok(_) => return Ok(possible_exec_name), - Err(_) => () - }; - } - return Err("Unable to locate python interpreter".to_owned()); + return Ok(interpreter_path); } /// Determine the python version we're supposed to be building @@ -388,32 +344,15 @@ fn main() { // library based on the python interpeter's compilation flags. This is // necessary for e.g. matching the right unicode and threading interfaces. // - // By default, try to use pkgconfig - this seems to be a rust norm. + // This locates the python interpreter based on the PATH, which should + // work smoothly with an activated virtualenv. // - // If you want to use a different python, setting the appropriate - // PYTHON_27_NO_PKG_CONFIG environment variable will cause the script - // to pick up the python in your PATH. - // - // This will work smoothly with an activated virtualenv. - // // If you have troubles with your shell accepting '.' in a var name, // 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 = 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 - // variable "PYTHON_27_NO_PKG_CONFIG". - Err(_) => configure_from_path(&version).unwrap() - }; - + let python_interpreter_path = configure_from_path(&version).unwrap(); let config_map = get_config_vars(&python_interpreter_path).unwrap(); - for (key, val) in &config_map { match cfg_line_for_var(key, val) { Some(line) => println!("{}", line), diff --git a/python3-sys/build.rs b/python3-sys/build.rs index 458acb8e..3c521e35 100644 --- a/python3-sys/build.rs +++ b/python3-sys/build.rs @@ -203,7 +203,7 @@ fn get_rustc_link_lib(_: &PythonVersion, ld_version: &str, _: bool) -> Result Ok(format!("cargo:rustc-link-lib=static=python{}", ld_version)), - "dynamic" => Ok(format!("cargo:rustc-link-lib=python{}", + "shared" => Ok(format!("cargo:rustc-link-lib=python{}", ld_version)), "framework" => Ok(format!("cargo:rustc-link-lib=python{}", ld_version)), @@ -240,33 +240,42 @@ fn matching_version(expected_version: &PythonVersion, actual_version: &PythonVer actual_version.minor == expected_version.minor) } -fn find_interpreter_and_get_config(expected_version: &PythonVersion) -> Result<(PythonVersion, Vec), String> { - let (interpreter_version, lines) = try!(get_config_from_interpreter("python")); - if matching_version(expected_version, &interpreter_version) { - return Ok((interpreter_version, lines)); +/// Locate a suitable python interpreter and extract config from it. +/// Tries to execute the interpreter as "python", "python{major version}", +/// "python{major version}.{minor version}" in order until one +/// is of the version we are expecting. +fn find_interpreter_and_get_config(expected_version: &PythonVersion) -> + Result<(PythonVersion, String, Vec), String> { + { + let interpreter_path = "python"; + let (interpreter_version, lines) = + try!(get_config_from_interpreter(interpreter_path)); + if matching_version(expected_version, &interpreter_version) { + return Ok((interpreter_version, interpreter_path.to_owned(), lines)); + } } { + let major_interpreter_path = &format!("python{}", expected_version.major); let (interpreter_version, lines) = try!(get_config_from_interpreter( - &format!("python{}", expected_version.major))); + major_interpreter_path)); if matching_version(expected_version, &interpreter_version) { - return Ok((interpreter_version, lines)); + return Ok((interpreter_version, major_interpreter_path.to_owned(), lines)); } } if let Some(minor) = expected_version.minor { + let minor_interpreter_path = &format!("python{}.{}", + expected_version.major, minor); let (interpreter_version, lines) = try!(get_config_from_interpreter( - &format!("python{}.{}", expected_version.major, minor))); + minor_interpreter_path)); if matching_version(expected_version, &interpreter_version) { - return Ok((interpreter_version, lines)); + return Ok((interpreter_version, minor_interpreter_path.to_owned(), lines)); } } - Err(format!("'python' is not version {} (is {})", - expected_version, interpreter_version)) + Err(format!("No python interpreter found of version {}", + expected_version)) } -/// Deduce configuration from the 'python' in the current PATH and print -/// cargo vars to stdout. -/// -/// Note that if the python doesn't satisfy expected_version, this will error. +/// Extract compilation vars from the specified interpreter. fn get_config_from_interpreter(interpreter: &str) -> Result<(PythonVersion, Vec), String> { let script = "import sys; import sysconfig; print(sys.version_info[0:2]); \ print(sysconfig.get_config_var('LIBDIR')); \ @@ -284,7 +293,8 @@ print(sys.exec_prefix);"; /// /// Note that if the python doesn't satisfy expected_version, this will error. fn configure_from_path(expected_version: &PythonVersion) -> Result { - let (interpreter_version, lines) = try!(find_interpreter_and_get_config(expected_version)); + let (interpreter_version, interpreter_path, lines) = + try!(find_interpreter_and_get_config(expected_version)); let libpath: &str = &lines[1]; let enable_shared: &str = &lines[2]; let ld_version: &str = &lines[3]; @@ -299,61 +309,7 @@ fn configure_from_path(expected_version: &PythonVersion) -> Result Result { - if env::var("PYTHON_3_NO_PKG_CONFIG").is_ok() { - return Err("PYTHON_3_NO_PKG_CONFIG set".to_owned()); - } - - // this emits relevant build info to stdout, which is picked up by the - // build chain (funny name for something with side-effects!) - try!(pkg_config::find_library(pkg_name)); - - // This seems to be a convention - unfortunately pkg-config doesn't - // tell you the executable name, but I've noticed at least on - // OS X homebrew the python bin dir for 3.4 doesn't actually contain - // a 'python'. - let exec_prefix = pkg_config::Config::get_variable(pkg_name, - "exec_prefix").unwrap(); - - // try to find the python interpreter in the exec_prefix somewhere. - // the .pc doesn't tell us :( - - 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); - match fs::metadata(&possible_exec_name) { - Ok(_) => return Ok(possible_exec_name), - Err(_) => () - }; - } - return Err("Unable to locate python interpreter".to_owned()); + return Ok(interpreter_path); } /// Determine the python version we're supposed to be building @@ -389,32 +345,15 @@ fn main() { // library based on the python interpeter's compilation flags. This is // necessary for e.g. matching the right unicode and threading interfaces. // - // By default, try to use pkgconfig - this seems to be a rust norm. - // - // If you want to use a different python, setting the appropriate - // PYTHON_3_NO_PKG_CONFIG environment variable will cause the script - // to pick up the python in your PATH; e.g. for python27 X.X is 2.7. - // - // This will work smoothly with an activated virtualenv. + // This locates the python interpreter based on the PATH, which should + // work smoothly with an activated virtualenv. // // If you have troubles with your shell accepting '.' in a var name, // 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 = 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 - // variable "PYTHON_3_NO_PKG_CONFIG". - Err(_) => configure_from_path(&version).unwrap() - }; - + let python_interpreter_path = configure_from_path(&version).unwrap(); let config_map = get_config_vars(&python_interpreter_path).unwrap(); - for (key, val) in &config_map { match cfg_line_for_var(key, val) { Some(line) => println!("{}", line),