""" Based on https://skia.googlesource.com/skia/+/0d4fcf388a6f9318e2a54fa85fddf3396d521767/toolchain/clang_layering_check.bzl. This file contains logic related to enforcing public API relationships, also known as layering checks. See also https://maskray.me/blog/2022-09-25-layering-check-with-clang and go/layering_check """ # https://github.com/bazelbuild/bazel/blob/master/tools/build_defs/cc/action_names.bzl load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES") # https://github.com/bazelbuild/bazel/blob/master/tools/cpp/cc_toolchain_config_lib.bzl load( "@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl", "feature", "feature_set", "flag_group", "flag_set", ) def make_layering_check_features(): """Returns a list of features which enforce "layering checks". Layering checks catch two types of problems: 1) A cc_library using private headers from another cc_library. 2) A cc_library using public headers from a transitive dependency instead of directly depending on that library. This is implemented using Clang module maps, which are generated for each cc_library as it is being built. This implementation is very similar to the one in the default Bazel C++ toolchain (which is not inherited by custom toolchains). https://github.com/bazelbuild/bazel/commit/8b9f74649512ee17ac52815468bf3d7e5e71c9fa Returns: A list of Bazel "features", the primary one being one called "layering_check". """ return [ feature( name = "use_module_maps", enabled = False, requires = [feature_set(features = ["module_maps"])], flag_sets = [ flag_set( actions = [ ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile, ], flag_groups = [ flag_group( flags = [ "-fmodule-name=%{module_name}", "-fmodule-map-file=%{module_map_file}", ], ), ], ), ], ), # This feature name is baked into Bazel # https://github.com/bazelbuild/bazel/blob/8f5b626acea0086be8a314d5efbf6bc6d3473cd2/src/main/java/com/google/devtools/build/lib/rules/cpp/CompileBuildVariables.java#L471 feature(name = "module_maps", enabled = True), feature( name = "layering_check", # This is currently disabled by default (although we aim to enable it by default) # because our current skia_public build does not pass the fmodules-strict-decluse # options with its current deps implementation (which was designed to pass these along). enabled = False, implies = ["use_module_maps"], flag_sets = [ flag_set( actions = [ ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile, ], flag_groups = [ flag_group(flags = [ # Identify issue #1 (see docstring) "-Wprivate-header", # Identify issue #2 "-fmodules-strict-decluse", ]), flag_group( iterate_over = "dependent_module_map_files", flags = [ "-fmodule-map-file=%{dependent_module_map_files}", ], ), ], ), ], ), ] def generate_system_module_map(ctx, module_file, folders): """Generates a module map [1] for all the "system" headers in the toolchain. The generated map looks something like: module "crosstool" [system] { textual header "lib/clang/15.0.1/include/__clang_cuda_builtin_vars.h" textual header "lib/clang/15.0.1/include/__clang_cuda_cmath.h" ... textual header "include/c++/v1/climits" textual header "include/c++/v1/clocale" textual header "include/c++/v1/cmath" textual header "symlinks/xcode/MacSDK/usr/share/man/mann/zip.n" } Notice how all the file paths are relative to *this* directory, where the toolchain_system_headers.modulemap. Annoyingly, Clang will silently ignore a file that is declared if it does not actually exist on disk. [1] https://clang.llvm.org/docs/Modules.html#module-map-language Args: ctx: A repository_ctx (https://bazel.build/rules/lib/repository_ctx) module_file: The name of the modulemap file to create. folders: List of strings corresponding to paths in the toolchain with system headers. """ # https://github.com/bazelbuild/bazel/blob/8f5b626acea0086be8a314d5efbf6bc6d3473cd2/tools/cpp/generate_system_module_map.sh script_path = ctx.path(Label("@bazel_tools//tools/cpp:generate_system_module_map.sh")) # https://bazel.build/rules/lib/repository_ctx#execute res = ctx.execute([script_path] + folders) if res.return_code != 0: fail("Could not generate module map") # https://bazel.build/rules/lib/repository_ctx#file ctx.file( module_file, content = res.stdout, executable = False, )