Skip to content

Commit 0453be4

Browse files
committed
refresh.template.py: for --file, don't continue traversing targets after the first command has been found
1 parent 6a6777a commit 0453be4

File tree

1 file changed

+92
-86
lines changed

1 file changed

+92
-86
lines changed

refresh.template.py

+92-86
Original file line numberDiff line numberDiff line change
@@ -942,95 +942,104 @@ def _get_compile_commands_for_aquery(aquery_target_statement: str, additional_aq
942942
return _convert_compile_commands(parsed_aquery_output, focused_on_file)
943943

944944

945-
def _get_commands(target: str, flags: str):
946-
"""Return compile_commands.json entries for a given target and flags, gracefully tolerating errors."""
947-
log_info(f">>> Analyzing commands used in {target}")
948-
949-
# Parse the --file= flag, if any, passing along all other arguments to aquery
950-
additional_flags = shlex.split(flags) + [arg for arg in sys.argv[1:] if not arg.startswith('--file=')]
951-
file_flags = [arg[len('--file='):] for arg in sys.argv[1:] if arg.startswith('--file=')]
952-
if len(file_flags) > 1:
953-
log_error(">>> At most one --file flag is supported.")
954-
sys.exit(1)
955-
if any(arg.startswith('--file') for arg in additional_flags):
956-
log_error(">>> Only the --file=<file_target> form is supported.")
957-
sys.exit(1)
945+
def _get_commands(pairs: list):
946+
"""Return compile_commands.json entries for a given list of target and flags, gracefully tolerating errors."""
947+
948+
all_compile_commands = []
949+
950+
for target, flags in pairs:
951+
log_info(f">>> Analyzing commands used in {target}")
952+
# Parse the --file= flag, if any, passing along all other arguments to aquery
953+
additional_flags = shlex.split(flags) + [arg for arg in sys.argv[1:] if not arg.startswith('--file=')]
954+
file_flags = [arg[len('--file='):] for arg in sys.argv[1:] if arg.startswith('--file=')]
955+
if len(file_flags) > 1:
956+
log_error(">>> At most one --file flag is supported.")
957+
sys.exit(1)
958+
if any(arg.startswith('--file') for arg in additional_flags):
959+
log_error(">>> Only the --file=<file_target> form is supported.")
960+
sys.exit(1)
961+
962+
963+
# Screen the remaining flags for obvious issues to help people debug.
964+
965+
# Detect anything that looks like a build target in the flags, and issue a warning.
966+
# Note that positional arguments after -- are all interpreted as target patterns.
967+
# And that we have to look for targets. Checking for a - prefix is not enough. Consider the case of `-c opt`, leading to a false positive.
968+
if ('--' in additional_flags
969+
or any(re.match(r'-?(@|:|//)', f) for f in additional_flags)):
970+
log_warning(""">>> The flags you passed seem to contain targets.
971+
Try adding them as targets in your refresh_compile_commands rather than flags.
972+
[Specifying targets at runtime isn't supported yet, and in a moment, Bazel will likely fail to parse without our help. If you need to be able to specify targets at runtime, and can't easily just add them to your refresh_compile_commands, please open an issue or file a PR. You may also want to refer to https://github.com/hedronvision/bazel-compile-commands-extractor/issues/62.]""")
973+
974+
# Quick (imperfect) effort at detecting flags in the targets.
975+
# Can't detect flags starting with -, because they could be subtraction patterns.
976+
if any(target.startswith('--') for target in shlex.split(target)):
977+
log_warning(""">>> The target you specified seems to contain flags.
978+
Try adding them as flags in your refresh_compile_commands rather than targets.
979+
In a moment, Bazel will likely fail to parse.""")
980+
958981

982+
# Then, actually query Bazel's compile actions for that configured target
983+
target_statement_candidates = []
984+
file_path = None
959985

960-
# Screen the remaining flags for obvious issues to help people debug.
961-
962-
# Detect anything that looks like a build target in the flags, and issue a warning.
963-
# Note that positional arguments after -- are all interpreted as target patterns.
964-
# And that we have to look for targets. Checking for a - prefix is not enough. Consider the case of `-c opt`, leading to a false positive.
965-
if ('--' in additional_flags
966-
or any(re.match(r'-?(@|:|//)', f) for f in additional_flags)):
967-
log_warning(""">>> The flags you passed seem to contain targets.
968-
Try adding them as targets in your refresh_compile_commands rather than flags.
969-
[Specifying targets at runtime isn't supported yet, and in a moment, Bazel will likely fail to parse without our help. If you need to be able to specify targets at runtime, and can't easily just add them to your refresh_compile_commands, please open an issue or file a PR. You may also want to refer to https://github.com/hedronvision/bazel-compile-commands-extractor/issues/62.]""")
970-
971-
# Quick (imperfect) effort at detecting flags in the targets.
972-
# Can't detect flags starting with -, because they could be subtraction patterns.
973-
if any(target.startswith('--') for target in shlex.split(target)):
974-
log_warning(""">>> The target you specified seems to contain flags.
975-
Try adding them as flags in your refresh_compile_commands rather than targets.
976-
In a moment, Bazel will likely fail to parse.""")
977-
978-
979-
# Then, actually query Bazel's compile actions for that configured target
980-
target_statement_candidates = []
981-
file_path = None
982-
compile_commands = []
983-
984-
if file_flags:
985-
file_path = file_flags[0]
986-
rel_path = os.path.relpath(file_path, os.getcwd())
987-
if not rel_path.startswith(".."):
988-
log_info(f">>> Detected file path {file_path} is relative path changed to {rel_path}")
989-
file_path = rel_path
990-
991-
target_statement = f"deps('{target}')"
992-
if file_path.endswith(_get_files.source_extensions):
993-
target_statement_candidates.append(f"inputs('{re.escape(file_path)}', {target_statement})")
986+
if file_flags:
987+
file_path = file_flags[0]
988+
rel_path = os.path.relpath(file_path, os.getcwd())
989+
if not rel_path.startswith(".."):
990+
log_info(f">>> Detected file path {file_path} is relative path changed to {rel_path}")
991+
file_path = rel_path
992+
993+
target_statement = f"deps('{target}')"
994+
if file_path.endswith(_get_files.source_extensions):
995+
target_statement_candidates.append(f"inputs('{re.escape(file_path)}', {target_statement})")
996+
else:
997+
fname = os.path.basename(file_path)
998+
label_candidates = subprocess.check_output(['bazel', 'query', f"filter('{fname}$', {target_statement})"], stderr = subprocess.PIPE, text = True).split()
999+
# TODO compatible with windows file path
1000+
file_candidates = list(filter(lambda label: file_path in label.replace(':', '/'), label_candidates))
1001+
file_statement = '|'.join(file_candidates) if len(file_candidates) > 0 else fname
1002+
1003+
header_target_statement = f"let v = {target_statement} in attr(hdrs, '{file_statement}', $v) + attr(srcs, '{file_statement}', $v)" # Bazel does not list headers as direct inputs, but rather hides them behind "middlemen", necessitating a query like this.
1004+
target_statement_candidates.extend([
1005+
header_target_statement,
1006+
f"allpaths({target}, {header_target_statement})", # Ordering is ideal, breadth-first from the deepest dependency, despite the docs. TODO (1) There's a bazel bug that produces extra actions, not on the path but downstream, so we probably want to pass --noinclude_aspects per https://github.com/bazelbuild/bazel/issues/18289 to eliminate them (at the cost of some valid aspects). (2) We might want to benchmark with --infer_universe_scope (if supported) and --universe-scope=target with query allrdeps({header_target_statement}, <maybe some limited depth>) or rdeps, checking speed but also ordering (the docs indicate it is likely to be lost, which is a problem) and for inclusion of the header target. We'd guess it'll have the same aspects bug as allpaths. (3) We probably also also want to *just* run this query, not the whole list, since it captures the former and is therefore unlikely to add much latency, since a given header is probabably either used internally to the target (find on first match) for header-only (must traverse all paths in all targets until you get a match) for all top-level targets, and since we can separate out the last, see below.
1007+
target_statement,
1008+
])
9941009
else:
995-
fname = os.path.basename(file_path)
996-
label_candidates = subprocess.check_output(['bazel', 'query', f"filter('{fname}$', {target_statement})"], stderr = subprocess.PIPE, text = True).split()
997-
# TODO compatible with windows file path
998-
file_candidates = list(filter(lambda label: file_path in label.replace(':', '/'), label_candidates))
999-
file_statement = '|'.join(file_candidates) if len(file_candidates) > 0 else fname
1000-
1001-
header_target_statement = f"let v = {target_statement} in attr(hdrs, '{file_statement}', $v) + attr(srcs, '{file_statement}', $v)" # Bazel does not list headers as direct inputs, but rather hides them behind "middlemen", necessitating a query like this.
1002-
target_statement_candidates.extend([
1003-
header_target_statement,
1004-
f"allpaths({target}, {header_target_statement})", # Ordering is ideal, breadth-first from the deepest dependency, despite the docs. TODO (1) There's a bazel bug that produces extra actions, not on the path but downstream, so we probably want to pass --noinclude_aspects per https://github.com/bazelbuild/bazel/issues/18289 to eliminate them (at the cost of some valid aspects). (2) We might want to benchmark with --infer_universe_scope (if supported) and --universe-scope=target with query allrdeps({header_target_statement}, <maybe some limited depth>) or rdeps, checking speed but also ordering (the docs indicate it is likely to be lost, which is a problem) and for inclusion of the header target. We'd guess it'll have the same aspects bug as allpaths. (3) We probably also also want to *just* run this query, not the whole list, since it captures the former and is therefore unlikely to add much latency, since a given header is probabably either used internally to the target (find on first match) for header-only (must traverse all paths in all targets until you get a match) for all top-level targets, and since we can separate out the last, see below.
1005-
target_statement,
1006-
])
1007-
else:
1008-
if {exclude_external_sources}:
1009-
# For efficiency, have bazel filter out external targets (and therefore actions) before they even get turned into actions or serialized and sent to us. Note: this is a different mechanism than is used for excluding just external headers.
1010-
target_statement_candidates.append(f"filter('^(//|@//)',{target_statement})")
1011-
1012-
found = False
1013-
for target_statement in target_statement_candidates:
1014-
commands = _get_compile_commands_for_aquery(target_statement, additional_flags, file_path)
1015-
compile_commands.extend(commands) # If we did the work to generate a command, we'll update it, whether it's for the requested file or not.
1010+
if {exclude_external_sources}:
1011+
# For efficiency, have bazel filter out external targets (and therefore actions) before they even get turned into actions or serialized and sent to us. Note: this is a different mechanism than is used for excluding just external headers.
1012+
target_statement_candidates.append(f"filter('^(//|@//)',{target_statement})")
1013+
1014+
found = False
1015+
compile_commands = []
1016+
for target_statement in target_statement_candidates:
1017+
commands = _get_compile_commands_for_aquery(target_statement, additional_flags, file_path)
1018+
compile_commands.extend(commands) # If we did the work to generate a command, we'll update it, whether it's for the requested file or not.
1019+
if file_flags:
1020+
if any(command.file.endswith(file_path) for command in commands):
1021+
found = True
1022+
break
1023+
log_info(f""">>> Couldn't quickly find a compile command for {file_path} in {target} under {target_statement}
1024+
Continuing gracefully...""")
1025+
1026+
all_compile_commands.extend(compile_commands)
1027+
10161028
if file_flags:
1017-
if any(command.file.endswith(file_path) for command in commands):
1018-
found = True
1029+
if found:
1030+
log_success(f">>> Finished extracting commands for {target} with --file {file_path}")
10191031
break
1020-
log_info(f""">>> Couldn't quickly find a compile command for {file_path} in {target} under {target_statement}
1021-
Continuing gracefully...""")
1022-
1023-
if file_flags and not found:
1024-
log_warning(f""">>> Couldn't quickly find a compile command for {file_path} in {target}
1025-
Continuing gracefully...""")
1032+
else:
1033+
log_warning(f""">>> Couldn't quickly find a compile command for {file_path} in {target}
1034+
Continuing gracefully...""")
10261035

1027-
if not compile_commands:
1028-
log_warning(f""">>> Bazel lists no applicable compile commands for {target}
1029-
If this is a header-only library, please instead specify a test or binary target that compiles it (search "header-only" in README.md).
1030-
Continuing gracefully...""")
1036+
if not compile_commands:
1037+
log_warning(f""">>> Bazel lists no applicable compile commands for {target}
1038+
If this is a header-only library, please instead specify a test or binary target that compiles it (search "header-only" in README.md).
1039+
Continuing gracefully...""")
10311040

1032-
log_success(f">>> Finished extracting commands for {target}")
1033-
return compile_commands
1041+
log_success(f">>> Finished extracting commands for {target}")
1042+
return all_compile_commands
10341043

10351044

10361045
def _ensure_external_workspaces_link_exists():
@@ -1156,16 +1165,13 @@ def _ensure_cwd_is_workspace_root():
11561165
_ensure_gitignore_entries_exist()
11571166
_ensure_external_workspaces_link_exists()
11581167

1159-
# TODO for --file, don't continue traversing targets after the first command has been found. Probably push this looping and template expansion inside of _get_commands().
11601168
target_flag_pairs = [
11611169
# Begin: template filled by Bazel
11621170
{target_flag_pairs}
11631171
# End: template filled by Bazel
11641172
]
11651173

1166-
compile_command_entries = []
1167-
for (target, flags) in target_flag_pairs:
1168-
compile_command_entries.extend(_get_commands(target, flags))
1174+
compile_command_entries = _get_commands(target_flag_pairs)
11691175

11701176
if not compile_command_entries:
11711177
log_error(">>> Not writing to compile_commands.json, since no commands were extracted.")

0 commit comments

Comments
 (0)