Catch and fix nested modules that require unpublishable versions - #4
Merged
Conversation
…sions A nested go.mod usually carries a replace so the working tree builds against itself. That replace is ignored the moment the module becomes somebody else's dependency, so the require line is what a consumer actually resolves. Several repos require the root at v0.0.0, which is not a version anyone can fetch: go get github.com/xraph/grove/drivers/pgdriver@v1.6.2 reading github.com/xraph/grove/go.mod at revision v0.0.0: unknown revision v0.0.0 It works today only for people who already require the root at a real version, so MVS picks that instead. Anyone reaching for the nested module first hits a wall, and no local build ever notices, because the replace hides it. Across the Go repos here that is 113 requires: nexus 85, grove 16, farp 5, trove 5, dtl 1, herald 1. go-ci gains a nested-modules job that reads every intra-repo require and fails on v0.0.0 or a version that is not semver. A version that is simply not published yet is reported and allowed, since release rewrites these before it tags. The job is a no-op in a single-module repo, so every caller picks it up without changing anything. go-release gains the other half: it discovers nested modules, builds and tests each, pins their intra-repo requires to the version being cut, tags each as <dir>/<version>, and then resolves every one of them through the public proxy. That last step is the real gate, because it fails the way a consumer would rather than the way CI does. The pin uses go mod edit, not a regex. It handles the single-line require form as well as the block, never touches a replace, and rejects a version whose major does not match the path suffix. A regex accepts all three. The pin commit is never pushed to a branch. It is reachable only through the nested tags, so the branch keeps pointing nested modules at the last release and keeps building against the working tree. Also corrects the submodules input description, which told callers nested go.mod repos were out of scope.
v0.0.0-00010101000000-000000000000 is what Go writes when a module is only ever reached through a replace. It is valid semver, so a format check waves it through, and it resolves for nobody. forge carries 19 of them and the check called it clean. Same failure as a bare v0.0.0, just wearing a shape that looks deliberate.
The go tool ignores a directory whose name starts with an underscore, so a module under _examples is not surface anyone consumes. nexus keeps three there that require providers which were never tagged, and flagging those asks for a fix that would mean publishing example-only modules. Discovery in release skips them for the same reason: they should not be tagged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
A nested
go.modusually carries areplaceso the working tree builds againstitself. That replace is ignored the moment the module becomes somebody else's
dependency, so the
requireline is what a consumer actually resolves, andseveral repos here require the root at
v0.0.0, which is not a version anyonecan fetch:
It works today only for people who already require the root at a real version,
because MVS then picks that one instead, which means the failure is invisible to
everyone inside the org and waiting for the first person outside it who reaches
for a driver before reaching for grove. No local build notices either. The
replace covers it.
Forge has the same bug wearing a different version string. It uses
v0.0.0-00010101000000-000000000000, the zero pseudo-version Go writes when amodule is reachable solely through a replace, and because that string is valid
semver it gets past a format check while still resolving for nobody.
Counted across the Go repos: 155 requires. nexus 90, grove 23, forge 19,
herald 11, trove 6, farp 5, dtl 1.
What this adds
go-cigains anested-modulesjob. It reads every intra-repo require andfails on
v0.0.0, on the zero pseudo-version, and on anything that is notsemver. A version that simply is not published yet gets reported and allowed,
because release rewrites those before it tags. The job is a no-op in a
single-module repo, so every caller picks it up without changing anything.
go-releasegains the other half. It discovers nested modules, builds and testseach one, pins their intra-repo requires to the version being cut, tags each as
<dir>/<version>, and then stands up a scratch module and runsgo getonevery one of them through the public proxy, which is the real gate here because
it fails the way a consumer would rather than the way CI does.
The pin uses
go mod edit. A regex missed the single-linerequire x v1.2.3form entirely, and grove has one of those sitting in
kv/go.mod, so the firstversion of this quietly left it broken while reporting success.
go mod editknows both forms, never touches a
replace, and rejects a version whose majordoes not match the path suffix.
The pin commit is never pushed to a branch. It is reachable only through the
nested tags, so your branch keeps pointing nested modules at the last release
and keeps building against the working tree.
Two things worth knowing
Underscore-prefixed directories are skipped. The go tool ignores them, and nexus
keeps three example modules under
_examplesthat require providers nobody evertagged.
The
submodulesinput description said nestedgo.modrepos were out of scope,which is why every affected repo hand-rolled its own release workflow, and why
each of those hand-rolled workflows reinvented the same mistake independently.
Corrected here.
Rollout
All 29 callers pin
@v1, so nothing changes until that tag moves. The reposlisted above are already fixed and pushed, so they pass on day one.
Verified against the real repos: grove and forge fail before their fixes and
pass after, cortex passes, single-module repos are unaffected.
actionlintisclean.