Add layering check support to cc_configure on Linux with Clang
This PR teaches the C++ toolchain autoconfiguration (`cc_configure`) about `layering_check`.
Layering check is a feature of C++ rules in concert with clang that will fail the compilation action when a transitive header was depended on directly. The fix is to add a direct dependency on the target providing the header. The result is more explicit dependency graph where one can be sure that all targets that depend on a header depend on it directly. Layering check also validates that private headers are not depended on directly.
To make your codebase satisfy the layering check incrementally, I suggest to enable layering check by default (e.g. by putting `build --features=layering_check` into your `.bazelrc` file), and then opting specific targets out temporarily by disabling the feature in the BUILD file by putting `features = ["-layering_check"]` attribute into the C++ rule.
In order to do that we need to generate a module map for the toolchain. This is done by taking any file present transitively in one of the default include directories reported by the compiler. For system installed compilers this map can be quite big as they often look into directories such as `/usr/include` etc. As anecdata, the header map on my home machine is ~ 27K lines long.
Computing this map takes too much time when implemented in idiomatic Starlark (~16s on my machine), therefore I've created a shell script that does the work faster (~2s on my machine). The map is only created when the compiler used is `clang`. If the compiler is `clang` is detected by running `$CC -v` and searching for string "clang" :)
Closes #11440.
PiperOrigin-RevId: 313173938
diff --git a/tools/cpp/unix_cc_toolchain_config.bzl b/tools/cpp/unix_cc_toolchain_config.bzl
index afcf651..6fca87a 100644
--- a/tools/cpp/unix_cc_toolchain_config.bzl
+++ b/tools/cpp/unix_cc_toolchain_config.bzl
@@ -26,6 +26,66 @@
)
load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES")
+def layering_check_features(compiler):
+ if compiler != "clang":
+ return []
+ return [
+ feature(
+ name = "use_module_maps",
+ requires = [feature_set(features = ["module_maps"])],
+ flag_sets = [
+ flag_set(
+ actions = [
+ ACTION_NAMES.c_compile,
+ ACTION_NAMES.cpp_compile,
+ ACTION_NAMES.cpp_header_parsing,
+ ACTION_NAMES.cpp_module_compile,
+ ],
+ flag_groups = [
+ flag_group(
+ flags = [
+ "-fmodule-name=%{module_name}",
+ "-fmodule-map-file=%{module_map_file}",
+ ],
+ ),
+ ],
+ ),
+ ],
+ ),
+
+ # Tell blaze we support module maps in general, so they will be generated
+ # for all c/c++ rules.
+ # Note: not all C++ rules support module maps; thus, do not imply this
+ # feature from other features - instead, require it.
+ feature(name = "module_maps", enabled = True),
+ feature(
+ name = "layering_check",
+ implies = ["use_module_maps"],
+ flag_sets = [
+ flag_set(
+ actions = [
+ ACTION_NAMES.c_compile,
+ ACTION_NAMES.cpp_compile,
+ ACTION_NAMES.cpp_header_parsing,
+ ACTION_NAMES.cpp_module_compile,
+ ],
+ flag_groups = [
+ flag_group(flags = [
+ "-fmodules-strict-decluse",
+ "-Wprivate-header",
+ ]),
+ flag_group(
+ iterate_over = "dependent_module_map_files",
+ flags = [
+ "-fmodule-map-file=%{dependent_module_map_files}",
+ ],
+ ),
+ ],
+ ),
+ ],
+ ),
+ ]
+
all_compile_actions = [
ACTION_NAMES.c_compile,
ACTION_NAMES.cpp_compile,
@@ -1131,7 +1191,7 @@
user_compile_flags_feature,
sysroot_feature,
unfiltered_compile_flags_feature,
- ]
+ ] + layering_check_features(ctx.attr.compiler)
else:
features = [
supports_pic_feature,
@@ -1150,7 +1210,7 @@
user_compile_flags_feature,
sysroot_feature,
unfiltered_compile_flags_feature,
- ]
+ ] + layering_check_features(ctx.attr.compiler)
return cc_common.create_cc_toolchain_config_info(
ctx = ctx,