Skip to content

Let the rules-based cc_toolchain be driven by a non-Xcode clang - #635

Open
AustinSchuh wants to merge 5 commits into
bazelbuild:mainfrom
AustinSchuh:toolchains-llvm-prototype
Open

Let the rules-based cc_toolchain be driven by a non-Xcode clang#635
AustinSchuh wants to merge 5 commits into
bazelbuild:mainfrom
AustinSchuh:toolchains-llvm-prototype

Conversation

@AustinSchuh

Copy link
Copy Markdown
Contributor

The macro is already parameterized by tool_map and sysroot_feature, but targeting macOS with anything other than wrapped_clang does not work: everything behind select(//configs:apple) assumes the Xcode configuration is the source of truth.

Most of that needs no API. The _BAZEL* placeholders and the STRIP_DEBUG_SYMBOLS / LINKED_BINARY= / DSYM_HINT_DSYM_PATH= sentinels are the toolchain's argument protocol, which a consumer's tool has to speak the way wrapped_clang does (now documented on tool_map), and layering_check works as-is since a plain clang ignores APPLE_SUPPORT_MODULEMAP.

What remains becomes a parameter, each defaulting to current behavior:

  • target_from_xcode = False uses the "target" argument instead of the Xcode-derived triple, for a toolchain that pins its own.
  • flags_from_env = False drops the BAZEL_*OPTS features, whose -std=c++17 default would override a toolchain's own standard and which cannot differ between two toolchains in one workspace.
  • coverage_instrumentation = False leaves the coverage-map-format features out for a consumer that instruments from its own args, since Bazel picks the gcc format by default.
  • apple_linker_flags = False drops -no_warn_duplicate_libraries and -reproducible, which ld64.lld only accepts from LLVM 19 on.
  • extra_{enabled,known}_features and extra_include_directories as attributes: the label_flags are global, so a repo rule stamping out more than one toolchain cannot vary them.
  • dynamic_runtime_lib / static_runtime_lib pass through to cc_toolchain, which is how a sanitizer runtime dylib reaches a test's runfiles.

Also make //toolchain:dynamic_toolchain_info public: the macro references it inside the Apple branch of a select(), so an Apple-targeting consumer fails analysis without it.

//test/... is unchanged.

The macro is already parameterized by tool_map and sysroot_feature, but
targeting macOS with anything other than wrapped_clang does not work:
everything behind select(//configs:apple) assumes the Xcode
configuration is the source of truth.

Most of that needs no API. The __BAZEL_* placeholders and the
STRIP_DEBUG_SYMBOLS / LINKED_BINARY= / DSYM_HINT_DSYM_PATH= sentinels
are the toolchain's argument protocol, which a consumer's tool has to
speak the way wrapped_clang does (now documented on tool_map), and
layering_check works as-is since a plain clang ignores
APPLE_SUPPORT_MODULEMAP.

What remains becomes a parameter, each defaulting to current behavior:

  - target_from_xcode = False uses the "target" argument instead of
    the Xcode-derived triple, for a toolchain that pins its own.
  - flags_from_env = False drops the BAZEL_*OPTS features, whose
    -std=c++17 default would override a toolchain's own standard and
    which cannot differ between two toolchains in one workspace.
  - coverage_instrumentation = False leaves the coverage-map-format
    features out for a consumer that instruments from its own args,
    since Bazel picks the gcc format by default.
  - apple_linker_flags = False drops -no_warn_duplicate_libraries and
    -reproducible, which ld64.lld only accepts from LLVM 19 on.
  - extra_{enabled,known}_features and extra_include_directories as
    attributes: the label_flags are global, so a repo rule stamping
    out more than one toolchain cannot vary them.
  - dynamic_runtime_lib / static_runtime_lib pass through to
    cc_toolchain, which is how a sanitizer runtime dylib reaches a
    test's runfiles.

Also make //toolchain:dynamic_toolchain_info public: the macro
references it inside the Apple branch of a select(), so an
Apple-targeting consumer fails analysis without it.

//test/... is unchanged.

Signed-off-by: Austin Schuh <austin.linux@gmail.com>
Comment thread toolchain/cc_toolchain.bzl Outdated
Comment thread toolchain/cc_toolchain.bzl Outdated
Comment thread toolchain/BUILD Outdated
Comment thread toolchain/cc_toolchain.bzl Outdated
Comment thread toolchain/cc_toolchain.bzl Outdated
Comment thread toolchain/cc_toolchain.bzl Outdated
Comment thread toolchain/cc_toolchain.bzl Outdated
Comment thread toolchain/cc_toolchain.bzl Outdated
Comment thread toolchain/cc_toolchain.bzl Outdated
Two parameters turned out not to earn their keep:

  - target_from_xcode is gone, along with plain_required_flags. The
    -target in default_required_flags is derived from the target
    platform and honors --macos_minimum_os, so it is right for a
    hermetic toolchain too -- switching toolchains_llvm onto it
    produced bit-identical binaries.

  - apple_linker_flags is replaced by llvm_version: the consumer says
    which LLVM its tools come from (None means Xcode's), and the macro
    itself drops -no_warn_duplicate_libraries and -reproducible for
    the versions whose ld64.lld predates them (before LLVM 19).

Also trims the parameter docstrings and drops a redundant comment, per
review.

Signed-off-by: Austin Schuh <austin.linux@gmail.com>
@AustinSchuh
AustinSchuh force-pushed the toolchains-llvm-prototype branch from 070b43f to 7ee3bb9 Compare August 28, 2026 23:21
Signed-off-by: Austin Schuh <austin.linux@gmail.com>
@AustinSchuh
AustinSchuh force-pushed the toolchains-llvm-prototype branch from d0f92d4 to 1a00dc0 Compare August 29, 2026 00:09
From Clang 14 on, -std=c++20 switches the driver to C++20 modules
semantics: a header covered non-textually by a module map can no
longer simply be included -- "module 'X' is needed but has not been
provided, and implicit use of module files is disabled". That breaks
the module maps layering_check is built on; Bazel does not support
C++20 modules yet. Pass -Xclang -fno-cxx-modules (and quiet the
module-import-in-extern-c warning the SDK headers then trip) on the
C++ compile actions whenever use_module_maps is enabled.

The maps this repo's own toolchain sees are immune: wrapped_clang's
generated map and the per-target maps Bazel writes are all-textual,
and Xcode's clang does not switch semantics the way upstream does
(verified against Apple clang 17: the repro below builds either way,
while upstream 19.1.7 fails without the flag). What is not immune is
a consumer driving upstream LLVM through this macro whose system
module map covers its headers with umbrella directories, the way
toolchains_llvm's does -- which is where this surfaced, on the first
-std=c++20 compile with layering_check enabled.

test/layering_check gains the repro (a non-textual module map on a
C++20 compile) and command-line tests asserting the flag is emitted
exactly when use_module_maps is enabled -- directly, via
layering_check, and never without. The command-line tests fail if the
flag is dropped; the repro additionally fails wherever the compiler
follows upstream's C++20 semantics.

Signed-off-by: Austin Schuh <austin.linux@gmail.com>
Drops coverage_instrumentation. Bazel's contract is the gcc format by
default and the llvm one under --experimental_use_llvm_covmap; clang
emits both, and a consumer's tool_map carries the collection side
(llvm-cov and llvm-profdata) for the llvm format. The coverage feature
lists revert to exactly what they were.

Signed-off-by: Austin Schuh <austin.linux@gmail.com>
@AustinSchuh

Copy link
Copy Markdown
Contributor Author

OK, everything should now be good to go. I pulled in a couple of the flags from toolchains_llvm up here where they seemed better. Happy to push those back into toolchains_llvm if you would prefer.

Thanks Keith!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants