Unit graphs

A unit graph is the second argument to Expand. It tells senro what to fan out over: the list of apps, modules, crates or packages your repository already has.

import "github.com/xavidop/senro/unit/gowork"

verify.Expand("test", gowork.Modules()).
	Template(func(u senro.Unit) *senro.StepBuilder {
		return senro.NewStep(exec.Command("go", "test", "./...")).WorkDir(u.Dir)
	})

Eight ship under github.com/xavidop/senro/unit. Pick the one that matches your repository.

Choosing one

GraphA unit isCan narrow to affected units?
globAny directory matching a patternNo
goworkA Go module, or a Go packageYes
cargoA Rust crateYes
jsworkAn npm, pnpm, Yarn or Bun workspace packageYes
mavenA Maven reactor projectYes
gradleA Gradle projectYes
pyprojectA Python distributionNo
bazelA Bazel packageOnly with bazel.Query()

None of these fits? Write your own: two methods, and Expand cannot tell the difference.

What “can narrow” means

Every graph can list units. Only some can also answer “which units does this change reach?”, which is what Affected needs:

verify.Expand("test", gowork.Modules()).
	Affected(change.FromTrigger(ev)).     // only gowork, cargo, jswork, maven, gradle, bazel.Query
	Template(...)

Adding .Affected(...) over a graph that cannot answer is refused at build time, with a message naming the graphs that can:

senro: expansion "lint": unit: glob dirs apps/* cannot compute an affected set: it discovers
units but knows nothing about which unit depends on which. Fan out over a graph that knows which
unit depends on which (gowork, cargo, jswork, maven or gradle, under
github.com/xavidop/senro/unit), or drop Affected and run every unit

Quietly running every unit would look friendlier and be wrong: an expansion that covered everything looks exactly like one that computed a real answer, and a CI that cannot tell them apart will eventually trust a green build that skipped the unit a change broke.

The question every graph is answering when it declines is the same one: can a unit in this ecosystem depend on another without saying so anywhere the graph can read? For a glob pattern and for Python, the answer is yes.

No toolchain required

Every graph except gowork and bazel.Query() reads manifests or walks the tree, without running the ecosystem’s own tool. They work on a machine with no cargo, node, mvn, gradle, JDK or bazel installed, which is usually the machine planning the run.

What a unit is

Whatever the graph, a senro.Unit is three fields:

FieldWhat it is
IDThe unit’s stable identity, and what lands in the child step’s id: test[unit=apps/web].
NameWhat a tool calls the unit: a module path, a crate name, a groupId:artifactId. What a template passes to --filter, -p or -pl.
DirThe unit’s directory, relative to the root. u.Sources() builds a Pure() step’s inputs from it.

Where to go next