Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix cross-compiling for Apple platforms #1389

Merged
merged 3 commits into from
Feb 8, 2025
Merged
Show file tree
Hide file tree
Changes from 2 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
35 changes: 35 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,16 @@ jobs:
rust: stable
target: x86_64-apple-ios-macabi
no_run: --no-run # FIXME(madsmtm): Fix running tests
- build: cross-macos-aarch64
os: ubuntu-latest
rust: stable
target: aarch64-apple-darwin
no_run: --no-run
- build: cross-ios-aarch64
os: ubuntu-latest
rust: stable
target: aarch64-apple-ios
no_run: --no-run
- build: windows-aarch64
os: windows-latest
rust: stable
Expand Down Expand Up @@ -155,6 +165,31 @@ jobs:
env:
CC: ${{ matrix.CC }}
CXX: ${{ matrix.CXX }}
- name: Install llvm tools (for llvm-ar)
if: startsWith(matrix.build, 'cross-macos') || startsWith(matrix.build, 'cross-ios')
run: sudo apt-get install llvm
- name: Download macOS SDK
if: startsWith(matrix.build, 'cross-macos')
run: |
wget https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX11.3.sdk.tar.xz
tar -xf MacOSX11.3.sdk.tar.xz
echo "SDKROOT=$(pwd)/MacOSX11.3.sdk" >> $GITHUB_ENV
- name: Download iOS SDK
if: startsWith(matrix.build, 'cross-ios')
run: |
wget https://github.com/xybp888/iOS-SDKs/releases/download/iOS18.1-SDKs/iPhoneOS18.1.sdk.zip
unzip iPhoneOS18.1.sdk.zip
echo "SDKROOT=$(pwd)/iPhoneOS18.1.sdk" >> $GITHUB_ENV
- name: Set up Apple cross-compilation
if: startsWith(matrix.build, 'cross-macos') || startsWith(matrix.build, 'cross-ios')
run: |
# Test with clang/llvm for now, has better cross-compilation support (GCC requires downloading a different toolchain)
echo "CC=clang" >> $GITHUB_ENV
echo "CXX=clang++" >> $GITHUB_ENV
echo "AR=llvm-ar" >> $GITHUB_ENV
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It is unclear to me why llvm-ar is required here, it seems to not be on my local Ubuntu 24 VM? But if we don't include it, we fail with:

rust-lld: error: /home/runner/work/cc-rs/cc-rs/target/aarch64-apple-darwin/debug/build/cc-test-5875314a6fda6e38/out/libOptLinkage.a: archive has no index; run ranlib to add one
rust-lld: error: undefined symbol: _answer

Copy link
Collaborator

Choose a reason for hiding this comment

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

No idea maybe there's some apple/clang stuff default ar not supporting?

