Skip to content

Commit 338292c

Browse files
committed
Adds the ability to use absolute paths for bazel managed artifacts
1 parent 936ae08 commit 338292c

File tree

2 files changed

+75
-4
lines changed

2 files changed

+75
-4
lines changed

refresh.template.py

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ class SGR(enum.Enum):
4949
FG_BLUE = '\033[0;34m'
5050

5151

52+
class BazelInfo(typing.NamedTuple):
53+
output_path: pathlib.Path
54+
external_path: pathlib.Path
55+
56+
5257
def _log_with_sgr(sgr, colored_message, uncolored_message=''):
5358
"""Log a message to stderr wrapped in an SGR context."""
5459
print(sgr.value, colored_message, SGR.RESET.value, uncolored_message, sep='', file=sys.stderr, flush=True)
@@ -765,7 +770,42 @@ def _all_platform_patch(compile_args: typing.List[str]):
765770
return compile_args
766771

767772

768-
def _get_cpp_command_for_files(compile_action):
773+
def _path_replacement_for_arg(arg: str, replacements: typing.Mapping[str, str]):
774+
for (prefix, replacement) in replacements.items():
775+
# Some commands are output with the argument name and the argument value
776+
# split across two arguments. This condition checks for these cases by
777+
# detecting arguments that start with e.g. bazel-out/.
778+
#
779+
# Example: -o, bazel-out/...
780+
if arg.startswith(prefix):
781+
return replacement + arg[len(prefix):]
782+
# Bazel adds directories to include search paths using -I options. This
783+
# condition checks for these cases.
784+
#
785+
# See: https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-I-dir
786+
#
787+
# Example: -Ibazel-out/...
788+
elif arg.startswith('-I' + prefix):
789+
return replacement + arg[2 + len(prefix):]
790+
# Some commands are output with the argument name and the argument value
791+
# combined within the same argument, seperated by an equals sign.
792+
#
793+
# Example: -frandom-seed=bazel-out/...
794+
elif '={}'.format(prefix) in arg:
795+
return arg.replace('={}'.format(prefix), '={}'.format(replacement), 1)
796+
return arg
797+
798+
799+
def _apply_path_replacements(compile_args: typing.List[str], bazel_info: BazelInfo):
800+
replacements = {
801+
"bazel-out/": os.fspath(bazel_info.output_path) + "/",
802+
"external/": os.fspath(bazel_info.external_path) + "/",
803+
}
804+
805+
return [_path_replacement_for_arg(arg, replacements) for arg in compile_args]
806+
807+
808+
def _get_cpp_command_for_files(compile_action, bazel_info):
769809
"""Reformat compile_action into a compile command clangd can understand.
770810
771811
Undo Bazel-isms and figures out which files clangd should apply the command to.
@@ -775,12 +815,15 @@ def _get_cpp_command_for_files(compile_action):
775815
compile_action.arguments = _apple_platform_patch(compile_action.arguments)
776816
# Android and Linux and grailbio LLVM toolchains: Fine as is; no special patching needed.
777817

818+
if {rewrite_bazel_paths}:
819+
compile_action.arguments = _apply_path_replacements(compile_action.arguments, bazel_info)
820+
778821
source_files, header_files = _get_files(compile_action)
779822

780823
return source_files, header_files, compile_action.arguments
781824

782825

783-
def _convert_compile_commands(aquery_output):
826+
def _convert_compile_commands(aquery_output, bazel_info):
784827
"""Converts from Bazel's aquery format to de-Bazeled compile_commands.json entries.
785828
786829
Input: jsonproto output from aquery, pre-filtered to (Objective-)C(++) compile actions for a given build.
@@ -799,12 +842,15 @@ def _convert_compile_commands(aquery_output):
799842
assert not target.startswith('//external'), f"Expecting external targets will start with @. Found //external for action {action}, target {target}"
800843
action.is_external = target.startswith('@') and not target.startswith('@//')
801844

