How observe reads a Bazel repository
The Bazel detector of @putnami/observe finds the packages of a Bazel
repository and the packages each one depends on. The change analysis uses
them to map each changed file to its package, to list the packages that
depend on it, and to place the tests and coverage Bazel wrote. The detector
reads files only: it never runs bazel.
A Bazel workspace
A repository whose root holds MODULE.bazel, WORKSPACE or
WORKSPACE.bazel is a Bazel workspace. Each of its packages is a component:
| Member | Value |
|---|---|
| Id | The package's directory, . for the root package |
| Name | The package label: //pricing, or // for the root package |
| Ecosystem | bazel |
| Source | orchestrator with the query export, manifest without it |
A component is a package, never a target: the targets of one BUILD file
share its directory, and a changed file belongs to the package that holds it.
The Bazel detector runs after the others. With an export, its packages outrank them by source, as an Nx graph export does. Without one, a package yields to any component another detector places at or above its directory, such as a pnpm workspace member or a Go module: that component keeps its files and its edges.
The query export comes first
Bazel computes its graph from the BUILD files, which are Starlark programs.
The detector does not evaluate them: the CI writes the graph with
bazel query, before putnami observe.
bazel query 'deps(//...)' --output=streamed_jsonproto > bazel-graph.json
putnami observeThe detector reads bazel-graph.json at the repository root, or the file
--bazel-graph names. It also reads the output of --output=jsonproto, one
query result that lists the targets.
- Each directory that holds a
BUILDorBUILD.bazelfile and at least one rule of the main repository in the export is a package. ABUILDfile that declares no rule, such as one that only callsexports_files, makes no component: its files belong to the package above it, if any, and an edge to one of its files is no edge. - A rule that names a target of another package among its inputs, through
deps,srcs,dataor any other label attribute, makes its package depend on the other one. - A label of the main repository is
//pkg:name,@//pkg:nameor@@//pkg:name. A target of an external repository, such as@maven//…,@npm//…or@@rules_go+//…, is no edge.
The export reflects the commit that was built: write it in the same job, after the checkout of that commit.
Without an export: the BUILD files
Without an export, each directory that holds a BUILD or BUILD.bazel file
is a package of source manifest, and no package has an edge. When it finds
one, the run gets the info diagnostic observe.bazel_graph_missing, which
names the export command. The walk skips
hidden directories, node_modules, the bazel-* links of the root and the
directories .bazelignore names, and stops after 200 000 entries with the
observe.bazel_walk_bounded warning.
An export that is not valid JSON, that holds no rule of the main repository,
or that --bazel-graph names and that does not exist gets the
observe.bazel_graph_unreadable warning, and the BUILD files answer
instead. The run does not fail.
Tests and coverage
bazel test and bazel coverage write their outputs in Bazel's output base,
outside the repository, and leave links to it at the root. Report discovery
follows two of them:
| Report | Path | Written by |
|---|---|---|
| JUnit | each test.xml under bazel-testlogs, except under a target's test.outputs and test_attempts |
bazel test, one per test target, shard and run |
| LCOV | bazel-out/_coverage/_coverage_report.dat |
bazel coverage --combined_report=lcov |
Each one is still recognized by its content, and named by its path through the
link, such as bazel-testlogs/api/api_test/test.xml.
Bazel writes the log of the test target //api:api_test under
bazel-testlogs/api/api_test/. Every case of that log goes to the package of
its test target, api: the rule of the export whose package and name match
the log's directory, else, without an export, the longest package whose
directory prefixes it. When that package yielded to another detector's
component, the cases go to the component that encloses it. A target whose name holds a /, such as
//pricing:rules/tax_test, is told from a package pricing/rules only with
an export.
The combined LCOV report names each file relative to the repository root, so each covered file goes to the package that holds it.
The links point into the output base of the machine that ran Bazel: run
putnami observe in the job that ran bazel test and bazel coverage.