diff --git a/README.md b/README.md index 89a94b7..834226e 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,19 @@ 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[=`_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 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`. + ## Editor Setup — for autocomplete based on `compile_commands.json` @@ -183,6 +196,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" + } + ] + } +} +``` + +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..be41238 100644 --- a/refresh.template.py +++ b/refresh.template.py @@ -49,9 +49,98 @@ 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-') and not arg.startswith('--nobcce')] + + +@functools.lru_cache(maxsize=None) +def _get_args(arg_name, is_bool=False): + """Return all values for `arg_name` in `sys.argv[1:]`.""" + 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, 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 + + +@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, is_bool=True) + if value.lower() in ['', '1', 'yes']: + return True + 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.""" - print(sgr.value, colored_message, SGR.RESET.value, uncolored_message, sep='', file=sys.stderr, flush=True) + if _can_do_color(): + 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 +791,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 +869,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 +932,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.)