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
- func BinaryBehindPin(pinned, binary string) bool
- func InstallCommand(version string) string
- func PinnedForgeVersion(dir string) string
- func ResolveProjectForge(projectDir string) (version string, local, ok bool)
- func ResolveTag(dir, tag string) string
- func RunBinaryCommand(binary string, args []string) string
- func RunPinnedCommand(version string, args []string) string
- func SkewDiagnosis(pinned, binary string) string
- func SkewLine(kind SkewKind, b Binary, br Bridge, c Checkout) string
- func SkewOverridden() bool
- type Assessment
- type Binary
- type Bridge
- type BridgeReport
- type Checkout
- type SkewKind
- type Verdict
Constants ¶
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.
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 ¶
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 ¶
InstallCommand is the single spelling of "install this forge version", shared by every diagnostic that offers one.
func PinnedForgeVersion ¶
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 ¶
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 ¶
ResolveTag returns the commit tag names in dir, or "".
func RunBinaryCommand ¶ added in v1.0.0
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 ¶
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 ¶
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 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.
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 ¶
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.
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 ¶
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 ¶
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 ¶
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.