Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile.pre.in
Original file line number Diff line number Diff line change
Expand Up @@ -3379,7 +3379,7 @@ Python/thread.o: @THREADHEADERS@ $(srcdir)/Python/condvar.h
# Module dependencies and platform-specific files

cpython-sys: Modules/cpython-sys/Cargo.toml Modules/cpython-sys/build.rs Modules/cpython-sys/wrapper.h Modules/cpython-sys/parser.h
CARGO_TARGET_DIR=$(abs_builddir)/target PYTHON_BUILD_DIR=$(abs_builddir) PY_CC="$(CC)" PY_CPPFLAGS="$(CPPFLAGS)" PY_CFLAGS="$(CFLAGS)" LLVM_TARGET="$(LLVM_TARGET)" $(CARGO_HOME)/bin/cargo build --lib --locked --package cpython-sys --profile $(CARGO_PROFILE) $(if $(CARGO_TARGET),--target=$(CARGO_TARGET)) --manifest-path $(srcdir)/Cargo.toml
CARGO_TARGET_DIR=$(abs_builddir)/target PYTHON_BUILD_DIR=$(abs_builddir) PY_CC="$(CC)" PY_CPPFLAGS="$(CPPFLAGS)" PY_CFLAGS="$(CFLAGS)" LLVM_TARGET="$(LLVM_TARGET)" IPHONEOS_DEPLOYMENT_TARGET=$(IPHONEOS_DEPLOYMENT_TARGET) $(CARGO_HOME)/bin/cargo build --lib --locked --package cpython-sys --profile $(CARGO_PROFILE) $(if $(CARGO_TARGET),--target=$(CARGO_TARGET)) --manifest-path $(srcdir)/Cargo.toml

RUST_STATICLIB_A= target/$(if $(CARGO_TARGET),$(CARGO_TARGET)/$(CARGO_TARGET_DIR),$(CARGO_TARGET_DIR))/libcpython_rust_staticlib.a

