From 353566ce69a2b1248bfdaee43f5759dff1d29258 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 5 Feb 2025 10:39:39 +0100 Subject: [PATCH 1/3] Add CI step that cross-compiles from Linux to macOS/iOS --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 110551e4..5c89a349 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -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 @@ -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 + # 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') From bfdf3d5cdf520cbfbf21742094210eae71b1c3cb Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 5 Feb 2025 09:40:24 +0100 Subject: [PATCH 2/3] Revert to always passing --target to Clang --- src/lib.rs | 69 ++++++++++++++++++++++++++++++---------------- src/target/llvm.rs | 23 ++++++++++++++++ tests/test.rs | 15 +++++----- 3 files changed, 75 insertions(+), 32 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0631584e..e799802d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 } => { @@ -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" { diff --git a/src/target/llvm.rs b/src/target/llvm.rs index 7d5fe9ab..e9127c76 100644 --- a/src/target/llvm.rs +++ b/src/target/llvm.rs @@ -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( diff --git a/tests/test.rs b/tests/test.rs index 231be41e..1ce79210 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -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"); } @@ -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", @@ -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"); } @@ -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"); From 7854f2f7ed497d1cce9f0207bad15c3e2db85de6 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sat, 8 Feb 2025 05:42:44 +0100 Subject: [PATCH 3/3] Expand test for GCC Apple -arch and version flag --- tests/test.rs | 87 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 79 insertions(+), 8 deletions(-) diff --git a/tests/test.rs b/tests/test.rs index 1ce79210..d1c11e93 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -524,28 +524,99 @@ fn asm_flags() { } #[test] -fn gnu_apple_darwin() { - for (arch, ld64_arch, version) in &[("x86_64", "x86_64", "10.7"), ("aarch64", "arm64", "11.0")] - { - let target = format!("{}-apple-darwin", arch); +fn gnu_apple_sysroot() { + let targets = ["aarch64-apple-darwin", "x86_64-apple-darwin"]; + + for target in targets { let test = Test::gnu(); test.shim("fake-gcc") .gcc() .compiler("fake-gcc") .target(&target) .host(&target) - // Avoid test maintenance when minimum supported OSes change. - .__set_env("MACOSX_DEPLOYMENT_TARGET", version) .file("foo.c") .compile("foo"); let cmd = test.cmd(0); - cmd.must_have_in_order("-arch", ld64_arch); - cmd.must_have(format!("-mmacosx-version-min={version}")); cmd.must_not_have("-isysroot"); } } +#[test] +#[cfg(target_os = "macos")] // Invokes xcrun +fn gnu_apple_arch() { + let cases = [ + ("x86_64-apple-darwin", "x86_64"), + ("x86_64h-apple-darwin", "x86_64h"), + ("aarch64-apple-darwin", "arm64"), + ("arm64e-apple-darwin", "arm64e"), + ("i686-apple-darwin", "i386"), + ("aarch64-apple-ios", "arm64"), + ("armv7s-apple-ios", "armv7s"), + ("arm64_32-apple-watchos", "arm64_32"), + ("armv7k-apple-watchos", "armv7k"), + ]; + + for (target, arch) in cases { + let test = Test::gnu(); + test.shim("fake-gcc") + .gcc() + .compiler("fake-gcc") + .target(&target) + .host(&"aarch64-apple-darwin") + .file("foo.c") + .compile("foo"); + + let cmd = test.cmd(0); + cmd.must_have_in_order("-arch", arch); + } +} + +#[test] +#[cfg(target_os = "macos")] // Invokes xcrun +fn gnu_apple_deployment_target() { + let cases = [ + ("x86_64-apple-darwin", "-mmacosx-version-min=10.12"), + ("aarch64-apple-darwin", "-mmacosx-version-min=10.12"), + ("aarch64-apple-ios", "-miphoneos-version-min=10.0"), + ("aarch64-apple-ios-sim", "-mios-simulator-version-min=10.0"), + ("x86_64-apple-ios", "-mios-simulator-version-min=10.0"), + ("aarch64-apple-ios-macabi", "-mtargetos=ios10.0-macabi"), + ("aarch64-apple-tvos", "-mappletvos-version-min=10.0"), + ( + "aarch64-apple-tvos-sim", + "-mappletvsimulator-version-min=10.0", + ), + ("aarch64-apple-watchos", "-mwatchos-version-min=5.0"), + ( + "aarch64-apple-watchos-sim", + "-mwatchsimulator-version-min=5.0", + ), + ("aarch64-apple-visionos", "-mtargetos=xros1.0"), + ("aarch64-apple-visionos-sim", "-mtargetos=xros1.0-simulator"), + ]; + + for (target, os_version_flag) in cases { + let test = Test::gnu(); + test.shim("fake-gcc") + .gcc() + .compiler("fake-gcc") + .target(&target) + .host(&"aarch64-apple-darwin") + // Avoid dependency on environment in test. + .__set_env("MACOSX_DEPLOYMENT_TARGET", "10.12") + .__set_env("IPHONEOS_DEPLOYMENT_TARGET", "10.0") + .__set_env("TVOS_DEPLOYMENT_TARGET", "10.0") + .__set_env("WATCHOS_DEPLOYMENT_TARGET", "5.0") + .__set_env("XROS_DEPLOYMENT_TARGET", "1.0") + .file("foo.c") + .compile("foo"); + + let cmd = test.cmd(0); + cmd.must_have(os_version_flag); + } +} + #[cfg(target_os = "macos")] #[test] fn macos_cpp_minimums() {