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
11pub 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#[derive(Debug)]
65pub struct InterpreterConfig {
66 pub version: PythonVersion,
67 pub libdir: Option<String>,
68 pub shared: bool,
69 pub ld_version: String,
70 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 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
107fn 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
139pub 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
153pub fn find_interpreter_matching<F>(f: F) -> Option<InterpreterConfig>
155where
156 F: FnMut(&InterpreterConfig) -> bool
157{
158 find_interpreters().find(f)
159}