cmd/gorelease: accept version queries for -base
This allows -base to be something like "latest" or "master".
Fixes golang/go#37410
Fixes golang/go#37412
Change-Id: I1bdf26e6524518079298a977c7de702db11ed286
Reviewed-on: https://go-review.googlesource.com/c/exp/+/236598
Run-TryBot: Jay Conrod <jayconrod@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
Reviewed-by: Michael Matloob <matloob@golang.org>
diff --git a/cmd/gorelease/gorelease.go b/cmd/gorelease/gorelease.go
index 5f26b7f..4a2dbed 100644
--- a/cmd/gorelease/gorelease.go
+++ b/cmd/gorelease/gorelease.go
@@ -38,8 +38,8 @@
// gorelease accepts the following flags:
//
// -base=version: The version that the current version of the module will be
-// compared against. The version must be a semantic version (for example,
-// "v2.3.4") or "none". If the version is "none", gorelease will not compare the
+// compared against. This may be a version like "v1.5.2", a version query like
+// "latest", or "none". If the version is "none", gorelease will not compare the
// current version against any previous version; it will only validate the
// current version. This is useful for checking the first release of a new major
// version. If -base is not specified, gorelease will attempt to infer a base
@@ -104,33 +104,12 @@
// the APIs are still compatible, just with a different module split).
// TODO(jayconrod):
-// * Allow -base to be an arbitrary revision name that resolves to a version
-// or pseudo-version.
-// * Don't accept -version that increments minor or patch version by more than 1
-// or increments the minor version without zeroing the patch, compared with
-// existing versions. Note that -base may be distant from -version,
-// for example, when reverting an incompatible change accidentally released.
-// * Report errors when packages can't be loaded without replace / exclude.
// * Clean up overuse of fmt.Errorf.
-// * Support -json output.
-// * Don't suggest a release tag that already exists.
-// * Suggest a minor release if dependency has been bumped by minor version.
-// * Updating go version, either in the main module or in a dependency that
-// provides packages transitively imported by non-internal, non-test packages
-// in the main module, should require a minor version bump.
// * Support migration to modules after v2.x.y+incompatible. Requires comparing
// packages with different module paths.
// * Error when packages import from earlier major version of same module.
// (this may be intentional; look for real examples first).
-// * Check that proposed prerelease will not sort below pseudo-versions.
-// * Error messages point to HTML documentation.
-// * Positional arguments should specify which packages to check. Without
-// these, we check all non-internal packages in the module.
// * Mechanism to suppress error messages.
-// * Check that the main module does not transitively require a newer version
-// of itself.
-// * Invalid file names and import paths should be reported sensibly.
-// golang.org/x/mod/zip should return structured errors for this.
func main() {
log.SetFlags(0)
@@ -172,17 +151,12 @@
if len(fs.Args()) > 0 {
return false, usageErrorf("no arguments allowed")
}
- if baseVersion != "" && baseVersion != "none" {
- if c := semver.Canonical(baseVersion); c != baseVersion {
- return false, usageErrorf("base version %q is not a canonical semantic version", baseVersion)
- }
- }
if releaseVersion != "" {
if c := semver.Canonical(releaseVersion); c != releaseVersion {
return false, usageErrorf("release version %q is not a canonical semantic version", releaseVersion)
}
}
- if baseVersion != "" && baseVersion != "none" && releaseVersion != "" {
+ if baseVersion != "" && semver.Canonical(baseVersion) == baseVersion && releaseVersion != "" {
if cmp := semver.Compare(baseVersion, releaseVersion); cmp == 0 {
return false, usageErrorf("-base and -version must be different")
} else if cmp > 0 {
@@ -262,11 +236,24 @@
panic(fmt.Sprintf("could not find version suffix in module path %q", modPath))
}
+ var baseVersionQuery string
baseVersionInferred := baseVersion == ""
if baseVersionInferred {
if baseVersion, err = inferBaseVersion(modPath, releaseVersion); err != nil {
return report{}, err
}
+ } else if baseVersion != "none" && baseVersion != module.CanonicalVersion(baseVersion) {
+ baseVersionQuery = baseVersion
+ if baseVersion, err = queryVersion(modPath, baseVersionQuery); err != nil {
+ return report{}, err
+ }
+ if baseVersion != "none" && releaseVersion != "" && semver.Compare(baseVersion, releaseVersion) >= 0 {
+ // TODO(jayconrod): reconsider this comparison for pseudo-versions in
+ // general. A query might match different pseudo-versions over time,
+ // depending on ancestor versions, so this might start failing with
+ // no local change.
+ return report{}, fmt.Errorf("base version %s (%s) must be lower than release version %s", baseVersion, baseVersionQuery, releaseVersion)
+ }
}
if baseVersion != "none" {
if err := module.Check(modPath, baseVersion); err != nil {
@@ -392,6 +379,7 @@
modulePath: modPath,
baseVersion: baseVersion,
baseVersionInferred: baseVersionInferred,
+ baseVersionQuery: baseVersionQuery,
releaseVersion: releaseVersion,
tagPrefix: tagPrefix,
diagnostics: diagnostics,
@@ -549,6 +537,37 @@
return "", fmt.Errorf("no versions found lower than %s", releaseVersion)
}
+// queryVersion returns the canonical version for a given module version query.
+func queryVersion(modPath, query string) (resolved string, err error) {
+ defer func() {
+ if err != nil {
+ err = fmt.Errorf("could not resolve version %s@%s: %w", modPath, query, err)
+ }
+ }()
+ if query == "upgrade" || query == "patch" {
+ return "", errors.New("query is based on requirements in main go.mod file")
+ }
+
+ tmpDir, err := ioutil.TempDir("", "")
+ if err != nil {
+ return "", err
+ }
+ defer func() {
+ if rerr := os.Remove(tmpDir); rerr != nil && err == nil {
+ err = rerr
+ }
+ }()
+ arg := modPath + "@" + query
+ cmd := exec.Command("go", "list", "-m", "-f", "{{.Version}}", "--", arg)
+ cmd.Dir = tmpDir
+ cmd.Env = append(os.Environ(), "GO111MODULE=on")
+ out, err := cmd.Output()
+ if err != nil {
+ return "", cleanCmdError(err)
+ }
+ return strings.TrimSpace(string(out)), nil
+}
+
// loadVersions loads the list of versions for the given module using
// 'go list -m -versions'. The returned versions are sorted in ascending
// semver order.
@@ -557,7 +576,11 @@
if err != nil {
return nil, err
}
- defer os.Remove(tmpDir)
+ defer func() {
+ if rerr := os.Remove(tmpDir); rerr != nil && err == nil {
+ err = rerr
+ }
+ }()
cmd := exec.Command("go", "list", "-m", "-versions", "--", modPath)
cmd.Dir = tmpDir
cmd.Env = append(os.Environ(), "GO111MODULE=on")
diff --git a/cmd/gorelease/gorelease_test.go b/cmd/gorelease/gorelease_test.go
index 3e94866..a29f438 100644
--- a/cmd/gorelease/gorelease_test.go
+++ b/cmd/gorelease/gorelease_test.go
@@ -104,7 +104,7 @@
// to gorelease.
baseVersion string
- // releaseVersion (set with version=...) is the value of the -version flag
+ // releaseVersion (set with release=...) is the value of the -version flag
// to pass to gorelease.
releaseVersion string
diff --git a/cmd/gorelease/report.go b/cmd/gorelease/report.go
index e26f153..1a1218f 100644
--- a/cmd/gorelease/report.go
+++ b/cmd/gorelease/report.go
@@ -31,6 +31,9 @@
// automatically (not specified with -base).
baseVersionInferred bool
+ // baseVersionQuery is set if -base was a version query (like "latest").
+ baseVersionQuery string
+
// releaseVersion is the version of the module to release, either
// proposed with -version or inferred with suggestVersion.
releaseVersion string
@@ -87,6 +90,8 @@
if r.baseVersionInferred {
fmt.Fprintf(buf, "Inferred base version: %s\n", r.baseVersion)
+ } else if r.baseVersionQuery != "" {
+ fmt.Fprintf(buf, "Base version: %s (%s)\n", r.baseVersion, r.baseVersionQuery)
}
if len(r.diagnostics) > 0 {
diff --git a/cmd/gorelease/testdata/basic/v1_querybase_higher.test b/cmd/gorelease/testdata/basic/v1_querybase_higher.test
new file mode 100644
index 0000000..7bf77bc
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v1_querybase_higher.test
@@ -0,0 +1,8 @@
+mod=example.com/basic
+version=v1.0.1
+release=v1.0.1
+base=>v1.0.1
+error=true
+
+-- want --
+base version v1.1.0 (>v1.0.1) must be lower than release version v1.0.1
diff --git a/cmd/gorelease/testdata/basic/v1_querybase_suggest.test b/cmd/gorelease/testdata/basic/v1_querybase_suggest.test
new file mode 100644
index 0000000..6bfc95c
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v1_querybase_suggest.test
@@ -0,0 +1,6 @@
+mod=example.com/basic
+version=v1.0.1
+base=version-1.0.1
+-- want --
+Base version: v1.0.1 (version-1.0.1)
+Suggested version: v1.0.2
diff --git a/cmd/gorelease/testdata/basic/v1_querybase_verify.test b/cmd/gorelease/testdata/basic/v1_querybase_verify.test
new file mode 100644
index 0000000..f4f6d10
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v1_querybase_verify.test
@@ -0,0 +1,7 @@
+mod=example.com/basic
+version=v1.0.1
+base=version-1.0.1
+release=v1.0.2
+-- want --
+Base version: v1.0.1 (version-1.0.1)
+v1.0.2 is a valid semantic version for this release.
diff --git a/cmd/gorelease/testdata/errors/bad_base.test b/cmd/gorelease/testdata/errors/bad_base.test
deleted file mode 100644
index 6147929..0000000
--- a/cmd/gorelease/testdata/errors/bad_base.test
+++ /dev/null
@@ -1,8 +0,0 @@
-mod=example.com/errors
-base=master
-error=true
-
--- want --
-usage: gorelease [-base=version] [-version=version]
-base version "master" is not a canonical semantic version
-For more information, run go doc golang.org/x/exp/cmd/gorelease
diff --git a/cmd/gorelease/testdata/errors/upgrade_base.test b/cmd/gorelease/testdata/errors/upgrade_base.test
new file mode 100644
index 0000000..d09b68a
--- /dev/null
+++ b/cmd/gorelease/testdata/errors/upgrade_base.test
@@ -0,0 +1,7 @@
+mod=example.com/errors
+version=v0.1.0
+base=upgrade
+error=true
+
+-- want --
+could not resolve version example.com/errors@upgrade: query is based on requirements in main go.mod file
diff --git a/cmd/gorelease/testdata/mod/example.com_basic_version-1.0.1.txt b/cmd/gorelease/testdata/mod/example.com_basic_version-1.0.1.txt
new file mode 100644
index 0000000..34e769e
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_basic_version-1.0.1.txt
@@ -0,0 +1,2 @@
+-- .info --
+{"Version":"v1.0.1"}
diff --git a/cmd/gorelease/testdata/mod/example.com_errors_master.txt b/cmd/gorelease/testdata/mod/example.com_errors_master.txt
deleted file mode 100644
index c4c52ed..0000000
--- a/cmd/gorelease/testdata/mod/example.com_errors_master.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-Non-canonical version, referenced in errors/bad_base.test.
-For now, it's an error to use a non-canonical -base. It won't be in the future.
--- .info --
-{"Version":"v0.2.0"}
diff --git a/go.mod b/go.mod
index 7e25677..65cda84 100644
--- a/go.mod
+++ b/go.mod
@@ -9,7 +9,6 @@
golang.org/x/image v0.0.0-20190802002840-cff245a6509b
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b
- golang.org/x/sync v0.0.0-20190423024810-112230192c58
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898