From d7a28301d812aeafa36469343538dbc025cec196 Mon Sep 17 00:00:00 2001 From: Christopher Sauer Date: Mon, 13 Feb 2023 23:38:44 -0800 Subject: [PATCH 1/6] Update README since Gazelle changes were merged https://github.com/bazelbuild/bazel-gazelle/pull/1384 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7933782..f7a353c 100644 --- a/README.md +++ b/README.md @@ -139,9 +139,9 @@ Adding `exclude_external_sources = True` and `exclude_headers = "external"` can For now, we'd suggest continuing on to set up `clangd` (below). Thereafter, if you your project proves to be large enough that it stretches the capacity of `clangd` and/or this tool to index quickly, take a look at the docs at the top of [`refresh_compile_commands.bzl`](./refresh_compile_commands.bzl) for instructions on how to tune those flags and others. -### If you're using Gazelle: +### If you're using Gazelle v0.29 or older: -Please add `# gazelle:exclude external` to the BUILD file in your workspace root--just until https://github.com/bazelbuild/bazel-gazelle/pull/1384 is resolved. They have an issue we fixed for them; follow (and 👍) [that PR](https://github.com/bazelbuild/bazel-gazelle/pull/1384), so you know when you can remove this workaround. +Please upgrade or add `# gazelle:exclude external` to the BUILD file in your workspace root. Gazelle had some problematic symlink handling in those versions that we fixed for them with a PR. (Conversely, if, at the time you're reading this, Gazelle v0.29 (January 2023) is so old that few would be using it, please file a quick PR to remove this section.) ## Editor Setup — for autocomplete based on `compile_commands.json` From 19808d64d08d7e8866bc7ebe2e3da98119935933 Mon Sep 17 00:00:00 2001 From: Chris Sauer Date: Wed, 22 Feb 2023 02:30:55 -0800 Subject: [PATCH 2/6] Update sysroot notes now that https://github.com/hedronvision/bazel-compile-commands-extractor/issues/82 is closed --- refresh.template.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/refresh.template.py b/refresh.template.py index 1fd38c4..de3e8b8 100644 --- a/refresh.template.py +++ b/refresh.template.py @@ -735,8 +735,8 @@ def _all_platform_patch(compile_args: typing.List[str]): # For more context see: https://github.com/hedronvision/bazel-compile-commands-extractor/issues/21 compile_args = (arg for arg in compile_args if not arg == '-fno-canonical-system-headers') - # Swap -isysroot for --sysroot to work around (probably) https://github.com/clangd/clangd/issues/1305 - # For context, see https://github.com/clangd/clangd/issues/1305 + # Swap -isysroot for --sysroot to work around some unknown sysroot bug in clangd. + # For context, see https://github.com/hedronvision/bazel-compile-commands-extractor/issues/82 # The = logic has to do with clang not accepting -isysroot=, but accepting --sysroot=. Note that -isysroot is accepted, though undocumented. compile_args = ('-isysroot'+arg[len('--sysroot')+arg.startswith('--sysroot='):] if arg.startswith('--sysroot') else arg for arg in compile_args) From abdd06e05c7949721dba4bf1ae465bde16b9d3e1 Mon Sep 17 00:00:00 2001 From: Chris Sauer Date: Thu, 23 Feb 2023 22:16:09 -0800 Subject: [PATCH 3/6] Switch to git rev-parse --show-prefix for gitignore pattern prefixes to handle worktrees For more, see https://github.com/hedronvision/bazel-compile-commands-extractor/issues/103 --- refresh.template.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/refresh.template.py b/refresh.template.py index de3e8b8..6147d8c 100644 --- a/refresh.template.py +++ b/refresh.template.py @@ -973,7 +973,7 @@ def _ensure_external_workspaces_link_exists(): def _ensure_gitignore_entries_exist(): """Ensure `//compile_commands.json`, `//external`, and other useful entries are `.gitignore`'d if in a git repo.""" - # Silently check if we're (nested) within a git repository. It isn't sufficient to check for the presence of a `.git` directory, in case the bazel workspace is nested inside the git repository. + # Silently check if we're (nested) within a git repository. It isn't sufficient to check for the presence of a `.git` directory, in case, e.g., the bazel workspace is nested inside the git repository or you're off in git worktree. git_dir_process = subprocess.run('git rev-parse --git-dir', shell=True, # Ensure this will still fail with a nonzero error code even if `git` isn't installed, unifying error cases. stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, @@ -988,9 +988,14 @@ def _ensure_gitignore_entries_exist(): # Hidden gitignore documented in https://git-scm.com/docs/gitignore git_dir = pathlib.Path(git_dir_process.stdout.rstrip()) hidden_gitignore_path = git_dir / 'info' / 'exclude' - pattern_prefix = str(pathlib.Path.cwd().relative_to(git_dir.parent.absolute())) - if pattern_prefix == '.': pattern_prefix = '' - elif pattern_prefix: pattern_prefix += '/' + + # Get path to the workspace root (current working directory) from the git repository root + git_prefix_process = subprocess.run(['git', 'rev-parse', '--show-prefix'], + stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, + encoding=locale.getpreferredencoding(), + check=True, # Should always succeed if the other did + ) + pattern_prefix = git_prefix_process.stdout.rstrip() # Each (pattern, explanation) will be added to the `.gitignore` file if the pattern isn't present. needed_entries = [ From a4a6a9040c6712130c9ed77ea5cf6f7d7c097f40 Mon Sep 17 00:00:00 2001 From: Snorlax Date: Fri, 23 Dec 2022 23:41:18 +0800 Subject: [PATCH 4/6] file based filter --- refresh.template.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/refresh.template.py b/refresh.template.py index 6147d8c..074b1fd 100644 --- a/refresh.template.py +++ b/refresh.template.py @@ -828,7 +828,9 @@ def _get_commands(target: str, flags: str): # Log clear completion messages log_info(f">>> Analyzing commands used in {target}") - additional_flags = shlex.split(flags) + sys.argv[1:] + additional_flags = shlex.split(flags) + [arg for arg in sys.argv[1:] if not arg.startswith('--file=')] + file_flags = [arg for arg in sys.argv[1:] if arg.startswith('--file=')] + assert len(file_flags) < 2, f"Only one or zero --file is supported current args = {sys.argv[1:]}" # Detect anything that looks like a build target in the flags, and issue a warning. # Note that positional arguments after -- are all interpreted as target patterns. (If it's at the end, then no worries.) @@ -851,6 +853,18 @@ def _get_commands(target: str, flags: str): if {exclude_external_sources}: # 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. target_statment = f"filter('^(//|@//)',{target_statment})" + if (len(file_flags) == 1): + # Strip --file= + file_path = file_flags[0][7:] + # Query escape + file_path = file_path.replace("+", "\+").replace("-", "\-") + # For header file we try to find from hdrs and srcs to get the targets + if file_path.endswith('.h'): + # Since attr function can't query with full path, get the file name to query + head, tail = os.path.split(file_path) + target_statment = f"attr(hdrs, '{tail}', {target_statment}) + attr(srcs, '{tail}', {target_statment})" + else: + target_statment = f"inputs('{file_path}', {target_statment})" aquery_args = [ 'bazel', 'aquery', From 0f2509088c0b686a9c1d44335aeffd7f660ef4a1 Mon Sep 17 00:00:00 2001 From: Christopher Peterson Sauer Date: Fri, 23 Dec 2022 16:53:22 -0800 Subject: [PATCH 5/6] Unify --file parsing logic Promote multiple --file assert to log_error Why? It's a usage error we're communicating to the user, not a code invariant we're checking in debug mode. We could recover, but we don't, because we expect only automated usage and want to give clear feedback. More pythonic if Improve my --file flag messaging consistency Call library to escape regex Helps avoid edge cases! Also handles the most common special character, '.' Add an error to catch unsupported --file form Tweak -- logic now that --file arguments could be after --file docs Make aquery spacing consistent Flip the if statement in constructor target_statment Use let to combine attr query Merge compile_commands.json if --file Small simplification: endswith already can take a tuple of possibilities (as elsewhere) Small simplification of header path for file flag. (This code section likely to be replaced later, though.) Fix my typo on --file= discoverable docs Some tweaks to merge Adds note about behavior, some edge case handling around other flags that might start with --file, permissions issues, etc. --- refresh.template.py | 50 ++++++++++++++++++++++++------------ refresh_compile_commands.bzl | 2 ++ 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/refresh.template.py b/refresh.template.py index 074b1fd..e62f5bc 100644 --- a/refresh.template.py +++ b/refresh.template.py @@ -3,8 +3,9 @@ Interface (after template expansion): - `bazel run` to regenerate compile_commands.json, so autocomplete (and any other clang tooling!) reflect the latest Bazel build files. - - No arguments are needed; info from the rule baked into the template expansion. + - No arguments are needed; info from the rule is baked into the template expansion. - Any arguments passed are interpreted as arguments needed for the builds being analyzed. + - The one exception is --file=, which can be used to update commands for just one file. This is intended for programmatic use from editor plugins. - Requires being run under Bazel so we can access the workspace root environment variable. - Output: a compile_commands.json in the workspace root that clang tooling (or you!) can look at to figure out how files are being compiled by Bazel - Crucially, this output is de-Bazeled; The result is a command that could be run from the workspace root directly, with no Bazel-specific requirements, environment variables, etc. @@ -828,14 +829,20 @@ def _get_commands(target: str, flags: str): # Log clear completion messages log_info(f">>> Analyzing commands used in {target}") + # Pass along all arguments to aquery, except for --file= additional_flags = shlex.split(flags) + [arg for arg in sys.argv[1:] if not arg.startswith('--file=')] - file_flags = [arg for arg in sys.argv[1:] if arg.startswith('--file=')] - assert len(file_flags) < 2, f"Only one or zero --file is supported current args = {sys.argv[1:]}" + file_flags = [arg[len('--file='):] for arg in sys.argv[1:] if arg.startswith('--file=')] + if len(file_flags) > 1: + log_error(">>> At most one --file flag is supported.") + sys.exit(1) + if any(arg.startswith('--file') for arg in additional_flags): + log_error(">>> Only the --file= form is supported.") + sys.exit(1) # Detect anything that looks like a build target in the flags, and issue a warning. - # Note that positional arguments after -- are all interpreted as target patterns. (If it's at the end, then no worries.) + # Note that positional arguments after -- are all interpreted as target patterns. # 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 - if ('--' in additional_flags[:-1] + if ('--' in additional_flags or any(re.match(r'-?(@|:|//)', f) for f in additional_flags)): log_warning(""">>> The flags you passed seem to contain targets. Try adding them as targets in your refresh_compile_commands rather than flags. @@ -853,25 +860,22 @@ def _get_commands(target: str, flags: str): if {exclude_external_sources}: # 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. target_statment = f"filter('^(//|@//)',{target_statment})" - if (len(file_flags) == 1): - # Strip --file= - file_path = file_flags[0][7:] - # Query escape - file_path = file_path.replace("+", "\+").replace("-", "\-") - # For header file we try to find from hdrs and srcs to get the targets - if file_path.endswith('.h'): - # Since attr function can't query with full path, get the file name to query - head, tail = os.path.split(file_path) - target_statment = f"attr(hdrs, '{tail}', {target_statment}) + attr(srcs, '{tail}', {target_statment})" + if file_flags: + file_path = file_flags[0] + if file_path.endswith(_get_files.source_extensions): + target_statment = f"inputs('{re.escape(file_path)}', {target_statment})" else: - target_statment = f"inputs('{file_path}', {target_statment})" + # For header files we try to find from hdrs and srcs to get the targets + # Since attr function can't query with full path, get the file name to query + fname = os.path.basename(file_path) + target_statment = f"let v = {target_statment} in attr(hdrs, '{fname}', $v) + attr(srcs, '{fname}', $v)" aquery_args = [ 'bazel', 'aquery', # Aquery docs if you need em: https://docs.bazel.build/versions/master/aquery.html # Aquery output proto reference: https://github.com/bazelbuild/bazel/blob/master/src/main/protobuf/analysis_v2.proto # One bummer, not described in the docs, is that aquery filters over *all* actions for a given target, rather than just those that would be run by a build to produce a given output. This mostly isn't a problem, but can sometimes surface extra, unnecessary, misconfigured actions. Chris has emailed the authors to discuss and filed an issue so anyone reading this could track it: https://github.com/bazelbuild/bazel/issues/14156. - f"mnemonic('(Objc|Cpp)Compile',{target_statment})", + f"mnemonic('(Objc|Cpp)Compile', {target_statment})", # We switched to jsonproto instead of proto because of https://github.com/bazelbuild/bazel/issues/13404. We could change back when fixed--reverting most of the commit that added this line and tweaking the build file to depend on the target in that issue. That said, it's kinda nice to be free of the dependency, unless (OPTIMNOTE) jsonproto becomes a performance bottleneck compated to binary protos. '--output=jsonproto', # We'll disable artifact output for efficiency, since it's large and we don't use them. Small win timewise, but dramatically less json output from aquery. @@ -1068,6 +1072,18 @@ def _ensure_cwd_is_workspace_root(): for (target, flags) in target_flag_pairs: compile_command_entries.extend(_get_commands(target, flags)) + # --file triggers incremental update of compile_commands.json + if any(arg.startswith('--file=') for arg in sys.argv[1:]) and os.path.isfile('compile_commands.json'): + previous_compile_command_entries = [] + try: + with open('compile_commands.json') as compile_commands_file: + previous_compile_command_entries = json.load(compile_commands_file) + except: + log_warning(">>> Couldn't read previous compile_commands.json. Overwriting instead of merging...") + else: + updated_files = set(entry['file'] for entry in compile_command_entries) + compile_command_entries += [entry for entry in previous_compile_command_entries if entry['file'] not in updated_files] + # Chain output into compile_commands.json with open('compile_commands.json', 'w') as output_file: json.dump( diff --git a/refresh_compile_commands.bzl b/refresh_compile_commands.bzl index b9c5d32..70cf8f5 100644 --- a/refresh_compile_commands.bzl +++ b/refresh_compile_commands.bzl @@ -49,6 +49,8 @@ refresh_compile_commands( # exclude_headers = "external", # Still not fast enough? # Make sure you're specifying just the targets you care about by setting `targets`, above. + # That's still not enough; I'm working on a huge codebase! + # This tool supports a fast, incremental mode that can be used to add/update commands as individual files are opened. If you'd be willing to collaborate on writing a simple editor plugin invokes this tool on file open, please write in! (And see --file flag in refresh.template.py) ``` """ From f4441c3f88949679f46f07b87feab1f668175b0c Mon Sep 17 00:00:00 2001 From: snorlax Date: Fri, 3 Mar 2023 13:44:31 +0800 Subject: [PATCH 6/6] file based filter: Handle header lib --- refresh.template.py | 178 ++++++++++++++++++++++++++------------------ 1 file changed, 106 insertions(+), 72 deletions(-) diff --git a/refresh.template.py b/refresh.template.py index e62f5bc..f7d021a 100644 --- a/refresh.template.py +++ b/refresh.template.py @@ -855,88 +855,122 @@ def _get_commands(target: str, flags: str): Try adding them as flags in your refresh_compile_commands rather than targets. In a moment, Bazel will likely fail to parse.""") - # First, query Bazel's C-family compile actions for that configured target - target_statment = f'deps({target})' - if {exclude_external_sources}: - # 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. - target_statment = f"filter('^(//|@//)',{target_statment})" - if file_flags: - file_path = file_flags[0] - if file_path.endswith(_get_files.source_extensions): - target_statment = f"inputs('{re.escape(file_path)}', {target_statment})" - else: - # For header files we try to find from hdrs and srcs to get the targets - # Since attr function can't query with full path, get the file name to query - fname = os.path.basename(file_path) - target_statment = f"let v = {target_statment} in attr(hdrs, '{fname}', $v) + attr(srcs, '{fname}', $v)" - aquery_args = [ - 'bazel', - 'aquery', - # Aquery docs if you need em: https://docs.bazel.build/versions/master/aquery.html - # Aquery output proto reference: https://github.com/bazelbuild/bazel/blob/master/src/main/protobuf/analysis_v2.proto - # One bummer, not described in the docs, is that aquery filters over *all* actions for a given target, rather than just those that would be run by a build to produce a given output. This mostly isn't a problem, but can sometimes surface extra, unnecessary, misconfigured actions. Chris has emailed the authors to discuss and filed an issue so anyone reading this could track it: https://github.com/bazelbuild/bazel/issues/14156. - f"mnemonic('(Objc|Cpp)Compile', {target_statment})", - # We switched to jsonproto instead of proto because of https://github.com/bazelbuild/bazel/issues/13404. We could change back when fixed--reverting most of the commit that added this line and tweaking the build file to depend on the target in that issue. That said, it's kinda nice to be free of the dependency, unless (OPTIMNOTE) jsonproto becomes a performance bottleneck compated to binary protos. - '--output=jsonproto', - # We'll disable artifact output for efficiency, since it's large and we don't use them. Small win timewise, but dramatically less json output from aquery. - '--include_artifacts=false', - # Shush logging. Just for readability. - '--ui_event_filters=-info', - '--noshow_progress', - # Disable param files, which would obscure compile actions - # Mostly, people enable param files on Windows to avoid the relatively short command length limit. - # For more, see compiler_param_file in https://bazel.build/docs/windows - # They are, however, technically supported on other platforms/compilers. - # That's all well and good, but param files would prevent us from seeing compile actions before the param files had been generated by compilation. - # Since clangd has no such length limit, we'll disable param files for our aquery run. - '--features=-compiler_param_file', - # Disable layering_check during, because it causes large-scale dependence on generated module map files that prevent header extraction before their generation - # For more context, see https://github.com/hedronvision/bazel-compile-commands-extractor/issues/83 - # If https://github.com/clangd/clangd/issues/123 is resolved and we're not doing header extraction, we could try removing this, checking that there aren't erroneous red squigglies squigglies before the module maps are generated. - # If Bazel starts supporting modules (https://github.com/bazelbuild/bazel/issues/4005), we'll probably need to make changes that subsume this. - '--features=-layering_check', - ] + additional_flags - - aquery_process = subprocess.run( - aquery_args, - capture_output=True, - encoding=locale.getpreferredencoding(), - check=False, # We explicitly ignore errors from `bazel aquery` and carry on. - ) + @enum.unique + class Stage(enum.Enum): + INIT = 0 + FINDED = 1 + # For header files we try to find from hdrs and srcs to get the targets + # Since attr function can't query with full path, get the file name to query + FIND_HEADER_FROM_ATTRS = 2 + # If the header can not found from attrs then we processing query from all inputs which includes + FIND_HEADER_FROM_INPUTS = 3 + # if still not found the query the whole tree + FIND_HEADER_FROM_WHO_DEPS = 4 + + def get_next_stage(self): + if self == Stage.FIND_HEADER_FROM_ATTRS: + return Stage.FIND_HEADER_FROM_INPUTS + elif self == Stage.FIND_HEADER_FROM_INPUTS: + return Stage.FIND_HEADER_FROM_WHO_DEPS + else: + return Stage.FINDED + compile_commands = [] + stage = Stage.INIT + while stage != Stage.FINDED: + # First, query Bazel's C-family compile actions for that configured target + target_statment = f'deps({target})' + if {exclude_external_sources}: + # 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. + target_statment = f"filter('^(//|@//)',{target_statment})" + if file_flags: + file_path = file_flags[0] + if file_path.endswith(_get_files.source_extensions): + target_statment = f"inputs('{re.escape(file_path)}', {target_statment})" + else: + if stage == Stage.INIT: + stage = Stage.FIND_HEADER_FROM_ATTRS + if stage == Stage.FIND_HEADER_FROM_ATTRS: + fname = os.path.basename(file_path) + target_statment = f"let v = {target_statment} in attr(hdrs, '{fname}', $v) + attr(srcs, '{fname}', $v)" + elif stage == Stage.FIND_HEADER_FROM_INPUTS: + target_statment = f"inputs('{re.escape(file_path)}', {target_statment})" + elif stage == Stage.FIND_HEADER_FROM_WHO_DEPS: + target_statment = f'deps({target})' + + aquery_args = [ + 'bazel', + 'aquery', + # Aquery docs if you need em: https://docs.bazel.build/versions/master/aquery.html + # Aquery output proto reference: https://github.com/bazelbuild/bazel/blob/master/src/main/protobuf/analysis_v2.proto + # One bummer, not described in the docs, is that aquery filters over *all* actions for a given target, rather than just those that would be run by a build to produce a given output. This mostly isn't a problem, but can sometimes surface extra, unnecessary, misconfigured actions. Chris has emailed the authors to discuss and filed an issue so anyone reading this could track it: https://github.com/bazelbuild/bazel/issues/14156. + f"mnemonic('(Objc|Cpp)Compile', {target_statment})", + # We switched to jsonproto instead of proto because of https://github.com/bazelbuild/bazel/issues/13404. We could change back when fixed--reverting most of the commit that added this line and tweaking the build file to depend on the target in that issue. That said, it's kinda nice to be free of the dependency, unless (OPTIMNOTE) jsonproto becomes a performance bottleneck compated to binary protos. + '--output=jsonproto', + # We'll disable artifact output for efficiency, since it's large and we don't use them. Small win timewise, but dramatically less json output from aquery. + '--include_artifacts=false', + # Shush logging. Just for readability. + '--ui_event_filters=-info', + '--noshow_progress', + # Disable param files, which would obscure compile actions + # Mostly, people enable param files on Windows to avoid the relatively short command length limit. + # For more, see compiler_param_file in https://bazel.build/docs/windows + # They are, however, technically supported on other platforms/compilers. + # That's all well and good, but param files would prevent us from seeing compile actions before the param files had been generated by compilation. + # Since clangd has no such length limit, we'll disable param files for our aquery run. + '--features=-compiler_param_file', + # Disable layering_check during, because it causes large-scale dependence on generated module map files that prevent header extraction before their generation + # For more context, see https://github.com/hedronvision/bazel-compile-commands-extractor/issues/83 + # If https://github.com/clangd/clangd/issues/123 is resolved and we're not doing header extraction, we could try removing this, checking that there aren't erroneous red squigglies squigglies before the module maps are generated. + # If Bazel starts supporting modules (https://github.com/bazelbuild/bazel/issues/4005), we'll probably need to make changes that subsume this. + '--features=-layering_check', + ] + additional_flags + + aquery_process = subprocess.run( + aquery_args, + capture_output=True, + encoding=locale.getpreferredencoding(), + check=False, # We explicitly ignore errors from `bazel aquery` and carry on. + ) - # Filter aquery error messages to just those the user should care about. - missing_targets_warning: typing.Pattern[str] = re.compile(r'(\(\d+:\d+:\d+\) )?(\033\[[\d;]+m)?WARNING: (\033\[[\d;]+m)?Targets were missing from graph:') # Regex handles --show_timestamps and --color=yes. Could use "in" if we ever need more flexibility. - for line in aquery_process.stderr.splitlines(): - # Shush known warnings about missing graph targets. - # The missing graph targets are not things we want to introspect anyway. - # Tracking issue https://github.com/bazelbuild/bazel/issues/13007. - if missing_targets_warning.match(line): - continue + # Filter aquery error messages to just those the user should care about. + missing_targets_warning: typing.Pattern[str] = re.compile(r'(\(\d+:\d+:\d+\) )?(\033\[[\d;]+m)?WARNING: (\033\[[\d;]+m)?Targets were missing from graph:') # Regex handles --show_timestamps and --color=yes. Could use "in" if we ever need more flexibility. + for line in aquery_process.stderr.splitlines(): + # Shush known warnings about missing graph targets. + # The missing graph targets are not things we want to introspect anyway. + # Tracking issue https://github.com/bazelbuild/bazel/issues/13007. + if missing_targets_warning.match(line): + continue - print(line, file=sys.stderr) + print(line, file=sys.stderr) - # Parse proto output from aquery - try: - # object_hook -> SimpleNamespace allows object.member syntax, like a proto, while avoiding the protobuf dependency - parsed_aquery_output = json.loads(aquery_process.stdout, object_hook=lambda d: types.SimpleNamespace(**d)) - except json.JSONDecodeError: - print("Bazel aquery failed. Command:", aquery_args, file=sys.stderr) - log_warning(f">>> Failed extracting commands for {target}\n Continuing gracefully...") - return - - if not getattr(parsed_aquery_output, 'actions', None): # Unifies cases: No actions (or actions list is empty) - log_warning(f""">>> Bazel lists no applicable compile commands for {target} - If this is a header-only library, please instead specify a test or binary target that compiles it (search "header-only" in README.md). - Continuing gracefully...""") - return + # Parse proto output from aquery + try: + # object_hook -> SimpleNamespace allows object.member syntax, like a proto, while avoiding the protobuf dependency + parsed_aquery_output = json.loads(aquery_process.stdout, object_hook=lambda d: types.SimpleNamespace(**d)) + except json.JSONDecodeError: + print("Bazel aquery failed. Command:", aquery_args, file=sys.stderr) + log_warning(f">>> Failed extracting commands for {target}\n Continuing gracefully...") + return + + if not getattr(parsed_aquery_output, 'actions', None): # Unifies cases: No actions (or actions list is empty) + log_warning(f""">>> Bazel lists no applicable compile commands for {target} + If this is a header-only library, please instead specify a test or binary target that compiles it (search "header-only" in README.md). + Continuing gracefully...""") + return - yield from _convert_compile_commands(parsed_aquery_output) + # Don't waste if we resolve other commands + compile_commands.extend(_convert_compile_commands(parsed_aquery_output)) + if file_flags: + if any(command['file'].endswith(file_flags[0]) for command in compile_commands): + stage = Stage.FINDED + stage = stage.get_next_stage() # Log clear completion messages log_success(f">>> Finished extracting commands for {target}") + return compile_commands def _ensure_external_workspaces_link_exists():