Documentation
¶
Overview ¶
Command importlayers is the internal import-layering rule's lint guard, run by make lint over the whole repository. The rule: the module's internal/... packages are assigned to layers by purpose — layer 1 the foundations, layer 2 the small primitives, layer 3 the host/PKI/node-state helpers, layer 4 the orchestration and domain services, layer 5 the user-facing interfaces — and a package may import only packages of its own or a strictly lower layer. Same-layer imports are legal on purpose: the orchestration packages legitimately share each other, and forbidding that would degenerate layer 4 into micro-layers. An upward import is a violation: the finding names the importer's file:line, both packages, both layers, and the two ways out — move the code to the layer its purpose belongs to, or add a reviewed exception stating the reason. The membership table is checked in beside this guard (tools/importlayers/layers.txt, one layer<N> prefix rule per line, longest prefix wins), so the layers and the gate that enforces them cannot drift apart. The table is renamed-package-proof in both directions: an internal package matching no rule fails the check, and so does a rule matching no package.
A second, absolute boundary rides along: an internal package must not import this module's pkg/... or cmd/... packages. internal/ is the implementation beneath them, and code below the interfaces reaching up into them is the same shape of defect the layer rule catches, so it fails here rather than at review time.
The exceptions file (tools/importlayers/exceptions.txt) holds the deliberate upward imports, one `importer -> imported # reason` line per edge. Every entry states the reason the upward import stands; the guard scans the repo like any other checker and fails on a stale entry — an edge the code no longer has, or one that is no longer a violation — so an exception cannot outlive the code that justified it and a new upward import cannot hide behind an entry written for a different edge.
Only files under the module's internal/ are read: go/parser (ImportsOnly) parses the imports of every non-test .go file in those packages, regardless of build tags and GOOS — a file gated to another platform still names the imports its package really carries — and testdata, vendor, node_modules, hidden- and underscore-prefixed files and directories are skipped, as the go tool skips them.
The go tool is not invoked: the check must run on a tree that has not built its generated assets. Test files are exempt: _test.go files may legally import upward (a table-driven fixture is deliberate), and the rule governs production code.
Exit codes: 0 when every internal package is clean; 1 when an upward import, an internal import of pkg/... or cmd/..., or a stale exception was found; 2 when the check cannot run — a bad argument, an unreadable file, a file that does not parse, a package the table assigns no layer, a rule that matches no package, a malformed entry in either data file. A package the guard cannot vouch for must fail the check, never pass it silently.