1""" 2This file contains logic related to enforcing public API relationships, also known as 3layering checks. 4 5See also https://maskray.me/blog/2022-09-25-layering-check-with-clang and go/layering_check 6 7""" 8 9# https://github.com/bazelbuild/bazel/blob/master/tools/build_defs/cc/action_names.bzl 10load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES") 11 12# https://github.com/bazelbuild/bazel/blob/master/tools/cpp/cc_toolchain_config_lib.bzl 13load( 14 "@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl", 15 "feature", 16 "feature_set", 17 "flag_group", 18 "flag_set", 19) 20 21def make_layering_check_features(): 22 """Returns a list of features which enforce "layering checks". 23 24 Layering checks catch two types of problems: 25 1) A cc_library using private headers from another cc_library. 26 2) A cc_library using public headers from a transitive dependency instead of 27 directly depending on that library. 28 29 This is implemented using Clang module maps, which are generated for each cc_library 30 as it is being built. 31 32 This implementation is very similar to the one in the default Bazel C++ toolchain 33 (which is not inherited by custom toolchains). 34 https://github.com/bazelbuild/bazel/commit/8b9f74649512ee17ac52815468bf3d7e5e71c9fa 35 36 Returns: 37 A list of Bazel "features", the primary one being one called "layering_check". 38 """ 39 return [ 40 feature( 41 name = "use_module_maps", 42 enabled = False, 43 requires = [feature_set(features = ["module_maps"])], 44 flag_sets = [ 45 flag_set( 46 actions = [ 47 ACTION_NAMES.c_compile, 48 ACTION_NAMES.cpp_compile, 49 ], 50 flag_groups = [ 51 flag_group( 52 flags = [ 53 "-fmodule-name=%{module_name}", 54 "-fmodule-map-file=%{module_map_file}", 55 ], 56 ), 57 ], 58 ), 59 ], 60 ), 61 # This feature name is baked into Bazel 62 # https://github.com/bazelbuild/bazel/blob/8f5b626acea0086be8a314d5efbf6bc6d3473cd2/src/main/java/com/google/devtools/build/lib/rules/cpp/CompileBuildVariables.java#L471 63 feature(name = "module_maps", enabled = True), 64 feature( 65 name = "layering_check", 66 # This is currently disabled by default (although we aim to enable it by default) 67 # because a previous build didn't support passing a flag along. 68 # TODO(kjlubick): enable this by default. 69 enabled = False, 70 implies = ["use_module_maps"], 71 flag_sets = [ 72 flag_set( 73 actions = [ 74 ACTION_NAMES.c_compile, 75 ACTION_NAMES.cpp_compile, 76 ], 77 flag_groups = [ 78 flag_group(flags = [ 79 # Identify issue #1 (see docstring) 80 "-Wprivate-header", 81 # Identify issue #2 82 "-fmodules-strict-decluse", 83 ]), 84 flag_group( 85 iterate_over = "dependent_module_map_files", 86 flags = [ 87 "-fmodule-map-file=%{dependent_module_map_files}", 88 ], 89 ), 90 ], 91 ), 92 ], 93 ), 94 ] 95 96def generate_system_module_map(ctx, module_file, folders): 97 """Generates a module map [1] for all the "system" headers in the toolchain. 98 99 The generated map looks something like: 100 module "crosstool" [system] { 101 textual header "lib/clang/15.0.1/include/__clang_cuda_builtin_vars.h" 102 textual header "lib/clang/15.0.1/include/__clang_cuda_cmath.h" 103 ... 104 textual header "include/c++/v1/climits" 105 textual header "include/c++/v1/clocale" 106 textual header "include/c++/v1/cmath" 107 textual header "symlinks/xcode/MacSDK/usr/share/man/mann/zip.n" 108 } 109 Notice how all the file paths are relative to *this* directory, where 110 the toolchain_system_headers.modulemap. Annoyingly, Clang will silently 111 ignore a file that is declared if it does not actually exist on disk. 112 113 [1] https://clang.llvm.org/docs/Modules.html#module-map-language 114 115 Args: 116 ctx: A repository_ctx (https://bazel.build/rules/lib/repository_ctx) 117 module_file: The name of the modulemap file to create. 118 folders: List of strings corresponding to paths in the toolchain with system headers. 119 120 """ 121 122 # https://github.com/bazelbuild/bazel/blob/8f5b626acea0086be8a314d5efbf6bc6d3473cd2/tools/cpp/generate_system_module_map.sh 123 script_path = ctx.path(Label("@bazel_tools//tools/cpp:generate_system_module_map.sh")) 124 125 # https://bazel.build/rules/lib/repository_ctx#execute 126 res = ctx.execute([script_path] + folders) 127 if res.return_code != 0: 128 fail("Could not generate module map") 129 130 # https://bazel.build/rules/lib/repository_ctx#file 131 ctx.file( 132 module_file, 133 content = res.stdout, 134 executable = False, 135 ) 136