# Link with rust-lld
UPPERCASE_TARGET_NAME=$(echo "${{ matrix.target }}" | tr '[:lower:]-' '[:upper:]_')
echo "CARGO_TARGET_${UPPERCASE_TARGET_NAME}_LINKER=rust-lld" >> $GITHUB_ENV
- name: setup dev environment
uses: ilammy/msvc-dev-cmd@v1
if: startsWith(matrix.build, 'windows-clang')
Expand Down
69 changes: 45 additions & 24 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2194,21 +2194,34 @@ impl Build {

// Pass `--target` with the LLVM target to configure Clang for cross-compiling.
//
// NOTE: In the past, we passed this, along with the deployment version in here
// on Apple targets, but versioned targets were found to have poor compatibility
// with older versions of Clang, especially around comes to configuration files.
// This is **required** for cross-compilation, as it's the only flag that
// consistently forces Clang to change the "toolchain" that is responsible for
// parsing target-specific flags:
// https://github.com/rust-lang/cc-rs/issues/1388
// https://github.com/llvm/llvm-project/blob/llvmorg-19.1.7/clang/lib/Driver/Driver.cpp#L1359-L1360
// https://github.com/llvm/llvm-project/blob/llvmorg-19.1.7/clang/lib/Driver/Driver.cpp#L6347-L6532
//
// Instead, we specify `-arch` along with `-mmacosx-version-min=`, `-mtargetos=`
// and similar flags in `.apple_flags()`.
// This can be confusing, because on e.g. host macOS, you can usually get by
// with `-arch` and `-mtargetos=`. But that only works because the _default_
// toolchain is `Darwin`, which enables parsing of darwin-specific options.
//
// Note that Clang errors when both `-mtargetos=` and `-target` are specified,
// so we omit this entirely on Apple targets (it's redundant when specifying
// both the `-arch` and the deployment target / OS flag) (in theory we _could_
// specify this on some of the Apple targets that use the older
// `-m*-version-min=`, but for consistency we omit it entirely).
if target.vendor != "apple" {
cmd.push_cc_arg(format!("--target={}", target.llvm_target).into());
}
// NOTE: In the past, we passed the deployment version in here on all Apple
// targets, but versioned targets were found to have poor compatibility with
// older versions of Clang, especially when it comes to configuration files:
// https://github.com/rust-lang/cc-rs/issues/1278
//
// So instead, we pass the deployment target with `-m*-version-min=`, and only
// pass it here on visionOS and Mac Catalyst where that option does not exist:
// https://github.com/rust-lang/cc-rs/issues/1383
let clang_target = if target.os == "visionos" || target.abi == "macabi" {
Cow::Owned(
target.versioned_llvm_target(&self.apple_deployment_target(target)),
)
} else {
Cow::Borrowed(target.llvm_target)
};

cmd.push_cc_arg(format!("--target={clang_target}").into());
}
}
ToolFamily::Msvc { clang_cl } => {
Expand Down Expand Up @@ -2648,21 +2661,29 @@ impl Build {
fn apple_flags(&self, cmd: &mut Tool) -> Result<(), Error> {
let target = self.get_target()?;

// Add `-arch` on all compilers. This is a Darwin/Apple-specific flag
// that works both on GCC and Clang.
// This is a Darwin/Apple-specific flag that works both on GCC and Clang, but it is only
// necessary on GCC since we specify `-target` on Clang.
// https://gcc.gnu.org/onlinedocs/gcc/Darwin-Options.html#:~:text=arch
// https://clang.llvm.org/docs/CommandGuide/clang.html#cmdoption-arch
let arch = map_darwin_target_from_rust_to_compiler_architecture(&target);
cmd.args.push("-arch".into());
cmd.args.push(arch.into());
if cmd.is_like_gnu() {
let arch = map_darwin_target_from_rust_to_compiler_architecture(&target);
cmd.args.push("-arch".into());
cmd.args.push(arch.into());
}

// Pass the deployment target via `-mmacosx-version-min=`, `-mtargetos=` and similar.
//
// It is also necessary on GCC, as it forces a compilation error if the compiler is not
// Pass the deployment target via `-mmacosx-version-min=`, `-miphoneos-version-min=` and
// similar. Also necessary on GCC, as it forces a compilation error if the compiler is not
// configured for Darwin: https://gcc.gnu.org/onlinedocs/gcc/Darwin-Options.html
let min_version = self.apple_deployment_target(&target);
cmd.args
.push(target.apple_version_flag(&min_version).into());
//
// On visionOS and Mac Catalyst, there is no -m*-version-min= flag:
// https://github.com/llvm/llvm-project/issues/88271
// And the workaround to use `-mtargetos=` cannot be used with the `--target` flag that we
// otherwise specify. So we avoid emitting that, and put the version in `--target` instead.
if cmd.is_like_gnu() || !(target.os == "visionos" || target.abi == "macabi") {
let min_version = self.apple_deployment_target(&target);
cmd.args
.push(target.apple_version_flag(&min_version).into());
}

// AppleClang sometimes requires sysroot even on macOS
if cmd.is_xctoolchain_clang() || target.os != "macos" {
Expand Down
23 changes: 23 additions & 0 deletions src/target/llvm.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
use super::TargetInfo;

impl TargetInfo<'_> {
/// The versioned LLVM/Clang target triple.
pub(crate) fn versioned_llvm_target(&self, version: &str) -> String {
// Only support versioned Apple targets for now.
assert_eq!(self.vendor, "apple");

let mut components = self.llvm_target.split("-");
let arch = components.next().expect("llvm_target should have arch");
let vendor = components.next().expect("llvm_target should have vendor");
let os = components.next().expect("LLVM target should have os");
let environment = components.next();
assert_eq!(components.next(), None, "too many LLVM target components");

if let Some(env) = environment {
format!("{arch}-{vendor}-{os}{version}-{env}")
} else {
format!("{arch}-{vendor}-{os}{version}")
}
}
}

/// Rust and Clang don't really agree on naming, so do a best-effort
/// conversion to support out-of-tree / custom target-spec targets.
pub(crate) fn guess_llvm_target_triple(
Expand Down
15 changes: 7 additions & 8 deletions tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,7 @@ fn clang_apple_tvos() {
.file("foo.c")
.compile("foo");

test.cmd(0).must_have_in_order("-arch", "arm64");
test.cmd(0).must_have("--target=arm64-apple-tvos");
test.cmd(0).must_have("-mappletvos-version-min=9.0");
}

Expand All @@ -640,10 +640,9 @@ fn clang_apple_mac_catalyst() {
.compile("foo");
let execution = test.cmd(0);

execution.must_have_in_order("-arch", "arm64");
execution.must_have("--target=arm64-apple-ios15.0-macabi");
// --target and -mtargetos= don't mix
execution.must_not_have("--target=arm64-apple-ios-macabi");
execution.must_have("-mtargetos=ios15.0-macabi");
execution.must_not_have("-mtargetos=");
execution.must_have_in_order("-isysroot", sdkroot);
execution.must_have_in_order(
"-isystem",
Expand Down Expand Up @@ -672,7 +671,8 @@ fn clang_apple_tvsimulator() {
.file("foo.c")
.compile("foo");

test.cmd(0).must_have_in_order("-arch", "x86_64");
test.cmd(0)
.must_have("--target=x86_64-apple-tvos-simulator");
test.cmd(0).must_have("-mappletvsimulator-version-min=9.0");
}

Expand All @@ -697,10 +697,9 @@ fn clang_apple_visionos() {

dbg!(test.cmd(0).args);

test.cmd(0).must_have_in_order("-arch", "arm64");
test.cmd(0).must_have("--target=arm64-apple-xros1.0");
// --target and -mtargetos= don't mix.
test.cmd(0).must_not_have("--target=arm64-apple-xros");
test.cmd(0).must_have("-mtargetos=xros1.0");
test.cmd(0).must_not_have("-mtargetos=");

// Flags that don't exist.
test.cmd(0).must_not_have("-mxros-version-min=1.0");
Expand Down