use {
anyhow::{anyhow, Result},
python_packaging::libpython::LibPythonBuildContext,
slog::warn,
std::{
fs,
fs::create_dir_all,
path::{Path, PathBuf},
},
tugger_file_manifest::FileData,
};
pub fn make_config_c<T>(extensions: &[(T, T)]) -> String
where
T: AsRef<str>,
{
let mut lines: Vec<String> = vec!["#include \"Python.h\"".to_string()];
for (_name, init_fn) in extensions {
if init_fn.as_ref() != "NULL" {
lines.push(format!("extern PyObject* {}(void);", init_fn.as_ref()));
}
}
lines.push(String::from("struct _inittab _PyImport_Inittab[] = {"));
for (name, init_fn) in extensions {
lines.push(format!("{{\"{}\", {}}},", name.as_ref(), init_fn.as_ref()));
}
lines.push(String::from("{0, 0}"));
lines.push(String::from("};"));
lines.join("\n")
}
#[derive(Debug)]
pub struct LibpythonInfo {
pub libpython_path: PathBuf,
pub libpyembeddedconfig_path: PathBuf,
pub cargo_metadata: Vec<String>,
}
pub fn link_libpython(
logger: &slog::Logger,
context: &LibPythonBuildContext,
out_dir: &Path,
host_triple: &str,
target_triple: &str,
opt_level: &str,
) -> Result<LibpythonInfo> {
let mut cargo_metadata: Vec<String> = Vec::new();
let temp_dir = tempfile::Builder::new().prefix("libpython").tempdir()?;
let temp_dir_path = temp_dir.path();
let windows = crate::environment::WINDOWS_TARGET_TRIPLES.contains(&target_triple);
warn!(
logger,
"deriving custom config.c from {} extension modules",
context.init_functions.len()
);
let config_c_source = make_config_c(&context.init_functions.iter().collect::<Vec<_>>());
let config_c_path = out_dir.join("config.c");
let config_c_temp_path = temp_dir_path.join("config.c");
fs::write(&config_c_path, config_c_source.as_bytes())?;
fs::write(&config_c_temp_path, config_c_source.as_bytes())?;
for (rel_path, location) in &context.includes {
let full = temp_dir_path.join(rel_path);
create_dir_all(
full.parent()
.ok_or_else(|| anyhow!("unable to resolve parent directory"))?,
)?;
let data = location.resolve()?;
std::fs::write(&full, &data)?;
}
warn!(logger, "compiling custom config.c to object file");
let mut build = cc::Build::new();
if let Some(flags) = &context.inittab_cflags {
for flag in flags {
build.flag(flag);
}
}
build
.out_dir(out_dir)
.host(host_triple)
.target(target_triple)
.opt_level_str(opt_level)
.file(config_c_temp_path)
.include(temp_dir_path)
.cargo_metadata(false)
.compile("pyembeddedconfig");
let libpyembeddedconfig_path = out_dir.join(if windows {
"pyembeddedconfig.lib"
} else {
"libpyembeddedconfig.a"
});
cargo_metadata.push("cargo:rustc-link-lib=static=pyembeddedconfig".to_string());
warn!(logger, "resolving inputs for custom Python library...");
let mut build = cc::Build::new();
build.out_dir(out_dir);
build.host(host_triple);
build.target(target_triple);
build.opt_level_str(opt_level);
build.cargo_metadata(false);
for (i, location) in context.object_files.iter().enumerate() {
match location {
FileData::Memory(data) => {
let out_path = temp_dir_path.join(format!("libpython.{}.o", i));
fs::write(&out_path, data)?;
build.object(&out_path);
}
FileData::Path(p) => {
build.object(&p);
}
}
}
for framework in &context.frameworks {
cargo_metadata.push(format!("cargo:rustc-link-lib=framework={}", framework));
}
for lib in &context.system_libraries {
cargo_metadata.push(format!("cargo:rustc-link-lib={}", lib));
}
for lib in &context.dynamic_libraries {
cargo_metadata.push(format!("cargo:rustc-link-lib={}", lib));
}
for lib in &context.static_libraries {
cargo_metadata.push(format!("cargo:rustc-link-lib=static={}", lib));
}
if target_triple.ends_with("-apple-darwin") {
if let Some(path) = macos_clang_search_path()? {
cargo_metadata.push(format!("cargo:rustc-link-search={}", path.display()));
}
cargo_metadata.push("cargo:rustc-link-lib=clang_rt.osx".to_string());
}
warn!(logger, "compiling libpythonXY...");
build.compile("pythonXY");
warn!(logger, "libpythonXY created");
let libpython_path = out_dir.join(if windows {
"pythonXY.lib"
} else {
"libpythonXY.a"
});
cargo_metadata.push("cargo:rustc-link-lib=static=pythonXY".to_string());
cargo_metadata.push(format!(
"cargo:rustc-link-search=native={}",
out_dir.display()
));
for path in &context.library_search_paths {
cargo_metadata.push(format!("cargo:rustc-link-search=native={}", path.display()));
}
Ok(LibpythonInfo {
libpython_path,
libpyembeddedconfig_path,
cargo_metadata,
})
}
fn macos_clang_search_path() -> Result<Option<PathBuf>> {
let output = std::process::Command::new("clang")
.arg("--print-search-dirs")
.output()?;
if !output.status.success() {
return Ok(None);
}
for line in String::from_utf8_lossy(&output.stdout).lines() {
if line.contains("libraries: =") {
let path = line
.split('=')
.nth(1)
.ok_or_else(|| anyhow!("could not parse libraries line"))?;
return Ok(Some(PathBuf::from(path).join("lib").join("darwin")));
}
}
Ok(None)
}