Skip to content

Commit c51d29a

Browse files
author
Chad Norvell
committed
Support target collections
1 parent c5490ce commit c51d29a

File tree

2 files changed

+54
-32
lines changed

2 files changed

+54
-32
lines changed

refresh.template.py

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
# Similarly, when upgrading, please search for that MIN_PY= tag.
1919

2020

21+
import collections
2122
import concurrent.futures
2223
import enum
2324
import functools # MIN_PY=3.9: Replace `functools.lru_cache(maxsize=None)` with `functools.cache`.
@@ -1405,30 +1406,26 @@ def main():
14051406
# End: template filled by Bazel
14061407
]
14071408

1408-
target_file_names = {
1409+
target_collections = {
14091410
# Begin: template filled by Bazel
1410-
{target_file_names}
1411+
{target_collections}
14111412
# End: template filled by Bazel
14121413
}
14131414

1414-
# Associates compilation database file names with lists of compile commands.
1415-
# __all__ is a special case: It's the "catch all" for any compile commands that aren't
1416-
# assigned to a specific file. If no targets are assigned to specific files (i.e., if
1417-
# target_file_names is empty), all compile commands will go into __all__ and end up
1418-
# in one file.
1419-
compile_command_sets = {
1420-
'__all__': []
1421-
}
1415+
# Associates lists of compile commands with compile command "collections".
1416+
# __all__ is a special case: It contains all generated compile commands.
1417+
# Any other collections defined in `target_collections` will contain only
1418+
# the compile commands for the targets defined for those collections.
1419+
compile_command_sets = collections.defaultdict(list)
14221420

14231421
for (target, flags) in target_flag_pairs:
1424-
# If the target has a specific file name assigned, put the compile commands in their
1425-
# own set, to be written to their own file.
1426-
if target in target_file_names:
1427-
target_name = target_file_names[target]
1428-
compile_command_sets[target_name] = list(_get_commands(target, flags))
1429-
# Otherwise, put them into the main file.
1430-
else:
1431-
compile_command_sets['__all__'].extend(_get_commands(target, flags))
1422+
commands = list(_get_commands(target, flags))
1423+
# If the target is assigned to any collections, put the compile commands in the compile command sets for those collections.
1424+
collections_for_target = [collection_name for target_name, collection_name in target_collections.items() if target == target_name]
1425+
for collection_name in collections_for_target:
1426+
compile_command_sets[collection_name].extend(commands)
1427+
# Also put them into the main file.
1428+
compile_command_sets['__all__'].extend(commands)
14321429

14331430
if len(compile_command_sets) <= 1 and len(compile_command_sets['__all__']) == 0:
14341431
log_error(""">>> Not (over)writing compile_commands.json, since no commands were extracted and an empty file is of no use.

refresh_compile_commands.bzl

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -71,42 +71,67 @@ load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
7171
def refresh_compile_commands(
7272
name,
7373
targets = None,
74-
target_file_names = None,
74+
target_collections = None,
7575
out_dir = None,
7676
exclude_headers = None,
7777
exclude_external_sources = False,
7878
**kwargs): # For the other common attributes. Tags, compatible_with, etc. https://docs.bazel.build/versions/main/be/common-definitions.html#common-attributes.
79+
80+
if not target_collections:
81+
target_collections = {}
82+
83+
target_collections = {
84+
_make_label_absolute(target): collection_name for target, collection_name in target_collections.items()
85+
}
86+
87+
# We want a list of targets specified in `target_collections`, and we want a list of all of the target collections.
88+
# There's a many-to-many relationship between targets and target collections, there can be duplicates in both of those lists.
89+
# We want to de-duplicate those lists, but Starlark doesn't have a set class like Python does, so we do it manually.
90+
target_collection_names = []
91+
target_collection_targets = []
92+
93+
for target, collection_name in target_collections.items():
94+
if not target in target_collection_targets:
95+
target_collection_targets.append(target)
96+
97+
if not collection_name in target_collection_names:
98+
target_collection_names.append(collection_name)
99+
100+
101+
79102
# Convert the various, acceptable target shorthands into the dictionary format
80103
# In Python, `type(x) == y` is an antipattern, but [Starlark doesn't support inheritance](https://bazel.build/rules/language), so `isinstance` doesn't exist, and this is the correct way to switch on type.
81-
if not targets: # Default to all targets in main workspace
104+
if not targets and len(target_collection_targets) == 0: # Default to all targets in main workspace
82105
targets = {"@//...": ""}
106+
if not targets: # In this case, targets were defined only in `target_collections`
107+
targets = {target: "" for target in target_collection_targets}
83108
elif type(targets) == "select": # Allow select: https://bazel.build/reference/be/functions#select
84109
# Pass select() to _expand_template to make it work
85110
# see https://bazel.build/docs/configurable-attributes#faq-select-macro
86111
pass
87112
elif type(targets) == "list": # Allow specifying a list of targets w/o arguments
88-
targets = {target: "" for target in targets}
113+
# The reason we want the list of target collection targets is that if you specify a target in `target_collections`, you don't have to redundantly specify it in `targets`, *unless* you want to specify build flags too.
114+
# So we want to cull the list of target collection targets down to only those that *aren't* specified in `targets`.
115+
unique_target_collection_targets = [target for target in target_collection_targets if target not in targets]
116+
targets = {target: "" for target in (targets + unique_target_collection_targets)}
89117
elif type(targets) != "dict": # Assume they've supplied a single string/label and wrap it
90-
targets = {targets: ""}
118+
unique_target_collection_targets = [target for target in target_collection_targets if target not in targets]
119+
targets = {target: "" for target in ([targets] + unique_target_collection_targets)}
120+
else: # Assume that they've provided a dict of targets with flags
121+
unique_target_collection_targets = [target for target in target_collection_targets if target not in targets]
122+
targets = targets | {target: "" for target in target_collection_targets}
91123

92124
targets = {
93125
_make_label_absolute(target): flags for target, flags in targets.items()
94126
}
95127

96-
if not target_file_names:
97-
target_file_names = {}
98-
99-
target_file_names = {
100-
_make_label_absolute(target): file_names for target, file_names in target_file_names.items()
101-
}
102-
103128
# Create a wrapper script that prints a helpful error message if the python version is too old, generated from check_python_version.template.py
104129
version_checker_script_name = name + ".check_python_version.py"
105130
_check_python_version(name = version_checker_script_name, to_run = name)
106131

107132
# Generate the core, runnable python script from refresh.template.py
108133
script_name = name + ".py"
109-
_expand_template(name = script_name, labels_to_flags = targets, labels_to_file_names = target_file_names, out_dir = out_dir, exclude_headers = exclude_headers, exclude_external_sources = exclude_external_sources, **kwargs)
134+
_expand_template(name = script_name, labels_to_flags = targets, labels_to_collections = target_collections, out_dir = out_dir, exclude_headers = exclude_headers, exclude_external_sources = exclude_external_sources, **kwargs)
110135

111136
# Combine them so the wrapper calls the main script
112137
native.py_binary(
@@ -132,7 +157,7 @@ def _expand_template_impl(ctx):
132157
substitutions = {
133158
# Note, don't delete whitespace. Correctly doing multiline indenting.
134159
" {target_flag_pairs}": "\n".join([" {},".format(pair) for pair in ctx.attr.labels_to_flags.items()]),
135-
" {target_file_names}": "\n".join([" '{}': '{}',".format(target, file_name) for (target, file_name) in ctx.attr.labels_to_file_names.items()]),
160+
" {target_collections}": "\n".join([" '{}': '{}',".format(target, collection_name) for (target, collection_name) in ctx.attr.labels_to_collections.items()]),
136161
" {windows_default_include_paths}": "\n".join([" %r," % path for path in find_cpp_toolchain(ctx).built_in_include_directories]), # find_cpp_toolchain is from https://docs.bazel.build/versions/main/integrating-with-rules-cc.html
137162
"{out_dir}": repr(ctx.attr.out_dir),
138163
"{exclude_headers}": repr(ctx.attr.exclude_headers),
@@ -145,7 +170,7 @@ def _expand_template_impl(ctx):
145170
_expand_template = rule(
146171
attrs = {
147172
"labels_to_flags": attr.string_dict(mandatory = True), # string keys instead of label_keyed because Bazel doesn't support parsing wildcard target patterns (..., *, :all) in BUILD attributes.
148-
"labels_to_file_names": attr.string_dict(),
173+
"labels_to_collections": attr.string_dict(),
149174
"out_dir": attr.string(default = "."),
150175
"exclude_external_sources": attr.bool(default = False),
151176
"exclude_headers": attr.string(values = ["all", "external", ""]), # "" needed only for compatibility with Bazel < 3.6.0

0 commit comments

Comments
 (0)