Expand Down
98 changes: 84 additions & 14 deletions Modules/cpython-build-helper/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,39 @@ use std::env;
/// Print necessary link arguments for the library depending on the build
/// configuration (static or shared)
pub fn print_linker_args() {
let target = env::var("TARGET").unwrap_or_default();
println!("cargo:rerun-if-env-changed=RUST_SHARED_BUILD");
println!("cargo:rerun-if-env-changed=BLDSHARED_EXE");
println!("cargo:rerun-if-env-changed=BLDSHARED_ARGS");
println!("cargo:rerun-if-env-changed=LIBPYTHON");
println!("cargo:rerun-if-env-changed=PY_CC");
println!("cargo:rerun-if-env-changed=PYTHON_BUILD_DIR");
println!("cargo:rerun-if-env-changed=CARGO_CFG_TARGET_OS");

let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
let shared_build =
env::var("RUST_SHARED_BUILD").unwrap_or_default() == "1" || target_os == "ios";

// On Apple platforms (macOS, iOS), Cargo's cdylib produces a Mach-O
// dynamiclib (via -dynamiclib), but CPython's C extensions are built as
// bundles (via -bundle). Unlike bundles, dynamiclibs require all symbols
// to be resolved at link time. Pass -undefined dynamic_lookup so that
// Python C API symbols are resolved at load time by the interpreter.
if target.contains("apple") {
if target_os == "macos" || target_os == "ios" {
println!("cargo:rustc-cdylib-link-arg=-undefined");
println!("cargo:rustc-cdylib-link-arg=dynamic_lookup");
}

// Apple framework builds for iOS encode framework search/link flags in
// BLDSHARED_EXE. Skip the linker executable itself and filter out flags
// that conflict with Cargo's own cdylib invocation.
if shared_build && let Ok(args) = env::var("BLDSHARED_EXE") {
print_link_args(&args, true);
}

// Pass platform-specific shared link arguments (e.g. PY_CORE_LDFLAGS)
// from the CPython build system.
if let Ok(args) = env::var("BLDSHARED_ARGS") {
let args = shlex::split(&args).expect("Invalid BLDSHARED_ARGS");
let mut iter = args.iter();
while let Some(arg) = iter.next() {
// -bundle_loader is incompatible with Cargo's cdylib on macOS
// (it only works with -bundle, not -dynamiclib). Skip it and
// its argument.
if arg == "-bundle_loader" {
iter.next(); // skip the path argument
continue;
}
println!("cargo:rustc-cdylib-link-arg={}", arg);
}
print_link_args(&args, false);
}

// On Android (and Cygwin), extension modules must link against libpython.
Expand All @@ -52,3 +58,67 @@ pub fn print_linker_args() {

// Static linker configuration is in cpython-rust-staticlib
}

fn print_link_args(raw: &str, skip_first: bool) {
let mut args = shlex::split(raw).expect("Invalid linker args");
if skip_first {
args = strip_linker_executable(args, env::var("PY_CC").ok().as_deref());
}

let build_dir = env::var("PYTHON_BUILD_DIR").ok();
let mut i = 0;
while i < args.len() {
match args[i].as_str() {
// -bundle_loader only works with -bundle, not with Cargo's
// cdylib mode (-dynamiclib).
"-bundle_loader" => {
i += 2;
}
// Cargo chooses the Mach-O output type for cdylib already.
"-bundle" | "-dynamiclib" => {
i += 1;
}
// dynamic_lookup is added explicitly for Apple targets above.
"-undefined" if i + 1 < args.len() && args[i + 1] == "dynamic_lookup" => {
i += 2;
}
"-F" if i + 1 < args.len() => {
let value = if args[i + 1] == "." {
build_dir.clone().unwrap_or_else(|| ".".to_string())
} else {
args[i + 1].clone()
};
println!("cargo:rustc-cdylib-link-arg=-F");
println!("cargo:rustc-cdylib-link-arg={value}");
i += 2;
}
_ => {
println!("cargo:rustc-cdylib-link-arg={}", args[i]);
i += 1;
}
}
}
}

// BLDSHARED_EXE may start with a wrapper/compiler command such as
// "xcrun --sdk iphoneos clang" or "ccache clang". Strip that prefix so only
// linker flags are forwarded to rustc.
fn strip_linker_executable(args: Vec<String>, compiler: Option<&str>) -> Vec<String> {
if let Some(compiler) = compiler
&& let Some(prefix) = compiler_command_prefix(compiler)
&& args.as_slice().starts_with(prefix.as_slice())
{
return args[prefix.len()..].to_vec();
}

if args.is_empty() {
return args;
}
args[1..].to_vec()
}

fn compiler_command_prefix(raw: &str) -> Option<Vec<String>> {
let tokens = shlex::split(raw)?;
let last_non_flag = tokens.iter().rposition(|token| !token.starts_with('-'))?;
Some(tokens.into_iter().take(last_non_flag + 1).collect())
}
83 changes: 71 additions & 12 deletions Modules/cpython-sys/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,37 @@ fn main() {
.expect("expected Modules/cpython-sys to live under the source tree");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
let builddir = env::var("PYTHON_BUILD_DIR").ok();
emit_rerun_instructions(builddir.as_deref());
if gil_disabled(srcdir, builddir.as_deref()) {
println!("cargo:rustc-cfg=py_gil_disabled");
}
println!("cargo::rustc-check-cfg=cfg(py_gil_disabled)");
generate_c_api_bindings(srcdir, builddir.as_deref(), out_path.as_path());
}

// Bindgen depends on build-time env and, on iOS, can also inherit the
// deployment target from the generated Makefile. Declare both so Cargo reruns
// the build script when those inputs change.
fn emit_rerun_instructions(builddir: Option<&str>) {
for var in [
"IPHONEOS_DEPLOYMENT_TARGET",
"LLVM_TARGET",
"PYTHON_BUILD_DIR",
"PY_CC",
"PY_CPPFLAGS",
"PY_CFLAGS",
"TARGET",
"WASI_SDK_PATH",
] {
println!("cargo:rerun-if-env-changed={var}");
}

if let Some(builddir) = builddir {
let makefile = Path::new(builddir).join("Makefile");
println!("cargo:rerun-if-changed={}", makefile.display());
}
}

fn gil_disabled(srcdir: &Path, builddir: Option<&str>) -> bool {
let mut candidates = Vec::new();
if let Some(build) = builddir {
Expand All @@ -39,16 +63,13 @@ fn generate_c_api_bindings(srcdir: &Path, builddir: Option<&str>, out_path: &Pat
// Suppress all clang warnings (deprecation warnings, etc.)
builder = builder.clang_arg("-w");

// Tell clang the correct target triple for cross-compilation.
// LLVM_TARGET is the clang/LLVM triple which may differ from the Rust
// target (e.g. arm64-apple-macosx vs aarch64-apple-darwin, or
// riscv64-unknown-linux-gnu vs riscv64gc-unknown-linux-gnu).
// Falls back to Cargo's TARGET if LLVM_TARGET is not set.
let target = env::var("LLVM_TARGET")
.or_else(|_| env::var("TARGET"))
.unwrap_or_default();
if !target.is_empty() {
builder = builder.clang_arg(format!("--target={}", target));
// Tell clang the correct target triple for cross-compilation when we have
// an LLVM-specific triple. Otherwise let bindgen translate Cargo's TARGET
// itself (e.g. aarch64-apple-ios-sim -> arm64-apple-ios-simulator).
let cargo_target = env::var("TARGET").unwrap_or_default();
let llvm_target = env::var("LLVM_TARGET").unwrap_or_default();
if !llvm_target.is_empty() && llvm_target != cargo_target {
builder = builder.clang_arg(format!("--target={llvm_target}"));
}

// Extract cross-compilation flags from the C compiler command (PY_CC),
Expand Down Expand Up @@ -88,7 +109,7 @@ fn generate_c_api_bindings(srcdir: &Path, builddir: Option<&str>, out_path: &Pat
// WASI SDK: WASI_SDK_PATH is set by Tools/wasm/wasi/__main__.py.
// The sysroot is at $WASI_SDK_PATH/share/wasi-sysroot.
if !have_sysroot
&& target.contains("wasi")
&& cargo_target.contains("wasi")
&& let Ok(sdk_path) = env::var("WASI_SDK_PATH")
{
let sysroot = PathBuf::from(&sdk_path).join("share").join("wasi-sysroot");
Expand All @@ -104,7 +125,7 @@ fn generate_c_api_bindings(srcdir: &Path, builddir: Option<&str>, out_path: &Pat
// The sysroot is a sibling of bin/:
// .../toolchains/llvm/prebuilt/<host>/sysroot
if !have_sysroot
&& target.contains("android")
&& cargo_target.contains("android")
&& let Ok(cc) = env::var("PY_CC")
&& let Some(parts) = shlex::split(&cc)
&& let Some(binary) = parts.first()
Expand All @@ -129,6 +150,7 @@ fn generate_c_api_bindings(srcdir: &Path, builddir: Option<&str>, out_path: &Pat
for dir in include_dirs {
builder = builder.clang_arg(format!("-I{}", dir.display()));
}
builder = add_target_clang_args(builder, builddir);

let bindings = builder
.allowlist_function("_?Py.*")
Expand All @@ -145,3 +167,40 @@ fn generate_c_api_bindings(srcdir: &Path, builddir: Option<&str>, out_path: &Pat
.write_to_file(out_path.join("c_api.rs"))
.expect("Couldn't write bindings!");
}

fn add_target_clang_args(
mut builder: bindgen::Builder,
builddir: Option<&str>,
) -> bindgen::Builder {
let target = env::var("TARGET").unwrap_or_default();
if !target.contains("apple-ios") {
return builder;
}

// For iOS targets, bindgen may parse headers with an iOS simulator/device
// target but without a deployment minimum, which disables TLS support.
let deployment_target = ios_deployment_target(builddir).unwrap_or_else(|| "13.0".to_string());
Copy link
Member Author

@Eclips4 Eclips4 Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the default value here is the current target (IPHONEOS_DEPLOYMENT_TARGET)

builder = builder.clang_arg(format!("-mios-version-min={deployment_target}"));
builder
}

fn ios_deployment_target(builddir: Option<&str>) -> Option<String> {
if let Ok(value) = env::var("IPHONEOS_DEPLOYMENT_TARGET")
&& !value.is_empty()
{
return Some(value);
}

let builddir = builddir?;
let makefile = Path::new(builddir).join("Makefile");
let text = std::fs::read_to_string(makefile).ok()?;
for line in text.lines() {
if let Some(value) = line.strip_prefix("IPHONEOS_DEPLOYMENT_TARGET=") {
let value = value.trim();
if !value.is_empty() {
return Some(value.to_string());
}
}
}
None
}
2 changes: 1 addition & 1 deletion Modules/makesetup
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ sed -e 's/[ ]*#.*//' -e '/^[ ]*$/d' |
BUILT_SHARED="$BUILT_SHARED $mod"
# depends on the headers through cpython-sys
rule="$rust_shared: cpython-sys \$(srcdir)/Cargo.toml \$(srcdir)/Cargo.lock \$(srcdir)/$srcdir/$manifest $prefixed_srcs \$(PYTHON_HEADERS) \$(MODULE_${mods_upper}_LDEPS) \$(LIBRARY)"
rule="$rule; CARGO_TARGET_DIR=\$(abs_builddir)/target PYTHON_BUILD_DIR=\$(abs_builddir) BLDSHARED_ARGS=\"\$(BLDSHARED_ARGS)\" LIBPYTHON=\"\$(LIBPYTHON)\" \$(if \$(CARGO_TARGET_LINKER_ENV),\$(CARGO_TARGET_LINKER_ENV)=\$(CC)) \$(CARGO_HOME)/bin/cargo build -vvv --lib --locked --package ${mod} --profile \$(CARGO_PROFILE) \$(if \$(CARGO_TARGET),--target=\$(CARGO_TARGET)) --manifest-path \$(srcdir)/Cargo.toml"
rule="$rule; CARGO_TARGET_DIR=\$(abs_builddir)/target PYTHON_BUILD_DIR=\$(abs_builddir) PY_CC=\"\$(CC)\" PY_CPPFLAGS=\"\$(CPPFLAGS)\" PY_CFLAGS=\"\$(CFLAGS)\" LLVM_TARGET=\"\$(LLVM_TARGET)\" RUST_SHARED_BUILD=\$(PY_ENABLE_SHARED) IPHONEOS_DEPLOYMENT_TARGET=\$(IPHONEOS_DEPLOYMENT_TARGET) BLDSHARED_EXE=\"\$(BLDSHARED_EXE)\" BLDSHARED_ARGS=\"\$(BLDSHARED_ARGS)\" LIBPYTHON=\"\$(LIBPYTHON)\" \$(if \$(CARGO_TARGET_LINKER_ENV),\$(CARGO_TARGET_LINKER_ENV)=\$(CC)) \$(CARGO_HOME)/bin/cargo build -vvv --lib --locked --package ${mod} --profile \$(CARGO_PROFILE) \$(if \$(CARGO_TARGET),--target=\$(CARGO_TARGET)) --manifest-path \$(srcdir)/Cargo.toml"
echo "$rule" >>$rulesf
echo "$file: $rust_shared; mv $rust_shared $file" >>$rulesf
done
Expand Down
Loading