modgen

package
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 14, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package modgen generates the provisional root go.mod of the extracted module and proves the Go toolchain agrees with what it says.

The generated module is not a copy of the source module. It has a different module path, it requires published staging module versions where the source required directories, and it carries no replace directives at all. What it does inherit is everything that changes how the code behaves rather than where it comes from: the language version and the godebug defaults.

Generation and verification are separate steps on purpose. Generating a go.mod is a pure function of already resolved inputs, so it is cheap, deterministic, and testable without a toolchain. Verification is the expensive half that needs a real module directory and a real go command, and it exists because a go.mod that merely parses proves nothing: minimal version selection can raise a requirement above the version this package wrote, and a raised requirement is a dependency the operator never approved.

Index

Constants

This section is empty.

Variables

View Source
var ErrModuleDrift = errors.New("the generated module file drifted")

ErrModuleDrift reports a generated module the go command changed in a way that is not a requirement version.

View Source
var ErrPinFloated = errors.New("a pinned requirement was raised by minimal version selection")

ErrPinFloated reports a requirement the go command resolved above the version the engine pinned.

Functions

func Generate

func Generate(opts Options) ([]byte, error)

Generate renders the provisional go.mod.

Every version has to be exact. A requirement written as a query such as "latest" would resolve to whatever the proxy served at the moment of the run, which is the one thing a reproducible extraction cannot contain, so a version that is not already a canonical semantic version is refused here rather than discovered later as an unexplained difference between two runs.

func RequirementsFor

func RequirementsFor(root *gomodmap.RootModule, staging []gomodmap.ModuleVersion) ([]gomodmap.Requirement, error)

RequirementsFor combines the source module's external requirements with the resolved staging versions.

The staging modules are a subset on purpose. Resolving one costs a proxy round trip, and a generated module needs only those its extracted packages can reach, so the caller resolves what it needs and passes exactly that. A resolved module the source does not stage, or one passed twice, is refused rather than absorbed: both mean the caller and the source commit disagree about what the staging layout is.

External requirements are copied whole instead, because they are already exact versions that cost nothing to carry and tidying removes the unused ones.

A staging module the source only replaces is refused along with the rest. The tree stages sample-cli-plugin and sample-controller so it publishes them, but the root module requires neither, so upstream's own build never resolved a version for either one. Turning such a replacement into a requirement would have the generated module depend on code the source commit was never built against, and the version it depended on would be one this engine chose rather than one upstream did.

Directness is the source module's marking, which is a starting point rather than an answer. The generated module contains a fraction of the source's packages, so tidying recomputes it, and Verify reports what it changed instead of promising the source's marking survived. Refusing the unrequired staging modules is also what makes that marking meaningful: a replacement carries no requirement, so it has no direct or indirect marking to start from.

Types

type Options

type Options struct {
	// ModulePath is the generated module's path, such as
	// monis.app/kk/rbac_authorizer.
	ModulePath string
	// Go is the language version, inherited from the source module so the
	// extracted code is compiled under the same language semantics upstream
	// compiled it under.
	Go string
	// Toolchain is the exact patch release the generated module pins. Empty
	// means the engine's own pin, and any other value is refused: the toolchain
	// that formats the output and the toolchain the output names have to be the
	// same one, or generated bytes stop being reproducible. It is written only
	// when the go directive does not already imply it, which is what the go
	// command itself would leave behind.
	Toolchain string
	// Godebug are the source module's godebug defaults, inherited for the same
	// reason as Go. A build that honours different godebug settings than upstream
	// is a behaviour change no test of the extracted code would catch.
	Godebug []gomodmap.Godebug
	// Require lists every requirement, staging and external, at an exact
	// version.
	Require []gomodmap.Requirement
}

Options describes the module to generate.

type Reclassification

type Reclassification struct {
	// Path is the module path.
	Path string
	// Indirect is what tidying decided, and therefore what the generated module
	// carries. What the source module said is its negation, so recording both
	// would only give the two a way to disagree.
	Indirect bool
}

Reclassification is one requirement whose directness the go command changed.

type Report

type Report struct {
	// GoMod is the module file as it stands after tidying.
	GoMod []byte
	// GoSum is the checksum file after tidying, empty when the module needs no
	// checksums because it requires nothing outside the standard library.
	GoSum []byte
	// Kept lists the requirements that survived tidying, sorted by path, each
	// carrying the directness the go command settled on rather than the one the
	// source module had.
	Kept []gomodmap.Requirement
	// Added lists transitive requirements introduced by an explicitly allowed
	// compatibility re-tidy, sorted by path.
	Added []gomodmap.Requirement
	// Dropped lists the module paths tidying removed because nothing in the
	// extracted sources imports them, sorted. A large Dropped set is the normal
	// outcome of extracting a few packages out of Kubernetes rather than a
	// problem.
	Dropped []string
	// Reclassified lists the requirements tidying kept at the pinned version but
	// marked differently than the source module did, sorted by path.
	//
	// It is reported rather than refused because the extracted module is a
	// subset. A module the source imports from a package that was not extracted
	// becomes indirect here, and one the source only reached through a dependency
	// becomes direct when an extracted package imports it. Both are the correct
	// answer for the module being generated, and neither changes which code is
	// built, so what matters is that the change is stated instead of being
	// absorbed by copying the source's marking into the report.
	Reclassified []Reclassification
}

Report is what one verification pass observed.

It is returned rather than acted on, because this package produces the provisional module: dependency policy, the facade, and publication all run later and all read this. Nothing here writes to a destination repository.

func Verify

func Verify(ctx context.Context, runner *gocli.Runner, opts VerifyOptions) (report *Report, err error)

Verify installs the generated go.mod in a scratch module, tidies it, and reparses the result.

Tidying is the point of the pass. Writing a go.mod with exact pins proves only that the engine can write exact pins; it says nothing about whether the go command agrees that those versions can build the extracted code together. Minimal version selection resolves a module to the highest version any requirement asks for, so a dependency that itself requires a newer release of something the engine pinned will raise that pin, and the raised version is then what a consumer would actually build against. That is refused here rather than published, because the operator approved the pin and not its successor.

Everything else the go command could change is compared too. A replace directive appearing in a generated module would mean the published module resolves to code from somewhere the module path does not name, and a changed go or godebug directive would mean the extracted code is compiled under different semantics than upstream compiled it under.

The scratch directory is left exactly as it was found unless the pass succeeds. Its precondition is that no go.mod is there yet, so a failed pass that left one behind would report a reused scratch module on the retry and would leave a module file that no report explains.

type VerifyOptions

type VerifyOptions struct {
	// Dir is the scratch module directory. It must already hold the extracted Go
	// sources and must hold neither a go.mod nor a go.sum, because this pass is
	// what decides what those files say and what removes them again if it fails.
	Dir string
	// GoMod is the generated module file to install.
	GoMod []byte
	// AllowAdditions permits tidy to add transitive requirements after a
	// compatibility transform deliberately removed a staging module whose go.mod
	// had supplied them. Added versions are recorded rather than hidden.
	AllowAdditions bool
}

VerifyOptions describes one verification pass.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL