Skip to content

Commit 0f7810e

Browse files
authored
Merge pull request python#26 from Eclips4/fix-macos-and-ios-builds
Fix macos and ios builds
2 parents 95753bb + 09fd68e commit 0f7810e

4 files changed

Lines changed: 157 additions & 28 deletions

File tree

Makefile.pre.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3379,7 +3379,7 @@ Python/thread.o: @THREADHEADERS@ $(srcdir)/Python/condvar.h
33793379
# Module dependencies and platform-specific files
33803380

33813381
cpython-sys: Modules/cpython-sys/Cargo.toml Modules/cpython-sys/build.rs Modules/cpython-sys/wrapper.h Modules/cpython-sys/parser.h
3382-
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
3382+
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
33833383

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

Modules/cpython-build-helper/src/lib.rs

Lines changed: 84 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,39 @@ use std::env;
33
/// Print necessary link arguments for the library depending on the build
44
/// configuration (static or shared)
55
pub fn print_linker_args() {
6-
let target = env::var("TARGET").unwrap_or_default();
6+
println!("cargo:rerun-if-env-changed=RUST_SHARED_BUILD");
7+
println!("cargo:rerun-if-env-changed=BLDSHARED_EXE");
8+
println!("cargo:rerun-if-env-changed=BLDSHARED_ARGS");
9+
println!("cargo:rerun-if-env-changed=LIBPYTHON");
10+
println!("cargo:rerun-if-env-changed=PY_CC");
11+
println!("cargo:rerun-if-env-changed=PYTHON_BUILD_DIR");
12+
println!("cargo:rerun-if-env-changed=CARGO_CFG_TARGET_OS");
13+
14+
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
15+
let shared_build =
16+
env::var("RUST_SHARED_BUILD").unwrap_or_default() == "1" || target_os == "ios";
717

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

28+
// Apple framework builds for iOS encode framework search/link flags in
29+
// BLDSHARED_EXE. Skip the linker executable itself and filter out flags
30+
// that conflict with Cargo's own cdylib invocation.
31+
if shared_build && let Ok(args) = env::var("BLDSHARED_EXE") {
32+
print_link_args(&args, true);
33+
}
34+
1835
// Pass platform-specific shared link arguments (e.g. PY_CORE_LDFLAGS)
1936
// from the CPython build system.
2037
if let Ok(args) = env::var("BLDSHARED_ARGS") {
21-
let args = shlex::split(&args).expect("Invalid BLDSHARED_ARGS");
22-
let mut iter = args.iter();
23-
while let Some(arg) = iter.next() {
24-
// -bundle_loader is incompatible with Cargo's cdylib on macOS
25-
// (it only works with -bundle, not -dynamiclib). Skip it and
26-
// its argument.
27-
if arg == "-bundle_loader" {
28-
iter.next(); // skip the path argument
29-
continue;
30-
}
31-
println!("cargo:rustc-cdylib-link-arg={}", arg);
32-
}
38+
print_link_args(&args, false);
3339
}
3440

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

5359
// Static linker configuration is in cpython-rust-staticlib
5460
}
61+
62+
fn print_link_args(raw: &str, skip_first: bool) {
63+
let mut args = shlex::split(raw).expect("Invalid linker args");
64+
if skip_first {
65+
args = strip_linker_executable(args, env::var("PY_CC").ok().as_deref());
66+
}
67+
68+
let build_dir = env::var("PYTHON_BUILD_DIR").ok();
69+
let mut i = 0;
70+
while i < args.len() {
71+
match args[i].as_str() {
72+
// -bundle_loader only works with -bundle, not with Cargo's
73+
// cdylib mode (-dynamiclib).
74+
"-bundle_loader" => {
75+
i += 2;
76+
}
77+
// Cargo chooses the Mach-O output type for cdylib already.
78+
"-bundle" | "-dynamiclib" => {
79+
i += 1;
80+
}
81+
// dynamic_lookup is added explicitly for Apple targets above.
82+
"-undefined" if i + 1 < args.len() && args[i + 1] == "dynamic_lookup" => {
83+
i += 2;
84+
}
85+
"-F" if i + 1 < args.len() => {
86+
let value = if args[i + 1] == "." {
87+
build_dir.clone().unwrap_or_else(|| ".".to_string())
88+
} else {
89+
args[i + 1].clone()
90+
};
91+
println!("cargo:rustc-cdylib-link-arg=-F");
92+
println!("cargo:rustc-cdylib-link-arg={value}");
93+
i += 2;
94+
}
95+
_ => {
96+
println!("cargo:rustc-cdylib-link-arg={}", args[i]);
97+
i += 1;
98+
}
99+
}
100+
}
101+
}
102+
103+
// BLDSHARED_EXE may start with a wrapper/compiler command such as
104+
// "xcrun --sdk iphoneos clang" or "ccache clang". Strip that prefix so only
105+
// linker flags are forwarded to rustc.
106+
fn strip_linker_executable(args: Vec<String>, compiler: Option<&str>) -> Vec<String> {
107+
if let Some(compiler) = compiler
108+
&& let Some(prefix) = compiler_command_prefix(compiler)
109+
&& args.as_slice().starts_with(prefix.as_slice())
110+
{
111+
return args[prefix.len()..].to_vec();
112+
}
113+
114+
if args.is_empty() {
115+
return args;
116+
}
117+
args[1..].to_vec()
118+
}
119+
120+
fn compiler_command_prefix(raw: &str) -> Option<Vec<String>> {
121+
let tokens = shlex::split(raw)?;
122+
let last_non_flag = tokens.iter().rposition(|token| !token.starts_with('-'))?;
123+
Some(tokens.into_iter().take(last_non_flag + 1).collect())
124+
}

