From 6973046f3b86240f3d9ff9f1515a8d89463912db Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Sun, 14 Jan 2024 20:47:53 +0100 Subject: [PATCH] Add support for `layering_check` The general support for this feature is inherited from the Bazel-provided Unix C++ toolchain, except that a module map for the toolchain and sysroot headers has to be supplied to the `cc_toolchain`. --- README.md | 8 +++ tests/.bazelrc | 1 + toolchain/cc_toolchain_config.bzl | 34 +++++++---- toolchain/internal/configure.bzl | 2 + toolchain/internal/system_module_map.bzl | 75 ++++++++++++++++++++++++ 5 files changed, 108 insertions(+), 12 deletions(-) create mode 100644 toolchain/internal/system_module_map.bzl diff --git a/README.md b/README.md index 42bdd0fd8..a69743b4f 100644 --- a/README.md +++ b/README.md @@ -239,6 +239,14 @@ then they can be referenced as: - `@llvm_toolchain//:clang-format` - `@llvm_toolchain//:llvm-cov` +### Strict header deps (Linux only) + +The toolchain supports Bazel's `layering_check` feature, which relies on +[Clang modules](https://clang.llvm.org/docs/Modules.html) to implement strict +deps (also known as "depend on what you use") for `cc_*` rules. This features +can be enabled by enabling the `layering_check` feature on a per-target, +per-package or global basis. + ## Prior Art Other examples of toolchain configuration: diff --git a/tests/.bazelrc b/tests/.bazelrc index fbd75a7ea..5c87221d1 100644 --- a/tests/.bazelrc +++ b/tests/.bazelrc @@ -1 +1,2 @@ build --incompatible_enable_cc_toolchain_resolution +build --features=layering_check diff --git a/toolchain/cc_toolchain_config.bzl b/toolchain/cc_toolchain_config.bzl index b2aa92aef..cabecb571 100644 --- a/toolchain/cc_toolchain_config.bzl +++ b/toolchain/cc_toolchain_config.bzl @@ -22,6 +22,10 @@ load( _host_tools = "host_tools", _os_arch_pair = "os_arch_pair", ) +load( + "//toolchain/internal:system_module_map.bzl", + "system_module_map", +) # Bazel 4.* doesn't support nested starlark functions, so we cannot simplify # _fmt_flags() by defining it as a nested function. @@ -41,6 +45,7 @@ def cc_toolchain_config( wrapper_bin_prefix, compiler_configuration, llvm_version, + all_files, host_tools_info = {}): host_os_arch_key = _os_arch_pair(host_os, host_arch) target_os_arch_key = _os_arch_pair(target_os, target_arch) @@ -262,18 +267,16 @@ def cc_toolchain_config( ## doesn't seem to have a feature for this. # C++ built-in include directories: - cxx_builtin_include_directories = [] - if toolchain_path_prefix.startswith("/"): - cxx_builtin_include_directories.extend([ - toolchain_path_prefix + "include/c++/v1", - toolchain_path_prefix + "include/{}/c++/v1".format(target_system_name), - toolchain_path_prefix + "lib/clang/{}/include".format(llvm_version), - toolchain_path_prefix + "lib/clang/{}/share".format(llvm_version), - toolchain_path_prefix + "lib64/clang/{}/include".format(llvm_version), - toolchain_path_prefix + "lib/clang/{}/include".format(major_llvm_version), - toolchain_path_prefix + "lib/clang/{}/share".format(major_llvm_version), - toolchain_path_prefix + "lib64/clang/{}/include".format(major_llvm_version), - ]) + cxx_builtin_include_directories = [ + toolchain_path_prefix + "include/c++/v1", + toolchain_path_prefix + "include/{}/c++/v1".format(target_system_name), + toolchain_path_prefix + "lib/clang/{}/include".format(llvm_version), + toolchain_path_prefix + "lib/clang/{}/share".format(llvm_version), + toolchain_path_prefix + "lib64/clang/{}/include".format(llvm_version), + toolchain_path_prefix + "lib/clang/{}/include".format(major_llvm_version), + toolchain_path_prefix + "lib/clang/{}/share".format(major_llvm_version), + toolchain_path_prefix + "lib64/clang/{}/include".format(major_llvm_version), + ] sysroot_path = compiler_configuration["sysroot_path"] sysroot_prefix = "" @@ -371,3 +374,10 @@ def cc_toolchain_config( supports_start_end_lib = supports_start_end_lib, builtin_sysroot = sysroot_path, ) + + system_module_map( + name = name + "-module", + toolchain = all_files, + cxx_builtin_include_directories = cxx_builtin_include_directories, + sysroot_path = sysroot_path, + ) diff --git a/toolchain/internal/configure.bzl b/toolchain/internal/configure.bzl index 12f141f04..33ff6648a 100644 --- a/toolchain/internal/configure.bzl +++ b/toolchain/internal/configure.bzl @@ -329,6 +329,7 @@ cc_toolchain_config( }}, llvm_version = "{llvm_version}", host_tools_info = {host_tools_info}, + all_files = "all-files-{suffix}", ) toolchain( @@ -436,6 +437,7 @@ cc_toolchain( objcopy_files = "objcopy-files-{suffix}", strip_files = "strip-files-{suffix}", toolchain_config = "local-{suffix}", + module_map = "local-{suffix}-module", ) """ diff --git a/toolchain/internal/system_module_map.bzl b/toolchain/internal/system_module_map.bzl new file mode 100644 index 000000000..60d08bfb9 --- /dev/null +++ b/toolchain/internal/system_module_map.bzl @@ -0,0 +1,75 @@ +# Copyright 2024 The Bazel Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +def _system_module_map(ctx): + module_map = ctx.actions.declare_file(ctx.attr.name + ".modulemap") + + # The builtin include directories are relative to the execroot, but the + # paths in the module map must be relative to the directory that contains + # the module map. + to_execroot = (module_map.dirname.count("/") + 1) * "../" + + dirs = [] + non_hermetic = False + for dir in ctx.attr.cxx_builtin_include_directories: + if ctx.attr.sysroot_path and dir.startswith("%sysroot%"): + dir = ctx.attr.sysroot_path + dir[len("%sysroot%"):] + if dir.startswith("/"): + non_hermetic = True + else: + dir = to_execroot + dir + dir = dir.replace("//", "/") + dirs.append(dir) + + # If the action references a file outside of the execroot, it isn't safe to + # cache or run remotely. + execution_requirements = {} + if non_hermetic: + execution_requirements = { + "no-cache": "", + "no-remote": "", + } + + ctx.actions.run_shell( + outputs = [module_map], + inputs = ctx.attr.toolchain[DefaultInfo].files, + arguments = [ + ctx.executable._generate_system_module_map.path, + module_map.path, + ] + dirs, + tools = [ctx.executable._generate_system_module_map], + command = """ +"$1" "${@:3}" > "$2" +""", + execution_requirements = execution_requirements, + mnemonic = "LlvmSystemModuleMap", + progress_message = "Generating system module map", + ) + return DefaultInfo(files = depset([module_map])) + +system_module_map = rule( + doc = """Generates a Clang module map for the toolchain and sysroot headers.""", + implementation = _system_module_map, + attrs = { + "toolchain": attr.label(mandatory = True), + "cxx_builtin_include_directories": attr.string_list(mandatory = True), + "sysroot_path": attr.string(), + "_generate_system_module_map": attr.label( + default = "@bazel_tools//tools/cpp:generate_system_module_map.sh", + allow_single_file = True, + cfg = "exec", + executable = True, + ), + }, +)