| """ | 
 | 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/cpp/cc_toolchain_config_lib.bzl | 
 | load( | 
 |     "@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl", | 
 |     "feature", | 
 |     "feature_set", | 
 |     "flag_group", | 
 |     "flag_set", | 
 | ) | 
 |  | 
 | # 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") | 
 |  | 
 | 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, | 
 |     ) |