Skip to content

Commit e4edc6c

Browse files
author
Rene Leveille
committed
Load compilation options from _sysconfigdata_*.py file
Following the discussion in PyO3#1077 this change allows the compilation script to load the configurations from a _sysconfigdata_ file in the library directory. This file is also provided on target systems in the same directory. At least on Manjaro Linux. Which could remove the need to run a python script at compile time for compiling the the host. I've also addressed the linking need for android in PyO3#1082.
1 parent 0e0993f commit e4edc6c

File tree

2 files changed

+145
-8
lines changed

2 files changed

+145
-8
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ assert_approx_eq = "1.1.0"
3131
trybuild = "1.0.23"
3232
rustversion = "1.0"
3333

34+
[build-dependencies]
35+
walkdir = "~2.3"
36+
regex = "~1.3"
37+
3438
[features]
3539
default = ["macros"]
3640
macros = ["ctor", "indoc", "inventory", "paste", "pyo3cls", "unindent"]

build.rs

Lines changed: 141 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,112 @@ fn fix_config_map(mut config_map: HashMap<String, String>) -> HashMap<String, St
134134
config_map
135135
}
136136

137-
fn load_cross_compile_info() -> Result<(InterpreterConfig, HashMap<String, String>)> {
138-
let python_include_dir = env::var("PYO3_CROSS_INCLUDE_DIR")?;
139-
let python_include_dir = Path::new(&python_include_dir);
137+
fn parse_sysconfigdata(config_path: impl AsRef<Path>) -> Result<HashMap<String, String>> {
138+
let config_reader = BufReader::new(File::open(config_path)?);
139+
let mut entries = HashMap::new();
140+
let entry_re = regex::Regex::new(r#"'([a-zA-Z_0-9]*)': ((?:"|')([\S ]*)(?:"|')|\d+)($|,$)"#)?;
141+
let subsequent_re = regex::Regex::new(r#"\s+(?:"|')([\S ]*)(?:"|')($|,)"#)?;
142+
let mut previous_finished = None;
143+
for maybe_line in config_reader.lines() {
144+
let line = maybe_line?;
145+
if previous_finished.is_none() {
146+
let captures = match entry_re.captures(&line) {
147+
Some(c) => c,
148+
None => continue,
149+
};
150+
let key = captures[1].to_owned();
151+
let val = if let Some(val) = captures.get(3) {
152+
val.as_str().to_owned()
153+
} else {
154+
captures[2].to_owned()
155+
};
156+
if &captures[4] != "," && &captures[4] != "}" {
157+
previous_finished = Some(key.clone());
158+
}
159+
entries.insert(key, val);
160+
} else if let Some(ref key) = previous_finished {
161+
let captures = match subsequent_re.captures(&line) {
162+
Some(c) => c,
163+
None => continue,
164+
};
165+
let prev = entries.remove(key).unwrap();
166+
entries.insert(key.clone(), prev + &captures[1]);
167+
168+
if &captures[2] == "," || &captures[2] == "}" {
169+
previous_finished = None;
170+
}
171+
}
172+
}
140173

174+
Ok(entries)
175+
}
176+
177+
fn load_cross_compile_from_sysconfigdata(
178+
python_include_dir: &Path,
179+
python_lib_dir: &str,
180+
) -> Result<(InterpreterConfig, HashMap<String, String>)> {
181+
// find info from sysconfig
182+
// first find sysconfigdata file
183+
let sysconfig_re = regex::Regex::new(r"_sysconfigdata_m?_linux_([a-z_\-0-9]*)?\.py$")?;
184+
let mut walker = walkdir::WalkDir::new(&python_lib_dir).into_iter();
185+
let sysconfig_path = loop {
186+
let entry = match walker.next() {
187+
Some(Ok(entry)) => entry,
188+
None => bail!("Could not find sysconfigdata file"),
189+
_ => continue,
190+
};
191+
let entry = entry.into_path();
192+
if sysconfig_re.is_match(entry.to_str().unwrap()) {
193+
break entry;
194+
}
195+
};
196+
let config_map = parse_sysconfigdata(sysconfig_path)?;
197+
198+
let shared = match config_map
199+
.get("Py_ENABLE_SHARED")
200+
.map(|x| x.as_str())
201+
.ok_or("Py_ENABLE_SHARED is not defined")?
202+
{
203+
"1" | "true" | "True" => true,
204+
"0" | "false" | "False" => false,
205+
_ => panic!("Py_ENABLE_SHARED must be a bool (1/true/True or 0/false/False"),
206+
};
207+
208+
let (major, minor) = match config_map.get("VERSION") {
209+
Some(s) => {
210+
let split = s.split(".").collect::<Vec<&str>>();
211+
(split[0].parse::<u8>()?, split[1].parse::<u8>()?)
212+
}
213+
None => bail!("Could not find python version"),
214+
};
215+
216+
let ld_version = match config_map.get("LDVERSION") {
217+
Some(s) => s.clone(),
218+
None => format!("{}.{}", major, minor),
219+
};
220+
let python_version = PythonVersion {
221+
major,
222+
minor: Some(minor),
223+
implementation: PythonInterpreterKind::CPython,
224+
};
225+
226+
let interpreter_config = InterpreterConfig {
227+
version: python_version,
228+
libdir: Some(python_lib_dir.to_owned()),
229+
shared,
230+
ld_version,
231+
base_prefix: "".to_string(),
232+
executable: PathBuf::new(),
233+
calcsize_pointer: None,
234+
};
235+
236+
Ok((interpreter_config, fix_config_map(config_map)))
237+
}
238+
239+
fn load_cross_compile_from_headers(
240+
python_include_dir: &Path,
241+
python_lib_dir: &str,
242+
) -> Result<(InterpreterConfig, HashMap<String, String>)> {
141243
let patchlevel_defines = parse_header_defines(python_include_dir.join("patchlevel.h"))?;
142244

143245
let major = match patchlevel_defines
@@ -177,9 +279,9 @@ fn load_cross_compile_info() -> Result<(InterpreterConfig, HashMap<String, Strin
177279

178280
let interpreter_config = InterpreterConfig {
179281
version: python_version,
180-
libdir: Some(env::var("PYO3_CROSS_LIB_DIR")?),
282+
libdir: Some(python_lib_dir.to_owned()),
181283
shared,
182-
ld_version: "".to_string(),
284+
ld_version: format!("{}.{}", major, minor),
183285
base_prefix: "".to_string(),
184286
executable: PathBuf::new(),
185287
calcsize_pointer: None,
@@ -188,6 +290,20 @@ fn load_cross_compile_info() -> Result<(InterpreterConfig, HashMap<String, Strin
188290
Ok((interpreter_config, fix_config_map(config_map)))
189291
}
190292

293+
fn load_cross_compile_info(
294+
python_include_dir: String,
295+
python_lib_dir: String,
296+
) -> Result<(InterpreterConfig, HashMap<String, String>)> {
297+
let python_include_dir = Path::new(&python_include_dir);
298+
// Try to configure from the sysconfigdata file which is more accurate for the information
299+
// provided at python's compile time
300+
match load_cross_compile_from_sysconfigdata(python_include_dir, &python_lib_dir) {
301+
Ok(ret) => Ok(ret),
302+
// If the config could not be loaded by sysconfigdata, failover to configuring from headers
303+
Err(_) => load_cross_compile_from_headers(python_include_dir, &python_lib_dir),
304+
}
305+
}
306+
191307
/// Examine python's compile flags to pass to cfg by launching
192308
/// the interpreter and printing variables of interest from
193309
/// sysconfig.get_config_vars.
@@ -567,10 +683,27 @@ fn main() -> Result<()> {
567683
// If you have troubles with your shell accepting '.' in a var name,
568684
// try using 'env' (sorry but this isn't our fault - it just has to
569685
// match the pkg-config package name, which is going to have a . in it).
570-
let cross_compiling =
571-
env::var("PYO3_CROSS_INCLUDE_DIR").is_ok() && env::var("PYO3_CROSS_LIB_DIR").is_ok();
686+
//
687+
// Detecting if cross-compiling by checking if the target triple is different from the host
688+
// rustc's triple.
689+
let cross_compiling = env::var("TARGET") != env::var("HOST");
572690
let (interpreter_config, mut config_map) = if cross_compiling {
573-
load_cross_compile_info()?
691+
// If cross compiling we need the path to the cross-compiled include dir and lib dir, else
692+
// fail quickly and loudly
693+
let python_include_dir = match env::var("PYO3_CROSS_INCLUDE_DIR") {
694+
Ok(v) => v,
695+
Err(_) => bail!(
696+
"Must provide PYO3_CROSS_INCLUDE_DIR environment variable when cross-compiling"
697+
),
698+
};
699+
let python_lib_dir = match env::var("PYO3_CROSS_LIB_DIR") {
700+
Ok(v) => v,
701+
Err(_) => {
702+
bail!("Must provide PYO3_CROSS_LIB_DIR environment variable when cross-compiling")
703+
}
704+
};
705+
706+
load_cross_compile_info(python_include_dir, python_lib_dir)?
574707
} else {
575708
find_interpreter_and_get_config()?
576709
};

0 commit comments

Comments
 (0)