python_config/
lib.rs

1use std::collections::HashMap;
2use std::io;
3use std::fmt;
4use std::path::{Path, PathBuf};
5use std::process::{Command, Stdio};
6use std::str::FromStr;
7
8pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
9
10
11/// Extract compilation vars from the specified interpreter.
12pub fn get_config_from_interpreter<P: AsRef<Path>>(interpreter: P) -> Result<InterpreterConfig> {
13    let script = r#"
14import json
15import platform
16import struct
17import sys
18import sysconfig
19
20PYPY = platform.python_implementation() == "PyPy"
21
22try:
23    base_prefix = sys.base_prefix
24except AttributeError:
25    base_prefix = sys.exec_prefix
26
27libdir = sysconfig.get_config_var('LIBDIR')
28
29print("version_major", sys.version_info[0])
30print("version_minor", sys.version_info[1])
31print("implementation", platform.python_implementation())
32if libdir is not None:
33    print("libdir", libdir)
34print("ld_version", sysconfig.get_config_var('LDVERSION') or sysconfig.get_config_var('py_version_short'))
35print("base_prefix", base_prefix)
36print("shared", PYPY or bool(sysconfig.get_config_var('Py_ENABLE_SHARED')))
37print("executable", sys.executable)
38print("calcsize_pointer", struct.calcsize("P"))
39"#;
40    let output = run_python_script(interpreter.as_ref(), script)?;
41    let map: HashMap<String, String> = output
42        .lines()
43        .filter_map(|line| {
44            let mut i = line.splitn(2, ' ');
45            Some((i.next()?.into(), i.next()?.into()))
46        })
47        .collect();
48    Ok(InterpreterConfig {
49        version: PythonVersion {
50            major: map["version_major"].parse()?,
51            minor: map["version_minor"].parse()?,
52            implementation: map["implementation"].parse()?,
53        },
54        libdir: map.get("libdir").cloned(),
55        shared: map["shared"] == "True",
56        ld_version: map["ld_version"].clone(),
57        base_prefix: map["base_prefix"].clone(),
58        executable: map["executable"].clone().into(),
59        calcsize_pointer: map["calcsize_pointer"].parse()?,
60    })
61}
62
63/// Information about a Python interpreter
64#[derive(Debug)]
65pub struct InterpreterConfig {
66    pub version: PythonVersion,
67    pub libdir: Option<String>,
68    pub shared: bool,
69    pub ld_version: String,
70    /// Prefix used for determining the directory of libpython
71    pub base_prefix: String,
72    pub executable: PathBuf,
73    pub calcsize_pointer: u32,
74}
75
76#[derive(Debug, Clone, PartialEq, Eq, Copy)]
77pub enum PythonImplementation {
78    CPython,
79    PyPy,
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
83pub struct PythonVersion {
84    pub major: u8,
85    // minor == None means any minor version will do
86    pub minor: u8,
87    pub implementation: PythonImplementation,
88}
89
90impl fmt::Display for PythonVersion {
91    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
92        write!(f, "{:?} {}.{}", self.implementation, self.major, self.minor)
93    }
94}
95
96impl FromStr for PythonImplementation {
97    type Err = Box<dyn std::error::Error>;
98    fn from_str(s: &str) -> Result<Self> {
99        match s {
100            "CPython" => Ok(PythonImplementation::CPython),
101            "PyPy" => Ok(PythonImplementation::PyPy),
102            _ => Err(format!("Invalid interpreter: {}", s).into()),
103        }
104    }
105}
106
107/// Run a python script using the specified interpreter binary.
108fn run_python_script(interpreter: &Path, script: &str) -> Result<String> {
109    let out = Command::new(interpreter)
110        .args(&["-c", script])
111        .stderr(Stdio::inherit())
112        .output();
113
114    match out {
115        Err(err) => {
116            if err.kind() == io::ErrorKind::NotFound {
117                return Err(format!(
118                    "Could not find any interpreter at {}, \
119                     are you sure you have Python installed on your PATH?",
120                    interpreter.display()
121                )
122                .into());
123            } else {
124                return Err(format!(
125                    "Failed to run the Python interpreter at {}: {}",
126                    interpreter.display(),
127                    err
128                )
129                .into());
130            }
131        }
132        Ok(ref ok) if !ok.status.success() => {
133            return Err(format!("Python script failed: {}", script).into())
134        }
135        Ok(ok) => Ok(String::from_utf8(ok.stdout)?),
136    }
137}
138
139/// Search for python interpreters and yield them in order.
140///
141/// The following locations are checked in the order listed:
142///
143/// 1. `python`
144/// 2. `python3`
145pub fn find_interpreters() -> impl Iterator<Item = InterpreterConfig> {
146    ["python", "python3"]
147        .iter()
148        .filter_map(|interpreter| {
149            get_config_from_interpreter(Path::new(interpreter)).ok()
150        })
151}
152
153/// Return the first interpreter matching the given criterion.
154pub fn find_interpreter_matching<F>(f: F) -> Option<InterpreterConfig>
155where
156    F: FnMut(&InterpreterConfig) -> bool
157{
158    find_interpreters().find(f)
159}