use crate::target::get_host_target;
use crate::Target;
use anyhow::{bail, Result};
use fs_err::{self as fs, DirEntry};
use std::collections::HashMap;
use std::env;
use std::io;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
pub fn is_cross_compiling(target: &Target) -> Result<bool> {
let target_triple = target.target_triple();
let host = get_host_target()?;
if target_triple == host {
return Ok(false);
}
if target_triple == "x86_64-apple-darwin" && host == "aarch64-apple-darwin" {
return Ok(false);
}
if target_triple == "aarch64-apple-darwin" && host == "x86_64-apple-darwin" {
return Ok(false);
}
if let Some(target_without_env) = target_triple
.rfind('-')
.map(|index| &target_triple[0..index])
{
if host.starts_with(target_without_env) {
return Ok(false);
}
}
Ok(true)
}
pub fn parse_sysconfigdata(
interpreter: &Path,
config_path: impl AsRef<Path>,
) -> Result<HashMap<String, String>> {
let mut script = fs::read_to_string(config_path)?;
script += r#"
print("version_major", build_time_vars["VERSION"][0]) # 3
print("version_minor", build_time_vars["VERSION"][2]) # E.g., 8
KEYS = [
"ABIFLAGS",
"EXT_SUFFIX",
"SOABI",
]
for key in KEYS:
print(key, build_time_vars.get(key, ""))
"#;
let output = run_python_script(interpreter, &script)?;
Ok(parse_script_output(&output))
}
fn parse_script_output(output: &str) -> HashMap<String, String> {
output
.lines()
.filter_map(|line| {
let mut i = line.splitn(2, ' ');
Some((i.next()?.into(), i.next()?.into()))
})
.collect()
}
fn run_python_script(interpreter: &Path, script: &str) -> Result<String> {
let out = Command::new(interpreter)
.env("PYTHONIOENCODING", "utf-8")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()
.and_then(|mut child| {
use std::io::Write;
child
.stdin
.as_mut()
.expect("piped stdin")
.write_all(script.as_bytes())?;
child.wait_with_output()
});
match out {
Err(err) => {
if err.kind() == io::ErrorKind::NotFound {
bail!(
"Could not find any interpreter at {}, \
are you sure you have Python installed on your PATH?",
interpreter.display()
);
} else {
bail!(
"Failed to run the Python interpreter at {}: {}",
interpreter.display(),
err
);
}
}
Ok(ok) if !ok.status.success() => bail!("Python script failed"),
Ok(ok) => Ok(String::from_utf8(ok.stdout)?),
}
}
fn starts_with(entry: &DirEntry, pat: &str) -> bool {
let name = entry.file_name();
name.to_string_lossy().starts_with(pat)
}
fn ends_with(entry: &DirEntry, pat: &str) -> bool {
let name = entry.file_name();
name.to_string_lossy().ends_with(pat)
}
pub fn find_sysconfigdata(lib_dir: &Path, target: &Target) -> Result<PathBuf> {
let sysconfig_paths = search_lib_dir(lib_dir, target);
let sysconfig_name = env::var_os("_PYTHON_SYSCONFIGDATA_NAME");
let mut sysconfig_paths = sysconfig_paths
.iter()
.filter_map(|p| {
let canonical = fs::canonicalize(p).ok();
match &sysconfig_name {
Some(_) => canonical.filter(|p| p.file_stem() == sysconfig_name.as_deref()),
None => canonical,
}
})
.collect::<Vec<PathBuf>>();
sysconfig_paths.dedup();
if sysconfig_paths.is_empty() {
bail!(
"Could not find either libpython.so or _sysconfigdata*.py in {}",
lib_dir.display()
);
} else if sysconfig_paths.len() > 1 {
bail!(
"Detected multiple possible python versions, please set the PYO3_PYTHON_VERSION \
variable to the wanted version on your system or set the _PYTHON_SYSCONFIGDATA_NAME \
variable to the wanted sysconfigdata file name\nsysconfigdata paths = {:?}",
sysconfig_paths
)
}
Ok(sysconfig_paths.remove(0))
}
fn search_lib_dir(path: impl AsRef<Path>, target: &Target) -> Vec<PathBuf> {
let mut sysconfig_paths = vec![];
let version_pat = if let Some(v) =
env::var_os("PYO3_CROSS_PYTHON_VERSION").map(|s| s.into_string().unwrap())
{
format!("python{}", v)
} else {
"python3.".into()
};
for f in fs::read_dir(path.as_ref()).expect("Path does not exist") {
let sysc = match &f {
Ok(f) if starts_with(f, "_sysconfigdata") && ends_with(f, "py") => vec![f.path()],
Ok(f) if starts_with(f, "build") => search_lib_dir(f.path(), target),
Ok(f) if starts_with(f, "lib.") => {
let name = f.file_name();
if !name.to_string_lossy().contains(target.get_python_os()) {
continue;
}
if !name
.to_string_lossy()
.contains(&target.target_arch().to_string())
{
continue;
}
search_lib_dir(f.path(), target)
}
Ok(f) if starts_with(f, &version_pat) => search_lib_dir(f.path(), target),
_ => continue,
};
sysconfig_paths.extend(sysc);
}
if sysconfig_paths.len() > 1 {
let temp = sysconfig_paths
.iter()
.filter(|p| {
p.to_string_lossy()
.contains(&target.target_arch().to_string())
})
.cloned()
.collect::<Vec<PathBuf>>();
if !temp.is_empty() {
sysconfig_paths = temp;
}
}
sysconfig_paths
}