diff --git a/.aspect/config.axl b/.aspect/config.axl index babd6632b..fc1012c51 100644 --- a/.aspect/config.axl +++ b/.aspect/config.axl @@ -362,7 +362,8 @@ def config(ctx: ConfigContext): # Run with: aspect dev test-bazel-flags ctx.tasks.add(bazel_flags_tests) - # bazel_runner.axl dispatch helpers: the bazel_attempt_end/build_end seam. + # bazel_runner.axl: the bazel_attempt_end/build_end dispatch seam, plus + # target_patterns (when the `targets` default is suppressed). # Run with: aspect dev test-bazel-runner ctx.tasks.add(bazel_runner_tests) diff --git a/.buildkite/pipeline.yaml b/.buildkite/pipeline.yaml index 905926452..a8e4b1685 100644 --- a/.buildkite/pipeline.yaml +++ b/.buildkite/pipeline.yaml @@ -329,7 +329,12 @@ steps: aspect test --task:name test-bk # Smoke-tests the new test-task flags. `--target-pattern-file` is forwarded # to Bazel verbatim, so the assertion is just that aspect resolves the file - # and the run completes. `--coverage` is exercised against an sh_test rather + # and the run completes. The IDE/BSP step re-runs the same pattern file in the + # shape the IntelliJ Bazel plugin produces through the tools/bazel wrapper — + # Bazel's own flag spellings behind `--bazel-flag` — and asserts the caller's + # BEP file is actually written. The CLI appends its own + # `--build_event_binary_file` last, and the Bazel option is single-valued. + # `--coverage` is exercised against an sh_test rather # than a rust_test: `toolchains_llvm_bootstrapped` 0.5.2 (pinned in # MODULE.bazel) ships without `libclang_rt.profile.a`, so any rust_test under # --collect_code_coverage fails CppLink on libld/libc shared libs (upstream @@ -356,7 +361,16 @@ steps: echo "# smoke: targets forwarded via --target_pattern_file" > $$PATTERNS echo "//examples/test_states:always_pass" >> $$PATTERNS aspect test --task:name test-bk-target-pattern-file --target-pattern-file=$$PATTERNS - rm -f $$PATTERNS + + echo "--- :aspect: IDE/BSP shape — forwarded --target_pattern_file + --build_event_binary_file" + BEP=$$(mktemp) + aspect build --task:name test-bk-ide-passthrough \ + --bazel-flag=--target_pattern_file=$$PATTERNS \ + --bazel-flag=--build_event_binary_file=$$BEP \ + --bazel-flag=--build_event_binary_file_upload_mode=wait_for_upload_complete \ + --bazel-flag=--tool_tag=bazelbsp:3.2.0 + test -s $$BEP || { echo "caller's --build_event_binary_file was not written"; exit 1; } + rm -f $$BEP $$PATTERNS echo "--- :aspect: aspect test --coverage (+ --coverage-report + --coverage-tool)" REPORT=$$(mktemp) diff --git a/.github/workflows/ci-workflows.yaml b/.github/workflows/ci-workflows.yaml index b6c780195..021ae6a7c 100644 --- a/.github/workflows/ci-workflows.yaml +++ b/.github/workflows/ci-workflows.yaml @@ -613,7 +613,12 @@ jobs: # Smoke-tests the new test-task flags. Ported from the Buildkite # `test-flags-task` step. `--target-pattern-file` is forwarded to Bazel # verbatim, so the assertion is just that aspect resolves the file and the run - # completes. `--coverage` is exercised against an sh_test rather than a + # completes. The IDE/BSP step re-runs the same pattern file in the shape the + # IntelliJ Bazel plugin produces through the tools/bazel wrapper — Bazel's own + # flag spellings behind `--bazel-flag` — and asserts the caller's BEP file is + # actually written. The CLI appends its own `--build_event_binary_file` last, + # and the Bazel option is single-valued. + # `--coverage` is exercised against an sh_test rather than a # rust_test: toolchains_llvm_bootstrapped 0.5.2 (pinned in MODULE.bazel) ships # without libclang_rt.profile.a, so any rust_test under --collect_code_coverage # fails CppLink (upstream hermeticbuild/hermetic-llvm#318, fixed in #468 — not @@ -636,7 +641,16 @@ jobs: echo "# smoke: targets forwarded via --target_pattern_file" > "$PATTERNS" echo "//examples/test_states:always_pass" >> "$PATTERNS" aspect test --task:name test-gha-target-pattern-file --target-pattern-file="$PATTERNS" - rm -f "$PATTERNS" + + echo "--- IDE/BSP shape — forwarded --target_pattern_file + --build_event_binary_file" + BEP=$(mktemp) + aspect build --task:name test-gha-ide-passthrough \ + --bazel-flag=--target_pattern_file="$PATTERNS" \ + --bazel-flag=--build_event_binary_file="$BEP" \ + --bazel-flag=--build_event_binary_file_upload_mode=wait_for_upload_complete \ + --bazel-flag=--tool_tag=bazelbsp:3.2.0 + test -s "$BEP" || { echo "caller's --build_event_binary_file was not written"; exit 1; } + rm -f "$BEP" "$PATTERNS" echo "--- aspect test --coverage (+ --coverage-report + --coverage-tool)" REPORT=$(mktemp) diff --git a/.gitignore b/.gitignore index 391c8a540..71e0e9c6c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,8 @@ site # macOS desktop services files .DS_Store -.claude \ No newline at end of file +.claude + +# IDE project state and the JetBrains Bazel plugin's generated aspect files +.idea/ +.bazelbsp/ \ No newline at end of file diff --git a/crates/aspect-cli/src/builtins/aspect/bazel/build_events.axl b/crates/aspect-cli/src/builtins/aspect/bazel/build_events.axl index f873ba93f..93d8fd920 100644 --- a/crates/aspect-cli/src/builtins/aspect/bazel/build_events.axl +++ b/crates/aspect-cli/src/builtins/aspect/bazel/build_events.axl @@ -12,6 +12,11 @@ once rather than twice. Because that can leave no sink at all — and the Aspect Web UI link keys on an id only a sink mints — a task pairs the collect call with `bes_streamed_by_bazel`, which redirects the link to Bazel's invocation id. +`collect_bes_sinks` also covers the one Bazel-facing BEP flag the CLI would +otherwise clobber: a caller's `--build_event_binary_file` becomes a CLI file sink, +because `Build::spawn` appends the CLI's own path to that single-valued option +last and last wins. See `collect_bes_sinks` for what that changes. + The Aspect login JWT is attached to an Aspect-owned backend's sink metadata when the user has not supplied their own `authorization` header — see `aspect_endpoint_auth.axl` for the host gate and best-effort credential @@ -55,6 +60,22 @@ def bazel_bes_backend(rc, command: str) -> str: return "" return rc.flag_value("--bes_backend", command = command) or "" +def bazel_bep_file(rc, command: str) -> str: + """The `--build_event_binary_file` path the caller asked Bazel to write, or `""`. + + Reads through the run command, so it sees the flag wherever it came from — + `--bazel-flag=--build_event_binary_file=…`, a `.bazelrc`, or an expanded + `--config`. `rc` may be `None` for a caller with no run command to consult. + + Nobody but the caller sets this: the CLI's own BEP file is appended inside + `Build::spawn`, after rc expansion, and never passes through the rc. + + Public so `build_events_test.axl` can assert the resolution. + """ + if rc == None: + return "" + return rc.flag_value("--build_event_binary_file", command = command) or "" + def _drop_bazel_streamed(items: list, uri_of, bazel_backend: str) -> list: """`items` minus those whose `uri_of(item)` names the same endpoint as Bazel's own `bazel_backend`. @@ -106,16 +127,38 @@ def collect_bes_sinks(ctx, bazel_trait, rc, command: str = "build", extra_backen Sinks to the endpoint Bazel's own `--bes_backend` uploads to are dropped from both groups so the invocation is streamed once — see `_drop_bazel_streamed`. + Last comes a file sink for the caller's own `--build_event_binary_file`, when + they asked for one. Bazel cannot write two: the option is single-valued and + `Build::spawn` appends the CLI's path last, so the caller's file would + silently never be written. Writing it as a sink instead re-creates it from + Bazel's own byte stream — the file sinks share the BES reader's raw-bytes + path, so the result is what Bazel would have written. This is what lets IDE / + BSP tooling (the IntelliJ Bazel plugin) drive builds through the + `tools/bazel` wrapper and still get its BEP back. Two consequences worth + knowing: `--build_event_binary_file_upload_mode` no longer governs that file + (the caller's `wait()` on the returned sinks is what completes it, before the + task concludes), and `--build_event_json_file` / `--build_event_text_file` + are untouched — different flags, so Bazel still writes those itself. + The one call a bazel-spawning task makes; pair it with `bes_streamed_by_bazel` to keep the Aspect Web UI link resolvable. The caller `wait()`s the returned sinks after the build. """ bazel_backend = bazel_bes_backend(rc, command) - return ( + sinks = ( collect_bes_from_args(ctx, extra_backends = extra_backends, bazel_backend = bazel_backend) + _drop_bazel_streamed(list(bazel_trait.build_event_sinks), lambda s: s.uri, bazel_backend) ) + # Not routed through `_drop_bazel_streamed`: a file is a local dump, not a + # second upload to an endpoint Bazel already streams to. + bep_file = bazel_bep_file(rc, command) + if bep_file: + trace.event("bes.caller_bep_file", fields = {"path": bep_file, "command": command}) + sinks.append(bazel.build_events.file(path = bep_file)) + + return sinks + def dropped_bes_backends(ctx, bazel_trait, rc, command: str = "build", extra_backends = []) -> list[str]: """The BES endpoints `collect_bes_sinks` skipped because Bazel uploads to them itself, deduped. Same inputs as that call — pass the same arguments. diff --git a/crates/aspect-cli/src/builtins/aspect/bazel/build_events_test.axl b/crates/aspect-cli/src/builtins/aspect/bazel/build_events_test.axl index 6e88f7a0a..302d2458d 100644 --- a/crates/aspect-cli/src/builtins/aspect/bazel/build_events_test.axl +++ b/crates/aspect-cli/src/builtins/aspect/bazel/build_events_test.axl @@ -1,13 +1,14 @@ """Tests for `bazel/build_events.axl` — the BES upload summary line, the gRPC-sink filter shared by the announce/summary helpers, sink collection, and the duplicate-stream drop that keeps the CLI from streaming to an endpoint Bazel's -own `--bes_backend` already uploads to. +own `--bes_backend` already uploads to, and the file-sink tee that keeps a +caller's `--build_event_binary_file` from being silently clobbered. Run with: aspect dev test-bes-sinks """ -load("@aspect//bazel/build_events.axl", "bazel_bes_backend", "bes_results_line", "bes_streamed_by_bazel", "bes_upload_line", "collect_bes_from_args", "collect_bes_sinks", "dropped_bes_backends", "grpc_backends") +load("@aspect//bazel/build_events.axl", "bazel_bep_file", "bazel_bes_backend", "bes_results_line", "bes_streamed_by_bazel", "bes_upload_line", "collect_bes_from_args", "collect_bes_sinks", "dropped_bes_backends", "grpc_backends") def _eq(label, got, want): if got != want: @@ -248,6 +249,44 @@ def _test_collect_bes_sinks(_): [None, "grpcs://bes.other.example.com"], ) +def _test_bazel_bep_file(_): + """`bazel_bep_file` reads the caller's `--build_event_binary_file` off the run + command, so it sees the flag from `--bazel-flag=`, a `.bazelrc`, or a + `--config` expansion alike.""" + _eq("caller asked for one", bazel_bep_file(_fake_rc({"--build_event_binary_file": "/tmp/bep.binpb"}), "build"), "/tmp/bep.binpb") + _eq("flag unset", bazel_bep_file(_fake_rc({}), "build"), "") + _eq("no run command", bazel_bep_file(None, "build"), "") + +def _test_collect_bes_sinks_tees_caller_bep_file(_): + """A caller's `--build_event_binary_file` becomes a trailing file sink, so the + path they asked for is written from Bazel's own byte stream. + + Without it their file is silently empty: the Bazel option is single-valued and + `Build::spawn` appends the CLI's own path last.""" + trait = _fake_trait([struct(uri = "grpcs://bes.other.example.com")]) + + def uris(rc_values): + ctx = _fake_ctx(bes_backends = []) + return [s.uri for s in collect_bes_sinks(ctx, trait, _fake_rc(rc_values))] + + _eq( + "file sink appended after the trait's own", + uris({"--build_event_binary_file": "/tmp/bep.binpb"}), + ["grpcs://bes.other.example.com", None], + ) + _eq("no BEP flag, no extra sink", uris({}), ["grpcs://bes.other.example.com"]) + + # The tee is a local dump, so it survives the duplicate-stream drop that + # suppresses a CLI sink to the endpoint Bazel itself uploads to. + duplicate = "grpcs://bes.acme.aspect.build" + ctx = _fake_ctx(bes_backends = [duplicate]) + rc = _fake_rc({"--bes_backend": duplicate, "--build_event_binary_file": "/tmp/bep.binpb"}) + _eq( + "kept while every gRPC sink is suppressed", + [s.uri for s in collect_bes_sinks(ctx, _fake_trait([struct(uri = duplicate)]), rc)], + [None], + ) + def _test_bes_streamed_by_bazel(_): """True only when Bazel's `--bes_backend` is the runner's own Aspect BES backend — the case where the Web UI still holds the invocation (under @@ -280,6 +319,8 @@ _UNIT_TESTS = [ _test_bazel_bes_backend, _test_collect_drops_duplicate_backends, _test_collect_bes_sinks, + _test_bazel_bep_file, + _test_collect_bes_sinks_tees_caller_bep_file, _test_bes_streamed_by_bazel, ] diff --git a/crates/aspect-cli/src/builtins/aspect/private/lib/bazel_runner.axl b/crates/aspect-cli/src/builtins/aspect/private/lib/bazel_runner.axl index 414cfc6c7..dbe262055 100644 --- a/crates/aspect-cli/src/builtins/aspect/private/lib/bazel_runner.axl +++ b/crates/aspect-cli/src/builtins/aspect/private/lib/bazel_runner.axl @@ -218,6 +218,37 @@ def _emit_terminal(ctx, lifecycle, command, data, exit_code, targets = []): flagged = final_status == "warning", ) +def target_patterns(explicit: bool, cli_targets: list[str], own_pattern_file: str, rc, command: str) -> list[str]: + """The target patterns to place on Bazel's command line. + + Bazel rejects an invocation carrying both command-line patterns and + `--target_pattern_file` — that pair has no last-wins rule — so the single + decision here is whether to materialize the `targets` arg's declared + default (`["..."]`), a pattern the user never typed. Explicit patterns + always pass through: when they conflict with a pattern file, Bazel reports + it, and this runner does not grow a second spelling of that error. + + A pattern file counts no matter which spelling delivered it: aspect's own + `--target-pattern-file` arrives as `own_pattern_file`, while Bazel's + `--target_pattern_file` reaches `rc` — from `--bazel-flag`, `.bazelrc`, or + a `--config` expansion alike, resolved with Bazel's own last-wins parsing. + + Args: + explicit: whether the user actually passed target patterns + (`ctx.args.is_explicit("targets")`) rather than + inheriting the arg's default. + cli_targets: `ctx.args.targets` — never empty (the arg declares + `default = ["..."]`, `minimum = 1`). + own_pattern_file: value of aspect's `--target-pattern-file`, or `""`. + rc: the active `RunCommand`; only `flag_value` is used. + command: Bazel subcommand whose options `rc` resolves. + """ + if explicit: + return cli_targets + if own_pattern_file or rc.flag_value("--target_pattern_file", command = command): + return [] + return cli_targets + def run_bazel_task(ctx: TaskContext, command: str, targets = None) -> TaskConclusion: """Shared impl for build/test tasks. @@ -258,18 +289,14 @@ def run_bazel_task(ctx: TaskContext, command: str, targets = None) -> TaskConclu deployment = deployment_endpoint_flags(ctx) base_flags.extend(deployment.base_flags) - if targets == None: - # --target-pattern-file is only honored when the task declares it. - pattern_file = getattr(ctx.args, "target_pattern_file", "") if hasattr(ctx.args, "target_pattern_file") else "" - if pattern_file: - if ctx.args.is_explicit("targets"): - fail("--target-pattern-file cannot be combined with command-line target patterns") - if not ctx.std.fs.exists(pattern_file): - fail("--target-pattern-file: file not found: " + pattern_file) - base_flags.append("--target_pattern_file=" + pattern_file) - targets = [] - else: - targets = ctx.args.targets + # Patterns resolve after the rc parse below, so `setup` opens with what the + # user stated and the spawn refines it to the resolved list. + if targets != None: + subject = " ".join(targets) + elif ctx.args.is_explicit("targets"): + subject = " ".join(ctx.args.targets) + else: + subject = "" data = init_data() @@ -277,7 +304,7 @@ def run_bazel_task(ctx: TaskContext, command: str, targets = None) -> TaskConclu # parse + `use_rc`, health checks. The active run command drives the # build/test below (a failed health check concludes the surface and fails # the task inside setup_phase). - rc = setup_phase(ctx, lifecycle, " ".join(targets), "bazel_results", data, hc_trait, bazel_trait, command, bazel_base_flags = base_flags) + rc = setup_phase(ctx, lifecycle, subject, "bazel_results", data, hc_trait, bazel_trait, command, bazel_base_flags = base_flags) announce_version, announce_command = resolve_bazel_announce(ctx) # Announced with the first spawn below (the streams belong to the spawn) and @@ -299,6 +326,26 @@ def run_bazel_task(ctx: TaskContext, command: str, targets = None) -> TaskConclu invocation_flags = aspect_endpoint_auth_flags(ctx, rc, command) invocation_flags.extend(bes_results_url_flag(ctx, rc, bes_sinks, deployment, command)) + if targets == None: + # aspect's own `--target-pattern-file` (declared by build/test only) is a + # per-invocation flag, not rc material — and keeping it out of the parsed + # rc is what lets `target_patterns` ask `rc` about the forwarded + # spelling. Forwarded rather than expanded into argv, since the flag + # exists to bypass OS command-line length limits. + own_pattern_file = getattr(ctx.args, "target_pattern_file", "") if hasattr(ctx.args, "target_pattern_file") else "" + if own_pattern_file: + if not ctx.std.fs.exists(own_pattern_file): + fail("--target-pattern-file: file not found: " + own_pattern_file) + invocation_flags.append("--target_pattern_file=" + own_pattern_file) + + targets = target_patterns( + ctx.args.is_explicit("targets"), + ctx.args.targets, + own_pattern_file, + rc, + command, + ) + # The same viewer reaches the CI surfaces through `data` — they key the link on the # sink's own id rather than reading it off the command line. A property of the # resolved flags, so it is restored onto each retry's fresh `data`. @@ -369,6 +416,10 @@ def run_bazel_task(ctx: TaskContext, command: str, targets = None) -> TaskConclu # work, just attempted again. emoji = "🧪" if command == "test" else "🔨", ), + # The resolved patterns are only known after the rc parse, so the + # `setup` surface opened without them; name them here. A pattern-file + # run resolves to no patterns, and `""` means "no change". + subject = " ".join(targets), ) # Disclose what this bazel call was wired with, after the spawn phase diff --git a/crates/aspect-cli/src/builtins/aspect/private/lib/bazel_runner_test.axl b/crates/aspect-cli/src/builtins/aspect/private/lib/bazel_runner_test.axl index 67f750a53..059d0bfd7 100644 --- a/crates/aspect-cli/src/builtins/aspect/private/lib/bazel_runner_test.axl +++ b/crates/aspect-cli/src/builtins/aspect/private/lib/bazel_runner_test.axl @@ -6,11 +6,17 @@ bazel-driving task calls after `build.wait()` / `test.wait()` to fire helpers are short, but they're the contract every task depends on — silent breakage here would skip every Workflows-feature recovery hook. +Also covers `target_patterns`, which decides whether the `targets` arg's +default reaches Bazel — get that wrong and a caller-forwarded +`--target_pattern_file` collides with an invented pattern, which Bazel +rejects outright. + Run with: aspect dev test-bazel-runner """ load("@aspect//bazel.axl", "BazelTrait", "dispatch_bazel_attempt_end", "dispatch_bazel_build_end") +load("./bazel_runner.axl", "target_patterns") def _eq(label, got, want): if got != want: @@ -84,13 +90,65 @@ def _test_attempt_dispatch_does_not_fire_build_end(ctx: TaskContext) -> None: _eq("attempt fired", recorder["attempt"], [("a", 0)]) _eq("build_end not fired", recorder["build_end"], []) +def _fake_rc(values = {}): + """Stand-in for a `RunCommand`: `target_patterns` calls only `flag_value`. + Same shape as the fakes in `bazel_flags_test.axl` / + `deployment_flags_test.axl`, keyed `{command: {flag: value}}` so a test can + pin that the lookup is command-scoped. The real last-wins / `=`-form / + two-token matching lives in `crates/bazelrc` and is tested there.""" + return struct(flag_value = lambda name, command: values.get(command, {}).get(name)) + +def _test_default_reaches_bazel_when_nothing_else_supplies_patterns(_): + """No pattern file anywhere → the arg default is the target list.""" + _eq( + "default pattern forwarded", + target_patterns(False, ["..."], "", _fake_rc(), "build"), + ["..."], + ) + +def _test_forwarded_flag_suppresses_the_default(_): + """`--bazel-flag=--target_pattern_file=…` with no positionals: Bazel must + see the flag and no residue, or it fails the invocation outright.""" + rc = _fake_rc({"build": {"--target_pattern_file": "/tmp/p"}}) + _eq("default suppressed", target_patterns(False, ["..."], "", rc, "build"), []) + +def _test_explicit_patterns_always_reach_bazel(_): + """Explicit patterns alongside a forwarded pattern file are forwarded as-is + so Bazel reports the conflict — the runner owns no second spelling of it.""" + rc = _fake_rc({"build": {"--target_pattern_file": "/tmp/p"}}) + _eq( + "explicit patterns preserved", + target_patterns(True, ["//foo:bar"], "", rc, "build"), + ["//foo:bar"], + ) + +def _test_aspect_own_flag_suppresses_the_default(_): + """aspect's own `--target-pattern-file` is resolved before the rc exists, so + it is passed in directly rather than read back off `rc`.""" + _eq( + "default suppressed", + target_patterns(False, ["..."], "/tmp/p", _fake_rc(), "build"), + [], + ) + +def _test_lookup_is_command_scoped(_): + """A pattern file set for `build` must not suppress the default for a + `test` run — `flag_value` is asked about the running subcommand.""" + rc = _fake_rc({"build": {"--target_pattern_file": "/tmp/p"}}) + _eq("test unaffected", target_patterns(False, ["..."], "", rc, "test"), ["..."]) + def _test_impl(ctx: TaskContext) -> int: _test_attempt_dispatch_empty_is_noop(ctx) _test_build_end_dispatch_empty_is_noop(ctx) _test_attempt_dispatch_invokes_in_order(ctx) _test_build_end_dispatch_invokes_in_order(ctx) _test_attempt_dispatch_does_not_fire_build_end(ctx) - print("bazel_runner_test.axl: OK (5 sections)") + _test_default_reaches_bazel_when_nothing_else_supplies_patterns(ctx) + _test_forwarded_flag_suppresses_the_default(ctx) + _test_explicit_patterns_always_reach_bazel(ctx) + _test_aspect_own_flag_suppresses_the_default(ctx) + _test_lookup_is_command_scoped(ctx) + print("bazel_runner_test.axl: OK (10 sections)") return 0 bazel_runner_tests = task( diff --git a/tools/bazel b/tools/bazel index a6adacba5..baf0f3f88 100755 --- a/tools/bazel +++ b/tools/bazel @@ -161,12 +161,22 @@ ASPECT_VERBS_WITH_BAZEL_FLAGS=( # The closed set of Bazel commands (Bazel 9). A verb here that is NOT in # ASPECT_VERBS_WITH_BAZEL_FLAGS forwards to vanilla bazel untouched (query, -# info, clean, mod, …). Generated by: `bazel help` (the "Available commands" -# list). +# info, clean, mod, …). +# +# Regenerate with: +# bazel help completion | sed -n 's/^BAZEL_COMMAND_LIST="\(.*\)"$/\1/p' +# +# Use `bazel help completion`, NOT `bazel help`: the latter's "Available +# commands" list omits hidden commands, and a verb missing from this table is +# routed to `aspect` instead of bazel. That is how `config` went missing — the +# IntelliJ Bazel plugin calls `bazel config --dump_all --output=json` during +# sync and got `error: unrecognized subcommand 'config'`. A gap here also +# silently degrades the pre-verb disambiguation walk, since KNOWN_VERBS_STR is +# built from this table. BAZEL_VERBS=( - aquery build canonicalize-flags clean coverage cquery dump fetch help - info license mobile-install mod print_action query run shutdown test - vendor version + aquery build canonicalize-flags clean config coverage cquery dump fetch + help info license mobile-install mod print_action query run shutdown + test vendor version ) # Bazel flags that take a SPACE-separated value (e.g. `--config foo` rather diff --git a/tools/bazel.md b/tools/bazel.md index c03231814..0d6bac1bd 100644 --- a/tools/bazel.md +++ b/tools/bazel.md @@ -151,7 +151,11 @@ The fix: aspect sets `ASPECT_CLI_RUNNING=1` on every child `bazel` it spawns. `t Two lists at the top of the script drive every routing decision; edit them in your repo copy: - `ASPECT_VERBS_WITH_BAZEL_FLAGS` — verbs routed to `aspect` with bazel-flag rewriting. Default: `build buildifier delivery format gazelle lint test`. Add your own bazel-flag-aware aspect commands (e.g. a custom task that shells out to bazel) — including `run`, once you're ready for `aspect run` to shadow `bazel run` in your workspace. (`ASPECT_WRAPPER_SKIP=1` bypasses this entirely — everything goes to vanilla bazel.) -- `BAZEL_VERBS` — the closed set of Bazel commands. A verb here that's *not* in the list above forwards to vanilla bazel. A verb in *neither* list is treated as a custom aspect task and routed to aspect verbatim. Update this only if Bazel adds a command. +- `BAZEL_VERBS` — the closed set of Bazel commands. A verb here that's *not* in the list above forwards to vanilla bazel. A verb in *neither* list is treated as a custom aspect task and routed to aspect verbatim. Update this only if Bazel adds a command, and regenerate it from `bazel help completion`'s `BAZEL_COMMAND_LIST` rather than `bazel help` — the latter hides some commands (`config`), and a hidden command missing here gets misrouted to `aspect`: + + ``` + bazel help completion | sed -n 's/^BAZEL_COMMAND_LIST="\(.*\)"$/\1/p' + ``` Plus the embedded Bazel flag lists (`BAZEL_VALUE_FLAGS`, `BAZEL_BOOL_FLAGS`, `BAZEL_SHORT_VALUE_FLAGS`, `BAZEL_SHORT_BOOL_FLAGS`) covered above. There is no aspect-flag list — anything not recognized as a Bazel flag passes through to aspect.