diff --git a/crates/aspect-cli/src/builtins/aspect/bazel.axl b/crates/aspect-cli/src/builtins/aspect/bazel.axl index d83c5ad62..becc10e9b 100644 --- a/crates/aspect-cli/src/builtins/aspect/bazel.axl +++ b/crates/aspect-cli/src/builtins/aspect/bazel.axl @@ -1,24 +1,51 @@ """Public entrypoint for the Bazel library. The implementation lives in -`./bazel/*.axl`; this facade re-exports the `BazelTrait` interface, the Bazel -exit codes, and the retry constants so existing +`./bazel/*.axl`; this facade assembles the `bazel` namespace and re-exports the +`BazelTrait` interface, the Bazel exit codes, and the retry constants so existing `load("@aspect//bazel.axl", ...)` call sites are unchanged. + +`BazelTrait`, `exit_codes`, and the retry constants stay top-level (tasks index +`ctx.traits[BazelTrait]` and read the constants directly). The flag/rc/auth +helpers hang off the `bazel` namespace — stateless functions taking `ctx` and +deriving the active `BazelTrait` from `ctx.traits` themselves (no `trait` +argument to pass). + +A file that also uses the builtin `bazel` global (e.g. `bazel.execution_log`) +must alias this import — `load("@aspect//bazel.axl", bzl = "bazel")` — so the two +`bazel` names don't collide. """ +load( + "@aspect//bazel/flags.axl", + "aspect_endpoint_auth_flags", + "bes_backend_auth_flags", + "remote_cache_auth_flags", + "testonly_config_names", + "testonly_flags_delta", + _announce = "announce", + _core_args = "core_args", + _flags = "flags", + _setup_command = "setup_command", + _sibling_rc = "sibling_rc", + _targets_arg = "targets_arg", +) load( "@aspect//bazel/trait.axl", - _BAZEL_RETRY_ATTEMPTS_DESCRIPTION = "BAZEL_RETRY_ATTEMPTS_DESCRIPTION", - _BazelTrait = "BazelTrait", - _DEFAULT_BAZEL_RETRY_ATTEMPTS = "DEFAULT_BAZEL_RETRY_ATTEMPTS", - _default_retry = "default_retry", - _dispatch_bazel_attempt_end = "dispatch_bazel_attempt_end", - _dispatch_bazel_build_end = "dispatch_bazel_build_end", - _exit_codes = "exit_codes", + "BAZEL_RETRY_ATTEMPTS_DESCRIPTION", + "BazelTrait", + "DEFAULT_BAZEL_RETRY_ATTEMPTS", + "default_retry", + "dispatch_bazel_attempt_end", + "dispatch_bazel_build_end", + "exit_codes", ) -BAZEL_RETRY_ATTEMPTS_DESCRIPTION = _BAZEL_RETRY_ATTEMPTS_DESCRIPTION -BazelTrait = _BazelTrait -DEFAULT_BAZEL_RETRY_ATTEMPTS = _DEFAULT_BAZEL_RETRY_ATTEMPTS -default_retry = _default_retry -dispatch_bazel_attempt_end = _dispatch_bazel_attempt_end -dispatch_bazel_build_end = _dispatch_bazel_build_end -exit_codes = _exit_codes +bazel = namespace( + setup_command = _setup_command, + sibling_rc = _sibling_rc, + core_args = _core_args, + targets_arg = _targets_arg, + TRAITS = [BazelTrait], + flags = _flags, + announce = _announce, + endpoint_auth_flags = aspect_endpoint_auth_flags, +) 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..42117c514 100644 --- a/crates/aspect-cli/src/builtins/aspect/bazel/build_events.axl +++ b/crates/aspect-cli/src/builtins/aspect/bazel/build_events.axl @@ -17,7 +17,7 @@ the user has not supplied their own `authorization` header — see `aspect_endpoint_auth.axl` for the host gate and best-effort credential resolution. (The companion case — BES routed through Bazel via `--bazel-flag=--bes_backend` — is handled by `bes_backend_auth_flags` in -`bazel_flags.axl` instead.) +`bazel/flags.axl` instead.) """ load("../private/lib/aspect_endpoint_auth.axl", "endpoint_host", "headers_have_auth", "is_aspect_host", "resolve_aspect_bearer", "same_bes_endpoint") diff --git a/crates/aspect-cli/src/builtins/aspect/bazel/flags.axl b/crates/aspect-cli/src/builtins/aspect/bazel/flags.axl new file mode 100644 index 000000000..46e1d3b58 --- /dev/null +++ b/crates/aspect-cli/src/builtins/aspect/bazel/flags.axl @@ -0,0 +1,332 @@ +"""Everything about Bazel flags: the `--bazel-flag` / `--bazel-startup-flag` / +`announce-bazel-*` CLI args, the task-facing arg bundles (`core_args` / +`targets_arg`), startup+command flag composition, `.bazelrc` parsing +(`setup_command` / `sibling_rc`), `--config=` expansion, and per-invocation +endpoint auth flags. + +Stateless functions taking `ctx` and deriving the active `BazelTrait` from +`ctx.traits` themselves (no `trait` argument to pass).""" + +load("@aspect//bazel/build_events.axl", "bes_args") +load("@aspect//bazel/trait.axl", "BAZEL_RETRY_ATTEMPTS_DESCRIPTION", "BazelTrait", "DEFAULT_BAZEL_RETRY_ATTEMPTS") +load("../private/lib/aspect_endpoint_auth.axl", "credential_helper_covers_host", "endpoint_host", "headers_have_auth", "needs_aspect_token", "resolve_aspect_bearer") +load("../private/lib/environment.axl", "color_enabled", "detect_ci") + +def _require_arg(ctx, name: str) -> None: + """Fail with a clear contract error when a helper's required CLI arg is + missing from the calling task.""" + if not hasattr(ctx.args, name): + fail( + ("Task is missing required `%s` arg. Tasks that use the `bazel` " + + "flag helpers must declare bazel_flags, bazel_startup_flags, " + + "announce_bazel_rc, announce_bazel_version, and " + + "announce_bazel_command on the task definition.") % name, + ) + +def _announce_value(ctx, value: str) -> bool: + """Resolve one `"auto" | "true" | "false"` announce flag to a bool. `"auto"` + is on under CI and off locally; `"true"`/`"false"` force it.""" + if value == "auto": + return bool(detect_ci(ctx.std.env)) + return value == "true" + +def _resolve_announce(ctx) -> tuple: + """Resolve the per-spawn announce flags to `(version, command)` bools, passed + to `ctx.bazel.build`/`.test` as `announce_version=`/`announce_command=`.""" + _require_arg(ctx, "announce_bazel_version") + _require_arg(ctx, "announce_bazel_command") + return ( + _announce_value(ctx, ctx.args.announce_bazel_version), + _announce_value(ctx, ctx.args.announce_bazel_command), + ) + +_ANNOUNCE_AUTO = "'auto' (default) prints on a recognized CI host and is quiet locally." +_ANNOUNCE_VALUES = ["auto", "true", "false"] + +def _announce_args(rc_phrase: str) -> dict: + """The three `announce_bazel_*` CLI args every Bazel-spawning task declares. + Splat into a task's `args = {...}`; `rc_phrase` names the thing being run + (e.g. `"the build"`, `"the lint build"`).""" + return { + "announce_bazel_rc": args.string( + default = "auto", + values = _ANNOUNCE_VALUES, + long = "announce-bazel-rc", + description = "Print the resolved Bazel `.bazelrc` flags before running %s. %s ASPECT_DEBUG=1 logs the same disclosure regardless." % (rc_phrase, _ANNOUNCE_AUTO), + ), + "announce_bazel_version": args.string( + default = "auto", + values = _ANNOUNCE_VALUES, + long = "announce-bazel-version", + description = "Print the detected Bazel version before each bazel spawn. %s" % _ANNOUNCE_AUTO, + ), + "announce_bazel_command": args.string( + default = "auto", + values = _ANNOUNCE_VALUES, + long = "announce-bazel-command", + description = "Print the exact bazel command line before each bazel spawn. %s" % _ANNOUNCE_AUTO, + ), + } + +def _flag_args(build_phrase: str) -> dict: + """The `bazel_flags` / `bazel_startup_flags` CLI args every Bazel-spawning + task declares. Splat into a task's `args = {...}`; `build_phrase` names the + bazel invocation (e.g. `"the build"`, `"the test invocation"`).""" + return { + "bazel_flags": args.string_list( + long = "bazel-flag", + description = "Additional Bazel flags forwarded to %s (e.g. --bazel-flag=--config=ci --bazel-flag=--keep_going). Repeat the flag to pass multiple." % build_phrase, + ), + "bazel_startup_flags": args.string_list( + long = "bazel-startup-flag", + description = "Additional Bazel startup flags (e.g. --bazel-startup-flag=--host_jvm_args=-Xmx2g). Repeat the flag to pass multiple. Note: changing startup flags restarts the Bazel server.", + ), + } + +def _core_args(phrase: str) -> dict: + """The command-agnostic CLI args every Bazel-spawning task registers (splat + into `task(args)`): the flag/startup/announce args, the BES args, and the + target-pattern-file / retry / cancel controls.""" + return _flag_args(phrase) | _announce_args(phrase) | bes_args() | { + "target_pattern_file": args.string( + default = "", + description = "Read newline-separated target patterns from this file instead of the command line (mirrors Bazel's --target_pattern_file). Blank lines and `#` comments are ignored. Cannot be combined with command-line target patterns.", + ), + "bazel_retry_attempts": args.int( + default = DEFAULT_BAZEL_RETRY_ATTEMPTS, + description = BAZEL_RETRY_ATTEMPTS_DESCRIPTION, + ), + "cancel": args.boolean( + default = False, + description = "Cancel any running Bazel invocation before starting.", + ), + } + +def _targets_arg(verb: str) -> dict: + """The positional target-pattern arg, with command-specific help. Splat into + a task's `args = {...}` alongside `core_args`.""" + return { + "targets": args.positional( + default = ["..."], + minimum = 1, + maximum = 512, + description = "Bazel target patterns to %s. Defaults to '...' which expands to all rule targets in the package at and beneath the current directory. When any pattern is hyphen-led (e.g. excludes like `-//experimental/...`), separate them from flags with `--`: `aspect %s -- //... -//experimental/...`." % (verb, verb), + ), + } + +def _resolve_startup_flags(ctx) -> list: + """`ctx.args.bazel_startup_flags + trait.extra_startup_flags`, then the + `trait.startup_flags(flags)` transform if set. Requires a `bazel_startup_flags` + CLI arg.""" + bazel_trait = ctx.traits[BazelTrait] + _require_arg(ctx, "bazel_startup_flags") + flags = list(ctx.args.bazel_startup_flags) + flags.extend(bazel_trait.extra_startup_flags) + if bazel_trait.startup_flags: + flags = bazel_trait.startup_flags(flags) + return flags + +def _resolve_flags(ctx) -> list: + """`ctx.args.bazel_flags + trait.extra_flags + trait.task_flags(ctx) hooks`, + then the `trait.flags(flags)` transform if set. Requires a `bazel_flags` CLI + arg.""" + bazel_trait = ctx.traits[BazelTrait] + _require_arg(ctx, "bazel_flags") + flags = list(ctx.args.bazel_flags) + flags.extend(bazel_trait.extra_flags) + for hook in bazel_trait.task_flags: + flags.extend(hook(ctx)) + if bazel_trait.flags: + flags = bazel_trait.flags(flags) + return flags + +def _setup_command(ctx, command: str, base_flags: list = []): + """Prepare `ctx.bazel` for upcoming `ctx.bazel.` calls and return the + active `RunCommand`. + + Resolves startup + command flags, parses the workspace `.bazelrc` into a + `RunCommand`, registers it via `ctx.bazel.use_rc`, and emits the parsed-rc + disclosure. Subsequent `ctx.bazel.build`/`test`/`query` use the active run + command — each self-expands and adds `--ignore_all_rc_files`. `base_flags` + are task-injected defaults the user can override via `--bazel-flag=...`. + + Built-in tasks reach this through `phases.setup`, which + calls it before the Bazel health-check hooks so the check inspects the same + active run command every Bazel call uses. + """ + startup_flags = _resolve_startup_flags(ctx) + flags = list(base_flags) + _resolve_flags(ctx) + rc = ctx.bazel.parse_rc( + startup_flags = startup_flags, + flags = flags, + skip_config_if_missing = _requested_config_names(flags), + ) + ctx.bazel.use_rc(rc) + trace.event("bazel.flags", fields = {"command": command, "flags": flags, "startup_flags": startup_flags}) + + _require_arg(ctx, "announce_bazel_rc") + if _announce_value(ctx, ctx.args.announce_bazel_rc): + print(ctx.bazel.announce_rc(rc, command = command, ansi = color_enabled(ctx.std))) + trace.log(ctx.bazel.announce_rc(rc, command = command, ansi = False)) + + return rc + +def _sibling_rc(ctx, startup_transform, command: str = "build", base_flags: list = []): + """A `RunCommand` mirroring `setup_command`'s flag composition, but with the + resolved startup flags run through `startup_transform` (a `list -> list`). + + For a phase that needs its own Bazel server within a task — e.g. delivery + routing its checksum phase and its release build to separate `--output_base`s. + Unlike `setup_command`, this neither registers the rc via `use_rc` nor emits + the disclosure — pass the returned rc as a per-call `rc=` on `ctx.bazel.build`. + """ + startup_flags = startup_transform(_resolve_startup_flags(ctx)) + flags = list(base_flags) + _resolve_flags(ctx) + return ctx.bazel.parse_rc( + startup_flags = startup_flags, + flags = flags, + skip_config_if_missing = _requested_config_names(flags), + ) + +_CONFIG_PREFIX = "--config=" + +def _requested_config_names(flags: list) -> list: + """The `--config=NAME` names in `flags` (items are `str` or `(value, cond)` + tuples). Fed to `parse_rc(skip_config_if_missing=...)` so a sidecar `query` + drops a build-only config instead of erroring.""" + names = [] + for flag in flags: + value = flag[0] if type(flag) == "tuple" else flag + if value.startswith(_CONFIG_PREFIX): + names.append(value[len(_CONFIG_PREFIX):]) + return names + +def _flags_delta(base_flags: list, full_flags: list) -> list: + """The multiset difference `full_flags − base_flags`, preserving `full_flags` + order. + + NOT a tail slice: `common`-section options render as `--default_override=0: + common=…` that sort into the overrides block ahead of the tail, so a + positional slice would drop them and misattribute later base flags. Entries + may be strings or `(flag, condition)` tuples; both compare by value.""" + remaining = list(base_flags) + delta = [] + for flag in full_flags: + if flag in remaining: + remaining.remove(flag) + else: + delta.append(flag) + return delta + +def _expand_config_flags(ctx, extra_flags: list, command: str = "build") -> list: + """Expand the `--config=NAME` references in `extra_flags` against the rc. + + A `RunCommand` in effect adds `--ignore_all_rc_files`, so a `--config=NAME` in + a per-call `flags = [...]` extras list would otherwise hit Bazel un-expanded + and fail. This resolves it exactly as the task's own flags do. Returns ONLY + the options `extra_flags` contributes (the `flags_delta` between expansions + with and without them), so rc defaults aren't re-applied over the user's + `--bazel-flag` overrides.""" + startup_flags = _resolve_startup_flags(ctx) + + base_rc = ctx.bazel.parse_rc(startup_flags = startup_flags, flags = []) + _, base = base_rc.expand_all(command = command) + + full_rc = ctx.bazel.parse_rc( + startup_flags = startup_flags, + flags = list(extra_flags), + skip_config_if_missing = _requested_config_names(list(extra_flags)), + ) + _, full = full_rc.expand_all(command = command) + + return _flags_delta(base, full) + +def _endpoint_auth_flags(ctx, rc, command, endpoint_flag, header_flag, what): + """Per-invocation `=authorization=Bearer ` flags for an + endpoint owned by a configured deployment named by `endpoint_flag`, or `[]`. + + Reads the effective `endpoint_flag` (from `--bazel-flag`, a first-class flag, + or `.bazelrc` — all resolved through `rc`). When a configured deployment owns + its host (see `aspect_endpoint_auth.axl`) and the stream will not already carry + an `authorization` — neither an explicit `=authorization=…` nor a + `--credential_helper` registered for the host — returns the one auth header + flag. Suppressing on a matching credential helper keeps Bazel from adding a + second `authorization` alongside the injected one, which gRPC would comma-join + into a token a JWT-validating proxy rejects. Best-effort: an owned endpoint + with no usable credential warns and returns `[]`. `what` describes the endpoint + in that warning. + """ + endpoint = rc.flag_value(endpoint_flag, command = command) + if not endpoint: + return [] + + # `--credential_helper` carries Bazel's `oldName` alias + # `--experimental_credential_helper`; both populate the same option, so a + # helper configured under either spelling makes Bazel add its own auth. + credential_helpers = ( + rc.flag_values("--credential_helper", command = command) + + rc.flag_values("--experimental_credential_helper", command = command) + ) + already_authenticated = ( + headers_have_auth(rc.flag_values(header_flag, command = command)) or + credential_helper_covers_host(credential_helpers, endpoint_host(endpoint)) + ) + if not needs_aspect_token(ctx, [endpoint], already_authenticated): + return [] + bearer = resolve_aspect_bearer(ctx, what, endpoint) + if not bearer: + return [] + return [header_flag + "=authorization=" + bearer] + +def remote_cache_auth_flags(ctx, rc, command): + """Per-invocation flags attaching the Aspect login JWT to a configured + deployment's `--remote_cache` via `--remote_header`, or `[]`. See + `_endpoint_auth_flags`. Most callers want the combined + `aspect_endpoint_auth_flags`. + """ + return _endpoint_auth_flags(ctx, rc, command, "--remote_cache", "--remote_header", "the remote cache") + +def bes_backend_auth_flags(ctx, rc, command): + """Per-invocation flags attaching the Aspect login JWT to a Bazel-streamed + `--bes_backend` owned by a configured deployment, via `--bes_header`, or `[]`. + See `_endpoint_auth_flags`. + + For the CLI's own BES sink (`--bes-backend`) the JWT is attached to the sink + metadata instead; this covers the case where the user routes BES through + Bazel via `--bazel-flag=--bes_backend=…`. + """ + return _endpoint_auth_flags(ctx, rc, command, "--bes_backend", "--bes_header", "build events") + +def aspect_endpoint_auth_flags(ctx, rc, command): + """Per-invocation flags attaching the Aspect login JWT to every Bazel-facing + endpoint a configured deployment owns: the remote cache (`--remote_header`) + and a Bazel-streamed BES backend (`--bes_header`). The one call every + bazel-spawning task adds to its `flags = [...]`. + """ + return ( + remote_cache_auth_flags(ctx, rc, command) + + bes_backend_auth_flags(ctx, rc, command) + ) + +flags = namespace( + resolve = _resolve_flags, + resolve_startup = _resolve_startup_flags, + args = _flag_args, + expand_config = _expand_config_flags, +) + +announce = namespace( + args = _announce_args, + resolve = _resolve_announce, +) + +# Surfaced for the facade namespace, the invocation handle, and direct unit +# tests. `_`-private to task authors, who reach these through the assembled +# `bazel` namespace. +setup_command = _setup_command +sibling_rc = _sibling_rc +resolve_announce = _resolve_announce +core_args = _core_args +targets_arg = _targets_arg +testonly_flags_delta = _flags_delta +testonly_config_names = _requested_config_names diff --git a/crates/aspect-cli/src/builtins/aspect/build.axl b/crates/aspect-cli/src/builtins/aspect/build.axl index 9cd61b3c8..7ac651b5c 100644 --- a/crates/aspect-cli/src/builtins/aspect/build.axl +++ b/crates/aspect-cli/src/builtins/aspect/build.axl @@ -3,9 +3,8 @@ A default 'build' task that wraps a 'bazel build' command. """ load("@aspect//bazel/build_events.axl", "bes_args") -load("@aspect//bazel.axl", "BAZEL_RETRY_ATTEMPTS_DESCRIPTION", "BazelTrait", "DEFAULT_BAZEL_RETRY_ATTEMPTS") +load("@aspect//bazel.axl", "BAZEL_RETRY_ATTEMPTS_DESCRIPTION", "BazelTrait", "DEFAULT_BAZEL_RETRY_ATTEMPTS", bzl = "bazel") load("./private/lib/artifacts.axl", "artifacts") -load("./private/lib/bazel_flags.axl", "announce_bazel_args", "bazel_flag_args") load("./private/lib/bazel_runner.axl", "run_bazel_task") load("./private/lib/deployment_flags.axl", "deployment_flag_args") load("./private/lib/health_check.axl", "HealthCheckTrait") @@ -37,7 +36,7 @@ build = task( default = False, description = "Cancel any running Bazel invocation before starting the build.", ), - } | bazel_flag_args("the build") | announce_bazel_args("the build") | bes_args() | deployment_flag_args() | repro_flavor_args(), + } | bzl.flags.args("the build") | bzl.announce.args("the build") | bes_args() | deployment_flag_args() | repro_flavor_args(), summary = "Build Bazel targets. Wraps `bazel build` with retries, remote cache/BES wiring, and CI status reporting.", traits = [ BazelTrait, diff --git a/crates/aspect-cli/src/builtins/aspect/cache_diff.axl b/crates/aspect-cli/src/builtins/aspect/cache_diff.axl index 08dd2d2c8..f963889df 100644 --- a/crates/aspect-cli/src/builtins/aspect/cache_diff.axl +++ b/crates/aspect-cli/src/builtins/aspect/cache_diff.axl @@ -60,19 +60,10 @@ both sides or neither). The probe resolves flags from the same rc/config as `aspect test`/`build`, so they align by default. """ -load("@aspect//bazel.axl", "BazelTrait") +load("@aspect//bazel.axl", "BazelTrait", bzl = "bazel") load("@bazel//proto/remote_logging.axl", "remote_logging") load("@std//time.axl", "time") load("./private/lib/ansi.axl", "ansi") -load( - "./private/lib/bazel_flags.axl", - "announce_bazel_args", - "aspect_endpoint_auth_flags", - "bazel_flag_args", - "resolve_bazel_announce", - "resolve_flags", - "resolve_startup_flags", -) load("./private/lib/environment.axl", "color_enabled", "warn") load("./private/lib/remote_executor.axl", "start_dummy_executor", "stop_dummy_executor") load("./private/lib/runnable.axl", "apparent_label") @@ -352,9 +343,9 @@ def _impl(ctx: TaskContext) -> int: timing = {} # seconds per phase — split bazel work vs our analysis at the end bazel_trait = ctx.traits[BazelTrait] - startup_flags = resolve_startup_flags(ctx, bazel_trait) - flags = resolve_flags(ctx, bazel_trait) - announce_version, announce_command = resolve_bazel_announce(ctx) + startup_flags = bzl.flags.resolve_startup(ctx) + flags = bzl.flags.resolve(ctx) + announce_version, announce_command = bzl.announce.resolve(ctx) # One run command for the queries, the precise pre-build, and the probe — all # on the default output base. The probe forces fresh remote lookups via @@ -370,7 +361,7 @@ def _impl(ctx: TaskContext) -> int: # Attach the Aspect login JWT when the cache is an Aspect-owned endpoint; # the probe and pre-build below both hit it. - endpoint_auth_flags = aspect_endpoint_auth_flags(ctx, rc, "test") + endpoint_auth_flags = bzl.endpoint_auth_flags(ctx, rc, "test") # Enumerate + reverse-dep under the same resolved flags as the probe/build, # so a `--config`/`--bazel-flag` that changes package loading or the test set @@ -529,5 +520,5 @@ diff = task( "workspace. Intersected with `tests()`." ), ), - } | bazel_flag_args("the cache-diff probe") | announce_bazel_args("the cache-diff probe"), + } | bzl.flags.args("the cache-diff probe") | bzl.announce.args("the cache-diff probe"), ) diff --git a/crates/aspect-cli/src/builtins/aspect/delivery.axl b/crates/aspect-cli/src/builtins/aspect/delivery.axl index ff54c6467..fc3d789b4 100644 --- a/crates/aspect-cli/src/builtins/aspect/delivery.axl +++ b/crates/aspect-cli/src/builtins/aspect/delivery.axl @@ -150,12 +150,11 @@ User-facing migration guide: https://aspect.build/docs/cli/migration/delivery """ load("@aspect//bazel/build_events.axl", "announce_bes_sinks", "bes_args", "bes_streamed_by_bazel", "collect_bes_sinks", "summarize_bes_upload") -load("@aspect//bazel.axl", "BAZEL_RETRY_ATTEMPTS_DESCRIPTION", "BazelTrait", "DEFAULT_BAZEL_RETRY_ATTEMPTS", "dispatch_bazel_attempt_end", "dispatch_bazel_build_end") +load("@aspect//bazel.axl", "BAZEL_RETRY_ATTEMPTS_DESCRIPTION", "BazelTrait", "DEFAULT_BAZEL_RETRY_ATTEMPTS", "dispatch_bazel_attempt_end", "dispatch_bazel_build_end", bzl = "bazel") load("@bazel//proto/remote_logging.axl", "remote_logging") load("@std//time.axl", "sleep_iter", "time") load("./private/lib/ansi.axl", "ansi") load("./private/lib/artifacts.axl", "artifacts") -load("./private/lib/bazel_flags.axl", "announce_bazel_args", "aspect_endpoint_auth_flags", "expand_config_flags", "resolve_bazel_announce", "sibling_rc") load("./private/lib/bazel_results.axl", "BES_DRAIN_TICK_MS", "MIN_HEARTBEAT_INTERVAL_MS", "archive_bazel_attempt", "bes_get", "now_ms", "process_event", bazel_init_data = "init_data") load("./private/lib/ci.axl", "resolve_build_url") load("./private/lib/delivery_results.axl", delivery_add_result = "add_result", delivery_build_manifest = "build_manifest", delivery_conclusion = "conclusion", delivery_init_data = "init_data") @@ -516,7 +515,7 @@ def _get_output_shas(ctx, bazel_trait, lifecycle, build_events, targets, rc, use output_groups = ["delivery_hash"], ) - endpoint_auth_flags = aspect_endpoint_auth_flags(ctx, rc, "build") + endpoint_auth_flags = bzl.endpoint_auth_flags(ctx, rc, "build") # Phase-1 extras layered on the active run command. Force `=minimal`: # phase 1 reads outputs via BES, never from disk. State is kept (no @@ -525,7 +524,7 @@ def _get_output_shas(ctx, bazel_trait, lifecycle, build_events, targets, rc, use # nonce instead of discarding the graph. phase1_flags = ["--remote_download_outputs=minimal"] + endpoint_auth_flags _t_phase1 = time.monotonic() - announce_version, announce_command = resolve_bazel_announce(ctx) + announce_version, announce_command = bzl.announce.resolve(ctx) # Snapshot pre-loop state so retries reset BES counters without re-running # setup. Restored via clear+update to keep the caller's `data` ref valid. @@ -1135,7 +1134,7 @@ def _delivery_impl(ctx): bazel_trait, "build", ) - announce_version, announce_command = resolve_bazel_announce(ctx) + announce_version, announce_command = bzl.announce.resolve(ctx) delivery_trait = ctx.traits[DeliveryTrait] delivery_trait.delivery_start() @@ -1510,7 +1509,7 @@ def _delivery_impl(ctx): # release build reaches Bazel under --ignore_all_rc_files, so an # unexpanded `--config=release` would fail with "Config value not # defined" (the task's own expanded_flags already went through this). - release_flags = expand_config_flags(ctx, bazel_trait, ctx.args.release_bazel_flags) + release_flags = bzl.flags.expand_config(ctx, ctx.args.release_bazel_flags) # Flag order (last-wins): active run command, then these per-call extras. # `run_tracker.flags` carries every flag needed to build-then-spawn the @@ -1523,7 +1522,7 @@ def _delivery_impl(ctx): # over `=toplevel` (the floor is a minimum, not a ceiling). # `--noremote_upload_local_results` keeps release artifacts out of the # shared remote cache (delivery-specific). - phase3_flags = run_tracker.flags + list(release_flags) + aspect_endpoint_auth_flags(ctx, rc, "build") + [ + phase3_flags = run_tracker.flags + list(release_flags) + bzl.endpoint_auth_flags(ctx, rc, "build") + [ "--noremote_upload_local_results", ] @@ -1534,7 +1533,7 @@ def _delivery_impl(ctx): # server. `--output_user_root` stays shared. phase3_rc = rc if delivery_suffix: - phase3_rc = sibling_rc(ctx, bazel_trait, lambda flags: apply_output_base_suffix(flags, "-" + delivery_suffix)) + phase3_rc = bzl.sibling_rc(ctx, lambda flags: apply_output_base_suffix(flags, "-" + delivery_suffix)) # Redirect stderr to a file: phase-3 re-materializes artifacts (OCI # layers, runfiles) and its progress looks like redundant rebuilds; @@ -1901,7 +1900,7 @@ Currently """ + ansi.ITALIC + "only" + ansi.ITALIC_OFF + """ supported on Aspect maximum = 1000000000, description = "Bazel target labels to deliver.", ), - } | announce_bazel_args("the delivery build phases") | bes_args(), + } | bzl.announce.args("the delivery build phases") | bes_args(), traits = [ BazelTrait, DeliveryTrait, diff --git a/crates/aspect-cli/src/builtins/aspect/format.axl b/crates/aspect-cli/src/builtins/aspect/format.axl index 8ca496db4..047b60483 100644 --- a/crates/aspect-cli/src/builtins/aspect/format.axl +++ b/crates/aspect-cli/src/builtins/aspect/format.axl @@ -16,10 +16,9 @@ Usage: """ load("@aspect//bazel/build_events.axl", "announce_bes_sinks", "bes_args", "bes_streamed_by_bazel", "collect_bes_sinks", "summarize_bes_upload") -load("@aspect//bazel.axl", "BazelTrait", "dispatch_bazel_attempt_end", "dispatch_bazel_build_end") +load("@aspect//bazel.axl", "BazelTrait", "dispatch_bazel_attempt_end", "dispatch_bazel_build_end", bzl = "bazel") load("@std//time.axl", "sleep_iter") load("./private/lib/artifacts.axl", "artifacts") -load("./private/lib/bazel_flags.axl", "announce_bazel_args", "aspect_endpoint_auth_flags", "bazel_flag_args", "resolve_bazel_announce") load("./private/lib/bazel_results.axl", "BES_DRAIN_TICK_MS", "MIN_HEARTBEAT_INTERVAL_MS", "now_ms", "process_event") load("./private/lib/change_detection.axl", "BASE_REF_DESCRIPTION_TAIL") load("./private/lib/environment.axl", "color_enabled", "error", "info", "warn") @@ -309,8 +308,8 @@ def _impl(ctx: TaskContext) -> int | TaskConclusion: # Single pre-task setup phase (status surface, rc parse, health checks); # returns resolved flags (a failed health check fails the task inside). rc = setup_phase(ctx, lifecycle, ctx.args.formatter_target, "format_results", data, hc_trait, bazel_trait) - announce_version, announce_command = resolve_bazel_announce(ctx) - endpoint_auth_flags = aspect_endpoint_auth_flags(ctx, rc, "build") + announce_version, announce_command = bzl.announce.resolve(ctx) + endpoint_auth_flags = bzl.endpoint_auth_flags(ctx, rc, "build") # `rc=` opts into the runinfo aspect: `r.flags` captures the formatter's # executable + `args` attribute + env so `r.spawn` replays them ahead of the @@ -714,7 +713,7 @@ format = task( maximum = 4096, minimum = 0, ), - } | bazel_flag_args("the formatter build") | announce_bazel_args("the formatter build") | bes_args() | repro_flavor_args(include_fix = True), + } | bzl.flags.args("the formatter build") | bzl.announce.args("the formatter build") | bes_args() | repro_flavor_args(include_fix = True), traits = [ BazelTrait, HealthCheckTrait, diff --git a/crates/aspect-cli/src/builtins/aspect/gazelle.axl b/crates/aspect-cli/src/builtins/aspect/gazelle.axl index e8a856eb7..e4e3623e6 100644 --- a/crates/aspect-cli/src/builtins/aspect/gazelle.axl +++ b/crates/aspect-cli/src/builtins/aspect/gazelle.axl @@ -82,10 +82,9 @@ https://github.com/bazel-contrib/bazel-gazelle#lazy-indexing-in-fix-and-update """ load("@aspect//bazel/build_events.axl", "announce_bes_sinks", "bes_args", "bes_streamed_by_bazel", "collect_bes_sinks", "summarize_bes_upload") -load("@aspect//bazel.axl", "BazelTrait", "dispatch_bazel_attempt_end", "dispatch_bazel_build_end") +load("@aspect//bazel.axl", "BazelTrait", "dispatch_bazel_attempt_end", "dispatch_bazel_build_end", bzl = "bazel") load("@std//time.axl", "sleep_iter") load("./private/lib/artifacts.axl", "artifacts") -load("./private/lib/bazel_flags.axl", "announce_bazel_args", "aspect_endpoint_auth_flags", "bazel_flag_args", "resolve_bazel_announce") load("./private/lib/bazel_results.axl", "BES_DRAIN_TICK_MS", "MIN_HEARTBEAT_INTERVAL_MS", "now_ms", "process_event") load("./private/lib/change_detection.axl", "BASE_REF_DESCRIPTION_TAIL") load("./private/lib/environment.axl", "color_enabled", "detect_ci", "error", "info", "warn") @@ -443,8 +442,8 @@ def _impl(ctx: TaskContext) -> int | TaskConclusion: # Single pre-task setup phase (status surface, rc parse, health checks); # returns resolved flags (a failed health check fails the task inside). rc = setup_phase(ctx, lifecycle, ctx.args.gazelle_target, "gazelle_results", data, hc_trait, bazel_trait) - announce_version, announce_command = resolve_bazel_announce(ctx) - endpoint_auth_flags = aspect_endpoint_auth_flags(ctx, rc, "build") + announce_version, announce_command = bzl.announce.resolve(ctx) + endpoint_auth_flags = bzl.endpoint_auth_flags(ctx, rc, "build") # Resolve dirs before the build: gazelle needs them when spawned, the no-op # short-circuit skips the build entirely, and the repro/fix builders read @@ -744,7 +743,7 @@ gazelle = task( maximum = 4096, description = "Directories gazelle should update BUILD files in. When empty (default), gazelle considers every directory in the workspace. Combine with --scope=changed to take the intersection with directories that contain changed files. Useful for scoping to a sub-workspace within a monorepo. CAUTION: under --scope=changed, supplying narrow dirs intersection-filters the derived set. Changed dirs that fall outside the listed roots are dropped, so the BUILD updates those changes would have triggered are silently missed. NOTE: positional dirs only narrow the BUILD-regeneration step. Under gazelle's default `-index=all`, gazelle still traverses every workspace directory, parses every BUILD, and re-determines target exports (often re-parsing source) repo-wide. Pair with `--gazelle-flag=-index=lazy` (plus `# gazelle:go_search` / `# gazelle:proto_search` directives) to also narrow that traversal + parse + indexing work. Optionally also pass `--gazelle-flag=-r=false` to skip recursion on top of that, but some gazelle language extensions are incompatible with non-recursive mode — test against your plugin set first. See https://github.com/bazel-contrib/bazel-gazelle#lazy-indexing-in-fix-and-update.", ), - } | bazel_flag_args("the gazelle build") | announce_bazel_args("the gazelle build") | bes_args() | repro_flavor_args(include_fix = True), + } | bzl.flags.args("the gazelle build") | bzl.announce.args("the gazelle build") | bes_args() | repro_flavor_args(include_fix = True), traits = [ BazelTrait, HealthCheckTrait, diff --git a/crates/aspect-cli/src/builtins/aspect/lint.axl b/crates/aspect-cli/src/builtins/aspect/lint.axl index f56458794..bd564c9a8 100644 --- a/crates/aspect-cli/src/builtins/aspect/lint.axl +++ b/crates/aspect-cli/src/builtins/aspect/lint.axl @@ -17,11 +17,10 @@ Usage: """ load("@aspect//bazel/build_events.axl", "announce_bes_sinks", "bes_args", "bes_streamed_by_bazel", "collect_bes_sinks", "summarize_bes_upload") -load("@aspect//bazel.axl", "BazelTrait", "dispatch_bazel_attempt_end", "dispatch_bazel_build_end") +load("@aspect//bazel.axl", "BazelTrait", "dispatch_bazel_attempt_end", "dispatch_bazel_build_end", bzl = "bazel") load("@std//time.axl", "sleep", "sleep_iter") load("./private/lib/ansi.axl", "ansi") load("./private/lib/artifacts.axl", "artifacts") -load("./private/lib/bazel_flags.axl", "announce_bazel_args", "aspect_endpoint_auth_flags", "bazel_flag_args", "resolve_bazel_announce") load("./private/lib/bazel_results.axl", "BES_DRAIN_TICK_MS", "MIN_HEARTBEAT_INTERVAL_MS", "bb_clientd_root", "bes_get", "file_uri_to_path", "now_ms", "process_event") load("./private/lib/change_detection.axl", "BASE_REF_DESCRIPTION_TAIL") load("./private/lib/environment.axl", "color_enabled", "detect_ci", "error", "warn") @@ -753,7 +752,7 @@ def _impl(ctx: TaskContext) -> int | TaskConclusion: data["lint"]["strategy"] = _strategy_name(strategy) data["target_pattern"] = " ".join(list(ctx.args.targets)) if ctx.args.targets else "" - # Base flags computed before setup_phase, which forwards them to setup_bazel_command. + # Base flags computed before setup_phase, which forwards them to bazel.setup_command. base_flags = ["--remote_download_regex='.*AspectRulesLint.*'"] for aspect in ctx.args.aspects: base_flags.append("--aspects=" + aspect) @@ -778,8 +777,8 @@ def _impl(ctx: TaskContext) -> int | TaskConclusion: # Pre-task setup phase (status surface, rc parse, health checks); returns # resolved flags (a failed health check fails the task inside setup_phase). rc = setup_phase(ctx, lifecycle, data["target_pattern"], "lint_results", data, hc_trait, bazel_trait, "build", bazel_base_flags = base_flags) - announce_version, announce_command = resolve_bazel_announce(ctx) - endpoint_auth_flags = aspect_endpoint_auth_flags(ctx, rc, "build") + announce_version, announce_command = bzl.announce.resolve(ctx) + endpoint_auth_flags = bzl.endpoint_auth_flags(ctx, rc, "build") # Detect changed files BEFORE lint_start hooks so features read the canonical # lint_trait.changed_files. Prefers GitHub PR Files API, falls back to local @@ -1247,7 +1246,7 @@ lint = task( default = ["..."], description = "Bazel target patterns to lint. Defaults to '...' which expands to all rule targets in the package at and beneath the current directory. Pass multiple patterns to combine includes and excludes; when any pattern is hyphen-led, separate them from flags with `--` (e.g. `aspect lint -- //... -//experimental/...`).", ), - } | bazel_flag_args("the lint build") | announce_bazel_args("the lint build") | bes_args() | repro_flavor_args(include_fix = True), + } | bzl.flags.args("the lint build") | bzl.announce.args("the lint build") | bes_args() | repro_flavor_args(include_fix = True), traits = [ BazelTrait, HealthCheckTrait, diff --git a/crates/aspect-cli/src/builtins/aspect/private/lib/aspect_endpoint_auth.axl b/crates/aspect-cli/src/builtins/aspect/private/lib/aspect_endpoint_auth.axl index 81713c8a7..abadb1a84 100644 --- a/crates/aspect-cli/src/builtins/aspect/private/lib/aspect_endpoint_auth.axl +++ b/crates/aspect-cli/src/builtins/aspect/private/lib/aspect_endpoint_auth.axl @@ -11,7 +11,7 @@ deployment hosts, with no built-in suffix or env-var auto-matching. This module owns the host parsing, ownership check, header-auth detection, and the best-effort bearer resolution; the consumers attach the result — -`bazel/build_events.axl` to the CLI sink's metadata, `bazel_flags.axl` to a +`bazel/build_events.axl` to the CLI sink's metadata, `bazel/flags.axl` to a `--bes_header` / `--remote_header` flag. Best-effort: an owned endpoint with no usable credential warns and proceeds unauthenticated rather than failing (the server's own rejection then surfaces downstream). diff --git a/crates/aspect-cli/src/builtins/aspect/private/lib/bazel_flags.axl b/crates/aspect-cli/src/builtins/aspect/private/lib/bazel_flags.axl deleted file mode 100644 index a3a696f03..000000000 --- a/crates/aspect-cli/src/builtins/aspect/private/lib/bazel_flags.axl +++ /dev/null @@ -1,402 +0,0 @@ -"""Helpers for resolving and applying Bazel flags in tasks that compose -`BazelTrait` and `HealthCheckTrait`. - -Tasks that use these helpers MUST declare these CLI args (the helpers -enforce this with a clear error if a task forgets): - - - `bazel_flags` (read by `resolve_flags`) - - `bazel_startup_flags` (read by `resolve_startup_flags`) - - `announce_bazel_rc` (read by `setup_bazel_command`) - - `announce_bazel_version` (read by `resolve_bazel_announce`) - - `announce_bazel_command` (read by `resolve_bazel_announce`) - -Declare the first two via `bazel_flag_args()` and the three -`announce_bazel_*` via `announce_bazel_args()` so every task carries -the same canonical descriptions. - -The three `announce_bazel_*` args are `"auto" | "true" | "false"` -enums; `resolve_announce` maps `"auto"` to on-under-CI. `_rc` gates the -parsed-rc disclosure here; `_version` / `_command` gate the per-spawn -`INFO:` lines and are passed to `ctx.bazel.build` / `.test` by the task -(see `resolve_bazel_announce`). - -Ordering contract: the active `RunCommand` (`ctx.bazel.use_rc`) must be set -before the Bazel health check runs, so the check targets the correct server. -The `setup_phase` helper in `lib/lifecycle.axl` enforces this by calling -`setup_bazel_command` ahead of the `health_check` hooks; the invariant is -also checked at runtime by `assert_ctx_bazel_ready_for_health_check` (see -`lib/health_check.axl`). - - - `setup_bazel_command` — resolves startup flags + command flags, parses - `.bazelrc` into a `RunCommand`, registers it via `ctx.bazel.use_rc`, - emits the parsed-rc disclosure, and returns the run command. Subsequent - `ctx.bazel.build` / `test` / `query` use the active run command — each - self-expands for its own command and adds `--ignore_all_rc_files`. Built-in - tasks reach this via `lib/lifecycle.axl::setup_phase`; call it directly only - for unusual layering. The smaller building blocks below exist mostly for - testing. `requested_config_names` extracts the `--config=` names fed to - `parse_rc(skip_config_if_missing=…)` so a sidecar `query` tolerates a - build-only config. - - - `resolve_startup_flags` — combine `ctx.args.bazel_startup_flags` - with `BazelTrait.extra_startup_flags` + transform. Returns the list. - - - `resolve_flags` — combine CLI bazel-flags + `BazelTrait` additions - + task-time hooks + transform into a single command flag list. - - - `resolve_announce` / `resolve_bazel_announce` — resolve the - `announce_bazel_*` flag values (`"auto"|"true"|"false"`) to bools; - `auto` is on under CI. `resolve_bazel_announce` returns the - `(version, command)` pair tasks pass to `ctx.bazel.build` / `.test`. - - - `remote_cache_auth_flags` / `bes_backend_auth_flags` — per-invocation - `--remote_header` / `--bes_header` flags that attach the Aspect login JWT - when the effective `--remote_cache` / `--bes_backend` is an Aspect-owned - endpoint. Passed as `flags = [...]` to `ctx.bazel.build` / `test` by every - bazel-spawning task. -""" - -load("./aspect_endpoint_auth.axl", "credential_helper_covers_host", "endpoint_host", "headers_have_auth", "needs_aspect_token", "resolve_aspect_bearer") -load("./environment.axl", "color_enabled", "detect_ci") - -def _require_arg(ctx, name): - """Fail with a clear contract error when a helper's required CLI - arg is missing from the calling task.""" - if not hasattr(ctx.args, name): - fail( - ("Task is missing required `%s` arg. Tasks that use " + - "lib/bazel_flags.axl helpers must declare " + - "bazel_flags, bazel_startup_flags, announce_bazel_rc, " + - "announce_bazel_version, and announce_bazel_command on " + - "the task definition.") % name, - ) - -def resolve_announce(ctx, value): - """Resolve an `"auto" | "true" | "false"` announce-flag value to a bool. - - `"auto"` is on under CI and off locally — CI logs are where the bazel - disclosure is useful, while a local run stays quiet. `"true"` / `"false"` - force the choice regardless of environment. - """ - if value == "auto": - return bool(detect_ci(ctx.std.env)) - return value == "true" - -def resolve_bazel_announce(ctx): - """Resolve the per-spawn announce flags to `(version, command)` bools. - - Tasks pass these to `ctx.bazel.build` / `.test` as `announce_version=` / - `announce_command=` so the runtime can print the `INFO:` lines just before - spawning bazel. See `resolve_announce` for the `auto` semantics. - """ - _require_arg(ctx, "announce_bazel_version") - _require_arg(ctx, "announce_bazel_command") - return ( - resolve_announce(ctx, ctx.args.announce_bazel_version), - resolve_announce(ctx, ctx.args.announce_bazel_command), - ) - -_ANNOUNCE_AUTO = "'auto' (default) prints on a recognized CI host and is quiet locally." - -def announce_bazel_args(rc_phrase): - """The three `announce_bazel_*` CLI args every Bazel-spawning task declares. - - Returns a dict to splat into a task's `args = {...}`. Identical across - tasks except for the rc disclosure's noun — pass `rc_phrase` as the thing - being run, e.g. `"the build"`, `"the lint build"`, `"the delivery build - phases"`. The resolved values are read back by `resolve_announce` / - `resolve_bazel_announce` / `setup_bazel_command`. - """ - enum = ["auto", "true", "false"] - return { - "announce_bazel_rc": args.string( - default = "auto", - values = enum, - long = "announce-bazel-rc", - description = "Print the resolved Bazel `.bazelrc` flags before running %s. %s ASPECT_DEBUG=1 logs the same disclosure regardless." % (rc_phrase, _ANNOUNCE_AUTO), - ), - "announce_bazel_version": args.string( - default = "auto", - values = enum, - long = "announce-bazel-version", - description = "Print the detected Bazel version before each bazel spawn. %s" % _ANNOUNCE_AUTO, - ), - "announce_bazel_command": args.string( - default = "auto", - values = enum, - long = "announce-bazel-command", - description = "Print the exact bazel command line before each bazel spawn. %s" % _ANNOUNCE_AUTO, - ), - } - -def bazel_flag_args(build_phrase: str) -> dict: - """The `bazel_flags` / `bazel_startup_flags` CLI args every Bazel-spawning - task declares, with one canonical description each. - - Returns a dict to splat into a task's `args = {...}`, mirroring - `announce_bazel_args`. Identical across tasks except for the noun naming - the bazel invocation — pass `build_phrase` as the thing being run, e.g. - `"the build"`, `"the test invocation"`, `"the gazelle build"`. The resolved - values are read back by `resolve_flags` / `resolve_startup_flags` / - `setup_bazel_command`. - """ - return { - "bazel_flags": args.string_list( - long = "bazel-flag", - description = "Additional Bazel flags forwarded to %s (e.g. --bazel-flag=--config=ci --bazel-flag=--keep_going). Repeat the flag to pass multiple." % build_phrase, - ), - "bazel_startup_flags": args.string_list( - long = "bazel-startup-flag", - description = "Additional Bazel startup flags (e.g. --bazel-startup-flag=--host_jvm_args=-Xmx2g). Repeat the flag to pass multiple. Note: changing startup flags restarts the Bazel server.", - ), - } - -def resolve_startup_flags(ctx, bazel_trait): - """Build the startup flag list: - - ctx.args.bazel_startup_flags + bazel_trait.extra_startup_flags - then bazel_trait.startup_flags(flags) transform if set. - - Requires the task to declare a `bazel_startup_flags` CLI arg. - - Returns: - list[str]: resolved startup flags, ready to apply to - `ctx.bazel.startup_flags`. - """ - _require_arg(ctx, "bazel_startup_flags") - flags = list(ctx.args.bazel_startup_flags) - flags.extend(bazel_trait.extra_startup_flags) - if bazel_trait.startup_flags: - flags = bazel_trait.startup_flags(flags) - return flags - -def resolve_flags(ctx, bazel_trait): - """Build the common Bazel command flag list: - - ctx.args.bazel_flags + bazel_trait.extra_flags - + bazel_trait.task_flags(ctx) hooks - then bazel_trait.flags(flags) transform if set. - - Requires the task to declare a `bazel_flags` CLI arg. - - Returns: - list[str]: resolved command flags. - """ - _require_arg(ctx, "bazel_flags") - flags = list(ctx.args.bazel_flags) - flags.extend(bazel_trait.extra_flags) - for hook in bazel_trait.task_flags: - flags.extend(hook(ctx)) - if bazel_trait.flags: - flags = bazel_trait.flags(flags) - return flags - -def setup_bazel_command(ctx, command, bazel_trait, base_flags = []): - """Prepare `ctx.bazel` for upcoming `ctx.bazel.` calls and return - the active `RunCommand`. - - Resolves startup + command flags, parses the workspace `.bazelrc` into a - `RunCommand`, registers it as the active run command via - `ctx.bazel.use_rc`, and emits the parsed-rc disclosure. Subsequent - `ctx.bazel.build` / `test` / `query` use the active run command — each - self-expands for its own command and adds `--ignore_all_rc_files` - automatically (the on-disk rc is already absorbed). Per-call extras go via - `flags = [...]`; a per-call `rc =` overrides the active one. - - Composed flag order: - - base_flags + ctx.args.bazel_flags + bazel_trait.extra_flags - + bazel_trait.task_flags(ctx) hooks - then bazel_trait.flags(flags) transform - then rc expansion for `--config=…` keys at invocation time. - - Built-in tasks reach this through `lib/lifecycle.axl::setup_phase`, - which calls it before the Bazel health-check hooks so the check inspects the - same active run command every Bazel call uses. - - `skip_config_if_missing` carries the requested `--config` names so a sidecar - `query` (no `build` ancestor) drops a build-only config rather than erroring. - - Args: - ctx: TaskContext. - command: Bazel subcommand the disclosure is rendered for (e.g. `"build"`). - bazel_trait: `BazelTrait` instance from `ctx.traits`. - base_flags: task-injected defaults the user can override via - `--bazel-flag=...`. Examples: lint's `--aspects=...`, spawn tasks' - `--remote_download_outputs=toplevel`. - - Requires the task to declare `bazel_startup_flags`, `bazel_flags`, - and `announce_bazel_rc` CLI args. - - Returns: - RunCommand: the active run command (also registered via `use_rc`). - """ - startup_flags = resolve_startup_flags(ctx, bazel_trait) - flags = list(base_flags) + resolve_flags(ctx, bazel_trait) - rc = ctx.bazel.parse_rc( - startup_flags = startup_flags, - flags = flags, - skip_config_if_missing = requested_config_names(flags), - ) - ctx.bazel.use_rc(rc) - trace.event("bazel.flags", fields = {"command": command, "flags": flags, "startup_flags": startup_flags}) - - _require_arg(ctx, "announce_bazel_rc") - if resolve_announce(ctx, ctx.args.announce_bazel_rc): - print(ctx.bazel.announce_rc(rc, command = command, ansi = color_enabled(ctx.std))) - trace.log(ctx.bazel.announce_rc(rc, command = command, ansi = False)) - - return rc - -def sibling_rc(ctx, bazel_trait, startup_transform, command = "build", base_flags = []): - """Build a `RunCommand` mirroring `setup_bazel_command`'s flag composition, - but with the resolved startup flags run through `startup_transform` first. - - For a phase that needs its own Bazel server within a task — e.g. delivery - routing its aspect-injecting checksum phase and its release build to - separate `--output_base`s. `startup_transform` is a `list[str] -> list[str]` - (typically `apply_output_base_suffix(_, suffix)`). - - Unlike `setup_bazel_command`, this neither registers the rc via `use_rc` nor - emits the parsed-rc disclosure — pass the returned rc as a per-call `rc=` on - the `ctx.bazel.build` calls that should run against it. - """ - startup_flags = startup_transform(resolve_startup_flags(ctx, bazel_trait)) - flags = list(base_flags) + resolve_flags(ctx, bazel_trait) - return ctx.bazel.parse_rc( - startup_flags = startup_flags, - flags = flags, - skip_config_if_missing = requested_config_names(flags), - ) - -_CONFIG_PREFIX = "--config=" - -def requested_config_names(flags: list) -> list: - """The `--config=NAME` names requested in `flags`. - - Each item is a plain `str` or a `(value, version-constraint)` tuple. - Fed to `parse_rc(skip_config_if_missing=...)` so a sidecar `query` drops a - build-only config instead of erroring. Exported for unit testing.""" - names = [] - for flag in flags: - value = flag[0] if type(flag) == "tuple" else flag - if value.startswith(_CONFIG_PREFIX): - names.append(value[len(_CONFIG_PREFIX):]) - return names - -def flags_delta(base_flags: list, full_flags: list) -> list: - """The multiset difference `full_flags − base_flags`, preserving `full_flags` - order: each flag in `full_flags` not accounted for by a `base_flags` entry. - - NOT a tail slice of `full_flags`: `common`-section options render as - `--default_override=0:common=…` that sort into the overrides block ahead of - the tail, not appended after it, so a positional slice would drop them and - misattribute later base flags. Entries may be strings or `(flag, condition)` - tuples; both compare by value. Exported for unit testing.""" - remaining = list(base_flags) - delta = [] - for flag in full_flags: - if flag in remaining: - remaining.remove(flag) - else: - delta.append(flag) - return delta - -def expand_config_flags(ctx, bazel_trait, extra_flags, command = "build"): - """Expand the `--config=NAME` references in `extra_flags` against the rc. - - A `RunCommand` in effect adds `--ignore_all_rc_files`, so any `--config=NAME` - in a per-call `flags = [...]` extras list (e.g. delivery's - `release_bazel_flags`) would otherwise hit Bazel un-expanded and fail with - "Config value 'NAME' is not defined". This resolves `--config=release` to - its `common:release`/`build:release` options, exactly as the task's own - flags do, so the caller can pass the result as per-call extras. - - Returns ONLY the options `extra_flags` contributes — the `flags_delta` - between a `RunCommand` expansion with `extra_flags` and without them. - Returning the whole base again would re-apply rc defaults and clobber the - user's `--bazel-flag` overrides under last-wins. - - Mutates nothing on `ctx.bazel` — the task's `setup_bazel_command` already - registered the active run command and emitted the disclosure. - """ - startup_flags = resolve_startup_flags(ctx, bazel_trait) - - base_rc = ctx.bazel.parse_rc(startup_flags = startup_flags, flags = []) - _, base_flags = base_rc.expand_all(command = command) - - full_rc = ctx.bazel.parse_rc( - startup_flags = startup_flags, - flags = list(extra_flags), - skip_config_if_missing = requested_config_names(list(extra_flags)), - ) - _, full_flags = full_rc.expand_all(command = command) - - return flags_delta(base_flags, full_flags) - -def _endpoint_auth_flags(ctx, rc, command, endpoint_flag, header_flag, what): - """Per-invocation `=authorization=Bearer ` flags for an - endpoint owned by a configured deployment named by `endpoint_flag`, or `[]`. - - Reads the effective `endpoint_flag` (from `--bazel-flag`, a first-class flag, - or `.bazelrc` — all resolved through `rc`). When a configured deployment owns - its host (see `aspect_endpoint_auth.axl`) and the stream will not already carry - an `authorization` — neither an explicit `=authorization=…` nor a - `--credential_helper` registered for the host — returns the one auth header - flag. Suppressing on a matching credential helper keeps Bazel from adding a - second `authorization` alongside the injected one, which gRPC would comma-join - into a token a JWT-validating proxy rejects. Best-effort: an owned endpoint - with no usable credential warns and returns `[]`. `what` describes the endpoint - in that warning. - """ - endpoint = rc.flag_value(endpoint_flag, command = command) - if not endpoint: - return [] - - # `--credential_helper` carries Bazel's `oldName` alias - # `--experimental_credential_helper`; both populate the same option, so a - # helper configured under either spelling makes Bazel add its own auth. - credential_helpers = ( - rc.flag_values("--credential_helper", command = command) + - rc.flag_values("--experimental_credential_helper", command = command) - ) - already_authenticated = ( - headers_have_auth(rc.flag_values(header_flag, command = command)) or - credential_helper_covers_host(credential_helpers, endpoint_host(endpoint)) - ) - if not needs_aspect_token(ctx, [endpoint], already_authenticated): - return [] - bearer = resolve_aspect_bearer(ctx, what, endpoint) - if not bearer: - return [] - return [header_flag + "=authorization=" + bearer] - -def remote_cache_auth_flags(ctx, rc, command): - """Per-invocation flags attaching the Aspect login JWT to a configured - deployment's `--remote_cache` via `--remote_header`, or `[]`. See - `_endpoint_auth_flags`. Most callers want the combined - `aspect_endpoint_auth_flags`. - """ - return _endpoint_auth_flags(ctx, rc, command, "--remote_cache", "--remote_header", "the remote cache") - -def bes_backend_auth_flags(ctx, rc, command): - """Per-invocation flags attaching the Aspect login JWT to a Bazel-streamed - `--bes_backend` owned by a configured deployment, via `--bes_header`, or `[]`. - See `_endpoint_auth_flags`. - - For the CLI's own BES sink (`--bes-backend`) the JWT is attached to the sink - metadata instead; this covers the case where the user routes BES through - Bazel via `--bazel-flag=--bes_backend=…`. - """ - return _endpoint_auth_flags(ctx, rc, command, "--bes_backend", "--bes_header", "build events") - -def aspect_endpoint_auth_flags(ctx, rc, command): - """Per-invocation flags attaching the Aspect login JWT to every Bazel-facing - endpoint a configured deployment owns: the remote cache (`--remote_header`) - and a Bazel-streamed BES backend (`--bes_header`). The one call every - bazel-spawning task adds to its `flags = [...]`. - """ - return ( - remote_cache_auth_flags(ctx, rc, command) + - bes_backend_auth_flags(ctx, rc, command) - ) diff --git a/crates/aspect-cli/src/builtins/aspect/private/lib/bazel_flags_test.axl b/crates/aspect-cli/src/builtins/aspect/private/lib/bazel_flags_test.axl index ddf6b1a60..30dac0fd1 100644 --- a/crates/aspect-cli/src/builtins/aspect/private/lib/bazel_flags_test.axl +++ b/crates/aspect-cli/src/builtins/aspect/private/lib/bazel_flags_test.axl @@ -1,62 +1,64 @@ -"""Tests for `lib/bazel_flags.axl`. +"""Tests for the `bazel` flag/rc/auth helpers (in @aspect//bazel.axl). Run with: aspect dev test-bazel-flags """ -load("./bazel_flags.axl", "announce_bazel_args", "aspect_endpoint_auth_flags", "bes_backend_auth_flags", "flags_delta", "remote_cache_auth_flags", "requested_config_names", "resolve_announce", "resolve_bazel_announce", "resolve_flags", "resolve_startup_flags", "setup_bazel_command", "sibling_rc") +load("@aspect//bazel.axl", "BazelTrait", "aspect_endpoint_auth_flags", "bazel", "testonly_config_names", "testonly_flags_delta") def _eq(label, got, want): if got != want: fail("%s: got %r, want %r" % (label, got, want)) -def _fake_trait(extra_startup = [], startup_transform = None, extra = [], task_flags = [], transform = None): - """Stand-in for `BazelTrait` covering the startup-flag and command-flag fields - the flag helpers read.""" - return struct( +def _trait(extra_startup = [], extra = [], task_flags = []): + """A real `BazelTrait` with just the startup/command-flag fields the helpers + read.""" + return BazelTrait( extra_startup_flags = extra_startup, - startup_flags = startup_transform, extra_flags = extra, task_flags = task_flags, - flags = transform, ) -def _fake_ctx(bazel_startup_flags = [], bazel_flags = []): - """Stand-in for `TaskContext` with the CLI args the helpers read. - `hasattr` returns True for declared fields, so the `_require_arg` - contract check inside the helpers is satisfied.""" - return struct(args = struct( - bazel_startup_flags = bazel_startup_flags, - bazel_flags = bazel_flags, - )) - -def _ctx_with_ci(on_ci): - """Stand-in `ctx` whose `std.env.var` reports a CI marker (or not), - so `resolve_announce`'s `auto` branch can be exercised deterministically.""" +def _fake_ctx(bazel_startup_flags = [], bazel_flags = [], trait = None): + """Stand-in for `TaskContext` with the CLI args and trait map the helpers + read. `ctx.traits[BazelTrait]` resolves against a plain dict — trait types + are hashable, so a dict keyed by `BazelTrait` mimics the real trait map. + `hasattr` returns True for declared args, satisfying `_require_arg`.""" + return struct( + args = struct( + bazel_startup_flags = bazel_startup_flags, + bazel_flags = bazel_flags, + ), + traits = {BazelTrait: trait if trait != None else _trait()}, + ) + +def _announce_ctx(on_ci, version = "auto", command = "auto"): + """Stand-in `ctx` whose `std.env.var` reports a CI marker (or not) and whose + args carry the announce flags, so `bazel.announce.resolve` is exercisable + deterministically.""" env_vars = {"CI": "1"} if on_ci else {} - return struct(std = struct(env = struct(var = lambda name: env_vars.get(name)))) - -def _test_resolve_announce_auto_follows_ci(ctx): - _eq("auto on CI", resolve_announce(_ctx_with_ci(True), "auto"), True) - _eq("auto off CI", resolve_announce(_ctx_with_ci(False), "auto"), False) - -def _test_resolve_announce_explicit_overrides_ci(ctx): - # Explicit true/false ignore the environment. - _eq("true off CI", resolve_announce(_ctx_with_ci(False), "true"), True) - _eq("false on CI", resolve_announce(_ctx_with_ci(True), "false"), False) - -def _test_resolve_bazel_announce_maps_args_to_tuple(_ctx): - # Distinct version/command values catch a swapped or copy-pasted read. - ctx = struct( - std = struct(env = struct(var = lambda name: None)), # off CI - args = struct(announce_bazel_version = "true", announce_bazel_command = "false"), + return struct( + std = struct(env = struct(var = lambda name: env_vars.get(name))), + args = struct(announce_bazel_version = version, announce_bazel_command = command), ) - _eq("(version, command) order", resolve_bazel_announce(ctx), (True, False)) -def _test_announce_bazel_args_shape(_ctx): - """`announce_bazel_args` returns exactly the three flags every - Bazel-spawning task must declare (the `resolve_*` contract).""" - aa = announce_bazel_args("the build") +def _test_announce_auto_follows_ci(ctx): + _eq("auto on CI", bazel.announce.resolve(_announce_ctx(True)), (True, True)) + _eq("auto off CI", bazel.announce.resolve(_announce_ctx(False)), (False, False)) + +def _test_announce_explicit_overrides_ci(ctx): + # Explicit true/false ignore the environment; distinct values catch a + # swapped or copy-pasted (version, command) read. + _eq( + "(version, command) order, explicit", + bazel.announce.resolve(_announce_ctx(True, version = "true", command = "false")), + (True, False), + ) + +def _test_announce_args_shape(ctx): + """`bazel.announce.args` returns exactly the three flags every + Bazel-spawning task must declare (the resolve contract).""" + aa = bazel.announce.args("the build") _eq( "announce arg keys", sorted(aa.keys()), @@ -67,109 +69,81 @@ def _test_startup_cli_only(ctx): """CLI flags pass through when the trait contributes nothing.""" _eq( "startup cli-only", - resolve_startup_flags(_fake_ctx(bazel_startup_flags = ["--client"]), _fake_trait()), + bazel.flags.resolve_startup(_fake_ctx(bazel_startup_flags = ["--client"])), ["--client"], ) def _test_startup_trait_extra_appended(ctx): - """`bazel_trait.extra_startup_flags` is appended after CLI flags.""" + """`trait.extra_startup_flags` is appended after CLI flags.""" _eq( "startup trait extras after CLI", - resolve_startup_flags(_fake_ctx(bazel_startup_flags = ["--client"]), _fake_trait(extra_startup = ["--trait"])), + bazel.flags.resolve_startup(_fake_ctx(bazel_startup_flags = ["--client"], trait = _trait(extra_startup = ["--trait"]))), ["--client", "--trait"], ) -def _test_startup_transform_replaces_list(ctx): - """`bazel_trait.startup_flags` is a list→list transform; the return - value is used as-is, not merged.""" - transform = lambda flags: flags + ["--from-transform"] - _eq( - "startup transform appended", - resolve_startup_flags( - _fake_ctx(bazel_startup_flags = ["--client"]), - _fake_trait(extra_startup = ["--trait"], startup_transform = transform), - ), - ["--client", "--trait", "--from-transform"], - ) - -def _test_startup_transform_can_filter(ctx): - """A transform can also drop or rewrite flags.""" - transform = lambda flags: [f for f in flags if f != "--drop-me"] - _eq( - "startup transform filters", - resolve_startup_flags( - _fake_ctx(bazel_startup_flags = ["--keep"]), - _fake_trait(extra_startup = ["--drop-me"], startup_transform = transform), - ), - ["--keep"], - ) - def _test_startup_input_list_not_mutated(ctx): - """`resolve_startup_flags` must not mutate the caller's CLI flag list + """`bazel.flags.resolve_startup` must not mutate the caller's CLI flag list (`ctx.args.bazel_startup_flags` is a live reference).""" cli = ["--client"] - resolve_startup_flags(_fake_ctx(bazel_startup_flags = cli), _fake_trait(extra_startup = ["--trait"])) + bazel.flags.resolve_startup(_fake_ctx(bazel_startup_flags = cli, trait = _trait(extra_startup = ["--trait"]))) _eq("startup cli list untouched", cli, ["--client"]) def _test_flags_full_chain_order(ctx): """Slot ordering: - ctx.args.bazel_flags + bazel_trait.extra_flags - + task_flags hooks → transform. + ctx.args.bazel_flags + trait.extra_flags + task_flags hooks. Verify each slot lands where the contract promises.""" task_hook = lambda c: ["--from-hook"] - transform = lambda fs: fs + ["--from-transform"] _eq( - "resolve_flags full chain", - resolve_flags( - _fake_ctx(bazel_flags = ["--cli"]), - _fake_trait(extra = ["--trait-extra"], task_flags = [task_hook], transform = transform), + "resolve full chain", + bazel.flags.resolve( + _fake_ctx(bazel_flags = ["--cli"], trait = _trait(extra = ["--trait-extra"], task_flags = [task_hook])), ), - ["--cli", "--trait-extra", "--from-hook", "--from-transform"], + ["--cli", "--trait-extra", "--from-hook"], ) def _test_flags_empty_defaults(ctx): """No CLI flags, no trait additions → empty result.""" _eq( - "resolve_flags empty", - resolve_flags(_fake_ctx(), _fake_trait()), + "resolve empty", + bazel.flags.resolve(_fake_ctx()), [], ) -def _test_setup_bazel_command_applies_to_ctx_bazel(ctx): +def _test_setup_command_applies_to_ctx_bazel(ctx): """End-to-end check on a real `ctx`: - - the returned `RunCommand` is registered as the active rc - (`ctx.bazel.active_rc()`); - - the caller's `base_flags` appear in its `build` expansion - (rc expansion may interleave them, so we don't assert position); + - the returned `RunCommand` is registered as the active rc; + - the caller's `base_flags` appear in its `build` expansion; - the trait's `extra_startup_flags` land on the rc's startup flags; - - `--ignore_all_rc_files` is present so the bazel subprocess - doesn't re-parse rc. + - `--ignore_all_rc_files` is present. The workspace `.bazelrc` is parsed as a side effect, which is fine for a dev test.""" - sentinel = "/tmp/test-setup-bazel-command-sentinel" - bazel_trait = _fake_trait(extra_startup = ["--output_base=" + sentinel]) + sentinel = "/tmp/test-setup-command-sentinel" + + # Inject a real trait into the live trait map and let setup_command read it + # back via ctx.traits[BazelTrait] — exercises the production (no-override) path. + ctx.traits[BazelTrait] = _trait(extra_startup = ["--output_base=" + sentinel]) - rc = setup_bazel_command(ctx, "build", bazel_trait, base_flags = ["--from-base"]) + rc = bazel.setup_command(ctx, "build", base_flags = ["--from-base"]) if rc != ctx.bazel.active_rc(): - fail("setup_bazel_command — returned rc not registered as active") + fail("setup_command — returned rc not registered as active") if "--from-base" not in rc.expand(command = "build"): - fail("setup_bazel_command — base_flag not in build expansion: %r" % rc.expand(command = "build")) + fail("setup_command — base_flag not in build expansion: %r" % rc.expand(command = "build")) startup = list(rc.startup_flags()) if ("--output_base=" + sentinel) not in startup: - fail("setup_bazel_command — trait --output_base not on rc startup flags: %r" % startup) + fail("setup_command — trait --output_base not on rc startup flags: %r" % startup) if "--ignore_all_rc_files" not in startup: - fail("setup_bazel_command — --ignore_all_rc_files not present: %r" % startup) + fail("setup_command — --ignore_all_rc_files not present: %r" % startup) def _test_sibling_rc_transforms_startup(ctx): - """`sibling_rc` mirrors setup_bazel_command's flags but runs the startup + """`bazel.sibling_rc` mirrors setup_command's flags but runs the startup flags through the transform, and does NOT register itself as active.""" base = "/tmp/test-sibling-rc-base" - bazel_trait = _fake_trait(extra_startup = ["--output_base=" + base], extra = ["--from-extra"]) - active = setup_bazel_command(ctx, "build", bazel_trait) + ctx.traits[BazelTrait] = _trait(extra_startup = ["--output_base=" + base], extra = ["--from-extra"]) + active = bazel.setup_command(ctx, "build") - sib = sibling_rc(ctx, bazel_trait, lambda flags: [f + "-checksum" if f.startswith("--output_base=") else f for f in flags]) + sib = bazel.sibling_rc(ctx, lambda flags: [f + "-checksum" if f.startswith("--output_base=") else f for f in flags]) startup = list(sib.startup_flags()) if ("--output_base=" + base + "-checksum") not in startup: @@ -183,57 +157,54 @@ def _test_sibling_rc_transforms_startup(ctx): if ctx.bazel.active_rc() != active: fail("sibling_rc — must not change the active run command") -def _test_requested_config_names(ctx): - """Extracts `--config=NAME` names from plain and version-gated flags, - ignoring everything else.""" - _eq("no configs", requested_config_names(["--keep_going", "--show_result=20"]), []) - _eq("plain config", requested_config_names(["--config=release", "--keep_going"]), ["release"]) - _eq("tuple config", requested_config_names([("--config=ci", ">=8.0.0")]), ["ci"]) +def _test_config_names(ctx): + """`testonly_config_names` extracts `--config=NAME` names from plain and + version-gated flags, ignoring everything else.""" + _eq("no configs", testonly_config_names(["--keep_going", "--show_result=20"]), []) + _eq("plain config", testonly_config_names(["--config=release", "--keep_going"]), ["release"]) + _eq("tuple config", testonly_config_names([("--config=ci", ">=8.0.0")]), ["ci"]) _eq( "mixed", - requested_config_names(["--config=a", "--foo", ("--config=b", "<7"), "--config=c"]), + testonly_config_names(["--config=a", "--foo", ("--config=b", "<7"), "--config=c"]), ["a", "b", "c"], ) def _test_flags_delta(ctx): - """`flags_delta` returns the multiset difference full − base in full's order. + """`testonly_flags_delta` returns the multiset difference full − base in + full's order. The regression case: a `--config` whose section options render as `--default_override=0:common=…` sort into the overrides block AHEAD of the - base's trailing regular flags, so the contribution is not a tail slice. A - positional slice would drop the leading override (this is how a - `common:release --stamp` silently vanished) and misattribute a base flag.""" + base's trailing regular flags, so the contribution is not a tail slice.""" # Plain suffix case: contribution appended after an unchanged base. - _eq("suffix", flags_delta(["--a", "--b"], ["--a", "--b", "--c"]), ["--c"]) + _eq("suffix", testonly_flags_delta(["--a", "--b"], ["--a", "--b", "--c"]), ["--c"]) - # Interleaved override ahead of a trailing base flag: --stamp is added at the - # front of the overrides block, --show_result=20 is shared base. + # Interleaved override ahead of a trailing base flag. base = ["--default_override=0:common=--foo", "--show_result=20"] full = [ "--default_override=0:common=--foo", "--default_override=0:common=--stamp", "--show_result=20", ] - _eq("interleaved override", flags_delta(base, full), ["--default_override=0:common=--stamp"]) + _eq("interleaved override", testonly_flags_delta(base, full), ["--default_override=0:common=--stamp"]) # Repeated flag: only the extra occurrence is in the delta. - _eq("multiset", flags_delta(["--x"], ["--x", "--x"]), ["--x"]) + _eq("multiset", testonly_flags_delta(["--x"], ["--x", "--x"]), ["--x"]) # Version-gated tuples compare by value. _eq( "tuple", - flags_delta([("--a", ">=8")], [("--a", ">=8"), ("--b", "<7")]), + testonly_flags_delta([("--a", ">=8")], [("--a", ">=8"), ("--b", "<7")]), [("--b", "<7")], ) # No extras → empty delta. - _eq("empty", flags_delta(["--a", "--b"], ["--a", "--b"]), []) + _eq("empty", testonly_flags_delta(["--a", "--b"], ["--a", "--b"]), []) def _fake_rc(values = {}): """Stand-in `RunCommand` over a `{flag: value}` map for single-value flags - (`--remote_cache`/`--bes_backend`) and a `{flag: [values]}` map for - repeatable flags (`--remote_header`/`--bes_header`). `command`/`version` + and a `{flag: [values]}` map for repeatable flags. `command`/`version` kwargs are accepted and ignored.""" return struct( flag_value = lambda name, command = None, version = None: values.get(name), @@ -242,9 +213,8 @@ def _fake_rc(values = {}): def _fake_auth_ctx(access_token = "", owned_hosts = []): """Stand-in `ctx` for the auth path: `deployment_for_host` reports a host as - owned (returning its host as the deployment name) iff it is in `owned_hosts`, - `credentials` returns a fake login (or None when no token), and `std` reports - non-TTY so `warn` stays quiet-but-valid.""" + owned iff it is in `owned_hosts`, `credentials` returns a fake login (or None + when no token), and `std` reports non-TTY so `warn` stays quiet-but-valid.""" creds = struct(access_token = access_token) if access_token else None owned = {h: h for h in owned_hosts} return struct( @@ -263,43 +233,51 @@ _TOK = "fake-access-token" _CACHE = "remote.acme.aspect.build" _BES = "bes.acme.aspect.build" -def _test_remote_cache_auth_flags(_): - """`--remote_header` auth only for a cache owned by a configured deployment, - lacking a user authorization header, and only when a credential resolves.""" - ctx = _fake_auth_ctx(access_token = _TOK, owned_hosts = [_CACHE]) +def _test_auth_flags(ctx): + """`aspect_endpoint_auth_flags` attaches `--remote_header`/`--bes_header` only for an + endpoint owned by a configured deployment, lacking a user authorization + header, and only when a credential resolves. Covers the cache path, the BES + path, and their combination through the one public entry point.""" + both = _fake_auth_ctx(access_token = _TOK, owned_hosts = [_CACHE, _BES]) - _eq("no --remote_cache", remote_cache_auth_flags(ctx, _fake_rc(), "build"), []) + _eq("no endpoints", aspect_endpoint_auth_flags(both, _fake_rc(), "build"), []) _eq( - "unowned cache", - remote_cache_auth_flags(ctx, _fake_rc({"--remote_cache": "grpcs://remote.buildbuddy.io"}), "build"), + "unowned endpoints", + aspect_endpoint_auth_flags(both, _fake_rc({"--remote_cache": "grpcs://remote.buildbuddy.io", "--bes_backend": "grpcs://bes.buildbuddy.io"}), "build"), [], ) _eq( - "owned cache + creds", - remote_cache_auth_flags(ctx, _fake_rc({"--remote_cache": "grpcs://" + _CACHE}), "build"), + "owned cache only", + aspect_endpoint_auth_flags(both, _fake_rc({"--remote_cache": "grpcs://" + _CACHE}), "build"), ["--remote_header=authorization=Bearer " + _TOK], ) _eq( - "owned cache + user authorization header", - remote_cache_auth_flags( - ctx, - _fake_rc({"--remote_cache": "grpcs://" + _CACHE, "--remote_header": ["authorization=Bearer user"]}), - "build", - ), + "owned bes only", + aspect_endpoint_auth_flags(both, _fake_rc({"--bes_backend": "grpcs://" + _BES}), "build"), + ["--bes_header=authorization=Bearer " + _TOK], + ) + _eq( + "owned cache + bes together", + aspect_endpoint_auth_flags(both, _fake_rc({"--remote_cache": "grpcs://" + _CACHE, "--bes_backend": "grpcs://" + _BES}), "build"), + ["--remote_header=authorization=Bearer " + _TOK, "--bes_header=authorization=Bearer " + _TOK], + ) + _eq( + "user authorization header skips", + aspect_endpoint_auth_flags(both, _fake_rc({"--remote_cache": "grpcs://" + _CACHE, "--remote_header": ["authorization=Bearer user"]}), "build"), [], ) _eq( "owned cache + credential helper for host", - remote_cache_auth_flags( - ctx, + aspect_endpoint_auth_flags( + both, _fake_rc({"--remote_cache": "grpcs://" + _CACHE, "--credential_helper": ["*.acme.aspect.build=/usr/bin/helper"]}), "build", ), [], ) _eq( - "owned cache + not logged in", - remote_cache_auth_flags( + "not logged in", + aspect_endpoint_auth_flags( _fake_auth_ctx(owned_hosts = [_CACHE]), _fake_rc({"--remote_cache": "grpcs://" + _CACHE}), "build", @@ -307,33 +285,12 @@ def _test_remote_cache_auth_flags(_): [], ) -def _test_bes_backend_auth_flags(_): - """`--bes_header` auth for a Bazel-streamed `--bes_backend` owned by a - configured deployment, same gate as the cache path but on the BES flag pair.""" - ctx = _fake_auth_ctx(access_token = _TOK, owned_hosts = [_BES]) - - _eq("unowned bes", bes_backend_auth_flags(ctx, _fake_rc({"--bes_backend": "grpcs://bes.buildbuddy.io"}), "build"), []) - _eq( - "owned bes + creds", - bes_backend_auth_flags(ctx, _fake_rc({"--bes_backend": "grpcs://" + _BES}), "build"), - ["--bes_header=authorization=Bearer " + _TOK], - ) - _eq( - "owned bes + user authorization header", - bes_backend_auth_flags( - ctx, - _fake_rc({"--bes_backend": "grpcs://" + _BES, "--bes_header": ["Authorization=Bearer user"]}), - "build", - ), - [], - ) - # A `--credential_helper` registered for the BES host means Bazel will add its # own `authorization`, so the CLI must not inject a second one (issue #1325). _eq( "owned bes + credential helper for host", - bes_backend_auth_flags( - ctx, + aspect_endpoint_auth_flags( + both, _fake_rc({"--bes_backend": "grpcs://" + _BES, "--credential_helper": [_BES + "=/usr/bin/helper"]}), "build", ), @@ -341,8 +298,8 @@ def _test_bes_backend_auth_flags(_): ) _eq( "owned bes + unscoped credential helper", - bes_backend_auth_flags( - ctx, + aspect_endpoint_auth_flags( + both, _fake_rc({"--bes_backend": "grpcs://" + _BES, "--credential_helper": ["/usr/bin/helper"]}), "build", ), @@ -353,8 +310,8 @@ def _test_bes_backend_auth_flags(_): # Bazel option, so it must suppress the injected auth too. _eq( "owned bes + experimental credential helper for host", - bes_backend_auth_flags( - ctx, + aspect_endpoint_auth_flags( + both, _fake_rc({"--bes_backend": "grpcs://" + _BES, "--experimental_credential_helper": [_BES + "=/usr/bin/helper"]}), "build", ), @@ -364,57 +321,36 @@ def _test_bes_backend_auth_flags(_): # A credential helper for a different host does not suppress the injected auth. _eq( "owned bes + credential helper for other host still injects", - bes_backend_auth_flags( - ctx, + aspect_endpoint_auth_flags( + both, _fake_rc({"--bes_backend": "grpcs://" + _BES, "--credential_helper": ["remote.buildbuddy.io=/usr/bin/helper"]}), "build", ), ["--bes_header=authorization=Bearer " + _TOK], ) -def _test_aspect_endpoint_auth_flags(_): - """The combined helper concatenates cache + BES auth; an owned cache and an - owned bes backend in the same invocation each get their header.""" - ctx = _fake_auth_ctx(access_token = _TOK, owned_hosts = [_CACHE, _BES]) - _eq( - "cache + bes together", - aspect_endpoint_auth_flags( - ctx, - _fake_rc({"--remote_cache": "grpcs://" + _CACHE, "--bes_backend": "grpcs://" + _BES}), - "build", - ), - ["--remote_header=authorization=Bearer " + _TOK, "--bes_header=authorization=Bearer " + _TOK], - ) - # The helpers fail with a clear message when a calling task omits the required -# args (`bazel_flags`, `bazel_startup_flags`, the `announce_bazel_*` trio). -# Happy-path coverage is implicit in every other subtest — the test task -# declares them all. Failure-path coverage would require a second task without -# the args, which isn't worth the test plumbing. +# args. Happy-path coverage is implicit in every other subtest — the test task +# declares them all. def _test_impl(ctx): _test_startup_cli_only(ctx) _test_startup_trait_extra_appended(ctx) - _test_startup_transform_replaces_list(ctx) - _test_startup_transform_can_filter(ctx) _test_startup_input_list_not_mutated(ctx) _test_flags_full_chain_order(ctx) _test_flags_empty_defaults(ctx) - _test_resolve_announce_auto_follows_ci(ctx) - _test_resolve_announce_explicit_overrides_ci(ctx) - _test_resolve_bazel_announce_maps_args_to_tuple(ctx) - _test_announce_bazel_args_shape(ctx) - _test_requested_config_names(ctx) + _test_announce_auto_follows_ci(ctx) + _test_announce_explicit_overrides_ci(ctx) + _test_announce_args_shape(ctx) + _test_config_names(ctx) _test_flags_delta(ctx) - _test_remote_cache_auth_flags(ctx) - _test_bes_backend_auth_flags(ctx) - _test_aspect_endpoint_auth_flags(ctx) + _test_auth_flags(ctx) # Run last: these register a real active rc on `ctx.bazel`, so keeping them # after the pure subtests prevents bleed-through. - _test_setup_bazel_command_applies_to_ctx_bazel(ctx) + _test_setup_command_applies_to_ctx_bazel(ctx) _test_sibling_rc_transforms_startup(ctx) - print("bazel_flags_test.axl: OK (18 sections)") + print("bazel_flags_test.axl: OK (13 sections)") return 0 bazel_flags_tests = task( 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 dbe262055..09f413623 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 @@ -13,9 +13,8 @@ applies uniformly to both tasks. """ load("@aspect//bazel/build_events.axl", "announce_bes_results_url", "announce_bes_sinks", "announce_dropped_bes_sinks", "bes_streamed_by_bazel", "collect_bes_sinks", "dropped_bes_backends", "summarize_bes_upload") -load("@aspect//bazel.axl", "BazelTrait", "DEFAULT_BAZEL_RETRY_ATTEMPTS", "dispatch_bazel_attempt_end", "dispatch_bazel_build_end") +load("@aspect//bazel.axl", "BazelTrait", "DEFAULT_BAZEL_RETRY_ATTEMPTS", "dispatch_bazel_attempt_end", "dispatch_bazel_build_end", bzl = "bazel") load("@std//time.axl", "sleep_iter") -load("./bazel_flags.axl", "aspect_endpoint_auth_flags", "resolve_bazel_announce") load("./bazel_results.axl", "BES_DRAIN_TICK_MS", "MIN_HEARTBEAT_INTERVAL_MS", "archive_bazel_attempt", "collapse_repro_targets", "failed_labels", "failed_labels_by_subcommand", "init_data", "now_ms", "process_event", bazel_conclusion = "conclusion") load("./deployment_flags.axl", "advertised_results_url", "announce_deployment_flags", "bes_results_url_flag", "deployment_endpoint_flags") load("./health_check.axl", "HealthCheckTrait") @@ -305,7 +304,7 @@ def run_bazel_task(ctx: TaskContext, command: str, targets = None) -> TaskConclu # build/test below (a failed health check concludes the surface and fails # the task inside setup_phase). 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) + announce_version, announce_command = bzl.announce.resolve(ctx) # Announced with the first spawn below (the streams belong to the spawn) and # summarized at build end. `dropped_bes` are the endpoints Bazel uploads to @@ -323,7 +322,7 @@ def run_bazel_task(ctx: TaskContext, command: str, targets = None) -> TaskConclu # Aspect-owned remote cache or Bazel-streamed `--bes_backend` (appended after # rc expansion on each bazel build/test call), plus `--bes_results_url` when this # build forwards BES to an advertised backend. - invocation_flags = aspect_endpoint_auth_flags(ctx, rc, command) + invocation_flags = bzl.endpoint_auth_flags(ctx, rc, command) invocation_flags.extend(bes_results_url_flag(ctx, rc, bes_sinks, deployment, command)) if targets == None: diff --git a/crates/aspect-cli/src/builtins/aspect/private/lib/health_check.axl b/crates/aspect-cli/src/builtins/aspect/private/lib/health_check.axl index d8da6044d..864722b81 100644 --- a/crates/aspect-cli/src/builtins/aspect/private/lib/health_check.axl +++ b/crates/aspect-cli/src/builtins/aspect/private/lib/health_check.axl @@ -14,7 +14,7 @@ is the Workflows-registered hook that: The `assert_ctx_bazel_ready_for_health_check` guard is exported so custom tasks composing `BazelTrait` + `HealthCheckTrait` get the same -fail-loud contract enforcement — see `lib/bazel_flags.axl` for the +fail-loud contract enforcement — see `bazel/flags.axl` for the canonical helpers that satisfy it. """ @@ -335,7 +335,7 @@ def assert_ctx_bazel_ready_for_health_check(ctx, environment): (`ctx.bazel.active_rc()`) is missing `--output_base`. Built-in tasks satisfy this via `lib/lifecycle.axl::setup_phase`, which - registers the active run command (through `setup_bazel_command` → + registers the active run command (through `bazel.setup_command` → `ctx.bazel.use_rc`) before running the `health_check` hooks. Custom tasks running the health check outside `setup_phase` must `use_rc` first. """ @@ -345,7 +345,7 @@ def assert_ctx_bazel_ready_for_health_check(ctx, environment): "Bazel health check would target the wrong server: the active run " + "command is missing --output_base on a Workflows runner. Run the " + "health check via lib/lifecycle.axl::setup_phase, or set an active " + - "run command via ctx.bazel.use_rc (e.g. through setup_bazel_command) " + + "run command via ctx.bazel.use_rc (e.g. through bazel.setup_command) " + "before the health_check hooks.", ) diff --git a/crates/aspect-cli/src/builtins/aspect/private/lib/lifecycle.axl b/crates/aspect-cli/src/builtins/aspect/private/lib/lifecycle.axl index da4e32db8..51bcc61a6 100644 --- a/crates/aspect-cli/src/builtins/aspect/private/lib/lifecycle.axl +++ b/crates/aspect-cli/src/builtins/aspect/private/lib/lifecycle.axl @@ -27,8 +27,8 @@ Status surfaces flip to the verdict via the task's own terminal # Record, trait, and emitter sit together here; `traits.axl` re-exports # them for user `config.axl` files. +load("@aspect//bazel.axl", bzl = "bazel") load("./ansi.axl", "ansi") -load("./bazel_flags.axl", "setup_bazel_command") load("./environment.axl", "color_enabled", "detect_ci", "feature_logger") load("./health_check.axl", "run_health_checks") load("./runner_job_history.axl", "append_runner_job_history") @@ -851,7 +851,7 @@ def setup_phase(ctx, lifecycle, subject, kind, data, hc_trait = None, bazel_trai self-init (GHSC check run / BK annotation) and render the first surface body from `data`. Runs before the steps that can fail (rc parse, health check) so failures surface on a created check run. - 3. Bazel setup (only with `bazel_trait`): `setup_bazel_command` + 3. Bazel setup (only with `bazel_trait`): `bazel.setup_command` resolves flags, parses `.bazelrc`, populates `ctx.bazel.startup_flags`, and returns the flag list. Else `None`. 4. `hc_trait.health_check` hooks (only with `hc_trait`) — must run @@ -869,7 +869,7 @@ def setup_phase(ctx, lifecycle, subject, kind, data, hc_trait = None, bazel_trai hc_trait: HealthCheckTrait or `None`. bazel_trait: BazelTrait or `None` (non-Bazel tasks). bazel_command: rc sections to expand ("build"/"test"); ignored sans bazel_trait. - bazel_base_flags: default flags for `setup_bazel_command`; ignored sans bazel_trait. + bazel_base_flags: default flags for `bazel.setup_command`; ignored sans bazel_trait. Returns: RunCommand | None: the active run command with `bazel_trait` (also @@ -902,7 +902,7 @@ def setup_phase(ctx, lifecycle, subject, kind, data, hc_trait = None, bazel_trai # checks, which inspect the active run command's startup flags. rc = None if bazel_trait != None: - rc = setup_bazel_command(ctx, bazel_command, bazel_trait, base_flags = bazel_base_flags) + rc = bzl.setup_command(ctx, bazel_command, base_flags = bazel_base_flags) # Health checks last. A non-None result is fatal: concludes the surface # and fails the task — never returns. diff --git a/crates/aspect-cli/src/builtins/aspect/run.axl b/crates/aspect-cli/src/builtins/aspect/run.axl index 13cde90d1..a1e60784d 100644 --- a/crates/aspect-cli/src/builtins/aspect/run.axl +++ b/crates/aspect-cli/src/builtins/aspect/run.axl @@ -1,10 +1,9 @@ """A default 'run' task that builds a target with `bazel build` and runs the resulting binary.""" load("@aspect//bazel/build_events.axl", "announce_bes_sinks", "bes_args", "bes_streamed_by_bazel", "collect_bes_sinks", "summarize_bes_upload") -load("@aspect//bazel.axl", "BazelTrait", "dispatch_bazel_attempt_end", "dispatch_bazel_build_end") +load("@aspect//bazel.axl", "BazelTrait", "dispatch_bazel_attempt_end", "dispatch_bazel_build_end", bzl = "bazel") load("@std//time.axl", "sleep_iter") load("./private/lib/artifacts.axl", "artifacts") -load("./private/lib/bazel_flags.axl", "announce_bazel_args", "aspect_endpoint_auth_flags", "bazel_flag_args", "resolve_bazel_announce") load("./private/lib/bazel_results.axl", "BES_DRAIN_TICK_MS", "init_data", "process_event", bazel_conclusion = "conclusion") load("./private/lib/environment.axl", "error") load("./private/lib/health_check.axl", "HealthCheckTrait") @@ -80,8 +79,8 @@ def _impl(ctx: TaskContext) -> int | TaskConclusion: # build`. The `run:` rc section can contain options Bazel's `build` command # rejects as unrecognized (e.g. `--script_path`). rc = setup_phase(ctx, lifecycle, target, "bazel_results", data, hc_trait, bazel_trait, "build") - announce_version, announce_command = resolve_bazel_announce(ctx) - endpoint_auth_flags = aspect_endpoint_auth_flags(ctx, rc, "build") + announce_version, announce_command = bzl.announce.resolve(ctx) + endpoint_auth_flags = bzl.endpoint_auth_flags(ctx, rc, "build") # `rc=` opts into the runinfo aspect: `r.flags` then carries the spawn # flags + aspect injection that capture the executable, the `args` attribute, @@ -186,7 +185,7 @@ run = task( "this flag as a no-op." ), ), - } | bazel_flag_args("the build") | announce_bazel_args("the build") | bes_args(), + } | bzl.flags.args("the build") | bzl.announce.args("the build") | bes_args(), traits = [ BazelTrait, HealthCheckTrait, diff --git a/crates/aspect-cli/src/builtins/aspect/runner_health_check.axl b/crates/aspect-cli/src/builtins/aspect/runner_health_check.axl index 41dabf1fc..8a738e461 100644 --- a/crates/aspect-cli/src/builtins/aspect/runner_health_check.axl +++ b/crates/aspect-cli/src/builtins/aspect/runner_health_check.axl @@ -8,7 +8,7 @@ complete, prints the last runner health check and warming status, then probes th runner's Bazel server (`ctx.bazel.health_check()`), signaling the runner unhealthy if the server is wedged. -Like every Bazel-calling task it resolves its flags through `setup_bazel_command`, +Like every Bazel-calling task it resolves its flags through `bazel.setup_command`, which folds in the Workflows runner metadata — notably the per-runner `--output_base` that `agent_health_check` requires so the probe targets the project-specific Bazel server (see `assert_ctx_bazel_ready_for_health_check` in @@ -19,13 +19,7 @@ Exits non-zero when the Bazel server is unhealthy. Off an Aspect Workflows runne there is no runner to check, so it prints an informational line and exits 0. """ -load("@aspect//bazel.axl", "BazelTrait") -load( - "./private/lib/bazel_flags.axl", - "announce_bazel_args", - "bazel_flag_args", - "setup_bazel_command", -) +load("@aspect//bazel.axl", "BazelTrait", bzl = "bazel") load( "./private/lib/environment.axl", "error", @@ -42,7 +36,7 @@ def _impl(ctx: TaskContext) -> int: # Register the active run command before the health check — it carries the # per-runner --output_base that `agent_health_check` requires (see docstring). - setup_bazel_command(ctx, "build", ctx.traits[BazelTrait]) + bzl.setup_command(ctx, "build") health_error = agent_health_check(ctx, environment) if health_error != None: @@ -59,5 +53,5 @@ runner_health_check = task( BazelTrait, HealthCheckTrait, ], - args = bazel_flag_args("the health check") | announce_bazel_args("the health check"), + args = bzl.flags.args("the health check") | bzl.announce.args("the health check"), ) diff --git a/crates/aspect-cli/src/builtins/aspect/test.axl b/crates/aspect-cli/src/builtins/aspect/test.axl index 323b651ee..9d221ef22 100644 --- a/crates/aspect-cli/src/builtins/aspect/test.axl +++ b/crates/aspect-cli/src/builtins/aspect/test.axl @@ -3,9 +3,8 @@ A default 'test' task that wraps a 'bazel test' command. """ load("@aspect//bazel/build_events.axl", "bes_args") -load("@aspect//bazel.axl", "BAZEL_RETRY_ATTEMPTS_DESCRIPTION", "BazelTrait", "DEFAULT_BAZEL_RETRY_ATTEMPTS") +load("@aspect//bazel.axl", "BAZEL_RETRY_ATTEMPTS_DESCRIPTION", "BazelTrait", "DEFAULT_BAZEL_RETRY_ATTEMPTS", bzl = "bazel") load("./private/lib/artifacts.axl", "artifacts") -load("./private/lib/bazel_flags.axl", "announce_bazel_args", "bazel_flag_args") load("./private/lib/bazel_runner.axl", "run_bazel_task") load("./private/lib/deployment_flags.axl", "deployment_flag_args") load("./private/lib/health_check.axl", "HealthCheckTrait") @@ -139,7 +138,7 @@ test = task( default = False, description = "Cancel any running Bazel invocation before starting the test.", ), - } | bazel_flag_args("the test invocation") | announce_bazel_args("the test") | bes_args() | deployment_flag_args() | repro_flavor_args(), + } | bzl.flags.args("the test invocation") | bzl.announce.args("the test") | bes_args() | deployment_flag_args() | repro_flavor_args(), traits = [ BazelTrait, HealthCheckTrait, diff --git a/crates/aspect-cli/src/builtins/aspect/warming.axl b/crates/aspect-cli/src/builtins/aspect/warming.axl index 629728b90..7a782d7b9 100644 --- a/crates/aspect-cli/src/builtins/aspect/warming.axl +++ b/crates/aspect-cli/src/builtins/aspect/warming.axl @@ -27,10 +27,9 @@ output base, and isn't a surface phase of its own. See `_impl`.) """ load("@aspect//bazel/build_events.axl", "announce_bes_sinks", "bes_args", "bes_streamed_by_bazel", "collect_bes_sinks", "summarize_bes_upload") -load("@aspect//bazel.axl", "BazelTrait", "dispatch_bazel_attempt_end", "dispatch_bazel_build_end") +load("@aspect//bazel.axl", "BazelTrait", "dispatch_bazel_attempt_end", "dispatch_bazel_build_end", bzl = "bazel") load("@std//time.axl", "sleep_iter") load("./private/lib/artifacts.axl", "artifacts") -load("./private/lib/bazel_flags.axl", "announce_bazel_args", "aspect_endpoint_auth_flags", "bazel_flag_args", "resolve_bazel_announce") load("./private/lib/bazel_results.axl", "BES_DRAIN_TICK_MS", "MIN_HEARTBEAT_INTERVAL_MS", "now_ms", "process_event") load("./private/lib/environment.axl", "error", "warn") load("./private/lib/health_check.axl", "HealthCheckTrait") @@ -113,8 +112,8 @@ def _impl(ctx: TaskContext) -> int | TaskConclusion: # runner metadata, incl. --output_base), and runs the health_check hooks (a # failed check concludes the surface and fails the task inside). rc = setup_phase(ctx, lifecycle, data["target_pattern"], _KIND, data, hc_trait, bazel_trait, "build") - announce_version, announce_command = resolve_bazel_announce(ctx) - endpoint_auth_flags = aspect_endpoint_auth_flags(ctx, rc, "build") + announce_version, announce_command = bzl.announce.resolve(ctx) + endpoint_auth_flags = bzl.endpoint_auth_flags(ctx, rc, "build") # Clean the prior Bazel state under the runner storage mount AFTER # `setup_phase` — the health check must run first (the Workflows @@ -284,5 +283,5 @@ warming = task( default = ["..."], description = "Bazel target patterns to warm. Defaults to '...' which expands to all rule targets in the package at and beneath the current directory.", ), - } | bazel_flag_args("the warming build") | announce_bazel_args("the warming build") | bes_args(), + } | bzl.flags.args("the warming build") | bzl.announce.args("the warming build") | bes_args(), )