1use std::error;
2use std::fmt;
3use std::str::FromStr;
4
5use rustpython_parser::ast::{Expression, ExpressionType, Number, StatementType, StringGroup};
6use rustpython_parser::error::ParseError;
7use rustpython_parser::parser;
8
9#[derive(Debug)]
11pub enum Error {
12 SyntaxError(ParseError),
14 MissingBuildTimeVars,
16 KeyError(&'static str),
18}
19
20impl fmt::Display for Error {
21 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22 match self {
23 Error::SyntaxError(err) => err.fmt(f),
24 Error::MissingBuildTimeVars => write!(f, "missing build_time_vars variable"),
25 Error::KeyError(key) => write!(f, "missing required key {}", key),
26 }
27 }
28}
29
30impl error::Error for Error {
31 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
32 match self {
33 Error::SyntaxError(err) => Some(err),
34 Error::MissingBuildTimeVars => None,
35 Error::KeyError(_) => None,
36 }
37 }
38}
39
40impl From<ParseError> for Error {
41 fn from(err: ParseError) -> Self {
42 Self::SyntaxError(err)
43 }
44}
45
46#[derive(Debug, Clone)]
48pub struct PythonConfig {
49 sys_config_data: SysConfigData,
50}
51
52impl PythonConfig {
53 pub fn parse(src: &str) -> Result<Self, Error> {
55 let sys_config_data = SysConfigData::parse(src)?;
56 Ok(Self { sys_config_data })
57 }
58
59 pub fn version(&self) -> &str {
61 &self.sys_config_data.build_time_vars.version
62 }
63
64 pub fn version_major(&self) -> u32 {
66 let version = self.version();
67 version
68 .split('.')
69 .next()
70 .and_then(|x| x.parse::<u32>().ok())
71 .unwrap()
72 }
73
74 pub fn version_minor(&self) -> u32 {
76 let version = self.version();
77 version
78 .split('.')
79 .nth(1)
80 .and_then(|x| x.parse::<u32>().ok())
81 .unwrap()
82 }
83
84 pub fn prefix(&self) -> &str {
86 &self.sys_config_data.build_time_vars.prefix
87 }
88
89 pub fn exec_prefix(&self) -> &str {
91 &self.sys_config_data.build_time_vars.exec_prefix
92 }
93
94 pub fn cflags(&self) -> &str {
96 &self.sys_config_data.build_time_vars.cflags
97 }
98
99 pub fn libs(&self) -> &str {
103 &self.sys_config_data.build_time_vars.libs
104 }
105
106 pub fn ldflags(&self) -> &str {
110 &self.sys_config_data.build_time_vars.ldflags
111 }
112
113 pub fn ext_suffix(&self) -> &str {
115 &self.sys_config_data.build_time_vars.ext_suffix
116 }
117
118 pub fn abiflags(&self) -> &str {
120 &self.sys_config_data.build_time_vars.abiflags
121 }
122
123 pub fn config_dir(&self) -> &str {
125 &self.sys_config_data.build_time_vars.config_dir
126 }
127
128 pub fn include_dir(&self) -> &str {
130 &self.sys_config_data.build_time_vars.include_dir
131 }
132
133 pub fn lib_dir(&self) -> &str {
135 &self.sys_config_data.build_time_vars.lib_dir
136 }
137
138 pub fn ld_version(&self) -> &str {
140 &self.sys_config_data.build_time_vars.ld_version
141 }
142
143 pub fn soabi(&self) -> &str {
145 &self.sys_config_data.build_time_vars.soabi
146 }
147
148 pub fn shlib_suffix(&self) -> &str {
150 &self.sys_config_data.build_time_vars.shlib_suffix
151 }
152
153 pub fn enable_shared(&self) -> bool {
155 self.sys_config_data.build_time_vars.py_enable_shared
156 }
157
158 pub fn debug(&self) -> bool {
160 self.sys_config_data.build_time_vars.py_debug
161 }
162
163 pub fn ref_debug(&self) -> bool {
165 self.sys_config_data.build_time_vars.py_ref_debug
166 }
167
168 pub fn with_thread(&self) -> bool {
170 self.sys_config_data.build_time_vars.with_thread
171 }
172
173 pub fn pointer_size(&self) -> u32 {
175 self.sys_config_data.build_time_vars.size_of_void_p
176 }
177}
178
179#[derive(Debug, Clone)]
180struct SysConfigData {
181 pub build_time_vars: BuildTimeVars,
182}
183
184#[derive(Debug, Clone, Default)]
185struct BuildTimeVars {
186 pub abiflags: String,
187 pub count_allocs: bool,
188 pub cflags: String,
189 pub config_dir: String,
190 pub ext_suffix: String,
191 pub exec_prefix: String,
192 pub include_dir: String,
193 pub lib_dir: String,
194 pub libs: String,
195 pub ldflags: String,
196 pub ld_version: String,
197 pub prefix: String,
198 pub py_debug: bool,
199 pub py_ref_debug: bool,
200 pub py_trace_refs: bool,
201 pub py_enable_shared: bool,
202 pub soabi: String,
203 pub shlib_suffix: String,
204 pub size_of_void_p: u32,
205 pub with_thread: bool,
206 pub version: String,
207}
208
209impl SysConfigData {
210 pub fn parse(src: &str) -> Result<Self, Error> {
211 let program = parser::parse_program(src)?;
212 let mut vars = BuildTimeVars::default();
213 for stmt in program.statements {
214 if let StatementType::Assign { targets, value } = stmt.node {
215 let var_name = targets.get(0).ok_or(Error::MissingBuildTimeVars)?;
216 match &var_name.node {
217 ExpressionType::Identifier { name } if name == "build_time_vars" => {}
218 _ => continue,
219 }
220 if let ExpressionType::Dict { elements } = value.node {
221 for (key, value) in elements {
222 if let Some(key) = key.and_then(|key| get_string(&key)) {
223 match key.as_str() {
224 "ABIFLAGS" => {
225 vars.abiflags = get_string(&value).unwrap_or_default()
226 }
227 "COUNT_ALLOCS" => vars.count_allocs = get_bool(&value),
228 "CFLAGS" => vars.cflags = get_string(&value).unwrap_or_default(),
229 "LIBPL" => vars.config_dir = get_string(&value).unwrap_or_default(),
230 "EXT_SUFFIX" => {
231 vars.ext_suffix = get_string(&value).unwrap_or_default()
232 }
233 "exec_prefix" => {
234 vars.exec_prefix = get_string(&value).unwrap_or_default()
235 }
236 "INCLUDEDIR" => {
237 vars.include_dir = get_string(&value).unwrap_or_default()
238 }
239 "LIBDIR" => vars.lib_dir = get_string(&value).unwrap_or_default(),
240 "LIBS" => vars.libs = get_string(&value).unwrap_or_default(),
241 "LDFLAGS" => vars.ldflags = get_string(&value).unwrap_or_default(),
242 "LDVERSION" => {
243 vars.ld_version = get_string(&value).unwrap_or_default()
244 }
245 "prefix" => vars.prefix = get_string(&value).unwrap_or_default(),
246 "Py_DEBUG" => vars.py_debug = get_bool(&value),
247 "Py_ENABLE_SHARED" => vars.py_enable_shared = get_bool(&value),
248 "Py_REF_DEBUG" => vars.py_ref_debug = get_bool(&value),
249 "Py_TRACE_REFS" => vars.py_trace_refs = get_bool(&value),
250 "SOABI" => vars.soabi = get_string(&value).unwrap_or_default(),
251 "SHLIB_SUFFIX" => {
252 vars.shlib_suffix = get_string(&value).unwrap_or_default()
253 }
254 "SIZEOF_VOID_P" => {
255 vars.size_of_void_p = get_number(&value)
256 .ok_or(Error::KeyError("SIZEOF_VOID_P"))?
257 as u32
258 }
259 "VERSION" => {
260 vars.version =
261 get_string(&value).ok_or(Error::KeyError("VERSION"))?
262 }
263 _ => continue,
264 }
265 } else {
266 continue;
267 }
268 }
269 }
270 }
271 }
272 if vars.version.is_empty() {
273 return Err(Error::MissingBuildTimeVars);
275 }
276 Ok(SysConfigData {
277 build_time_vars: vars,
278 })
279 }
280}
281
282fn get_string(expr: &Expression) -> Option<String> {
283 match &expr.node {
284 ExpressionType::String { value: sg } => match sg {
285 StringGroup::Constant { value } => Some(value.to_string()),
286 StringGroup::Joined { values } => {
287 let mut s = String::new();
288 for value in values {
289 if let StringGroup::Constant { value: cs } = value {
290 s.push_str(&cs)
291 }
292 }
293 Some(s)
294 }
295 _ => None,
296 },
297 _ => None,
298 }
299}
300
301fn get_number(expr: &Expression) -> Option<i32> {
302 use num_traits::cast::ToPrimitive;
303
304 match &expr.node {
305 ExpressionType::Number { value } => {
306 if let Number::Integer { value } = value {
307 value.to_i32()
308 } else {
309 None
310 }
311 }
312 _ => None,
313 }
314}
315
316fn get_bool(expr: &Expression) -> bool {
317 get_number(expr).map(|x| x == 1).unwrap_or(false)
318}
319
320impl FromStr for PythonConfig {
321 type Err = Error;
322
323 fn from_str(s: &str) -> Result<Self, Self::Err> {
324 Self::parse(s)
325 }
326}
327
328#[cfg(test)]
329mod tests {
330 use super::{Error, PythonConfig};
331 use std::fs;
332
333 #[test]
334 fn read_python_sysconfig_data() {
335 let src =
336 fs::read_to_string("tests/fixtures/cpython38_sysconfigdata__darwin_darwin.py").unwrap();
337 let config = PythonConfig::parse(&src).unwrap();
338 assert_eq!(config.abiflags(), "");
339 assert_eq!(config.soabi(), "cpython-38-darwin");
340 assert_eq!(config.version(), "3.8");
341 assert_eq!(config.version_major(), 3);
342 assert_eq!(config.version_minor(), 8);
343
344 let config: PythonConfig = src.parse().unwrap();
346 assert_eq!(config.abiflags(), "");
347 }
348
349 #[test]
350 fn read_invalid_python_sysconfig_data() {
351 let config = PythonConfig::parse("i++").unwrap_err();
352 assert!(matches!(config, Error::SyntaxError(_)));
353
354 let config = PythonConfig::parse("").unwrap_err();
355 assert!(matches!(config, Error::MissingBuildTimeVars));
356
357 let config = PythonConfig::parse("i = 0").unwrap_err();
358 assert!(matches!(config, Error::MissingBuildTimeVars));
359
360 let config =
361 PythonConfig::parse("build_time_vars = {'VERSION': '3.8', 'SIZEOF_VOID_P': ''}")
362 .unwrap_err();
363 assert!(matches!(config, Error::KeyError("SIZEOF_VOID_P")));
364 }
365}