cmd/gorelease: fix handling of circular module dependency When inferring a release version, gorelease currently defines a versioned replace directive in the temporary go.mod created for analysis. If the module being analyzed has an indirect dependency on itself the replace directive is not matched by the version selected during MVS, resulting in the analysis being performed against the indirect dependency version rather than the current branch. Fixes golang/go#80497 Change-Id: I8fa0a99272f1b63b6e06af68537c54edba4c285d Reviewed-on: https://go-review.googlesource.com/c/exp/+/806400 Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Sean Liao <sean@liao.dev> Reviewed-by: David Chase <drchase@google.com>
diff --git a/cmd/gorelease/gorelease.go b/cmd/gorelease/gorelease.go index 47cedc5..80bf195 100644 --- a/cmd/gorelease/gorelease.go +++ b/cmd/gorelease/gorelease.go
@@ -1092,7 +1092,18 @@ f.AddModuleStmt("gorelease-load-module") f.AddRequire(modPath, version) if !cached { - f.AddReplace(modPath, version, modRoot, "") + // Replace all versions of modPath (not just the specific version + // required above) with the local module directory. A version-scoped + // replace only takes effect when minimal version selection picks that + // exact version. If the module appears in its own build graph as a + // transitive dependency at a higher version (an import cycle across + // modules, e.g. a -> b -> a), MVS selects that higher version, the + // version-scoped replace is silently ignored, and the published + // module is loaded from the cache instead of the local directory. + // That makes the local module's API look like the older published + // version and reports spurious changes. A wildcard replace pins the + // local directory regardless of the selected version. + f.AddReplace(modPath, "", modRoot, "") } if modFile != nil { if modFile.Go != nil {
diff --git a/cmd/gorelease/testdata/mod/example.com_transitive_v1.0.0.txt b/cmd/gorelease/testdata/mod/example.com_transitive_v1.0.0.txt new file mode 100644 index 0000000..7245be9 --- /dev/null +++ b/cmd/gorelease/testdata/mod/example.com_transitive_v1.0.0.txt
@@ -0,0 +1,19 @@ +-- go.mod -- +module example.com/transitive + +go 1.25.0 + +require example.com/transitivedep v1.0.0 +-- go.sum -- +example.com/transitivedep v1.0.0 h1:Ak5Wu1MyvLk7hssGej2xlrSZQcBQUGZTEkfQQ6Saxcg= +example.com/transitivedep v1.0.0/go.mod h1:2hwwDmbWo8gHf50CkEkNIWqFIDNdhb1qGdU/AgCehf0= +-- transitive.go -- +package transitive + +import "example.com/transitivedep" + +func Foo() string { return transitivedep.Helper() } +-- p/constants.go -- +package p + +const Help = "help"
diff --git a/cmd/gorelease/testdata/mod/example.com_transitive_v1.1.0.txt b/cmd/gorelease/testdata/mod/example.com_transitive_v1.1.0.txt new file mode 100644 index 0000000..81aa37d --- /dev/null +++ b/cmd/gorelease/testdata/mod/example.com_transitive_v1.1.0.txt
@@ -0,0 +1,20 @@ +-- go.mod -- +module example.com/transitive + +go 1.25.0 + +require example.com/transitivedep v1.0.0 +-- go.sum -- +example.com/transitivedep v1.0.0 h1:Ak5Wu1MyvLk7hssGej2xlrSZQcBQUGZTEkfQQ6Saxcg= +example.com/transitivedep v1.0.0/go.mod h1:2hwwDmbWo8gHf50CkEkNIWqFIDNdhb1qGdU/AgCehf0= +-- transitive.go -- +package transitive + +import "example.com/transitivedep" + +func Foo() string { return transitivedep.Helper() } +func Bar() string { return "bar" } +-- p/constants.go -- +package p + +const Help = "help"
diff --git a/cmd/gorelease/testdata/mod/example.com_transitivedep_v1.0.0.txt b/cmd/gorelease/testdata/mod/example.com_transitivedep_v1.0.0.txt new file mode 100644 index 0000000..288a615 --- /dev/null +++ b/cmd/gorelease/testdata/mod/example.com_transitivedep_v1.0.0.txt
@@ -0,0 +1,12 @@ +-- go.mod -- +module example.com/transitivedep + +go 1.25.0 + +require example.com/transitive v1.0.0 +-- mod.go -- +package transitivedep + +import "example.com/transitive/p" + +func Helper() string { return p.Help }
diff --git a/cmd/gorelease/testdata/transitive/README.md b/cmd/gorelease/testdata/transitive/README.md new file mode 100644 index 0000000..a27e449 --- /dev/null +++ b/cmd/gorelease/testdata/transitive/README.md
@@ -0,0 +1,2 @@ +Module example.com/transitive tests a module that has a transitive +dependency on an earlier version of itself.
diff --git a/cmd/gorelease/testdata/transitive/defined_release.test b/cmd/gorelease/testdata/transitive/defined_release.test new file mode 100644 index 0000000..61dbc4f --- /dev/null +++ b/cmd/gorelease/testdata/transitive/defined_release.test
@@ -0,0 +1,7 @@ +mod=example.com/transitive +version=v1.1.0 +base=v1.1.0 +release=v1.1.1 +-- want -- +# summary +v1.1.1 is a valid semantic version for this release.
diff --git a/cmd/gorelease/testdata/transitive/inferred_all.test b/cmd/gorelease/testdata/transitive/inferred_all.test new file mode 100644 index 0000000..3fce2eb --- /dev/null +++ b/cmd/gorelease/testdata/transitive/inferred_all.test
@@ -0,0 +1,6 @@ +mod=example.com/transitive +version=v1.1.0 +-- want -- +# summary +Inferred base version: v1.1.0 +Suggested version: v1.1.1
diff --git a/cmd/gorelease/testdata/transitive/inferred_release.test b/cmd/gorelease/testdata/transitive/inferred_release.test new file mode 100644 index 0000000..00acaf0 --- /dev/null +++ b/cmd/gorelease/testdata/transitive/inferred_release.test
@@ -0,0 +1,6 @@ +mod=example.com/transitive +version=v1.1.0 +base=v1.1.0 +-- want -- +# summary +Suggested version: v1.1.1