Help us shape the future of Go! Take the 2026 Go Developer Survey

forgecompat

package
v1.0.14 Latest Latest
Warning

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

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

Documentation

Overview

Package forgecompat decides whether the running forge binary may generate into a project, from the forge version that project ACTUALLY resolves.

The generator emits code that calls into forge/pkg/* — orm, crud, testkit, serverkit. Those are packages in the SINGLE forge module, so the library that code compiles against is whatever version the project requires, and the symbols it may call are the ones that existed as of THIS binary's version. A project pinning a forge OLDER than this binary can therefore lack symbols the generated code names.

WHY THIS IS A VERSION COMPARISON AND NOT A SYMBOL PROBE. It used to compile a throwaway program against the project's forge/pkg, exercising a hand-maintained list of symbols that whoever taught an emitter to call something new was expected to extend. That list rotted: forge grew `testkit.StubNotConfigured`, nobody added the entry, and a control-plane `forge generate` failed in validate with the tree already rewritten.

Generator and runtime ship as one module, so their versions are one number and the honest check is an inequality over it: the project's forge must be >= the forge doing the generating. That covers every symbol ever added, with no registry to forget, and costs one `go list`.

A project resolving forge NEWER than this binary is allowed through: older templates calling into a newer library is the ordinary upgrade order.

It lives in its own package, rather than inside the generate command, because more than generate needs the answer: `forge env render` advises running `forge generate` to refresh a stale .forge-kcl/, and that advice is wrong whenever generate would refuse.

Index

Constants

View Source
const ModulePath = "github.com/reliant-labs/forge"

ModulePath is the module a project requires to get forge's runtime libraries. `forge/pkg` is a package prefix inside it, not a module.

View Source
const SkewOverrideEnv = "FORGE_ALLOW_VERSION_SKEW"

SkewOverrideEnv lets a user proceed past the refusal. Pre-1.0 forge owes no compatibility, but a wall with no door is its own defect — someone debugging forge itself, or rendering a project whose pin is wrong, needs to get through. Set to any non-empty value.

Variables

This section is empty.

Functions

func BinaryBehindPin

func BinaryBehindPin(pinned, binary string) bool

BinaryBehindPin reports whether the running binary is older than the project's declared forge pin — the pairing whose KCL render fails while blaming the project.

func InstallCommand

func InstallCommand(version string) string

InstallCommand is the single spelling of "install this forge version", shared by every diagnostic that offers one.

func PinnedForgeVersion

func PinnedForgeVersion(dir string) string

PinnedForgeVersion reads forge.yaml's forge_version, walking UP from dir to find the project root.

The walk is load-bearing, not convenience. The render seam knows a workDir, which for some callers is the project root and for others a directory beneath it. A reader that only looked in dir would go silent for exactly the deep-render callers, which is where this bug was hit.

Returns "" for anything it cannot answer. A diagnostic must never be the thing that fails, so every error path here is a silent one.

func ResolveProjectForge

func ResolveProjectForge(projectDir string) (version string, local, ok bool)

ResolveProjectForge asks the go toolchain how forge ACTUALLY resolves for this project — which is not necessarily what go.mod says, because a go.work or a replace can override it.

local is true when forge resolves to a directory on disk (a workspace `use` or a directory `replace`) rather than a published version: a module in a workspace has no version, which is the signal. ok is false when the toolchain could not answer at all.

func ResolveTag

func ResolveTag(dir, tag string) string

ResolveTag returns the commit tag names in dir, or "".

func RunBinaryCommand added in v1.0.0

func RunBinaryCommand(binary string, args []string) string

RunBinaryCommand spells the refused command run by a forge that is already on disk: binary (shell-quoted if it needs to be) then the arguments.

func RunPinnedCommand

func RunPinnedCommand(version string, args []string) string

RunPinnedCommand spells "run THIS command with forge `version`", without installing anything: `go run <module>/cmd/forge@<version> <args>`.

It is the fix to offer when the forge on PATH is not the one a project pins. InstallCommand REPLACES the forge on PATH, which is the wrong advice for a machine whose installed forge is deliberate — a developer's own dev build, or another project's pin — and following it is how one project's fix silently breaks the next. `go run` builds the pinned forge into the Go build cache (fetched once, reused after) and runs exactly the command that was refused. It works because forge's go.mod carries no `replace`.

args are the forge arguments of the refused invocation (no binary name); each is shell-quoted only when it needs to be, so the line pastes as-is.

func SkewDiagnosis

func SkewDiagnosis(pinned, binary string) string

SkewDiagnosis returns the leading diagnosis for a binary older than the project's pin, or "" when there is no comparable skew to report.

It is phrased as a runbook (expected / found / Fix) to match forge's other refusals, and it says explicitly that the project's KCL is not at fault — that sentence is the one that saves the reader from the line number KCL blamed.

func SkewLine

func SkewLine(kind SkewKind, b Binary, br Bridge, c Checkout) string

SkewLine renders the one-line warning: what disagrees, and the fix.

func SkewOverridden

func SkewOverridden() bool

SkewOverridden reports whether the user has asked to proceed despite a version skew.

Types

type Assessment

type Assessment struct {
	Verdict Verdict
	// ProjectVersion is the forge version the project resolves; "" when it
	// resolves to a local directory (a go.work `use` or directory replace).
	ProjectVersion string
	// BinaryVersion is buildinfo.InstallableVersion(): "" for a build no
	// module proxy can serve.
	BinaryVersion string
}

Assessment is the verdict plus the two versions it was computed from, so a caller can name them in its own message.

func Assess

func Assess(projectDir string) (a Assessment, ok bool)

Assess answers the question for projectDir. ok is false whenever it does not apply or cannot be answered — no go.mod, no forge requirement, or a toolchain that will not say how forge resolves.

type Binary

type Binary struct {
	// Name is what the user calls it ("forge", "reliant").
	Name string
	// Revision / Modified / Tag / Embedded: see buildinfo.Source.
	Revision string
	Modified bool
	Tag      string
	Embedded bool
	// Root is the checkout the binary was compiled from ("" unknown).
	Root string
	// BuiltAt is the executable's modification time — when `go install`
	// wrote it. Zero when unreadable.
	BuiltAt time.Time
}

Binary is the running forge binary, as the skew check sees it.

func CurrentBinary

func CurrentBinary() Binary

CurrentBinary describes the running binary.

type Bridge

type Bridge struct {
	// Dir is the checkout, absolute and cleaned.
	Dir string
	// File is the go.work or go.mod that declares the bridge.
	File string
	// Replace is true for a directory `replace`, false for a go.work `use`.
	Replace bool
	// contains filtered or unexported fields
}

Bridge is a project resolving forge to a directory on disk.

func LocalBridge

func LocalBridge(projectDir string) (Bridge, bool)

LocalBridge reports the local forge checkout projectDir compiles against, if any, read from the files that declare it rather than from the toolchain.

It follows the go command's own rules for the one module it cares about: GOWORK=off disables the workspace, GOWORK=<file> selects one, otherwise the nearest go.work at or above the project applies; a workspace `use` of the forge module wins, then a go.work directory `replace`, then the project go.mod's. Reading the files keeps this answerable when the module graph is not — a fresh scaffold before tidy, a go.work naming a missing gen/ — which is precisely when a `go list` would fail and a warning would go silent.

func (Bridge) String

func (b Bridge) String() string

String names the bridge for a one-line message.

func (Bridge) Undo

func (b Bridge) Undo() string

Undo is the command that removes the bridge, run from the project.

type BridgeReport

type BridgeReport struct {
	Bridged    bool
	Bridge     Bridge
	CheckoutOK bool // the bridged directory is a readable git checkout
	Checkout   Checkout
	Binary     Binary
	Skewed     bool
	Kind       SkewKind
}

BridgeReport is everything the skew check learned about one project.

func InspectBridge

func InspectBridge(projectDir string) BridgeReport

InspectBridge runs the whole check for projectDir. It never fails: an unbridged project, or a bridge that is not a git checkout, simply reports nothing to warn about.

func (BridgeReport) Line

func (r BridgeReport) Line() string

Line is the one-line warning, or "" when there is nothing to warn about.

type Checkout

type Checkout struct {
	Dir  string
	Head string // full commit sha
	// Dirty: tracked changes or untracked files (what `go build` stamps as
	// vcs.modified).
	Dirty bool
	// SourceEdits: some of those changes are to a file a build of forge can
	// read (sourceEdit) — the ones that make a binary built from HEAD a
	// different forge. A test report a CI job tees into the root, an
	// editor's scratch notes or a CHANGELOG edit is Dirty but not this.
	SourceEdits bool
	// HeadMovedAt is when HEAD last moved in this checkout — a commit, pull,
	// rebase or checkout — read from the reflog; HEAD's commit time when
	// there is no reflog.
	HeadMovedAt time.Time
	// LastSourceEditAt is the newest modification among the source edits.
	// A deleted file has no mtime and counts as edited now.
	LastSourceEditAt time.Time
}

Checkout is a bridged directory as git sees it now.

func ReadCheckout

func ReadCheckout(dir string) (Checkout, bool)

ReadCheckout reads dir with git. ok is false when dir is not a git checkout (or git is missing): there is no HEAD to compare against.

dir must be the ROOT of its checkout. A directory that merely sits inside some other repository (a vendored copy of forge's module under a project, `replace ... => ./.forge-pkg`) is not a forge checkout: git would answer with the enclosing project's HEAD, and forge would then try to build itself from a directory with no cmd/forge.

type SkewKind

type SkewKind int

SkewKind names how a binary and its bridged checkout disagree.

const (
	// SkewRevision means the binary was built from a different commit.
	SkewRevision SkewKind = iota + 1
	// SkewCheckoutEdits means the checkout has uncommitted changes the
	// binary was not built from (or edits newer than the binary).
	SkewCheckoutEdits
	// SkewBinaryEdits means the binary was built from uncommitted changes
	// the clean checkout does not have.
	SkewBinaryEdits
	// SkewUnverifiable means the binary records no forge commit and cannot
	// be shown to have been built from the checkout as it is now.
	SkewUnverifiable
)

func AssessSkew

func AssessSkew(b Binary, c Checkout) (SkewKind, bool)

AssessSkew is the pure decision: do b and c hold the same forge source?

With a recorded revision the comparison is exact, refined by time for a dirty tree: a dirty binary built from THIS checkout after its newest edit carries those edits. Without one — forge embedded in a host through a workspace records nothing — only a proof by time can clear it: the binary was compiled from this checkout, and the checkout has not moved or been edited since. Anything that proof cannot cover is reported, because a silent mismatch is the failure this exists to end.

type Verdict

type Verdict int

Verdict is the outcome of the version comparison, split from both the toolchain query and any error prose so the decision table can be tested without a module graph.

const (
	// OK means generating is safe (or the question could not be answered,
	// and guessing is worse than letting generate's validate step speak).
	OK Verdict = iota
	// UnreleasableNoBridge means this binary exists on no module proxy and
	// the project resolves forge to a published version — the pairing that
	// produced `undefined: testkit.StubNotConfigured`.
	UnreleasableNoBridge
	// StalePin means the project's forge is older than the binary
	// generating into it.
	StalePin
	// BehindPin means the running binary is OLDER than the project's pin.
	// Unlike StalePin this is not about the Go library half — forge's KCL
	// schema module is embedded in the binary, so an older binary renders
	// the project's KCL against an older schema and reports the missing
	// field against the PROJECT's line number. See skew.go.
	BehindPin
)

func Decide

func Decide(binaryVersion, rawBuildVersion, projectVersion string, local bool) Verdict

Decide is the pure decision.

binaryVersion is buildinfo.InstallableVersion() — "" for a build no proxy can serve. rawBuildVersion is buildinfo.Version(). projectVersion/local describe how forge resolves IN the project.

A local resolution always passes HERE: the project compiles against source, so there is no version to be behind. Whether this binary was built from that source is a different question, answered by InspectBridge (bridge.go) and reported by generate, lint and doctor. An unknown projectVersion also passes — guessing is worse than letting validate speak.

Jump to

Keyboard shortcuts

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