Modules/cpython-sys/build.rs

Lines changed: 71 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,37 @@ fn main() {
99
.expect("expected Modules/cpython-sys to live under the source tree");
1010
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
1111
let builddir = env::var("PYTHON_BUILD_DIR").ok();
12+
emit_rerun_instructions(builddir.as_deref());
1213
if gil_disabled(srcdir, builddir.as_deref()) {
1314
println!("cargo:rustc-cfg=py_gil_disabled");
1415
}
1516
println!("cargo::rustc-check-cfg=cfg(py_gil_disabled)");
1617
generate_c_api_bindings(srcdir, builddir.as_deref(), out_path.as_path());
1718
}
1819

20+
// Bindgen depends on build-time env and, on iOS, can also inherit the
21+
// deployment target from the generated Makefile. Declare both so Cargo reruns
22+
// the build script when those inputs change.
23+
fn emit_rerun_instructions(builddir: Option<&str>) {
24+
for var in [
25+
"IPHONEOS_DEPLOYMENT_TARGET",
26+
"LLVM_TARGET",
27+
"PYTHON_BUILD_DIR",
28+
"PY_CC",
29+
"PY_CPPFLAGS",
30+
"PY_CFLAGS",
31+
"TARGET",
32+
"WASI_SDK_PATH",
33+
] {
34+
println!("cargo:rerun-if-env-changed={var}");
35+
}
36+
37+
if let Some(builddir) = builddir {
38+
let makefile = Path::new(builddir).join("Makefile");
39+
println!("cargo:rerun-if-changed={}", makefile.display());
40+
}
41+
}
42+
1943
fn gil_disabled(srcdir: &Path, builddir: Option<&str>) -> bool {
2044
let mut candidates = Vec::new();
2145
if let Some(build) = builddir {
@@ -39,16 +63,13 @@ fn generate_c_api_bindings(srcdir: &Path, builddir: Option<&str>, out_path: &Pat
3963
// Suppress all clang warnings (deprecation warnings, etc.)
4064
builder = builder.clang_arg("-w");
4165

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

5475
// Extract cross-compilation flags from the C compiler command (PY_CC),
@@ -88,7 +109,7 @@ fn generate_c_api_bindings(srcdir: &Path, builddir: Option<&str>, out_path: &Pat
88109
// WASI SDK: WASI_SDK_PATH is set by Tools/wasm/wasi/__main__.py.
89110
// The sysroot is at $WASI_SDK_PATH/share/wasi-sysroot.
90111
if !have_sysroot
91-
&& target.contains("wasi")
112+
&& cargo_target.contains("wasi")
92113
&& let Ok(sdk_path) = env::var("WASI_SDK_PATH")
93114
{
94115
let sysroot = PathBuf::from(&sdk_path).join("share").join("wasi-sysroot");
@@ -104,7 +125,7 @@ fn generate_c_api_bindings(srcdir: &Path, builddir: Option<&str>, out_path: &Pat
104125
// The sysroot is a sibling of bin/:
105126
// .../toolchains/llvm/prebuilt/<host>/sysroot
106127
if !have_sysroot
107-
&& target.contains("android")
128+
&& cargo_target.contains("android")
108129
&& let Ok(cc) = env::var("PY_CC")
109130
&& let Some(parts) = shlex::split(&cc)
110131
&& let Some(binary) = parts.first()
@@ -129,6 +150,7 @@ fn generate_c_api_bindings(srcdir: &Path, builddir: Option<&str>, out_path: &Pat
129150
for dir in include_dirs {
130151
builder = builder.clang_arg(format!("-I{}", dir.display()));
131152
}
153+
builder = add_target_clang_args(builder, builddir);
132154

133155
let bindings = builder
134156
.allowlist_function("_?Py.*")
@@ -145,3 +167,40 @@ fn generate_c_api_bindings(srcdir: &Path, builddir: Option<&str>, out_path: &Pat
145167
.write_to_file(out_path.join("c_api.rs"))
146168
.expect("Couldn't write bindings!");
147169
}
170+
171+
fn add_target_clang_args(
172+
mut builder: bindgen::Builder,
173+
builddir: Option<&str>,
174+
) -> bindgen::Builder {
175+
let target = env::var("TARGET").unwrap_or_default();
176+
if !target.contains("apple-ios") {
177+
return builder;
178+
}
179+
180+
// For iOS targets, bindgen may parse headers with an iOS simulator/device
181+
// target but without a deployment minimum, which disables TLS support.
182+
let deployment_target = ios_deployment_target(builddir).unwrap_or_else(|| "13.0".to_string());
183+
builder = builder.clang_arg(format!("-mios-version-min={deployment_target}"));
184+
builder
185+
}
186+
187+
fn ios_deployment_target(builddir: Option<&str>) -> Option<String> {
188+
if let Ok(value) = env::var("IPHONEOS_DEPLOYMENT_TARGET")
189+
&& !value.is_empty()
190+
{
191+
return Some(value);
192+
}
193+
194+
let builddir = builddir?;
195+
let makefile = Path::new(builddir).join("Makefile");
196+
let text = std::fs::read_to_string(makefile).ok()?;
197+
for line in text.lines() {
198+
if let Some(value) = line.strip_prefix("IPHONEOS_DEPLOYMENT_TARGET=") {
199+
let value = value.trim();
200+
if !value.is_empty() {
201+
return Some(value.to_string());
202+
}
203+
}
204+
}
205+
None
206+
}

Modules/makesetup

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ sed -e 's/[ ]*#.*//' -e '/^[ ]*$/d' |
307307
BUILT_SHARED="$BUILT_SHARED $mod"
308308
# depends on the headers through cpython-sys
309309
rule="$rust_shared: cpython-sys \$(srcdir)/Cargo.toml \$(srcdir)/Cargo.lock \$(srcdir)/$srcdir/$manifest $prefixed_srcs \$(PYTHON_HEADERS) \$(MODULE_${mods_upper}_LDEPS) \$(LIBRARY)"
310-
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"
310+
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"
311311
echo "$rule" >>$rulesf
312312
echo "$file: $rust_shared; mv $rust_shared $file" >>$rulesf
313313
done

0 commit comments

Comments
 (0)