mod cmdr;
use cmdr::SysCommand;
use semver;
use std::io;
use std::path::{self, PathBuf};
#[derive(PartialEq, Eq, Debug)]
pub enum Version {
Three,
Two,
}
#[derive(Debug)]
pub enum Error {
IO(io::Error),
Python3Only,
Other(&'static str),
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Self {
Error::IO(err)
}
}
impl From<Error> for io::Error {
fn from(err: Error) -> Self {
match err {
Error::IO(err) => err,
Error::Python3Only => io::Error::new(
io::ErrorKind::Other,
"this function is only available for Python 3",
),
Error::Other(why) => io::Error::new(io::ErrorKind::Other, why),
}
}
}
pub type PyResult<T> = Result<T, Error>;
pub type Py3Only<T> = Result<T, Error>;
#[inline]
fn other_err(what: &'static str) -> Error {
Error::Other(what)
}
fn build_script(lines: &[&str]) -> String {
let mut script = String::new();
script.push_str("from __future__ import print_function; ");
script.push_str("import sysconfig; ");
script.push_str("pyver = sysconfig.get_config_var('VERSION'); ");
script.push_str("getvar = sysconfig.get_config_var; ");
script.push_str(&lines.join("; "));
script
}
pub struct PythonConfig {
cmdr: SysCommand,
ver: Version,
}
impl Default for PythonConfig {
fn default() -> PythonConfig {
PythonConfig::new()
}
}
impl PythonConfig {
pub fn new() -> Self {
PythonConfig::version(Version::Three)
}
pub fn version(version: Version) -> Self {
match version {
Version::Three => Self::with_commander(version, SysCommand::new("python3")),
Version::Two => Self::with_commander(version, SysCommand::new("python2")),
}
}
fn with_commander(ver: Version, cmdr: SysCommand) -> Self {
PythonConfig { cmdr, ver }
}
fn is_py3(&self) -> Result<(), Error> {
if self.ver != Version::Three {
Err(Error::Python3Only)
} else {
Ok(())
}
}
pub fn interpreter<P: AsRef<path::Path>>(interpreter: P) -> PyResult<Self> {
let cmdr = SysCommand::new(
interpreter
.as_ref()
.to_str()
.ok_or_else(|| other_err("unable to coerce interpreter path to string"))?,
);
let mut cfg = PythonConfig {
cmdr,
ver: Version::Three,
};
if cfg.semantic_version()?.major == 2 {
cfg.ver = Version::Two;
}
Ok(cfg)
}
pub fn version_raw(&self) -> PyResult<String> {
self.cmdr.command("--version").map_err(From::from)
}
pub fn semantic_version(&self) -> PyResult<semver::Version> {
self.version_raw()
.and_then(|resp| {
let mut witer = resp.split_whitespace();
witer.next(); let ver = witer.next().ok_or_else(|| {
other_err("expected --version to return a string resembling 'Python X.Y.Z'")
})?;
semver::Version::parse(ver).map_err(|_| other_err("unable to parse semver"))
})
.map_err(From::from)
}
fn script(&self, lines: &[&str]) -> PyResult<String> {
self.cmdr
.commands(&["-c", &build_script(lines)])
.map_err(From::from)
}
pub fn prefix(&self) -> PyResult<String> {
self.script(&["print(sysconfig.get_config_var('prefix'))"])
}
pub fn prefix_path(&self) -> PyResult<PathBuf> {
self.prefix().map(PathBuf::from)
}
pub fn exec_prefix(&self) -> PyResult<String> {
self.script(&["print(sysconfig.get_config_var('exec_prefix'))"])
}
pub fn exec_prefix_path(&self) -> PyResult<PathBuf> {
self.exec_prefix().map(PathBuf::from)
}
pub fn includes(&self) -> PyResult<String> {
self.script(&[
"flags = ['-I' + sysconfig.get_path('include'), '-I' + sysconfig.get_path('platinclude')]",
"print(' '.join(flags))",
])
}
pub fn include_paths(&self) -> PyResult<Vec<PathBuf>> {
self.script(&[
"print(sysconfig.get_path('include'))",
"print(sysconfig.get_path('platinclude'))",
])
.map(|resp| resp.lines().map(PathBuf::from).collect())
}
pub fn cflags(&self) -> PyResult<String> {
self.script(&[
"flags = ['-I' + sysconfig.get_path('include'), '-I' + sysconfig.get_path('platinclude')]",
"flags.extend(sysconfig.get_config_var('CFLAGS').split())",
"print(' '.join(flags))",
])
}
pub fn libs(&self) -> PyResult<String> {
self.script(&[
"import sys",
"libs = ['-lpython' + pyver + sys.abiflags]",
"libs += getvar('LIBS').split()",
"libs += getvar('SYSLIBS').split()",
"print(' '.join(libs))",
])
}
pub fn ldflags(&self) -> PyResult<String> {
self.script(&[
"import sys",
"libs = ['-lpython' + pyver + sys.abiflags]",
"libs += getvar('LIBS').split()",
"libs += getvar('SYSLIBS').split()",
"libs.insert(0, '-L' + getvar('LIBPL')) if not getvar('Py_ENABLE_SHARED') else None",
"libs.extend(getvar('LINKFORSHARED').split()) if not getvar('PYTHONFRAMEWORK') else None",
"print(' '.join(libs))",
])
}
pub fn extension_suffix(&self) -> Py3Only<String> {
self.is_py3()?;
let resp = self.script(&["print(sysconfig.get_config_var('EXT_SUFFIX'))"])?;
Ok(resp)
}
pub fn abi_flags(&self) -> Py3Only<String> {
self.is_py3()?;
let resp = self.script(&["import sys", "print(sys.abiflags)"])?;
Ok(resp)
}
pub fn config_dir(&self) -> Py3Only<String> {
self.is_py3()?;
let resp = self.script(&["print(sysconfig.get_config_var('LIBPL'))"])?;
Ok(resp)
}
pub fn config_dir_path(&self) -> Py3Only<PathBuf> {
self.config_dir().map(PathBuf::from)
}
}