Skip to content

Commit 0d01e83

Browse files
committed
Allow custom json output path with Make variable substitution support
fixes hedronvision#217 fixes hedronvision#210
1 parent abb61a6 commit 0d01e83

File tree

3 files changed

+21
-4
lines changed

3 files changed

+21
-4
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,9 @@ refresh_compile_commands(
146146
# Wildcard patterns, like //... for everything, *are* allowed here, just like a build.
147147
# As are additional targets (+) and subtractions (-), like in bazel query https://docs.bazel.build/versions/main/query.html#expressions
148148
# And if you're working on a header-only library, specify a test or binary target that compiles it.
149+
150+
# Optionally, you can change the output path to the compile_commands.json file. Make Variable Substitutions are also supported: https://bazel.build/reference/be/make-variables
151+
json_output_path = "myFolder/NewCompileCommands.json",
149152
)
150153
```
151154

refresh.template.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1356,7 +1356,7 @@ def _ensure_gitignore_entries_exist():
13561356
needed_entries = [
13571357
(f'/{pattern_prefix}external', "# Ignore the `external` link (that is added by `bazel-compile-commands-extractor`). The link differs between macOS/Linux and Windows, so it shouldn't be checked in. The pattern must not end with a trailing `/` because it's a symlink on macOS/Linux."),
13581358
(f'/{pattern_prefix}bazel-*', "# Ignore links to Bazel's output. The pattern needs the `*` because people can change the name of the directory into which your repository is cloned (changing the `bazel-<workspace_name>` symlink), and must not end with a trailing `/` because it's a symlink on macOS/Linux. This ignore pattern should almost certainly be checked into a .gitignore in your workspace root, too, for folks who don't use this tool."),
1359-
(f'/{pattern_prefix}compile_commands.json', "# Ignore generated output. Although valuable (after all, the primary purpose of `bazel-compile-commands-extractor` is to produce `compile_commands.json`!), it should not be checked in."),
1359+
(f'/{pattern_prefix}{json_output_path}', "# Ignore generated output. Although valuable (after all, the primary purpose of `bazel-compile-commands-extractor` is to produce `compile_commands.json`!), it should not be checked in."),
13601360
('.cache/', "# Ignore the directory in which `clangd` stores its local index."),
13611361
]
13621362

@@ -1410,15 +1410,20 @@ def main():
14101410
compile_command_entries.extend(_get_commands(target, flags))
14111411

14121412
if not compile_command_entries:
1413-
log_error(""">>> Not (over)writing compile_commands.json, since no commands were extracted and an empty file is of no use.
1413+
log_error(f""">>> Not (over)writing {json_output_path}, since no commands were extracted and an empty file is of no use.
14141414
There should be actionable warnings, above, that led to this.""")
14151415
sys.exit(1)
14161416

1417+
# Create the containing directory if it doesn't exist.
1418+
json_output_dir = pathlib.Path({json_output_path}).parent
1419+
json_output_dir.mkdir(parents=True, exist_ok=True)
1420+
14171421
# Chain output into compile_commands.json
1418-
with open('compile_commands.json', 'w') as output_file:
1422+
with open({json_output_path}, 'w') as output_file:
14191423
json.dump(
14201424
compile_command_entries,
14211425
output_file,
14221426
indent=2, # Yay, human readability!
14231427
check_circular=False # For speed.
14241428
)
1429+

refresh_compile_commands.bzl

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ refresh_compile_commands(
4949
# exclude_headers = "external",
5050
# Still not fast enough?
5151
# Make sure you're specifying just the targets you care about by setting `targets`, above.
52+
53+
# Want to change the filename or path for the output compile_commands.json file?
54+
# json_output_path = "NewCompileCommands.json",
55+
# Make Variable Substitutions are also supported: https://bazel.build/reference/be/make-variables
56+
# json_output_path = "$(BINDIR)/compile_commands/bazel_compile_commands.json",
57+
# This will result in bazel-out/compile_commands/bazel_compile_commands.json or a slightly different directory if you are using --symlink-prefix or similar to change where bazel's binary directory is.
5258
```
5359
"""
5460

@@ -64,6 +70,7 @@ def refresh_compile_commands(
6470
targets = None,
6571
exclude_headers = None,
6672
exclude_external_sources = False,
73+
json_output_path = "compile_commands.json",
6774
**kwargs): # For the other common attributes. Tags, compatible_with, etc. https://docs.bazel.build/versions/main/be/common-definitions.html#common-attributes.
6875
# Convert the various, acceptable target shorthands into the dictionary format
6976
# 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.
@@ -89,7 +96,7 @@ def refresh_compile_commands(
8996

9097
# Generate the core, runnable python script from refresh.template.py
9198
script_name = name + ".py"
92-
_expand_template(name = script_name, labels_to_flags = targets, exclude_headers = exclude_headers, exclude_external_sources = exclude_external_sources, **kwargs)
99+
_expand_template(name = script_name, labels_to_flags = targets, exclude_headers = exclude_headers, exclude_external_sources = exclude_external_sources, json_output_path = json_output_path, **kwargs)
93100

94101
# Combine them so the wrapper calls the main script
95102
native.py_binary(
@@ -114,6 +121,7 @@ def _expand_template_impl(ctx):
114121
" {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
115122
"{exclude_headers}": repr(ctx.attr.exclude_headers),
116123
"{exclude_external_sources}": repr(ctx.attr.exclude_external_sources),
124+
"{json_output_path}": repr(ctx.expand_make_variables("json_output_path_expansion", ctx.attr.json_output_path, {})), # Subject to make variable substitutions
117125
"{print_args_executable}": repr(ctx.executable._print_args_executable.path),
118126
},
119127
)
@@ -124,6 +132,7 @@ _expand_template = rule(
124132
"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.
125133
"exclude_external_sources": attr.bool(default = False),
126134
"exclude_headers": attr.string(values = ["all", "external", ""]), # "" needed only for compatibility with Bazel < 3.6.0
135+
"json_output_path": attr.string(),
127136
"_script_template": attr.label(allow_single_file = True, default = "refresh.template.py"),
128137
"_print_args_executable": attr.label(executable = True, cfg = "target", default = "//:print_args"),
129138
# For Windows INCLUDE. If this were eliminated, for example by the resolution of https://github.com/clangd/clangd/issues/123, we'd be able to just use a macro and skylib's expand_template rule: https://github.com/bazelbuild/bazel-skylib/pull/330

0 commit comments

Comments
 (0)