845+
def worker(compile_action):
846+
return _get_cpp_command_for_files(compile_action, bazel_info)
847+
802848
# Process each action from Bazelisms -> file paths and their clang commands
803849
# Threads instead of processes because most of the execution time is farmed out to subprocesses. No need to sidestep the GIL. Might change after https://github.com/clangd/clangd/issues/123 resolved
804850
with concurrent.futures.ThreadPoolExecutor(
805851
max_workers=min(32, (os.cpu_count() or 1) + 4) # Backport. Default in MIN_PY=3.8. See "using very large resources implicitly on many-core machines" in https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor
806852
) as threadpool:
807-
outputs = threadpool.map(_get_cpp_command_for_files, aquery_output.actions)
853+
outputs = threadpool.map(worker, aquery_output.actions)
808854

809855
# Yield as compile_commands.json entries
810856
header_files_already_written = set()
@@ -924,7 +970,25 @@ def _get_commands(target: str, flags: str):
924970
Continuing gracefully...""")
925971
return
926972

927-
yield from _convert_compile_commands(parsed_aquery_output)
973+
output_path_process = subprocess.run(
974+
['bazel', 'info', 'output_path'],
975+
capture_output=True,
976+
encoding=locale.getpreferredencoding(),
977+
)
978+
979+
output_path = pathlib.Path(output_path_process.stdout.strip())
980+
981+
output_base_process = subprocess.run(
982+
['bazel', 'info', 'output_base'],
983+
capture_output=True,
984+
encoding=locale.getpreferredencoding(),
985+
)
986+
987+
output_base = pathlib.Path(output_base_process.stdout.strip())
988+
external_path = output_base.joinpath("external")
989+
990+
bazel_info = BazelInfo(output_path, external_path)
991+
yield from _convert_compile_commands(parsed_aquery_output, bazel_info)
928992

929993

930994
# Log clear completion messages

refresh_compile_commands.bzl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ refresh_compile_commands(
5555
5656
# refresh_compile_commands will automatically create a symlink for external workspaces at /external.
5757
# You can disable this behavior with link_external = False.
58+
59+
# refresh_compile_commands does not work with --experimental_convenience_symlinks=ignore.
60+
# For these workspaces, you can use absolute paths to the bazel build artifacts by setting rewrite_bazel_paths = True.
5861
```
5962
"""
6063

@@ -71,6 +74,7 @@ def refresh_compile_commands(
7174
exclude_external_sources = False,
7275
update_gitignore = True,
7376
link_external = True,
77+
rewrite_bazel_paths = False,
7478
**kwargs): # For the other common attributes. Tags, compatible_with, etc. https://docs.bazel.build/versions/main/be/common-definitions.html#common-attributes.
7579
# Convert the various, acceptable target shorthands into the dictionary format
7680
# 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.
@@ -103,6 +107,7 @@ def refresh_compile_commands(
103107
exclude_external_sources = exclude_external_sources,
104108
update_gitignore = update_gitignore,
105109
link_external = link_external,
110+
rewrite_bazel_paths = rewrite_bazel_paths,
106111
**kwargs
107112
)
108113

@@ -133,6 +138,7 @@ def _expand_template_impl(ctx):
133138
"{exclude_external_sources}": repr(ctx.attr.exclude_external_sources),
134139
"{update_gitignore}": repr(ctx.attr.update_gitignore),
135140
"{link_external}": repr(ctx.attr.link_external),
141+
"{rewrite_bazel_paths}": repr(ctx.attr.rewrite_bazel_paths),
136142
},
137143
)
138144
return DefaultInfo(files = depset([script]))
@@ -144,6 +150,7 @@ _expand_template = rule(
144150
"exclude_headers": attr.string(values = ["all", "external", ""]), # "" needed only for compatibility with Bazel < 3.6.0
145151
"update_gitignore": attr.bool(default = True),
146152
"link_external": attr.bool(default = True),
153+
"rewrite_bazel_paths": attr.bool(default = False),
147154
"_script_template": attr.label(allow_single_file = True, default = "refresh.template.py"),
148155
# 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
149156
# Once https://github.com/bazelbuild/bazel/pull/17108 is widely released, we should be able to eliminate this and get INCLUDE directly. Perhaps for 7.0? Should be released in the sucessor to 6.0

0 commit comments

Comments
 (0)