Skip to content

Commit 81f85c0

Browse files
authored
Make system module map generation faster and fully hermetic (#280)
* `system_module_maps` no longer performs any IO. * The generated module map no longer references any non-hermetic paths and can thus be cached remotely, even when toolchain or sysroot are provided as absolute paths.
1 parent 6bca3e2 commit 81f85c0

File tree

6 files changed

+75
-79
lines changed

6 files changed

+75
-79
lines changed

README.md

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -251,11 +251,6 @@ deps (also known as "depend on what you use") for `cc_*` rules. This feature
251251
can be enabled by enabling the `layering_check` feature on a per-target,
252252
per-package or global basis.
253253

254-
If one of toolchain or sysroot are specified via an absolute path rather than
255-
managed by Bazel, the `layering_check` feature may require running
256-
`bazel clean --expunge` after making changes to the set of header files
257-
installed on the host.
258-
259254
## Prior Art
260255

261256
Other examples of toolchain configuration:

toolchain/internal/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
exports_files(["generate_system_module_map.sh"])
15+
exports_files(["template.modulemap"])

toolchain/internal/configure.bzl

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,14 @@ cc_toolchain(
545545
unfiltered_compile_flags = _list_to_string(_dict_value(toolchain_info.unfiltered_compile_flags_dict, target_pair)),
546546
extra_files_str = extra_files_str,
547547
host_tools_info = host_tools_info,
548-
cxx_builtin_include_directories = _list_to_string(cxx_builtin_include_directories),
548+
cxx_builtin_include_directories = _list_to_string([
549+
# Filter out non-existing directories with absolute paths as they
550+
# result in a -Wincomplete-umbrella warning when mentioned in the
551+
# system module map.
552+
dir
553+
for dir in cxx_builtin_include_directories
554+
if _is_hermetic_or_exists(rctx, dir, sysroot_prefix)
555+
]),
549556
extra_compiler_files = ("\"%s\"," % str(toolchain_info.extra_compiler_files)) if toolchain_info.extra_compiler_files else "",
550557
)
551558

@@ -586,3 +593,9 @@ native_binary(
586593
llvm_dist_label_prefix = llvm_dist_label_prefix,
587594
host_dl_ext = host_dl_ext,
588595
)
596+
597+
def _is_hermetic_or_exists(rctx, path, sysroot_prefix):
598+
path = path.replace("%sysroot%", sysroot_prefix)
599+
if not path.startswith("/"):
600+
return True
601+
return rctx.path(path).exists

toolchain/internal/generate_system_module_map.sh

Lines changed: 0 additions & 35 deletions
This file was deleted.

toolchain/internal/system_module_map.bzl

Lines changed: 56 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -14,62 +14,81 @@
1414

1515
load("@bazel_skylib//lib:paths.bzl", "paths")
1616

17+
def _textual_header(file, *, include_prefixes, execroot_prefix):
18+
path = file.path
19+
for include_prefix in include_prefixes:
20+
if path.startswith(include_prefix):
21+
return " textual header \"{}{}\"".format(execroot_prefix, path)
22+
23+
# The file is not under any of the include prefixes,
24+
return None
25+
26+
def _umbrella_submodule(path):
27+
return """
28+
module "{path}" {{
29+
umbrella "{path}"
30+
}}""".format(path = path)
31+
1732
def _system_module_map(ctx):
1833
module_map = ctx.actions.declare_file(ctx.attr.name + ".modulemap")
1934

20-
dirs = []
21-
non_hermetic = False
22-
for dir in ctx.attr.cxx_builtin_include_directories:
23-
if ctx.attr.sysroot_path and dir.startswith("%sysroot%"):
24-
dir = ctx.attr.sysroot_path + dir[len("%sysroot%"):]
25-
if dir.startswith("/"):
26-
non_hermetic = True
27-
dirs.append(paths.normalize(dir))
28-
29-
# If the action references a file outside of the execroot, it isn't safe to
30-
# cache or run remotely.
31-
execution_requirements = {}
32-
if non_hermetic:
33-
execution_requirements = {
34-
"no-cache": "",
35-
"no-remote": "",
36-
}
35+
absolute_path_dirs = []
36+
relative_include_prefixes = []
37+
for include_dir in ctx.attr.cxx_builtin_include_directories:
38+
if ctx.attr.sysroot_path and include_dir.startswith("%sysroot%"):
39+
include_dir = ctx.attr.sysroot_path + include_dir[len("%sysroot%"):]
40+
include_dir = paths.normalize(include_dir)
41+
if include_dir.startswith("/"):
42+
absolute_path_dirs.append(include_dir)
43+
else:
44+
relative_include_prefixes.append(include_dir + "/")
3745

3846
# The builtin include directories are relative to the execroot, but the
3947
# paths in the module map must be relative to the directory that contains
4048
# the module map.
4149
execroot_prefix = (module_map.dirname.count("/") + 1) * "../"
50+
textual_header_closure = lambda file: _textual_header(
51+
file,
52+
include_prefixes = relative_include_prefixes,
53+
execroot_prefix = execroot_prefix,
54+
)
4255

43-
ctx.actions.run_shell(
44-
outputs = [module_map],
45-
inputs = ctx.attr.cxx_builtin_include_files[DefaultInfo].files,
46-
command = """
47-
{tool} "$@" > {module_map}
48-
""".format(
49-
tool = ctx.executable._generate_system_module_map.path,
50-
module_map = module_map.path,
51-
),
52-
arguments = dirs,
53-
tools = [ctx.executable._generate_system_module_map],
54-
env = {"EXECROOT_PREFIX": execroot_prefix},
55-
execution_requirements = execution_requirements,
56-
mnemonic = "LLVMSystemModuleMap",
57-
progress_message = "Generating system module map",
56+
template_dict = ctx.actions.template_dict()
57+
template_dict.add_joined(
58+
"%textual_headers%",
59+
ctx.attr.cxx_builtin_include_files[DefaultInfo].files,
60+
join_with = "\n",
61+
map_each = textual_header_closure,
62+
allow_closure = True,
63+
)
64+
template_dict.add_joined(
65+
"%umbrella_submodules%",
66+
depset(absolute_path_dirs),
67+
join_with = "\n",
68+
map_each = _umbrella_submodule,
69+
)
70+
71+
ctx.actions.expand_template(
72+
template = ctx.file._module_map_template,
73+
output = module_map,
74+
computed_substitutions = template_dict,
5875
)
5976
return DefaultInfo(files = depset([module_map]))
6077

6178
system_module_map = rule(
62-
doc = """Generates a Clang module map for the toolchain and sysroot headers.""",
79+
doc = """Generates a Clang module map for the toolchain and sysroot headers.
80+
81+
Files under the configured built-in include directories that are managed by
82+
Bazel are included as textual headers. All directories referenced by
83+
absolute paths are included as umbrella submodules.""",
6384
implementation = _system_module_map,
6485
attrs = {
6586
"cxx_builtin_include_files": attr.label(mandatory = True),
6687
"cxx_builtin_include_directories": attr.string_list(mandatory = True),
6788
"sysroot_path": attr.string(),
68-
"_generate_system_module_map": attr.label(
69-
default = ":generate_system_module_map.sh",
89+
"_module_map_template": attr.label(
90+
default = "template.modulemap",
7091
allow_single_file = True,
71-
cfg = "exec",
72-
executable = True,
7392
),
7493
},
7594
)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module "crosstool" [system] {
2+
%textual_headers%
3+
%umbrella_submodules%
4+
}

0 commit comments

Comments
 (0)