From a5e64ce710ed88181cd149eae5be13aba0833c9c Mon Sep 17 00:00:00 2001 From: Marcus Boerger <6420169+helly25@users.noreply.github.com> Date: Mon, 22 May 2023 13:24:30 +0200 Subject: [PATCH 1/6] When working with VSCode full automation works best with a few tweaks that need custom command line options. We add the following options that are all prefixed by `--bcce-` (for Bazel-Compile-Command-Extractor): --bcce-color allows to enable/disable colored output. --bcce-compiler allows to override the compiler (this goes beyond the current apple patching). --bcce-copt allows to pass down additional args to the arg lists. --- README.md | 34 ++++++++++++++++++++++++++++++++ refresh.template.py | 48 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 89a94b7..8541e09 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,18 @@ For now, we'd suggest continuing on to set up `clangd` (below). Thereafter, if y 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.) +### Customizing the `compile_commands.json` generation + +The tool has a few parameters that control output generation: + +* `--bcce-color[=`_no_`]` A boolean flag that enables or disables colored output. This is useful for environments where the color codes are not handled (e.g. VSCode output window). +* `--bcce-compiler=[`_compiler_`]` Allows to override the detected compiler. This is helpful if the compiler found in the editor environment is different from the compiler that should be used for `compile_commands.json`. +* `--bcce-copt=[`_option_`]` Enables passing additional `option`s to arg lists in `compile_commands.json` (can be repeated). + +Similar to optons passed down to the bazel aquery, these options must be separated by `--`. For instance in order to suppress colored output use: + +`bazel run @hedron_compile_commands//:refresh_all -- --bcce-color=no`. + ## Editor Setup — for autocomplete based on `compile_commands.json` @@ -183,6 +195,28 @@ You may need to subsequently reload VSCode [(CMD/CTRL+SHIFT+P)->reload] for the ... and would like these settings to be automatically applied for your teammates, also add the settings to the VSCode *workspace* settings and then check `.vscode/settings.json` into source control. +#### Automating the regeneration of `compile_commands.json` + +There are VSCode plugins that allow to run commands whenever a file is being saved. One such extension is [Run on Save from emeraldwalk](https://github.com/emeraldwalk/vscode-runonsave). + +After installing the plugin add the following to your user `settings.json` file: + +```json +{ + "emeraldwalk.runonsave": { + "commands": [ + { + "match": "WORKSPACE|BUILD|.*[.]bzl|.*[.]bazel", + "isAsync": true, + "cmd": "bazel run @hedron_compile_commands//:refresh_all -- --bcce-color=0 --bcce-compiler=$(which clang))" + } + ] + } +} +``` + +The above only triggers on Bazel's `WORKSPACE`, `BUILD` and other bazel files, as changes to the header files or dependencies require a change in those files. + ### Other Editors If you're using another editor, you'll need to follow the same rough steps as above: [get the latest version of clangd set up to extend the editor](https://clangd.llvm.org/installation.html#editor-plugins) and then supply the same flags as VSCode. We know people have had an easy time setting up this tool with other editors, like Emacs and Vim+YouCompleteMe(YCM), for example. diff --git a/refresh.template.py b/refresh.template.py index 80a2447..64db5b3 100644 --- a/refresh.template.py +++ b/refresh.template.py @@ -49,9 +49,45 @@ class SGR(enum.Enum): FG_BLUE = '\033[0;34m' +@functools.lru_cache(maxsize=None) +def _non_bcce_args(): + """Returns `sys.argv[1:]` with all bcce args removed.""" + return [arg for arg in sys.argv[1:] if not arg.startswith('--bcce-')] + + +@functools.lru_cache(maxsize=None) +def _get_args(arg_name): + """Return all values for `arg_name` in `sys.argv[1:]`.""" + return [arg.lstrip('--' + arg_name).lstrip('=') for arg in sys.argv[1:] if arg.startswith('--' + arg_name)] + + +@functools.lru_cache(maxsize=None) +def _get_last_arg(arg_name, default = None): + """Get last value for `arg_name` in `sys.argv[1:]`.""" + args = _get_args(arg_name) + return args[-1] if args else default + + +@functools.lru_cache(maxsize=None) +def _get_bool_arg(arg_name, default): + """Get the last value for `arg_name` in `sys.argv[1:]` as boolean or `default` value.""" + value = _get_last_arg(arg_name) + if value in ['', '1', 'yes']: + return True + if value in ['0', 'no']: + return False + return default + + def _log_with_sgr(sgr, colored_message, uncolored_message=''): """Log a message to stderr wrapped in an SGR context.""" - print(sgr.value, colored_message, SGR.RESET.value, uncolored_message, sep='', file=sys.stderr, flush=True) + if _get_bool_arg('bcce-color', True): + sgr_start = sgr.value + sgr_reset = SGR.RESET.value + else: + sgr_start = '' + sgr_reset = '' + print(sgr_start, colored_message, sgr_reset, uncolored_message, sep='', file=sys.stderr, flush=True) def log_error(colored_message, uncolored_message=''): @@ -702,6 +738,13 @@ def _get_apple_DEVELOPER_DIR(): # Traditionally stored in DEVELOPER_DIR environment variable, but not provided by Bazel. See https://github.com/bazelbuild/bazel/issues/12852 +def _manual_platform_patch(compile_args: typing.List[str]): + """Apply manual fixes to the compile args.""" + compile_args[0] = _get_last_arg('bcce-compiler', compile_args[0]) + compile_args += _get_args('bcce-copt') + return compile_args + + def _apple_platform_patch(compile_args: typing.List[str]): """De-Bazel the command into something clangd can parse. @@ -773,6 +816,7 @@ def _get_cpp_command_for_files(compile_action): # Patch command by platform compile_action.arguments = _all_platform_patch(compile_action.arguments) compile_action.arguments = _apple_platform_patch(compile_action.arguments) + compile_action.arguments = _manual_platform_patch(compile_action.arguments) # Android and Linux and grailbio LLVM toolchains: Fine as is; no special patching needed. source_files, header_files = _get_files(compile_action) @@ -835,7 +879,7 @@ 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) + _non_bcce_args() # 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.) From 6e5b1f0102fbc300d20132df02ca18da0fc70637 Mon Sep 17 00:00:00 2001 From: Marcus Boerger <6420169+helly25@users.noreply.github.com> Date: Mon, 22 May 2023 16:03:15 +0200 Subject: [PATCH 2/6] In the flag descriptins, the '=' sign should be in the optional part. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8541e09..03cfae5 100644 --- a/README.md +++ b/README.md @@ -148,8 +148,8 @@ Please upgrade or add `# gazelle:exclude external` to the BUILD file in your wor The tool has a few parameters that control output generation: * `--bcce-color[=`_no_`]` A boolean flag that enables or disables colored output. This is useful for environments where the color codes are not handled (e.g. VSCode output window). -* `--bcce-compiler=[`_compiler_`]` Allows to override the detected compiler. This is helpful if the compiler found in the editor environment is different from the compiler that should be used for `compile_commands.json`. -* `--bcce-copt=[`_option_`]` Enables passing additional `option`s to arg lists in `compile_commands.json` (can be repeated). +* `--bcce-compiler[=`_compiler_`]` Allows to override the detected compiler. This is helpful if the compiler found in the editor environment is different from the compiler that should be used for `compile_commands.json`. +* `--bcce-copt[=`_option_`]` Enables passing additional `option`s to arg lists in `compile_commands.json` (can be repeated). Similar to optons passed down to the bazel aquery, these options must be separated by `--`. For instance in order to suppress colored output use: From 6f63be6e2ccfdb6a1f248abbb3614107106de4a9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 22 May 2023 14:06:57 +0000 Subject: [PATCH 3/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- refresh.template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/refresh.template.py b/refresh.template.py index 64db5b3..1482416 100644 --- a/refresh.template.py +++ b/refresh.template.py @@ -743,7 +743,7 @@ def _manual_platform_patch(compile_args: typing.List[str]): compile_args[0] = _get_last_arg('bcce-compiler', compile_args[0]) compile_args += _get_args('bcce-copt') return compile_args - + def _apple_platform_patch(compile_args: typing.List[str]): """De-Bazel the command into something clangd can parse. From 1387bed91426bb64348fcf8399f7e38569c0a02a Mon Sep 17 00:00:00 2001 From: Marcus Boerger <6420169+helly25@users.noreply.github.com> Date: Tue, 23 May 2023 12:24:01 +0200 Subject: [PATCH 4/6] Allow boolean command line options to be prefixed with '--no'. --- README.md | 2 +- refresh.template.py | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 03cfae5..95d405e 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,7 @@ Please upgrade or add `# gazelle:exclude external` to the BUILD file in your wor The tool has a few parameters that control output generation: -* `--bcce-color[=`_no_`]` A boolean flag that enables or disables colored output. This is useful for environments where the color codes are not handled (e.g. VSCode output window). +* `--bcce-color[=`_no_`]` A boolean flag that enables or disables colored output. This is useful for environments where the color codes are not handled (e.g. VSCode output window). The flag also suppots the `--nobcce-color` notation. * `--bcce-compiler[=`_compiler_`]` Allows to override the detected compiler. This is helpful if the compiler found in the editor environment is different from the compiler that should be used for `compile_commands.json`. * `--bcce-copt[=`_option_`]` Enables passing additional `option`s to arg lists in `compile_commands.json` (can be repeated). diff --git a/refresh.template.py b/refresh.template.py index 64db5b3..67c9628 100644 --- a/refresh.template.py +++ b/refresh.template.py @@ -52,26 +52,32 @@ class SGR(enum.Enum): @functools.lru_cache(maxsize=None) def _non_bcce_args(): """Returns `sys.argv[1:]` with all bcce args removed.""" - return [arg for arg in sys.argv[1:] if not arg.startswith('--bcce-')] + return [arg for arg in sys.argv[1:] if not arg.startswith('--bcce-') and not arg.startswith('--nobcce')] @functools.lru_cache(maxsize=None) -def _get_args(arg_name): +def _get_args(arg_name, is_bool=False): """Return all values for `arg_name` in `sys.argv[1:]`.""" - return [arg.lstrip('--' + arg_name).lstrip('=') for arg in sys.argv[1:] if arg.startswith('--' + arg_name)] + args = [] + for arg in sys.argv[1:]: + if arg.startswith('--'+arg_name): + args.append(arg.lstrip('--' + arg_name).lstrip('=')) + if is_bool and arg.startswith('--no' + arg_name): + args.append('no' + arg.lstrip('--no' + arg_name).lstrip('=')) + return args @functools.lru_cache(maxsize=None) -def _get_last_arg(arg_name, default = None): +def _get_last_arg(arg_name, default = None, is_bool=False): """Get last value for `arg_name` in `sys.argv[1:]`.""" - args = _get_args(arg_name) + args = _get_args(arg_name, is_bool) return args[-1] if args else default @functools.lru_cache(maxsize=None) def _get_bool_arg(arg_name, default): """Get the last value for `arg_name` in `sys.argv[1:]` as boolean or `default` value.""" - value = _get_last_arg(arg_name) + value = _get_last_arg(arg_name, is_bool=True) if value in ['', '1', 'yes']: return True if value in ['0', 'no']: From d0a2f332044c6e63658ee661cfb580ebc2a93ca8 Mon Sep 17 00:00:00 2001 From: Marcus Boerger <6420169+helly25@users.noreply.github.com> Date: Fri, 2 Jun 2023 11:49:02 +0200 Subject: [PATCH 5/6] Automatically detect color support with -bcce-color=auto (new default). Add further documentaton to the new flags. --- README.md | 13 ++++++----- refresh.template.py | 55 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 95d405e..834226e 100644 --- a/README.md +++ b/README.md @@ -147,11 +147,12 @@ Please upgrade or add `# gazelle:exclude external` to the BUILD file in your wor The tool has a few parameters that control output generation: -* `--bcce-color[=`_no_`]` A boolean flag that enables or disables colored output. This is useful for environments where the color codes are not handled (e.g. VSCode output window). The flag also suppots the `--nobcce-color` notation. -* `--bcce-compiler[=`_compiler_`]` Allows to override the detected compiler. This is helpful if the compiler found in the editor environment is different from the compiler that should be used for `compile_commands.json`. -* `--bcce-copt[=`_option_`]` Enables passing additional `option`s to arg lists in `compile_commands.json` (can be repeated). +* `--bcce-color[=`_auto_`]` A flag that enables or disables colored output. This is useful for environments where the color codes are not handled (e.g. VSCode OUTPUT window). If the value is `auto` (default), then the environment is checked to determne whetehr colors can be used (both [`NO_COLOR`](https://no-color.org) and `TERM` are checked). To disabe colors the value needs to be `0` or `no`, or the flag specified as `--nobcce-color`. To enable colors the value needs to be `1` or `yes`. +* `--bcce-compiler[=`_compiler_`]` Allows to override the detected compiler. This is helpful if the compiler found in the editor environment is different from the compiler that should be used for `compile_commands.json`. Note that this +may interfere with cross-compilation. If the issue is with `clangd`, then the [clangd compileflags](https://clangd.llvm.org/config#compileflags) can be used so clangd will perform the override for its own use. +* `--bcce-copt[=`_option_`]` Enables passing additional `option`s to arg lists in `compile_commands.json` (can be repeated). Similar to the above, compiler options can be added and removed using clangd's compileflags. -Similar to optons passed down to the bazel aquery, these options must be separated by `--`. For instance in order to suppress colored output use: +Similar to options passed down to `bazel aquery`, these options must be separated by `--`. For instance in order to suppress colored output use: `bazel run @hedron_compile_commands//:refresh_all -- --bcce-color=no`. @@ -206,9 +207,9 @@ After installing the plugin add the following to your user `settings.json` file: "emeraldwalk.runonsave": { "commands": [ { - "match": "WORKSPACE|BUILD|.*[.]bzl|.*[.]bazel", + "match": "(WORKSPACE|BUILD|.*[.]bzl|.*[.]bazel)$", "isAsync": true, - "cmd": "bazel run @hedron_compile_commands//:refresh_all -- --bcce-color=0 --bcce-compiler=$(which clang))" + "cmd": "bazel run @hedron_compile_commands//:refresh_all" } ] } diff --git a/refresh.template.py b/refresh.template.py index 9731823..997319c 100644 --- a/refresh.template.py +++ b/refresh.template.py @@ -68,7 +68,7 @@ def _get_args(arg_name, is_bool=False): @functools.lru_cache(maxsize=None) -def _get_last_arg(arg_name, default = None, is_bool=False): +def _get_last_arg(arg_name, default=None, is_bool=False): """Get last value for `arg_name` in `sys.argv[1:]`.""" args = _get_args(arg_name, is_bool) return args[-1] if args else default @@ -78,16 +78,63 @@ def _get_last_arg(arg_name, default = None, is_bool=False): def _get_bool_arg(arg_name, default): """Get the last value for `arg_name` in `sys.argv[1:]` as boolean or `default` value.""" value = _get_last_arg(arg_name, is_bool=True) - if value in ['', '1', 'yes']: + if value.lower() in ['', '1', 'yes']: return True - if value in ['0', 'no']: + if value.lower() in ['0', 'no']: return False return default +@enum.unique +class COLOR_MODE(enum.Enum): + COLOR_AUTO = -1 + COLOR_NO = 0 + COLOR_YES = 1 + + +# Automatically determine whether colors are supported. +USE_COLOR=COLOR_MODE.COLOR_AUTO + + +def _can_do_color() -> bool: + """Check --bcce-color and env vars for color mode.""" + global USE_COLOR + if USE_COLOR == COLOR_MODE.COLOR_NO: + return False + if USE_COLOR == COLOR_MODE.COLOR_YES: + return True + + if _get_last_arg('bcce-color', default='auto', is_bool=True) != 'auto': + if _get_bool_arg('bcce-color', True): + USE_COLOR=COLOR_MODE.COLOR_YES + return True + else: + USE_COLOR=COLOR_MODE.COLOR_NO + return False + + # Check environment, see https://no-color.org + if "NO_COLOR" in os.environ: + if os.environ["NO_COLOR"] == "0": + USE_COLOR=COLOR_MODE.COLOR_YES + return True + else: + USE_COLOR=COLOR_MODE.COLOR_NO + return False + if ( + hasattr(sys.stdout, "isatty") + and sys.stdout.isatty() + and os.environ.get("TERM") != "dumb" + ): + USE_COLOR=COLOR_MODE.COLOR_YES + return True + else: + USE_COLOR=COLOR_MODE.COLOR_NO + return False + + def _log_with_sgr(sgr, colored_message, uncolored_message=''): """Log a message to stderr wrapped in an SGR context.""" - if _get_bool_arg('bcce-color', True): + if _can_do_color(): sgr_start = sgr.value sgr_reset = SGR.RESET.value else: From b4f15a039bec4243ce118c24b407f0171b9eb64b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 10:02:32 +0000 Subject: [PATCH 6/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- refresh.template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/refresh.template.py b/refresh.template.py index 997319c..be41238 100644 --- a/refresh.template.py +++ b/refresh.template.py @@ -103,7 +103,7 @@ def _can_do_color() -> bool: return False if USE_COLOR == COLOR_MODE.COLOR_YES: return True - + if _get_last_arg('bcce-color', default='auto', is_bool=True) != 'auto': if _get_bool_arg('bcce-color', True): USE_COLOR=COLOR_MODE.COLOR_YES