blob: c95ea9031b8aa2b61e447d49e3cd8224bdf7bb95 [file] [edit]
// This file is dual licensed under the terms of the Apache License, Version
// 2.0, and the BSD License. See the LICENSE file in the root of this repository
// for complete details.
use std::env;
use std::path::Path;
use std::process::Command;
use pyo3_build_config::{PythonAbiKind, StableAbi};
fn main() {
let target = env::var("TARGET").unwrap();
let openssl_static = env::var("OPENSSL_STATIC")
.map(|x| x == "1")
.unwrap_or(false);
if target.contains("apple") && openssl_static {
// On (older) OSX we need to link against the clang runtime,
// which is hidden in some non-default path.
//
// More details at https://github.com/alexcrichton/curl-rust/issues/279.
if let Some(path) = macos_link_search_path() {
println!("cargo:rustc-link-lib=clang_rt.osx");
println!("cargo:rustc-link-search={path}");
}
}
let out_dir = env::var("OUT_DIR").unwrap();
// FIXME: maybe pyo3-build-config should provide a way to do this?
let python = env::var("PYO3_PYTHON").unwrap_or_else(|_| "python3".to_string());
println!("cargo:rerun-if-env-changed=PYO3_PYTHON");
println!("cargo:rerun-if-changed=../../_cffi_src/");
println!("cargo:rerun-if-changed=../../cryptography/__about__.py");
let output = Command::new(&python)
.env("OUT_DIR", &out_dir)
.arg("../../_cffi_src/build_openssl.py")
.output()
.expect("failed to execute build_openssl.py");
if !output.status.success() {
panic!(
"failed to run build_openssl.py, stdout: \n{}\nstderr: \n{}\n",
String::from_utf8(output.stdout).unwrap(),
String::from_utf8(output.stderr).unwrap()
);
}
let python_impl = run_python_script(
&python,
"import platform; print(platform.python_implementation(), end='')",
)
.unwrap();
println!("cargo:rustc-cfg=python_implementation=\"{python_impl}\"");
println!("cargo:rerun-if-env-changed=PYO3_CROSS_LIB_DIR");
// When cross-compiling, PyO3 expects the build system to point
// PYO3_CROSS_LIB_DIR at the target's libpython directory. Derive the
// matching include dir from it instead of querying the host
// interpreter's setuptools, which returns host headers (e.g.
// /usr/include/python3.x) and breaks the cross build whenever the host
// happens to have same-version Python development headers installed.
let python_includes = if let Ok(lib_dir) = env::var("PYO3_CROSS_LIB_DIR") {
let lib = Path::new(&lib_dir);
let py_ver = lib
.file_name()
.and_then(|s| s.to_str())
.unwrap_or("python3");
let prefix = lib
.parent()
.and_then(|p| p.parent())
.expect("PYO3_CROSS_LIB_DIR has unexpected layout");
prefix
.join("include")
.join(py_ver)
.to_string_lossy()
.into_owned()
} else {
run_python_script(
&python,
"import os; \
import setuptools.dist; \
import setuptools.command.build_ext; \
b = setuptools.command.build_ext.build_ext(setuptools.dist.Distribution()); \
b.finalize_options(); \
print(os.pathsep.join(b.include_dirs), end='')",
)
.unwrap()
};
let openssl_c = Path::new(&out_dir).join("_openssl.c");
let mut build = cc::Build::new();
build
.file(openssl_c)
.includes(std::env::var_os("DEP_OPENSSL_INCLUDE"))
.flag_if_supported("-Wconversion")
.flag_if_supported("-Wno-error=sign-conversion")
.flag_if_supported("-Wno-unused-parameter");
// We use the `-fmacro-prefix-map` option to replace the output directory in macros with a dot.
// This is because we don't want a potentially random build path to end up in the binary because
// CFFI generated code uses the __FILE__ macro in its debug messages.
if let Some(out_dir_str) = Path::new(&out_dir).to_str() {
build.flag_if_supported(format!("-fmacro-prefix-map={out_dir_str}=.").as_str());
}
for python_include in env::split_paths(&python_includes) {
build.include(python_include);
}
// Derive the target ABI from what pyo3 is targeting for this build,
// resolved from the abi3/abi3t Cargo features
let config = pyo3_build_config::get();
match config.target_abi().kind() {
// GIL-enabled limited API (abi3): a single wheel for CPython 3.9 to 3.14 (not free-threaded)
PythonAbiKind::Stable(StableAbi::Abi3) => {
// cp39 (Python 3.9 to help our grep when we some day drop 3.9 support)
build.define("Py_LIMITED_API", "0x030900f0");
}
// Free-threaded limited API (abi3t): a single wheel CPython 3.15+.
PythonAbiKind::Stable(StableAbi::Abi3t) => {
// cp315 (Python 3.15 to help our grep when we some day drop 3.15 support)
build.define("Py_LIMITED_API", "0x030f00f0");
}
// PyPy, or a free-threaded build older than abi3t (e.g. 3.14t):
// compile against the full, version-specific ABI.
PythonAbiKind::VersionSpecific(_) => {}
}
if cfg!(windows) {
build.define("WIN32_LEAN_AND_MEAN", None);
// python.h doesn't set this on the Windows free-threaded build
// see https://github.com/python/cpython/issues/127294
if config.is_free_threaded() {
build.define("Py_GIL_DISABLED", "1");
}
// The C code we build auto-links the Python import library via
// `#pragma comment(lib, ...)` in pyconfig.h. pyo3 0.29+ links
// libpython with raw-dylib and no longer emits Python's libs
// directory as a link search path, so we have to do it ourselves.
let libs_dir = run_python_script(
&python,
"import os, sys; print(os.path.join(sys.base_prefix, 'libs'), end='')",
)
.unwrap();
println!("cargo:rustc-link-search=native={libs_dir}");
}
build.compile("_openssl.a");
}
/// Run a python script using the specified interpreter binary.
fn run_python_script(interpreter: impl AsRef<Path>, script: &str) -> Result<String, String> {
let interpreter = interpreter.as_ref();
let out = Command::new(interpreter)
.env("PYTHONIOENCODING", "utf-8")
.arg("-c")
.arg(script)
.output();
match out {
Err(err) => Err(format!(
"failed to run the Python interpreter at {}: {}",
interpreter.display(),
err
)),
Ok(ok) if !ok.status.success() => Err(format!(
"Python script failed: {}",
String::from_utf8(ok.stderr).expect("failed to parse Python script stderr as utf-8")
)),
Ok(ok) => Ok(
String::from_utf8(ok.stdout).expect("failed to parse Python script stdout as utf-8")
),
}
}
fn macos_link_search_path() -> Option<String> {
let output = Command::new("clang")
.arg("--print-search-dirs")
.output()
.ok()?;
if !output.status.success() {
println!(
"failed to run 'clang --print-search-dirs', continuing without a link search path"
);
return None;
}
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
if line.contains("libraries: =") {
let path = line.split('=').nth(1)?;
return Some(format!("{path}/lib/darwin"));
}
}
println!("failed to determine link search path, continuing without it");
None
}