x/exp/cmd/gorelease: err when main module requests lower version than cycle
When there is a cycle like,
main@v1.0.0 -> foo@v1.0.0
foo@v1.0.0 -> main@v1.5.0
And the user suggests main@v1.1.1 (or anything <= 1.5.0), report an error, since
MVS will never choose that version.
Fixes golang/go#37567
Change-Id: I95628c49e0949116ec8019364f5c760dcc7c80a3
Reviewed-on: https://go-review.googlesource.com/c/exp/+/273288
Run-TryBot: Jean de Klerk <deklerk@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Jay Conrod <jayconrod@google.com>
Trust: Jean de Klerk <deklerk@google.com>
diff --git a/cmd/gorelease/gorelease.go b/cmd/gorelease/gorelease.go
index 8152903..10860bd 100644
--- a/cmd/gorelease/gorelease.go
+++ b/cmd/gorelease/gorelease.go
@@ -242,14 +242,15 @@
}
type moduleInfo struct {
- modRoot string // module root directory
- repoRoot string // repository root directory (may be "")
- modPath string // module path in go.mod
- version string // resolved version or "none"
- versionQuery string // a query like "latest" or "dev-branch", if specified
- versionInferred bool // true if the version was unspecified and inferred
- modPathMajor string // major version suffix like "/v3" or ".v2"
- tagPrefix string // prefix for version tags if module not in repo root
+ modRoot string // module root directory
+ repoRoot string // repository root directory (may be "")
+ modPath string // module path in go.mod
+ version string // resolved version or "none"
+ versionQuery string // a query like "latest" or "dev-branch", if specified
+ versionInferred bool // true if the version was unspecified and inferred
+ highestTransitiveVersion string // version of the highest transitive self-dependency (cycle)
+ modPathMajor string // major version suffix like "/v3" or ".v2"
+ tagPrefix string // prefix for version tags if module not in repo root
goModPath string // file path to go.mod
goModData []byte // content of go.mod
@@ -391,6 +392,19 @@
m.diagnostics = append(m.diagnostics, prepareDiagnostics...)
m.diagnostics = append(m.diagnostics, loadDiagnostics...)
+ highestVersion, err := findSelectedVersion(tmpLoadDir, m.modPath)
+ if err != nil {
+ return moduleInfo{}, err
+ }
+
+ if highestVersion != "" {
+ // A version of the module is included in the transitive dependencies.
+ // Add it to the moduleInfo so that the release report stage can use it
+ // in verifying the version or suggestion a new version, depending on
+ // whether the user provided a version already.
+ m.highestTransitiveVersion = highestVersion
+ }
+
return m, nil
}
@@ -1202,3 +1216,18 @@
}
return pairs
}
+
+// findSelectedVersion returns the highest version of the given modPath at
+// modDir, if a module cycle exists. modDir should be a writable directory
+// containing the go.mod for modPath.
+//
+// If no module cycle exists, it returns empty string.
+func findSelectedVersion(modDir, modPath string) (latestVersion string, err error) {
+ cmd := exec.Command("go", "list", "-m", "-f", "{{.Version}}", "--", modPath)
+ cmd.Dir = modDir
+ out, err := cmd.Output()
+ if err != nil {
+ return "", cleanCmdError(err)
+ }
+ return strings.TrimSpace(string(out)), nil
+}
diff --git a/cmd/gorelease/report.go b/cmd/gorelease/report.go
index ee95d13..aede072 100644
--- a/cmd/gorelease/report.go
+++ b/cmd/gorelease/report.go
@@ -191,6 +191,11 @@
over the base version (%s).`, r.base.version)
return
}
+
+ if r.release.highestTransitiveVersion != "" && semver.Compare(r.release.highestTransitiveVersion, r.release.version) > 0 {
+ setNotValid(`Module indirectly depends on a higher version of itself (%s).
+ `, r.release.highestTransitiveVersion)
+ }
}
// suggestReleaseVersion suggests a new version consistent with observed
@@ -219,8 +224,14 @@
var major, minor, patch, pre string
if r.base.version != "none" {
+ minVersion := r.base.version
+ if r.release.highestTransitiveVersion != "" && semver.Compare(r.release.highestTransitiveVersion, minVersion) > 0 {
+ setNotValid("Module indirectly depends on a higher version of itself (%s) than the base version (%s).", r.release.highestTransitiveVersion, r.base.version)
+ return
+ }
+
var err error
- major, minor, patch, pre, _, err = parseVersion(r.base.version)
+ major, minor, patch, pre, _, err = parseVersion(minVersion)
if err != nil {
panic(fmt.Sprintf("could not parse base version: %v", err))
}
@@ -253,6 +264,7 @@
patch = incDecimal(patch)
}
setVersion(fmt.Sprintf("v%s.%s.%s", major, minor, patch))
+ return
}
// canVerifyReleaseVersion returns true if we can safely suggest a new version
diff --git a/cmd/gorelease/testdata/README.md b/cmd/gorelease/testdata/README.md
index 28e171b..9be53a3 100644
--- a/cmd/gorelease/testdata/README.md
+++ b/cmd/gorelease/testdata/README.md
@@ -33,7 +33,7 @@
* `mod`: sets the module path. Must be specified together with `version`. Copies
the content of a module out of the test proxy into a temporary directory
where `gorelease` is run.
-* `version`: specified together with `mod`. sets the version to retreive from
+* `version`: specified together with `mod`, it sets the version to retrieve from
the test proxy.
* `base`: the value of the `-base` flag passed to `gorelease`.
* `release`: the value of the `-version` flag passed to `gorelease`.
diff --git a/cmd/gorelease/testdata/cycle/README.md b/cmd/gorelease/testdata/cycle/README.md
new file mode 100644
index 0000000..3596c8b
--- /dev/null
+++ b/cmd/gorelease/testdata/cycle/README.md
@@ -0,0 +1 @@
+This directory is for tests related to module cycles.
\ No newline at end of file
diff --git a/cmd/gorelease/testdata/cycle/cycle_suggest.test b/cmd/gorelease/testdata/cycle/cycle_suggest.test
new file mode 100644
index 0000000..3e8252f
--- /dev/null
+++ b/cmd/gorelease/testdata/cycle/cycle_suggest.test
@@ -0,0 +1,7 @@
+mod=example.com/cycle
+base=v1.0.0
+version=v1.0.0
+success=false
+-- want --
+Cannot suggest a release version.
+Module indirectly depends on a higher version of itself (v1.5.0) than the base version (v1.0.0).
\ No newline at end of file
diff --git a/cmd/gorelease/testdata/cycle/cycle_verify.test b/cmd/gorelease/testdata/cycle/cycle_verify.test
new file mode 100644
index 0000000..64e1fdf
--- /dev/null
+++ b/cmd/gorelease/testdata/cycle/cycle_verify.test
@@ -0,0 +1,8 @@
+mod=example.com/cycle
+base=v1.0.0
+version=v1.0.0
+release=v1.0.1
+success=false
+-- want --
+v1.0.1 is not a valid semantic version for this release.
+Module indirectly depends on a higher version of itself (v1.5.0).
\ No newline at end of file
diff --git a/cmd/gorelease/testdata/mod/example.com_cycle_v1.0.0.txt b/cmd/gorelease/testdata/mod/example.com_cycle_v1.0.0.txt
new file mode 100644
index 0000000..7200d20
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_cycle_v1.0.0.txt
@@ -0,0 +1,15 @@
+-- go.sum --
+example.com/cycle v1.5.0 h1:j2Bju3xKUT09utc7WwS5sXwrOSVUr5a7vOzOyB4ivac=
+example.com/cycle v1.5.0/go.mod h1://AqZbyNHeLOKZB3J/UPPXaBvk3nCqvqVRbPkffDx60=
+example.com/cycledep v1.0.0/go.mod h1:Gc4hO1S1BMZaxOcGHwCRmdVcQP8+jAu/PyEgLdGe0xU=
+-- go.mod --
+module example.com/cycle
+
+go 1.12
+
+require example.com/cycledep v1.0.0
+require example.com/cycle v1.5.0
+-- cycle/main.go --
+package main
+
+import _ "example.com/cycledep"
\ No newline at end of file
diff --git a/cmd/gorelease/testdata/mod/example.com_cycle_v1.5.0.txt b/cmd/gorelease/testdata/mod/example.com_cycle_v1.5.0.txt
new file mode 100644
index 0000000..be01d7f
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_cycle_v1.5.0.txt
@@ -0,0 +1,6 @@
+-- go.mod --
+module example.com/cycle
+
+go 1.12
+-- cycle/a.go --
+package a
\ No newline at end of file
diff --git a/cmd/gorelease/testdata/mod/example.com_cycledep_v1.0.0.txt b/cmd/gorelease/testdata/mod/example.com_cycledep_v1.0.0.txt
new file mode 100644
index 0000000..6ea4610
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_cycledep_v1.0.0.txt
@@ -0,0 +1,10 @@
+-- go.mod --
+module example.com/cycledep
+
+go 1.12
+
+require example.com/cycle v1.5.0
+-- cycledep/a.go --
+package a
+
+import _ "example.com/cycle"