Skip to content

Apple: Always pass SDK root when linking with cc, and pass it via SDKROOT env var #131477

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

Merged
merged 4 commits into from
Aug 12, 2025

Conversation

madsmtm
Copy link
Contributor

@madsmtm madsmtm commented Oct 10, 2024

Fixes #80817, fixes #96943, and generally simplifies our linker invocation on Apple platforms.

Part of #129432.

Necessary background on trampoline binaries

The developer binaries such as /usr/bin/cc and /usr/bin/clang are actually trampolines (similar in spirit to the Rust binaries in ~/.cargo/bin) which effectively invokes xcrun to get the current Xcode developer directory, which allows it to find the actual binary under /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/*.

This binary is then launched with the following environment variables set (but none of them are set if SDKROOT is set explicitly):

  • SDKROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk
  • LIBRARY_PATH=/usr/local/lib (appended)
  • CPATH=/usr/local/include (appended)
  • MANPATH=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/share/man:/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/usr/share/man:/Applications/Xcode.app/Contents/Developer/usr/share/man:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/share/man: (prepended)

This allows the user to type e.g. clang foo.c in their terminal on macOS, and have it automatically pick up a suitable Clang binary and SDK from either an installed Xcode.app or the Xcode Command Line Tools.
(It acts roughly as-if you typed xcrun -sdk macosx clang foo.c).

Finding a suitable SDK

All compilation on macOS is cross-compilation using SDKs, there are no system headers any more (/usr/include is gone), and the system libraries are elsewhere in the file system (/usr/lib is basically empty). Instead, the logic for finding the SDK is handled by the /usr/bin/cc trampoline (see above).

But relying on the cc trampoline doesn't work when:

  • Cross-compiling, since a different SDK is needed there.
  • Invoking the linker directly, since the linker doesn't understand SDKROOT.
  • Linking build scripts inside Xcode (see Linker errors when cross-compiling from Xcode on Big Sur #80817), since Xcode prepends /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin to PATH, which means cc refers to the actual Clang binary, and we end up with the wrong SDK root specified.

Basically, we cannot rely on the trampoline at all, so the last commit removes the special-casing that was done when linking with cc for macOS (i.e. the most common path), so that we now always invoke xcrun (if SDKROOT is not explicitly specified) to find the SDK root.

Making sure this is non-breaking has a few difficulties though, namely that the user might not have Xcode installed, and that the compiler driver may not understand the -isysroot flag. These difficulties are explored below.

No Xcode

There are several compiler drivers which work without Xcode by bundling their own SDK, including zig cc, Nixpkgs' clang and Homebrew's llvm package. Additionally, xcrun is rarely available when cross-compiling from non-macOS and instead the user might provide a downloaded SDK manually with -Clink-args=....

We do still want to try to invoke xcrun if possible, since it is usually the SDK that the user wants (and if not, the environment should override xcrun, such as is done by Nixpkgs). But we do not want failure to invoke xcrun to stop the linking process. This is changed in the second-to-last commit.

SDKROOT vs. -isysroot

The exact reasoning why we do not always pass the SDK root when linking on macOS eludes me (the git history dead ends in #100286), but I suspect it's because we want to support compiler drivers which do not support the -isysroot option.

To make sure that such use-cases continue to work, we now pass the SDK root via the SDKROOT environment variable. This way, compiler drivers that support setting the SDK root (such as Clang and GCC) can use it, while compiler drivers that don't (presumably because they figure out the SDK in some other way) can just ignore it.

One small danger here would be if there's some compiler driver out there which works with the -isysroot flag, but not with the SDKROOT environment variable. I am not aware of any?

In a sense, this also shifts the blame; if a compiler driver does not understand SDKROOT, it won't work with e.g. xcrun -sdk macosx15.0 $tool either, so it can more clearly be argued that this is incorrect behaviour on the part of the tool.

Note also that this overrides the behaviour discussed above (/usr/bin/cc sets some extra environment variables), I will argue that is fine since MANPATH and CPATH is useless when linking, and /usr/local/lib is empty on a default system at least since macOS 10.14 (it might be filled by extra libraries installed by the user, but I'll argue that if we want it to be part of the default library search path, we should set it explicitly so that it's also set when linking with -Clinker=ld).

Considered alternatives

  • Invoke /usr/bin/cc instead of cc.
    • This breaks many other use-cases though where overriding cc in the PATH is desired.
  • Look up which cc, and do special logic if in Xcode toolchain.
    • Seems brittle, and besides, it's not the cc in the Xcode toolchain that's wrong, it's the /usr/bin/cc behaviour that is a bit too magical.
  • Invoke xcrun --sdk macosx cc.
    • This completely ignores SDKROOT, so we'd still have to parse that first to figure out if it's suitable or not, but would probably be workable.
  • Maybe somehow configure the linker with extra flags such that it'll be able to link regardless of linking for macOS or e.g. iOS? Though I doubt this is possible.
  • Bundle the SDK, similar to zig-cc.
    • Comes with it's own host of problems.

Testing

Tested that this works with the following -Clinker=...:

  • Default (cc)
  • /usr/bin/ld
  • Actual Clang from Xcode (/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang)
  • /usr/bin/clang (invoked via clang instead of cc)
  • Homebrew's llvm package (ignores SDKROOT, uses their own SDK)
  • Homebrew's gcc package (SDKROOT is preferred over their own SDK)
  • Macports clang Couldn't get it to build
  • Macports gcc (SDKROOT is preferred over their own SDK)
  • Zig CC installed via. homebrew (ignores both -isysroot and SDKROOT, uses their own SDK)
  • Nixpkgs clang (ignores SDKROOT, uses their own SDK)
  • Nixpkgs gcc (ignores SDKROOT, uses their own SDK)
  • cosmocc? Doesn't accept common flags (like -arch)

CC @BlackHoleFox @thomcc

@rustbot
Copy link
Collaborator

rustbot commented Oct 10, 2024

r? @nnethercote

rustbot has assigned @nnethercote.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added O-apple Operating system: Apple (macOS, iOS, tvOS, visionOS, watchOS) S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. S-blocked Status: Blocked on something else such as an RFC or other implementation work. labels Oct 10, 2024
@madsmtm madsmtm marked this pull request as ready for review October 10, 2024 01:20
@rustbot
Copy link
Collaborator

rustbot commented Oct 10, 2024

These commits modify compiler targets.
(See the Target Tier Policy.)

@rust-log-analyzer

This comment has been minimized.

@madsmtm madsmtm force-pushed the sdkroot-via-env-var branch from 6aaa795 to 97e382d Compare October 10, 2024 01:23
@nnethercote
Copy link
Contributor

This looks reasonable, but I don't really have the first idea about any of this stuff and that means I'm not a great reviewer for it. @thomcc, are you able to take a look? I can rubber-stamp it if you are happy.

One question: there are no tests in this PR or in #131433. Should there be?

@madsmtm madsmtm force-pushed the sdkroot-via-env-var branch from 97e382d to eeea5f0 Compare October 10, 2024 05:44
@rustbot rustbot added the A-run-make Area: port run-make Makefiles to rmake.rs label Oct 10, 2024
@rustbot
Copy link
Collaborator

rustbot commented Oct 10, 2024

This PR modifies tests/run-make/. If this PR is trying to port a Makefile
run-make test to use rmake.rs, please update the
run-make port tracking issue
so we can track our progress. You can either modify the tracking issue
directly, or you can comment on the tracking issue and link this PR.

cc @jieyouxu

@madsmtm
Copy link
Contributor Author

madsmtm commented Oct 10, 2024

One question: there are no tests in this PR or in #131433. Should there be?

There are a few unit tests under compiler/rustc_codegen_ssa/src/back/apple/tests.rs, but your point stands, there are no integration tests for this.

The main reason for that is that this is really hard to write a robust test for, since this is so heavily environment specific - but I guess a test that adds the Xcode paths to PATH as a regression test for #80817 would be doable, I've done that and rebased, thanks for the nudge to do it ;).

@rust-log-analyzer

This comment has been minimized.

@madsmtm madsmtm force-pushed the sdkroot-via-env-var branch from eeea5f0 to 8ca8d4f Compare October 10, 2024 05:52
@rust-log-analyzer

This comment has been minimized.

@madsmtm madsmtm force-pushed the sdkroot-via-env-var branch from 25e7f0f to 5d010db Compare October 31, 2024 20:39
@rustbot
Copy link
Collaborator

rustbot commented Oct 31, 2024

Some changes occurred in src/doc/rustc/src/platform-support

cc @Noratrieb

@rustbot rustbot added the relnotes Marks issues that should be documented in the release notes of the next release. label Oct 31, 2024
Copy link
Member

@thomcc thomcc left a comment

Choose a reason for hiding this comment

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

I approve of the macOS specific parts of this. I'm not a compiler reviewer so I haven't done any review for style/etc. That said that part does look fine to me. It's a great PR overall. Great PR description too, clarified some things I had always wondered about.

@nnethercote
Copy link
Contributor

Thanks, @thomcc. Once #131433 is merged I'm happy for this to merge.

@bors delegate=madsmtm

@rustbot

This comment has been minimized.

The exact reasoning why we do not always pass the SDK root when linking
on macOS eludes me, but I suspect it's because we want to support
compiler drivers which do not support the `-isysroot` option.

Since we now pass the SDK root via the environment variable SDKROOT,
compiler drivers that don't support it can just ignore it.

Similarly, since we only warn when xcrun fails, users that expect their
compiler driver to provide the SDK location can do so now.
@madsmtm madsmtm force-pushed the sdkroot-via-env-var branch from 4f25b1e to 1d13162 Compare August 11, 2025 21:31
@rustbot rustbot added the A-run-make Area: port run-make Makefiles to rmake.rs label Aug 11, 2025
@madsmtm madsmtm added the A-linkage Area: linking into static, shared libraries and binaries label Aug 11, 2025
@nnethercote
Copy link
Contributor

@bors r+

@bors
Copy link
Collaborator

bors commented Aug 11, 2025

📌 Commit 1d13162 has been approved by nnethercote

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Aug 11, 2025
fmease added a commit to fmease/rust that referenced this pull request Aug 11, 2025
…thercote

Apple: Always pass SDK root when linking with `cc`, and pass it via `SDKROOT` env var

Fixes rust-lang#80817, fixes rust-lang#96943, and generally simplifies our linker invocation on Apple platforms.

Part of rust-lang#129432.

### Necessary background on trampoline binaries

The developer binaries such as `/usr/bin/cc` and `/usr/bin/clang` are actually trampolines (similar in spirit to the Rust binaries in `~/.cargo/bin`) which effectively invokes `xcrun` to get the current Xcode developer directory, which allows it to find the actual binary under `/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/*`.

This binary is then launched with the following environment variables set (but none of them are set if `SDKROOT` is set explicitly):
- `SDKROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk`
- `LIBRARY_PATH=/usr/local/lib` (appended)
- `CPATH=/usr/local/include` (appended)
- `MANPATH=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/share/man:/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/usr/share/man:/Applications/Xcode.app/Contents/Developer/usr/share/man:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/share/man:` (prepended)

This allows the user to type e.g. `clang foo.c` in their terminal on macOS, and have it automatically pick up a suitable Clang binary and SDK from either an installed Xcode.app or the Xcode Command Line Tools.
(It acts roughly as-if you typed `xcrun -sdk macosx clang foo.c`).

### Finding a suitable SDK

All compilation on macOS is cross-compilation using SDKs, there are no system headers any more (`/usr/include` is gone), and the system libraries are elsewhere in the file system (`/usr/lib` is basically empty). Instead, the logic for finding the SDK is handled by the `/usr/bin/cc` trampoline (see above).

But relying on the `cc` trampoline doesn't work when:
- Cross-compiling, since a different SDK is needed there.
- Invoking the linker directly, since the linker doesn't understand `SDKROOT`.
- Linking build scripts inside Xcode (see rust-lang#80817), since Xcode prepends `/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin` to `PATH`, which means `cc` refers to the _actual_ Clang binary, and we end up with the wrong SDK root specified.

Basically, we cannot rely on the trampoline at all, so the last commit removes the special-casing that was done when linking with `cc` for macOS (i.e. the most common path), so that **we now always invoke `xcrun` (if `SDKROOT` is not explicitly specified) to find the SDK root**.

Making sure this is non-breaking has a few difficulties though, namely that the user might not have Xcode installed, and that the compiler driver may not understand the `-isysroot` flag. These difficulties are explored below.

#### No Xcode

There are several compiler drivers which work without Xcode by bundling their own SDK, including `zig cc`, Nixpkgs' `clang` and Homebrew's `llvm` package. Additionally, `xcrun` is rarely available when cross-compiling from non-macOS and instead the user might provide a downloaded SDK manually with `-Clink-args=...`.

We do still want to _try_ to invoke `xcrun` if possible, since it is usually the SDK that the user wants (and if not, the environment should override `xcrun`, such as is done by Nixpkgs). But we do not want failure to invoke `xcrun` to stop the linking process. This is changed in the second-to-last commit.

#### `SDKROOT` vs. `-isysroot`

The exact reasoning why we do not always pass the SDK root when linking on macOS eludes me (the git history dead ends in rust-lang#100286), but I suspect it's because we want to support compiler drivers which do not support the `-isysroot` option.

To make sure that such use-cases continue to work, we now pass the SDK root via the `SDKROOT` environment variable. This way, compiler drivers that support setting the SDK root (such as Clang and GCC) can use it, while compiler drivers that don't (presumably because they figure out the SDK in some other way) can just ignore it.

One small danger here would be if there's some compiler driver out there which works with the `-isysroot` flag, but not with the `SDKROOT` environment variable. I am not aware of any?

In a sense, this also shifts the blame; if a compiler driver does not understand `SDKROOT`, it won't work with e.g. `xcrun -sdk macosx15.0 $tool` either, so it can more clearly be argued that this is incorrect behaviour on the part of the tool.

Note also that this overrides the behaviour discussed above (`/usr/bin/cc` sets some extra environment variables), I will argue that is fine since `MANPATH` and `CPATH` is useless when linking, and `/usr/local/lib` is empty on a default system at least since macOS 10.14 (it might be filled by extra libraries installed by the user, but I'll argue that if we want it to be part of the default library search path, we should set it explicitly so that it's also set when linking with `-Clinker=ld`).

### Considered alternatives

- Invoke `/usr/bin/cc` instead of `cc`.
  - This breaks many other use-cases though where overriding `cc` in the PATH is desired.
- Look up `which cc`, and do special logic if in Xcode toolchain.
  - Seems brittle, and besides, it's not the `cc` in the Xcode toolchain that's wrong, it's the `/usr/bin/cc` behaviour that is a bit too magical.
- Invoke `xcrun --sdk macosx cc`.
  - This completely ignores `SDKROOT`, so we'd still have to parse that first to figure out if it's suitable or not, but would probably be workable.
- Maybe somehow configure the linker with extra flags such that it'll be able to link regardless of linking for macOS or e.g. iOS? Though I doubt this is possible.
- Bundle the SDK, similar to `zig-cc`.
  - Comes with it's own host of problems.

### Testing

Tested that this works with the following `-Clinker=...`:
- [x] Default (`cc`)
- [x] `/usr/bin/ld`
- [x] Actual Clang from Xcode (`/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang`)
- [x] `/usr/bin/clang` (invoked via `clang` instead of `cc`)
- [x] Homebrew's `llvm` package (ignores `SDKROOT`, uses their own SDK)
- [x] Homebrew's `gcc` package (`SDKROOT` is preferred over their own SDK)
- [x] ~Macports `clang`~ Couldn't get it to build
- [x] Macports `gcc` (`SDKROOT` is preferred over their own SDK)
- [x] Zig CC installed via. homebrew (ignores both `-isysroot` and `SDKROOT`, uses their own SDK)
- [x] Nixpkgs `clang` (ignores `SDKROOT`, uses their own SDK)
- [x] Nixpkgs `gcc` (ignores `SDKROOT`, uses their own SDK)
- [x] ~[`cosmocc`](https://github.com/jart/cosmopolitan)?~ Doesn't accept common flags (like `-arch`)

CC `@BlackHoleFox` `@thomcc`
fmease added a commit to fmease/rust that referenced this pull request Aug 12, 2025
…thercote

Apple: Always pass SDK root when linking with `cc`, and pass it via `SDKROOT` env var

Fixes rust-lang#80817, fixes rust-lang#96943, and generally simplifies our linker invocation on Apple platforms.

Part of rust-lang#129432.

### Necessary background on trampoline binaries

The developer binaries such as `/usr/bin/cc` and `/usr/bin/clang` are actually trampolines (similar in spirit to the Rust binaries in `~/.cargo/bin`) which effectively invokes `xcrun` to get the current Xcode developer directory, which allows it to find the actual binary under `/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/*`.

This binary is then launched with the following environment variables set (but none of them are set if `SDKROOT` is set explicitly):
- `SDKROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk`
- `LIBRARY_PATH=/usr/local/lib` (appended)
- `CPATH=/usr/local/include` (appended)
- `MANPATH=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/share/man:/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/usr/share/man:/Applications/Xcode.app/Contents/Developer/usr/share/man:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/share/man:` (prepended)

This allows the user to type e.g. `clang foo.c` in their terminal on macOS, and have it automatically pick up a suitable Clang binary and SDK from either an installed Xcode.app or the Xcode Command Line Tools.
(It acts roughly as-if you typed `xcrun -sdk macosx clang foo.c`).

### Finding a suitable SDK

All compilation on macOS is cross-compilation using SDKs, there are no system headers any more (`/usr/include` is gone), and the system libraries are elsewhere in the file system (`/usr/lib` is basically empty). Instead, the logic for finding the SDK is handled by the `/usr/bin/cc` trampoline (see above).

But relying on the `cc` trampoline doesn't work when:
- Cross-compiling, since a different SDK is needed there.
- Invoking the linker directly, since the linker doesn't understand `SDKROOT`.
- Linking build scripts inside Xcode (see rust-lang#80817), since Xcode prepends `/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin` to `PATH`, which means `cc` refers to the _actual_ Clang binary, and we end up with the wrong SDK root specified.

Basically, we cannot rely on the trampoline at all, so the last commit removes the special-casing that was done when linking with `cc` for macOS (i.e. the most common path), so that **we now always invoke `xcrun` (if `SDKROOT` is not explicitly specified) to find the SDK root**.

Making sure this is non-breaking has a few difficulties though, namely that the user might not have Xcode installed, and that the compiler driver may not understand the `-isysroot` flag. These difficulties are explored below.

#### No Xcode

There are several compiler drivers which work without Xcode by bundling their own SDK, including `zig cc`, Nixpkgs' `clang` and Homebrew's `llvm` package. Additionally, `xcrun` is rarely available when cross-compiling from non-macOS and instead the user might provide a downloaded SDK manually with `-Clink-args=...`.

We do still want to _try_ to invoke `xcrun` if possible, since it is usually the SDK that the user wants (and if not, the environment should override `xcrun`, such as is done by Nixpkgs). But we do not want failure to invoke `xcrun` to stop the linking process. This is changed in the second-to-last commit.

#### `SDKROOT` vs. `-isysroot`

The exact reasoning why we do not always pass the SDK root when linking on macOS eludes me (the git history dead ends in rust-lang#100286), but I suspect it's because we want to support compiler drivers which do not support the `-isysroot` option.

To make sure that such use-cases continue to work, we now pass the SDK root via the `SDKROOT` environment variable. This way, compiler drivers that support setting the SDK root (such as Clang and GCC) can use it, while compiler drivers that don't (presumably because they figure out the SDK in some other way) can just ignore it.

One small danger here would be if there's some compiler driver out there which works with the `-isysroot` flag, but not with the `SDKROOT` environment variable. I am not aware of any?

In a sense, this also shifts the blame; if a compiler driver does not understand `SDKROOT`, it won't work with e.g. `xcrun -sdk macosx15.0 $tool` either, so it can more clearly be argued that this is incorrect behaviour on the part of the tool.

Note also that this overrides the behaviour discussed above (`/usr/bin/cc` sets some extra environment variables), I will argue that is fine since `MANPATH` and `CPATH` is useless when linking, and `/usr/local/lib` is empty on a default system at least since macOS 10.14 (it might be filled by extra libraries installed by the user, but I'll argue that if we want it to be part of the default library search path, we should set it explicitly so that it's also set when linking with `-Clinker=ld`).

### Considered alternatives

- Invoke `/usr/bin/cc` instead of `cc`.
  - This breaks many other use-cases though where overriding `cc` in the PATH is desired.
- Look up `which cc`, and do special logic if in Xcode toolchain.
  - Seems brittle, and besides, it's not the `cc` in the Xcode toolchain that's wrong, it's the `/usr/bin/cc` behaviour that is a bit too magical.
- Invoke `xcrun --sdk macosx cc`.
  - This completely ignores `SDKROOT`, so we'd still have to parse that first to figure out if it's suitable or not, but would probably be workable.
- Maybe somehow configure the linker with extra flags such that it'll be able to link regardless of linking for macOS or e.g. iOS? Though I doubt this is possible.
- Bundle the SDK, similar to `zig-cc`.
  - Comes with it's own host of problems.

### Testing

Tested that this works with the following `-Clinker=...`:
- [x] Default (`cc`)
- [x] `/usr/bin/ld`
- [x] Actual Clang from Xcode (`/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang`)
- [x] `/usr/bin/clang` (invoked via `clang` instead of `cc`)
- [x] Homebrew's `llvm` package (ignores `SDKROOT`, uses their own SDK)
- [x] Homebrew's `gcc` package (`SDKROOT` is preferred over their own SDK)
- [x] ~Macports `clang`~ Couldn't get it to build
- [x] Macports `gcc` (`SDKROOT` is preferred over their own SDK)
- [x] Zig CC installed via. homebrew (ignores both `-isysroot` and `SDKROOT`, uses their own SDK)
- [x] Nixpkgs `clang` (ignores `SDKROOT`, uses their own SDK)
- [x] Nixpkgs `gcc` (ignores `SDKROOT`, uses their own SDK)
- [x] ~[`cosmocc`](https://github.com/jart/cosmopolitan)?~ Doesn't accept common flags (like `-arch`)

CC ``@BlackHoleFox`` ``@thomcc``
fmease added a commit to fmease/rust that referenced this pull request Aug 12, 2025
…thercote

Apple: Always pass SDK root when linking with `cc`, and pass it via `SDKROOT` env var

Fixes rust-lang#80817, fixes rust-lang#96943, and generally simplifies our linker invocation on Apple platforms.

Part of rust-lang#129432.

### Necessary background on trampoline binaries

The developer binaries such as `/usr/bin/cc` and `/usr/bin/clang` are actually trampolines (similar in spirit to the Rust binaries in `~/.cargo/bin`) which effectively invokes `xcrun` to get the current Xcode developer directory, which allows it to find the actual binary under `/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/*`.

This binary is then launched with the following environment variables set (but none of them are set if `SDKROOT` is set explicitly):
- `SDKROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk`
- `LIBRARY_PATH=/usr/local/lib` (appended)
- `CPATH=/usr/local/include` (appended)
- `MANPATH=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/share/man:/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/usr/share/man:/Applications/Xcode.app/Contents/Developer/usr/share/man:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/share/man:` (prepended)

This allows the user to type e.g. `clang foo.c` in their terminal on macOS, and have it automatically pick up a suitable Clang binary and SDK from either an installed Xcode.app or the Xcode Command Line Tools.
(It acts roughly as-if you typed `xcrun -sdk macosx clang foo.c`).

### Finding a suitable SDK

All compilation on macOS is cross-compilation using SDKs, there are no system headers any more (`/usr/include` is gone), and the system libraries are elsewhere in the file system (`/usr/lib` is basically empty). Instead, the logic for finding the SDK is handled by the `/usr/bin/cc` trampoline (see above).

But relying on the `cc` trampoline doesn't work when:
- Cross-compiling, since a different SDK is needed there.
- Invoking the linker directly, since the linker doesn't understand `SDKROOT`.
- Linking build scripts inside Xcode (see rust-lang#80817), since Xcode prepends `/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin` to `PATH`, which means `cc` refers to the _actual_ Clang binary, and we end up with the wrong SDK root specified.

Basically, we cannot rely on the trampoline at all, so the last commit removes the special-casing that was done when linking with `cc` for macOS (i.e. the most common path), so that **we now always invoke `xcrun` (if `SDKROOT` is not explicitly specified) to find the SDK root**.

Making sure this is non-breaking has a few difficulties though, namely that the user might not have Xcode installed, and that the compiler driver may not understand the `-isysroot` flag. These difficulties are explored below.

#### No Xcode

There are several compiler drivers which work without Xcode by bundling their own SDK, including `zig cc`, Nixpkgs' `clang` and Homebrew's `llvm` package. Additionally, `xcrun` is rarely available when cross-compiling from non-macOS and instead the user might provide a downloaded SDK manually with `-Clink-args=...`.

We do still want to _try_ to invoke `xcrun` if possible, since it is usually the SDK that the user wants (and if not, the environment should override `xcrun`, such as is done by Nixpkgs). But we do not want failure to invoke `xcrun` to stop the linking process. This is changed in the second-to-last commit.

#### `SDKROOT` vs. `-isysroot`

The exact reasoning why we do not always pass the SDK root when linking on macOS eludes me (the git history dead ends in rust-lang#100286), but I suspect it's because we want to support compiler drivers which do not support the `-isysroot` option.

To make sure that such use-cases continue to work, we now pass the SDK root via the `SDKROOT` environment variable. This way, compiler drivers that support setting the SDK root (such as Clang and GCC) can use it, while compiler drivers that don't (presumably because they figure out the SDK in some other way) can just ignore it.

One small danger here would be if there's some compiler driver out there which works with the `-isysroot` flag, but not with the `SDKROOT` environment variable. I am not aware of any?

In a sense, this also shifts the blame; if a compiler driver does not understand `SDKROOT`, it won't work with e.g. `xcrun -sdk macosx15.0 $tool` either, so it can more clearly be argued that this is incorrect behaviour on the part of the tool.

Note also that this overrides the behaviour discussed above (`/usr/bin/cc` sets some extra environment variables), I will argue that is fine since `MANPATH` and `CPATH` is useless when linking, and `/usr/local/lib` is empty on a default system at least since macOS 10.14 (it might be filled by extra libraries installed by the user, but I'll argue that if we want it to be part of the default library search path, we should set it explicitly so that it's also set when linking with `-Clinker=ld`).

### Considered alternatives

- Invoke `/usr/bin/cc` instead of `cc`.
  - This breaks many other use-cases though where overriding `cc` in the PATH is desired.
- Look up `which cc`, and do special logic if in Xcode toolchain.
  - Seems brittle, and besides, it's not the `cc` in the Xcode toolchain that's wrong, it's the `/usr/bin/cc` behaviour that is a bit too magical.
- Invoke `xcrun --sdk macosx cc`.
  - This completely ignores `SDKROOT`, so we'd still have to parse that first to figure out if it's suitable or not, but would probably be workable.
- Maybe somehow configure the linker with extra flags such that it'll be able to link regardless of linking for macOS or e.g. iOS? Though I doubt this is possible.
- Bundle the SDK, similar to `zig-cc`.
  - Comes with it's own host of problems.

### Testing

Tested that this works with the following `-Clinker=...`:
- [x] Default (`cc`)
- [x] `/usr/bin/ld`
- [x] Actual Clang from Xcode (`/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang`)
- [x] `/usr/bin/clang` (invoked via `clang` instead of `cc`)
- [x] Homebrew's `llvm` package (ignores `SDKROOT`, uses their own SDK)
- [x] Homebrew's `gcc` package (`SDKROOT` is preferred over their own SDK)
- [x] ~Macports `clang`~ Couldn't get it to build
- [x] Macports `gcc` (`SDKROOT` is preferred over their own SDK)
- [x] Zig CC installed via. homebrew (ignores both `-isysroot` and `SDKROOT`, uses their own SDK)
- [x] Nixpkgs `clang` (ignores `SDKROOT`, uses their own SDK)
- [x] Nixpkgs `gcc` (ignores `SDKROOT`, uses their own SDK)
- [x] ~[`cosmocc`](https://github.com/jart/cosmopolitan)?~ Doesn't accept common flags (like `-arch`)

CC ```@BlackHoleFox``` ```@thomcc```
fmease added a commit to fmease/rust that referenced this pull request Aug 12, 2025
…thercote

Apple: Always pass SDK root when linking with `cc`, and pass it via `SDKROOT` env var

Fixes rust-lang#80817, fixes rust-lang#96943, and generally simplifies our linker invocation on Apple platforms.

Part of rust-lang#129432.

### Necessary background on trampoline binaries

The developer binaries such as `/usr/bin/cc` and `/usr/bin/clang` are actually trampolines (similar in spirit to the Rust binaries in `~/.cargo/bin`) which effectively invokes `xcrun` to get the current Xcode developer directory, which allows it to find the actual binary under `/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/*`.

This binary is then launched with the following environment variables set (but none of them are set if `SDKROOT` is set explicitly):
- `SDKROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk`
- `LIBRARY_PATH=/usr/local/lib` (appended)
- `CPATH=/usr/local/include` (appended)
- `MANPATH=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/share/man:/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/usr/share/man:/Applications/Xcode.app/Contents/Developer/usr/share/man:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/share/man:` (prepended)

This allows the user to type e.g. `clang foo.c` in their terminal on macOS, and have it automatically pick up a suitable Clang binary and SDK from either an installed Xcode.app or the Xcode Command Line Tools.
(It acts roughly as-if you typed `xcrun -sdk macosx clang foo.c`).

### Finding a suitable SDK

All compilation on macOS is cross-compilation using SDKs, there are no system headers any more (`/usr/include` is gone), and the system libraries are elsewhere in the file system (`/usr/lib` is basically empty). Instead, the logic for finding the SDK is handled by the `/usr/bin/cc` trampoline (see above).

But relying on the `cc` trampoline doesn't work when:
- Cross-compiling, since a different SDK is needed there.
- Invoking the linker directly, since the linker doesn't understand `SDKROOT`.
- Linking build scripts inside Xcode (see rust-lang#80817), since Xcode prepends `/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin` to `PATH`, which means `cc` refers to the _actual_ Clang binary, and we end up with the wrong SDK root specified.

Basically, we cannot rely on the trampoline at all, so the last commit removes the special-casing that was done when linking with `cc` for macOS (i.e. the most common path), so that **we now always invoke `xcrun` (if `SDKROOT` is not explicitly specified) to find the SDK root**.

Making sure this is non-breaking has a few difficulties though, namely that the user might not have Xcode installed, and that the compiler driver may not understand the `-isysroot` flag. These difficulties are explored below.

#### No Xcode

There are several compiler drivers which work without Xcode by bundling their own SDK, including `zig cc`, Nixpkgs' `clang` and Homebrew's `llvm` package. Additionally, `xcrun` is rarely available when cross-compiling from non-macOS and instead the user might provide a downloaded SDK manually with `-Clink-args=...`.

We do still want to _try_ to invoke `xcrun` if possible, since it is usually the SDK that the user wants (and if not, the environment should override `xcrun`, such as is done by Nixpkgs). But we do not want failure to invoke `xcrun` to stop the linking process. This is changed in the second-to-last commit.

#### `SDKROOT` vs. `-isysroot`

The exact reasoning why we do not always pass the SDK root when linking on macOS eludes me (the git history dead ends in rust-lang#100286), but I suspect it's because we want to support compiler drivers which do not support the `-isysroot` option.

To make sure that such use-cases continue to work, we now pass the SDK root via the `SDKROOT` environment variable. This way, compiler drivers that support setting the SDK root (such as Clang and GCC) can use it, while compiler drivers that don't (presumably because they figure out the SDK in some other way) can just ignore it.

One small danger here would be if there's some compiler driver out there which works with the `-isysroot` flag, but not with the `SDKROOT` environment variable. I am not aware of any?

In a sense, this also shifts the blame; if a compiler driver does not understand `SDKROOT`, it won't work with e.g. `xcrun -sdk macosx15.0 $tool` either, so it can more clearly be argued that this is incorrect behaviour on the part of the tool.

Note also that this overrides the behaviour discussed above (`/usr/bin/cc` sets some extra environment variables), I will argue that is fine since `MANPATH` and `CPATH` is useless when linking, and `/usr/local/lib` is empty on a default system at least since macOS 10.14 (it might be filled by extra libraries installed by the user, but I'll argue that if we want it to be part of the default library search path, we should set it explicitly so that it's also set when linking with `-Clinker=ld`).

### Considered alternatives

- Invoke `/usr/bin/cc` instead of `cc`.
  - This breaks many other use-cases though where overriding `cc` in the PATH is desired.
- Look up `which cc`, and do special logic if in Xcode toolchain.
  - Seems brittle, and besides, it's not the `cc` in the Xcode toolchain that's wrong, it's the `/usr/bin/cc` behaviour that is a bit too magical.
- Invoke `xcrun --sdk macosx cc`.
  - This completely ignores `SDKROOT`, so we'd still have to parse that first to figure out if it's suitable or not, but would probably be workable.
- Maybe somehow configure the linker with extra flags such that it'll be able to link regardless of linking for macOS or e.g. iOS? Though I doubt this is possible.
- Bundle the SDK, similar to `zig-cc`.
  - Comes with it's own host of problems.

### Testing

Tested that this works with the following `-Clinker=...`:
- [x] Default (`cc`)
- [x] `/usr/bin/ld`
- [x] Actual Clang from Xcode (`/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang`)
- [x] `/usr/bin/clang` (invoked via `clang` instead of `cc`)
- [x] Homebrew's `llvm` package (ignores `SDKROOT`, uses their own SDK)
- [x] Homebrew's `gcc` package (`SDKROOT` is preferred over their own SDK)
- [x] ~Macports `clang`~ Couldn't get it to build
- [x] Macports `gcc` (`SDKROOT` is preferred over their own SDK)
- [x] Zig CC installed via. homebrew (ignores both `-isysroot` and `SDKROOT`, uses their own SDK)
- [x] Nixpkgs `clang` (ignores `SDKROOT`, uses their own SDK)
- [x] Nixpkgs `gcc` (ignores `SDKROOT`, uses their own SDK)
- [x] ~[`cosmocc`](https://github.com/jart/cosmopolitan)?~ Doesn't accept common flags (like `-arch`)

CC ````@BlackHoleFox```` ````@thomcc````
bors added a commit that referenced this pull request Aug 12, 2025
Rollup of 15 pull requests

Successful merges:

 - #131477 (Apple: Always pass SDK root when linking with `cc`, and pass it via `SDKROOT` env var)
 - #139806 (std: sys: pal: uefi: Overhaul Time)
 - #144386 (Extract TraitImplHeader in AST/HIR)
 - #144542 (Stabilize `sse4a` and `tbm` target features)
 - #144921 (Don't emit `rustdoc::broken_intra_doc_links` for GitHub-flavored Markdown admonitions like `[!NOTE]`)
 - #145155 (Port `#[allow_internal_unsafe]` to the new attribute system (attempt 2))
 - #145214 (fix: re-enable self-assignment)
 - #145216 (rustdoc: correct negative-to-implicit discriminant display)
 - #145238 (Tweak invalid builtin attribute output)
 - #145249 (Rename entered trace span variables from `_span` to  `_trace`)
 - #145251 (Support using #[unstable_feature_bound] on trait)
 - #145253 (Document compiler and stdlib in stage1 in `pr-check-2` CI job)
 - #145260 (Make explicit guarantees about `Vec`’s allocator)
 - #145263 (Update books)
 - #145273 (Account for new `assert!` desugaring in `!condition` suggestion)

r? `@ghost`
`@rustbot` modify labels: rollup
fmease added a commit to fmease/rust that referenced this pull request Aug 12, 2025
…thercote

Apple: Always pass SDK root when linking with `cc`, and pass it via `SDKROOT` env var

Fixes rust-lang#80817, fixes rust-lang#96943, and generally simplifies our linker invocation on Apple platforms.

Part of rust-lang#129432.

### Necessary background on trampoline binaries

The developer binaries such as `/usr/bin/cc` and `/usr/bin/clang` are actually trampolines (similar in spirit to the Rust binaries in `~/.cargo/bin`) which effectively invokes `xcrun` to get the current Xcode developer directory, which allows it to find the actual binary under `/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/*`.

This binary is then launched with the following environment variables set (but none of them are set if `SDKROOT` is set explicitly):
- `SDKROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk`
- `LIBRARY_PATH=/usr/local/lib` (appended)
- `CPATH=/usr/local/include` (appended)
- `MANPATH=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/share/man:/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/usr/share/man:/Applications/Xcode.app/Contents/Developer/usr/share/man:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/share/man:` (prepended)

This allows the user to type e.g. `clang foo.c` in their terminal on macOS, and have it automatically pick up a suitable Clang binary and SDK from either an installed Xcode.app or the Xcode Command Line Tools.
(It acts roughly as-if you typed `xcrun -sdk macosx clang foo.c`).

### Finding a suitable SDK

All compilation on macOS is cross-compilation using SDKs, there are no system headers any more (`/usr/include` is gone), and the system libraries are elsewhere in the file system (`/usr/lib` is basically empty). Instead, the logic for finding the SDK is handled by the `/usr/bin/cc` trampoline (see above).

But relying on the `cc` trampoline doesn't work when:
- Cross-compiling, since a different SDK is needed there.
- Invoking the linker directly, since the linker doesn't understand `SDKROOT`.
- Linking build scripts inside Xcode (see rust-lang#80817), since Xcode prepends `/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin` to `PATH`, which means `cc` refers to the _actual_ Clang binary, and we end up with the wrong SDK root specified.

Basically, we cannot rely on the trampoline at all, so the last commit removes the special-casing that was done when linking with `cc` for macOS (i.e. the most common path), so that **we now always invoke `xcrun` (if `SDKROOT` is not explicitly specified) to find the SDK root**.

Making sure this is non-breaking has a few difficulties though, namely that the user might not have Xcode installed, and that the compiler driver may not understand the `-isysroot` flag. These difficulties are explored below.

#### No Xcode

There are several compiler drivers which work without Xcode by bundling their own SDK, including `zig cc`, Nixpkgs' `clang` and Homebrew's `llvm` package. Additionally, `xcrun` is rarely available when cross-compiling from non-macOS and instead the user might provide a downloaded SDK manually with `-Clink-args=...`.

We do still want to _try_ to invoke `xcrun` if possible, since it is usually the SDK that the user wants (and if not, the environment should override `xcrun`, such as is done by Nixpkgs). But we do not want failure to invoke `xcrun` to stop the linking process. This is changed in the second-to-last commit.

#### `SDKROOT` vs. `-isysroot`

The exact reasoning why we do not always pass the SDK root when linking on macOS eludes me (the git history dead ends in rust-lang#100286), but I suspect it's because we want to support compiler drivers which do not support the `-isysroot` option.

To make sure that such use-cases continue to work, we now pass the SDK root via the `SDKROOT` environment variable. This way, compiler drivers that support setting the SDK root (such as Clang and GCC) can use it, while compiler drivers that don't (presumably because they figure out the SDK in some other way) can just ignore it.

One small danger here would be if there's some compiler driver out there which works with the `-isysroot` flag, but not with the `SDKROOT` environment variable. I am not aware of any?

In a sense, this also shifts the blame; if a compiler driver does not understand `SDKROOT`, it won't work with e.g. `xcrun -sdk macosx15.0 $tool` either, so it can more clearly be argued that this is incorrect behaviour on the part of the tool.

Note also that this overrides the behaviour discussed above (`/usr/bin/cc` sets some extra environment variables), I will argue that is fine since `MANPATH` and `CPATH` is useless when linking, and `/usr/local/lib` is empty on a default system at least since macOS 10.14 (it might be filled by extra libraries installed by the user, but I'll argue that if we want it to be part of the default library search path, we should set it explicitly so that it's also set when linking with `-Clinker=ld`).

### Considered alternatives

- Invoke `/usr/bin/cc` instead of `cc`.
  - This breaks many other use-cases though where overriding `cc` in the PATH is desired.
- Look up `which cc`, and do special logic if in Xcode toolchain.
  - Seems brittle, and besides, it's not the `cc` in the Xcode toolchain that's wrong, it's the `/usr/bin/cc` behaviour that is a bit too magical.
- Invoke `xcrun --sdk macosx cc`.
  - This completely ignores `SDKROOT`, so we'd still have to parse that first to figure out if it's suitable or not, but would probably be workable.
- Maybe somehow configure the linker with extra flags such that it'll be able to link regardless of linking for macOS or e.g. iOS? Though I doubt this is possible.
- Bundle the SDK, similar to `zig-cc`.
  - Comes with it's own host of problems.

### Testing

Tested that this works with the following `-Clinker=...`:
- [x] Default (`cc`)
- [x] `/usr/bin/ld`
- [x] Actual Clang from Xcode (`/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang`)
- [x] `/usr/bin/clang` (invoked via `clang` instead of `cc`)
- [x] Homebrew's `llvm` package (ignores `SDKROOT`, uses their own SDK)
- [x] Homebrew's `gcc` package (`SDKROOT` is preferred over their own SDK)
- [x] ~Macports `clang`~ Couldn't get it to build
- [x] Macports `gcc` (`SDKROOT` is preferred over their own SDK)
- [x] Zig CC installed via. homebrew (ignores both `-isysroot` and `SDKROOT`, uses their own SDK)
- [x] Nixpkgs `clang` (ignores `SDKROOT`, uses their own SDK)
- [x] Nixpkgs `gcc` (ignores `SDKROOT`, uses their own SDK)
- [x] ~[`cosmocc`](https://github.com/jart/cosmopolitan)?~ Doesn't accept common flags (like `-arch`)

CC `````@BlackHoleFox````` `````@thomcc`````
bors added a commit that referenced this pull request Aug 12, 2025
Rollup of 14 pull requests

Successful merges:

 - #131477 (Apple: Always pass SDK root when linking with `cc`, and pass it via `SDKROOT` env var)
 - #139806 (std: sys: pal: uefi: Overhaul Time)
 - #144210 (std: thread: Return error if setting thread stack size fails)
 - #144386 (Extract TraitImplHeader in AST/HIR)
 - #144921 (Don't emit `rustdoc::broken_intra_doc_links` for GitHub-flavored Markdown admonitions like `[!NOTE]`)
 - #145155 (Port `#[allow_internal_unsafe]` to the new attribute system (attempt 2))
 - #145214 (fix: re-enable self-assignment)
 - #145216 (rustdoc: correct negative-to-implicit discriminant display)
 - #145238 (Tweak invalid builtin attribute output)
 - #145249 (Rename entered trace span variables from `_span` to  `_trace`)
 - #145251 (Support using #[unstable_feature_bound] on trait)
 - #145253 (Document compiler and stdlib in stage1 in `pr-check-2` CI job)
 - #145263 (Update books)
 - #145273 (Account for new `assert!` desugaring in `!condition` suggestion)

r? `@ghost`
`@rustbot` modify labels: rollup
Zalathar added a commit to Zalathar/rust that referenced this pull request Aug 12, 2025
…thercote

Apple: Always pass SDK root when linking with `cc`, and pass it via `SDKROOT` env var

Fixes rust-lang#80817, fixes rust-lang#96943, and generally simplifies our linker invocation on Apple platforms.

Part of rust-lang#129432.

### Necessary background on trampoline binaries

The developer binaries such as `/usr/bin/cc` and `/usr/bin/clang` are actually trampolines (similar in spirit to the Rust binaries in `~/.cargo/bin`) which effectively invokes `xcrun` to get the current Xcode developer directory, which allows it to find the actual binary under `/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/*`.

This binary is then launched with the following environment variables set (but none of them are set if `SDKROOT` is set explicitly):
- `SDKROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk`
- `LIBRARY_PATH=/usr/local/lib` (appended)
- `CPATH=/usr/local/include` (appended)
- `MANPATH=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/share/man:/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/usr/share/man:/Applications/Xcode.app/Contents/Developer/usr/share/man:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/share/man:` (prepended)

This allows the user to type e.g. `clang foo.c` in their terminal on macOS, and have it automatically pick up a suitable Clang binary and SDK from either an installed Xcode.app or the Xcode Command Line Tools.
(It acts roughly as-if you typed `xcrun -sdk macosx clang foo.c`).

### Finding a suitable SDK

All compilation on macOS is cross-compilation using SDKs, there are no system headers any more (`/usr/include` is gone), and the system libraries are elsewhere in the file system (`/usr/lib` is basically empty). Instead, the logic for finding the SDK is handled by the `/usr/bin/cc` trampoline (see above).

But relying on the `cc` trampoline doesn't work when:
- Cross-compiling, since a different SDK is needed there.
- Invoking the linker directly, since the linker doesn't understand `SDKROOT`.
- Linking build scripts inside Xcode (see rust-lang#80817), since Xcode prepends `/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin` to `PATH`, which means `cc` refers to the _actual_ Clang binary, and we end up with the wrong SDK root specified.

Basically, we cannot rely on the trampoline at all, so the last commit removes the special-casing that was done when linking with `cc` for macOS (i.e. the most common path), so that **we now always invoke `xcrun` (if `SDKROOT` is not explicitly specified) to find the SDK root**.

Making sure this is non-breaking has a few difficulties though, namely that the user might not have Xcode installed, and that the compiler driver may not understand the `-isysroot` flag. These difficulties are explored below.

#### No Xcode

There are several compiler drivers which work without Xcode by bundling their own SDK, including `zig cc`, Nixpkgs' `clang` and Homebrew's `llvm` package. Additionally, `xcrun` is rarely available when cross-compiling from non-macOS and instead the user might provide a downloaded SDK manually with `-Clink-args=...`.

We do still want to _try_ to invoke `xcrun` if possible, since it is usually the SDK that the user wants (and if not, the environment should override `xcrun`, such as is done by Nixpkgs). But we do not want failure to invoke `xcrun` to stop the linking process. This is changed in the second-to-last commit.

#### `SDKROOT` vs. `-isysroot`

The exact reasoning why we do not always pass the SDK root when linking on macOS eludes me (the git history dead ends in rust-lang#100286), but I suspect it's because we want to support compiler drivers which do not support the `-isysroot` option.

To make sure that such use-cases continue to work, we now pass the SDK root via the `SDKROOT` environment variable. This way, compiler drivers that support setting the SDK root (such as Clang and GCC) can use it, while compiler drivers that don't (presumably because they figure out the SDK in some other way) can just ignore it.

One small danger here would be if there's some compiler driver out there which works with the `-isysroot` flag, but not with the `SDKROOT` environment variable. I am not aware of any?

In a sense, this also shifts the blame; if a compiler driver does not understand `SDKROOT`, it won't work with e.g. `xcrun -sdk macosx15.0 $tool` either, so it can more clearly be argued that this is incorrect behaviour on the part of the tool.

Note also that this overrides the behaviour discussed above (`/usr/bin/cc` sets some extra environment variables), I will argue that is fine since `MANPATH` and `CPATH` is useless when linking, and `/usr/local/lib` is empty on a default system at least since macOS 10.14 (it might be filled by extra libraries installed by the user, but I'll argue that if we want it to be part of the default library search path, we should set it explicitly so that it's also set when linking with `-Clinker=ld`).

### Considered alternatives

- Invoke `/usr/bin/cc` instead of `cc`.
  - This breaks many other use-cases though where overriding `cc` in the PATH is desired.
- Look up `which cc`, and do special logic if in Xcode toolchain.
  - Seems brittle, and besides, it's not the `cc` in the Xcode toolchain that's wrong, it's the `/usr/bin/cc` behaviour that is a bit too magical.
- Invoke `xcrun --sdk macosx cc`.
  - This completely ignores `SDKROOT`, so we'd still have to parse that first to figure out if it's suitable or not, but would probably be workable.
- Maybe somehow configure the linker with extra flags such that it'll be able to link regardless of linking for macOS or e.g. iOS? Though I doubt this is possible.
- Bundle the SDK, similar to `zig-cc`.
  - Comes with it's own host of problems.

### Testing

Tested that this works with the following `-Clinker=...`:
- [x] Default (`cc`)
- [x] `/usr/bin/ld`
- [x] Actual Clang from Xcode (`/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang`)
- [x] `/usr/bin/clang` (invoked via `clang` instead of `cc`)
- [x] Homebrew's `llvm` package (ignores `SDKROOT`, uses their own SDK)
- [x] Homebrew's `gcc` package (`SDKROOT` is preferred over their own SDK)
- [x] ~Macports `clang`~ Couldn't get it to build
- [x] Macports `gcc` (`SDKROOT` is preferred over their own SDK)
- [x] Zig CC installed via. homebrew (ignores both `-isysroot` and `SDKROOT`, uses their own SDK)
- [x] Nixpkgs `clang` (ignores `SDKROOT`, uses their own SDK)
- [x] Nixpkgs `gcc` (ignores `SDKROOT`, uses their own SDK)
- [x] ~[`cosmocc`](https://github.com/jart/cosmopolitan)?~ Doesn't accept common flags (like `-arch`)

CC ``````@BlackHoleFox`````` ``````@thomcc``````
bors added a commit that referenced this pull request Aug 12, 2025
Rollup of 17 pull requests

Successful merges:

 - #131477 (Apple: Always pass SDK root when linking with `cc`, and pass it via `SDKROOT` env var)
 - #139806 (std: sys: pal: uefi: Overhaul Time)
 - #144210 (std: thread: Return error if setting thread stack size fails)
 - #144386 (Extract TraitImplHeader in AST/HIR)
 - #144921 (Don't emit `rustdoc::broken_intra_doc_links` for GitHub-flavored Markdown admonitions like `[!NOTE]`)
 - #145155 (Port `#[allow_internal_unsafe]` to the new attribute system (attempt 2))
 - #145214 (fix: re-enable self-assignment)
 - #145216 (rustdoc: correct negative-to-implicit discriminant display)
 - #145238 (Tweak invalid builtin attribute output)
 - #145249 (Rename entered trace span variables from `_span` to  `_trace`)
 - #145251 (Support using #[unstable_feature_bound] on trait)
 - #145253 (Document compiler and stdlib in stage1 in `pr-check-2` CI job)
 - #145260 (Make explicit guarantees about `Vec`’s allocator)
 - #145263 (Update books)
 - #145273 (Account for new `assert!` desugaring in `!condition` suggestion)
 - #145283 (Make I-miscompile imply I-prioritize)
 - #145291 (bootstrap: Only warn about `rust.debug-assertions` if downloading rustc)

r? `@ghost`
`@rustbot` modify labels: rollup
bors added a commit that referenced this pull request Aug 12, 2025
Rollup of 17 pull requests

Successful merges:

 - #131477 (Apple: Always pass SDK root when linking with `cc`, and pass it via `SDKROOT` env var)
 - #139806 (std: sys: pal: uefi: Overhaul Time)
 - #144386 (Extract TraitImplHeader in AST/HIR)
 - #144921 (Don't emit `rustdoc::broken_intra_doc_links` for GitHub-flavored Markdown admonitions like `[!NOTE]`)
 - #145155 (Port `#[allow_internal_unsafe]` to the new attribute system (attempt 2))
 - #145214 (fix: re-enable self-assignment)
 - #145216 (rustdoc: correct negative-to-implicit discriminant display)
 - #145238 (Tweak invalid builtin attribute output)
 - #145249 (Rename entered trace span variables from `_span` to  `_trace`)
 - #145251 (Support using #[unstable_feature_bound] on trait)
 - #145253 (Document compiler and stdlib in stage1 in `pr-check-2` CI job)
 - #145260 (Make explicit guarantees about `Vec`’s allocator)
 - #145263 (Update books)
 - #145273 (Account for new `assert!` desugaring in `!condition` suggestion)
 - #145283 (Make I-miscompile imply I-prioritize)
 - #145291 (bootstrap: Only warn about `rust.debug-assertions` if downloading rustc)
 - #145292 (Fix a typo in range docs)

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit f22c389 into rust-lang:master Aug 12, 2025
10 checks passed
@rustbot rustbot added this to the 1.91.0 milestone Aug 12, 2025
rust-timer added a commit that referenced this pull request Aug 12, 2025
Rollup merge of #131477 - madsmtm:sdkroot-via-env-var, r=nnethercote

Apple: Always pass SDK root when linking with `cc`, and pass it via `SDKROOT` env var

Fixes #80817, fixes #96943, and generally simplifies our linker invocation on Apple platforms.

Part of #129432.

### Necessary background on trampoline binaries

The developer binaries such as `/usr/bin/cc` and `/usr/bin/clang` are actually trampolines (similar in spirit to the Rust binaries in `~/.cargo/bin`) which effectively invokes `xcrun` to get the current Xcode developer directory, which allows it to find the actual binary under `/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/*`.

This binary is then launched with the following environment variables set (but none of them are set if `SDKROOT` is set explicitly):
- `SDKROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk`
- `LIBRARY_PATH=/usr/local/lib` (appended)
- `CPATH=/usr/local/include` (appended)
- `MANPATH=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/share/man:/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/usr/share/man:/Applications/Xcode.app/Contents/Developer/usr/share/man:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/share/man:` (prepended)

This allows the user to type e.g. `clang foo.c` in their terminal on macOS, and have it automatically pick up a suitable Clang binary and SDK from either an installed Xcode.app or the Xcode Command Line Tools.
(It acts roughly as-if you typed `xcrun -sdk macosx clang foo.c`).

### Finding a suitable SDK

All compilation on macOS is cross-compilation using SDKs, there are no system headers any more (`/usr/include` is gone), and the system libraries are elsewhere in the file system (`/usr/lib` is basically empty). Instead, the logic for finding the SDK is handled by the `/usr/bin/cc` trampoline (see above).

But relying on the `cc` trampoline doesn't work when:
- Cross-compiling, since a different SDK is needed there.
- Invoking the linker directly, since the linker doesn't understand `SDKROOT`.
- Linking build scripts inside Xcode (see #80817), since Xcode prepends `/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin` to `PATH`, which means `cc` refers to the _actual_ Clang binary, and we end up with the wrong SDK root specified.

Basically, we cannot rely on the trampoline at all, so the last commit removes the special-casing that was done when linking with `cc` for macOS (i.e. the most common path), so that **we now always invoke `xcrun` (if `SDKROOT` is not explicitly specified) to find the SDK root**.

Making sure this is non-breaking has a few difficulties though, namely that the user might not have Xcode installed, and that the compiler driver may not understand the `-isysroot` flag. These difficulties are explored below.

#### No Xcode

There are several compiler drivers which work without Xcode by bundling their own SDK, including `zig cc`, Nixpkgs' `clang` and Homebrew's `llvm` package. Additionally, `xcrun` is rarely available when cross-compiling from non-macOS and instead the user might provide a downloaded SDK manually with `-Clink-args=...`.

We do still want to _try_ to invoke `xcrun` if possible, since it is usually the SDK that the user wants (and if not, the environment should override `xcrun`, such as is done by Nixpkgs). But we do not want failure to invoke `xcrun` to stop the linking process. This is changed in the second-to-last commit.

#### `SDKROOT` vs. `-isysroot`

The exact reasoning why we do not always pass the SDK root when linking on macOS eludes me (the git history dead ends in #100286), but I suspect it's because we want to support compiler drivers which do not support the `-isysroot` option.

To make sure that such use-cases continue to work, we now pass the SDK root via the `SDKROOT` environment variable. This way, compiler drivers that support setting the SDK root (such as Clang and GCC) can use it, while compiler drivers that don't (presumably because they figure out the SDK in some other way) can just ignore it.

One small danger here would be if there's some compiler driver out there which works with the `-isysroot` flag, but not with the `SDKROOT` environment variable. I am not aware of any?

In a sense, this also shifts the blame; if a compiler driver does not understand `SDKROOT`, it won't work with e.g. `xcrun -sdk macosx15.0 $tool` either, so it can more clearly be argued that this is incorrect behaviour on the part of the tool.

Note also that this overrides the behaviour discussed above (`/usr/bin/cc` sets some extra environment variables), I will argue that is fine since `MANPATH` and `CPATH` is useless when linking, and `/usr/local/lib` is empty on a default system at least since macOS 10.14 (it might be filled by extra libraries installed by the user, but I'll argue that if we want it to be part of the default library search path, we should set it explicitly so that it's also set when linking with `-Clinker=ld`).

### Considered alternatives

- Invoke `/usr/bin/cc` instead of `cc`.
  - This breaks many other use-cases though where overriding `cc` in the PATH is desired.
- Look up `which cc`, and do special logic if in Xcode toolchain.
  - Seems brittle, and besides, it's not the `cc` in the Xcode toolchain that's wrong, it's the `/usr/bin/cc` behaviour that is a bit too magical.
- Invoke `xcrun --sdk macosx cc`.
  - This completely ignores `SDKROOT`, so we'd still have to parse that first to figure out if it's suitable or not, but would probably be workable.
- Maybe somehow configure the linker with extra flags such that it'll be able to link regardless of linking for macOS or e.g. iOS? Though I doubt this is possible.
- Bundle the SDK, similar to `zig-cc`.
  - Comes with it's own host of problems.

### Testing

Tested that this works with the following `-Clinker=...`:
- [x] Default (`cc`)
- [x] `/usr/bin/ld`
- [x] Actual Clang from Xcode (`/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang`)
- [x] `/usr/bin/clang` (invoked via `clang` instead of `cc`)
- [x] Homebrew's `llvm` package (ignores `SDKROOT`, uses their own SDK)
- [x] Homebrew's `gcc` package (`SDKROOT` is preferred over their own SDK)
- [x] ~Macports `clang`~ Couldn't get it to build
- [x] Macports `gcc` (`SDKROOT` is preferred over their own SDK)
- [x] Zig CC installed via. homebrew (ignores both `-isysroot` and `SDKROOT`, uses their own SDK)
- [x] Nixpkgs `clang` (ignores `SDKROOT`, uses their own SDK)
- [x] Nixpkgs `gcc` (ignores `SDKROOT`, uses their own SDK)
- [x] ~[`cosmocc`](https://github.com/jart/cosmopolitan)?~ Doesn't accept common flags (like `-arch`)

CC ```````@BlackHoleFox``````` ```````@thomcc```````
@madsmtm madsmtm deleted the sdkroot-via-env-var branch August 12, 2025 19:09
github-actions bot pushed a commit to rust-lang/miri that referenced this pull request Aug 13, 2025
Rollup of 17 pull requests

Successful merges:

 - rust-lang/rust#131477 (Apple: Always pass SDK root when linking with `cc`, and pass it via `SDKROOT` env var)
 - rust-lang/rust#139806 (std: sys: pal: uefi: Overhaul Time)
 - rust-lang/rust#144386 (Extract TraitImplHeader in AST/HIR)
 - rust-lang/rust#144921 (Don't emit `rustdoc::broken_intra_doc_links` for GitHub-flavored Markdown admonitions like `[!NOTE]`)
 - rust-lang/rust#145155 (Port `#[allow_internal_unsafe]` to the new attribute system (attempt 2))
 - rust-lang/rust#145214 (fix: re-enable self-assignment)
 - rust-lang/rust#145216 (rustdoc: correct negative-to-implicit discriminant display)
 - rust-lang/rust#145238 (Tweak invalid builtin attribute output)
 - rust-lang/rust#145249 (Rename entered trace span variables from `_span` to  `_trace`)
 - rust-lang/rust#145251 (Support using #[unstable_feature_bound] on trait)
 - rust-lang/rust#145253 (Document compiler and stdlib in stage1 in `pr-check-2` CI job)
 - rust-lang/rust#145260 (Make explicit guarantees about `Vec`’s allocator)
 - rust-lang/rust#145263 (Update books)
 - rust-lang/rust#145273 (Account for new `assert!` desugaring in `!condition` suggestion)
 - rust-lang/rust#145283 (Make I-miscompile imply I-prioritize)
 - rust-lang/rust#145291 (bootstrap: Only warn about `rust.debug-assertions` if downloading rustc)
 - rust-lang/rust#145292 (Fix a typo in range docs)

r? `@ghost`
`@rustbot` modify labels: rollup
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-linkage Area: linking into static, shared libraries and binaries A-run-make Area: port run-make Makefiles to rmake.rs O-apple Operating system: Apple (macOS, iOS, tvOS, visionOS, watchOS) relnotes Marks issues that should be documented in the release notes of the next release. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
8 participants