Skip to content

Commit 3b326fe

Browse files
committed
internal/task, cmd/relui: add definition for releasing gopls
1. Param: coordinator need to provide the version string that will be released. 2. Task: validate the input version a valid version. 3. Output: returns whether the input version is valid. For golang/go#57643 Change-Id: Ib6d3f10c9a46ad2f24b0a1fc053265456d152f41 Reviewed-on: https://go-review.googlesource.com/c/build/+/601236 Reviewed-by: Dmitri Shuralyov <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]>
1 parent e0f2cac commit 3b326fe

File tree

3 files changed

+215
-0
lines changed

3 files changed

+215
-0
lines changed

cmd/relui/main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,11 @@ func main() {
308308
}
309309
dh.RegisterDefinition("Tag a new version of x/telemetry/config (if necessary)", tagTelemetryTasks.NewDefinition())
310310

311+
releaseGoplsTasks := task.ReleaseGoplsTasks{
312+
Gerrit: gerritClient,
313+
}
314+
dh.RegisterDefinition("Release a new version of gopls", releaseGoplsTasks.NewDefinition())
315+
311316
privateSyncTask := &task.PrivateMasterSyncTask{
312317
Git: gitClient,
313318
PrivateGerritURL: "https://go-internal.googlesource.com/golang/go-private",

internal/task/releasegopls.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Copyright 2020 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package task
6+
7+
import (
8+
"fmt"
9+
"slices"
10+
"strings"
11+
12+
wf "golang.org/x/build/internal/workflow"
13+
"golang.org/x/mod/semver"
14+
)
15+
16+
// ReleaseGoplsTasks implements a new workflow definition include all the tasks
17+
// to release a gopls.
18+
type ReleaseGoplsTasks struct {
19+
Gerrit GerritClient
20+
}
21+
22+
// NewDefinition create a new workflow definition for releasing gopls.
23+
func (r *ReleaseGoplsTasks) NewDefinition() *wf.Definition {
24+
wd := wf.New()
25+
26+
// TODO(hxjiang): provide potential release versions in the relui where the
27+
// coordinator can choose which version to release instead of manual input.
28+
version := wf.Param(wd, wf.ParamDef[string]{Name: "version"})
29+
isValid := wf.Task1(wd, "validating input version", r.isValidVersion, version)
30+
wf.Output(wd, "valid", isValid)
31+
return wd
32+
}
33+
34+
func (r *ReleaseGoplsTasks) isValidVersion(ctx *wf.TaskContext, ver string) (bool, error) {
35+
if !semver.IsValid(ver) {
36+
return false, nil
37+
}
38+
39+
versions, err := r.possibleGoplsVersions(ctx)
40+
if err != nil {
41+
return false, fmt.Errorf("failed to get latest Gopls version tags from x/tool: %w", err)
42+
}
43+
44+
return slices.Contains(versions, ver), nil
45+
}
46+
47+
// semversion is a parsed semantic version.
48+
type semversion struct {
49+
major, minor, patch int
50+
pre string
51+
}
52+
53+
// parseSemver attempts to parse semver components out of the provided semver
54+
// v. If v is not valid semver in canonical form, parseSemver returns false.
55+
func parseSemver(v string) (_ semversion, ok bool) {
56+
var parsed semversion
57+
v, parsed.pre, _ = strings.Cut(v, "-")
58+
if _, err := fmt.Sscanf(v, "v%d.%d.%d", &parsed.major, &parsed.minor, &parsed.patch); err == nil {
59+
ok = true
60+
}
61+
return parsed, ok
62+
}
63+
64+
// possibleGoplsVersions identifies suitable versions for the upcoming release
65+
// based on the current tags in the repo.
66+
func (r *ReleaseGoplsTasks) possibleGoplsVersions(ctx *wf.TaskContext) ([]string, error) {
67+
tags, err := r.Gerrit.ListTags(ctx, "tools")
68+
if err != nil {
69+
return nil, err
70+
}
71+
72+
var semVersions []semversion
73+
majorMinorPatch := map[int]map[int]map[int]bool{}
74+
for _, tag := range tags {
75+
v, ok := strings.CutPrefix(tag, "gopls/")
76+
if !ok {
77+
continue
78+
}
79+
80+
if !semver.IsValid(v) {
81+
continue
82+
}
83+
84+
// Skip for pre-release versions.
85+
if semver.Prerelease(v) != "" {
86+
continue
87+
}
88+
89+
semv, ok := parseSemver(v)
90+
semVersions = append(semVersions, semv)
91+
92+
if majorMinorPatch[semv.major] == nil {
93+
majorMinorPatch[semv.major] = map[int]map[int]bool{}
94+
}
95+
if majorMinorPatch[semv.major][semv.minor] == nil {
96+
majorMinorPatch[semv.major][semv.minor] = map[int]bool{}
97+
}
98+
majorMinorPatch[semv.major][semv.minor][semv.patch] = true
99+
}
100+
101+
var possible []string
102+
seen := map[string]bool{}
103+
for _, v := range semVersions {
104+
nextMajor := fmt.Sprintf("v%d.%d.%d", v.major+1, 0, 0)
105+
if _, ok := majorMinorPatch[v.major+1]; !ok && !seen[nextMajor] {
106+
seen[nextMajor] = true
107+
possible = append(possible, nextMajor)
108+
}
109+
110+
nextMinor := fmt.Sprintf("v%d.%d.%d", v.major, v.minor+1, 0)
111+
if _, ok := majorMinorPatch[v.major][v.minor+1]; !ok && !seen[nextMinor] {
112+
seen[nextMinor] = true
113+
possible = append(possible, nextMinor)
114+
}
115+
116+
nextPatch := fmt.Sprintf("v%d.%d.%d", v.major, v.minor, v.patch+1)
117+
if _, ok := majorMinorPatch[v.major][v.minor][v.patch+1]; !ok && !seen[nextPatch] {
118+
seen[nextPatch] = true
119+
possible = append(possible, nextPatch)
120+
}
121+
}
122+
123+
semver.Sort(possible)
124+
return possible, nil
125+
}

internal/task/releasegopls_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright 2020 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package task
6+
7+
import (
8+
"context"
9+
"testing"
10+
11+
"github.com/google/go-cmp/cmp"
12+
"golang.org/x/build/internal/workflow"
13+
)
14+
15+
func TestPossibleGoplsVersions(t *testing.T) {
16+
tests := []struct {
17+
name string
18+
tags []string
19+
want []string
20+
}{
21+
{
22+
name: "any one version tag should have three possible next versions",
23+
tags: []string{"gopls/v1.2.3"},
24+
want: []string{"v1.2.4", "v1.3.0", "v2.0.0"},
25+
},
26+
{
27+
name: "1.2.0 should be skipped because 1.2.3 already exist",
28+
tags: []string{"gopls/v1.2.3", "gopls/v1.1.0"},
29+
want: []string{"v1.1.1", "v1.2.4", "v1.3.0", "v2.0.0"},
30+
},
31+
{
32+
name: "2.0.0 should be skipped because 2.1.3 already exist",
33+
tags: []string{"gopls/v1.2.3", "gopls/v2.1.3"},
34+
want: []string{"v1.2.4", "v1.3.0", "v2.1.4", "v2.2.0", "v3.0.0"},
35+
},
36+
{
37+
name: "1.2.0 is still consider valid version because there is no 1.2.X",
38+
tags: []string{"gopls/v1.1.3", "gopls/v1.3.2", "gopls/v2.1.2"},
39+
want: []string{"v1.1.4", "v1.2.0", "v1.3.3", "v1.4.0", "v2.1.3", "v2.2.0", "v3.0.0"},
40+
},
41+
{
42+
name: "2.0.0 is still consider valid version because there is no 2.X.X",
43+
tags: []string{"gopls/v1.2.3", "gopls/v3.1.2"},
44+
want: []string{"v1.2.4", "v1.3.0", "v2.0.0", "v3.1.3", "v3.2.0", "v4.0.0"},
45+
},
46+
{
47+
name: "pre-release version tag should not have any effect on the next version",
48+
tags: []string{"gopls/v0.16.1-pre.1", "gopls/v0.16.1-pre.2", "gopls/v0.16.0"},
49+
want: []string{"v0.16.1", "v0.17.0", "v1.0.0"},
50+
},
51+
{
52+
name: "other unrelated tag should not have any effect on the next version",
53+
tags: []string{"v0.9.2", "v0.9.3", "v0.23.0", "gopls/v0.16.0"},
54+
want: []string{"v0.16.1", "v0.17.0", "v1.0.0"},
55+
},
56+
}
57+
58+
for _, tc := range tests {
59+
t.Run(tc.name, func(t *testing.T) {
60+
tools := NewFakeRepo(t, "tools")
61+
commit := tools.Commit(map[string]string{
62+
"go.mod": "module golang.org/x/tools\n",
63+
"go.sum": "\n",
64+
})
65+
66+
for _, tag := range tc.tags {
67+
tools.Tag(tag, commit)
68+
}
69+
70+
gerrit := NewFakeGerrit(t, tools)
71+
72+
tasks := &ReleaseGoplsTasks{
73+
Gerrit: gerrit,
74+
}
75+
76+
got, err := tasks.possibleGoplsVersions(&workflow.TaskContext{Context: context.Background()})
77+
if err != nil {
78+
t.Fatalf("possibleGoplsVersions() should not return error, but return %v", err)
79+
}
80+
if diff := cmp.Diff(tc.want, got); diff != "" {
81+
t.Errorf("possibleGoplsVersions() mismatch (-want +got):\n%s", diff)
82+
}
83+
})
84+
}
85+
}

0 commit comments

Comments
 (0)