envoyproxy / envoyproxy/toolshed

dependency validate: emit build-graph reachability data via an aspect (to replace bazel-query scraping)

Open
#5,022 0 comments 1 reaction 2 assignees Claimed by @phlax View on GitHub
dependencies enhancement
Dominant language
Python
Stars
12
Forks
24
Avg merge
6h 37m
Merged PRs (30d)
92

Description

## Outcome (crystal clear)

The deliverable is **data**, not a rewritten checker.

We need a build-graph **aspect** that emits JSON describing, for every external dependency, **which targets/surfaces actually reach it** — with enough fidelity that we can then **delete the existing `tools/dependency/validate` logic entirely** and replace it with something that does nothing but read this pre-computed ("pre-aspected") data and assert against `deps.yaml`.

No `bazel query`. No shelling out. The validation step becomes: load aspect-emitted JSON + load `deps.yaml` → compare. That's it.

## Why

`tools/dependency/validate` (envoy) currently validates that each dependency's `use_category` marking in `deps.yaml` matches how the dep is actually reached in the build graph. It does this by shelling out to:

```
bazel query 'filter("^@.*//", deps(set()))'
```

and string-scraping the output. This is slow, historically flaky, and **broken under bzlmod** — canonical repo-name mangling breaks the `@(\w+)//` scraping, and unrelated graph-load failures (e.g. `rules_rust`/wasmtime target resolution) bring the whole thing down with empty output.

An aspect runs *inside* the graph: it sees canonical + apparent repo names directly, attributes each dep to the exact consuming target, and avoids re-importing toolchain/implicit noise. It is bzlmod-native by construction.

## What the current tool asserts (must remain expressible from the emitted data)

All six validations are built on one primitive — "set of external repos reachable from a target set, optionally minus another target set". The emitted data must let us reconstruct each **without** any query:

1. **build-graph structure** — `deps(//source/...)` == `deps(//source/exe:core)` ∪ `deps(//source/extensions/...)`.
2. **test-only** — no `test_only`-marked dep reachable from `//source/...`; marginal `//test/...` deps (beyond `//source`) must be `test_only` (currently modulo `test_only_ignore` heuristics + an `openssl` special-case).
3. **dataplane_core** — deps reached from the dataplane path-set (`//source/common/{api,buffer,crypto,conn_pool,formatter,http,ssl,tcp,tcp_proxy,network}/...`) covered by `dataplane_core ∪ api` (minus `boringssl_fips`).
4. **controlplane** — deps reached from `//source/common/config/...` covered by `controlplane ∪ api`.
5. **per-extension** — for each extension in `extensions_build_config`, its marginal deps (target closure minus core) each marked `dataplane_ext | observability_ext | other | api`, **and** if the dep declares an `extensions` allowlist, this extension is in it.
6. **implied_untracked_deps** — an untracked transitive repo is attributed back to its tracking dep (revmap).

## Per-extension identity is required

The tool's value is the diagnostic. "dep X marked `dataplane_ext` is reached by core" is only actionable with "…via `//source/extensions/filters/http/foo`." The emitted data **must retain which extension** attributes each reaching path — a core-vs-ext boolean is insufficient.

## Surfaces / roots (inputs to the aspect/rule, not hardcoded)

- **core** — `//source/exe:envoy_main_common_with_core_extensions_lib`
- **extensions** — `//source/extensions/...`, attributed per owning `//source/extensions/` package (core `extensions_build_config`)
- **contrib** — `//contrib/...` (own `extensions_build_config`; contrib deps are present in `deps.yaml`)
- **test** — `//test/...`
- plus the dataplane/controlplane path-sets above

## The data to compile

Per external dep — keyed by **canonical** repo, carrying the **apparent/module** name so it joins `deps.yaml` keys — emit the set of consuming in-repo targets, each tagged with:

- owning **surface** (core / extension / contrib / test),
- owning `//source/extensions/` (or `//contrib/...`) label where applicable,
- `testonly` (from the real graph attribute).

This must be sufficient to reconstruct all six checks and to **name the offending extension** on a mismatch.

Filtering currently in the tool (`IGNORE_DEPS`, and `test_only_ignore`'s `raze__` / `remotejdk` / `_pip3` heuristics) was largely **performance scaffolding** for the query approach and should **not** be baked into the aspect. The aspect emits raw truth; the consumer applies policy. Real `testonly` should replace the string heuristics where possible.

## Acceptance

- An aspect (here in toolshed) + a rule that writes a JSON reachability map as described.
- Given the surface roots as inputs, the JSON captures, per external dep: the surfaces and **specific extensions** that reach it, and whether any non-testonly path exists.
- The existing `tools/dependency/validate` Python (bazel-query shellout, `@(\w+)//` scraping, `exclude=` closure subtraction, ignore/test-only prefilters) can be **removed** and replaced by a consumer that only reads this JSON + `deps.yaml`.
- No `bazel query` / no shellout in the validation path.

## Open design points (resolve against the real graph)

- Which edge attributes constitute "production reachability" (`deps` + `data`/`srcs`?) without re-importing toolchain/implicit noise the old prefilters suppressed.
- Whether real `testonly` fully replaces the `raze__` / `remotejdk` / `_pip3` heuristics for parity.
- How the three `extensions_build_config`s (core / ext / contrib) join to `deps.yaml`.

## Out of scope / separate

- The `rules_rust` / wasmtime `wasmtime-internal-c-api-macros` target-resolution failure that currently trips `validate` is a distinct bzlmod bug (possibly visibility vs. crate_universe generation) — tracked separately.

## Related

- envoyproxy/toolshed#5021 — repurpose the dependency *checker* for bzlmod.
- envoyproxy/envoy#42890 — bzlmod migration.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.