Skip to content

Commit 9ff3bee

Browse files
h9jianggopherbot
authored andcommitted
internal/task: add workflow to create vscode-go stable release candidate
1. Add an input selection parameter allowing the coordinator to choose between targeting the next minor or patch version. 2. Add a step to automatically determine the appropriate version number based on the coordinator's selection and prompt for release approval. A local relui screenshot is at golang/vscode-go#3500 (comment) For golang/vscode-go#3500 Change-Id: I38fcd861ff864dc3683fc571e9a39bccf4e9cb63 Reviewed-on: https://go-review.googlesource.com/c/build/+/607176 Reviewed-by: Hyang-Ah Hana Kim <[email protected]> Auto-Submit: Hongxiang Jiang <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 7d24b5e commit 9ff3bee

File tree

3 files changed

+249
-0
lines changed

3 files changed

+249
-0
lines changed

cmd/relui/main.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,12 @@ func main() {
307307
}
308308
dh.RegisterDefinition("Update x/crypto NSS root bundle", bundleTasks.NewDefinition())
309309

310+
releaseVSCodeGoTasks := task.ReleaseVSCodeGoTasks{
311+
Gerrit: gerritClient,
312+
ApproveAction: relui.ApproveActionDep(dbPool),
313+
}
314+
dh.RegisterDefinition("Create a vscode-go release candidate", releaseVSCodeGoTasks.NewPrereleaseDefinition())
315+
310316
tagTelemetryTasks := &task.TagTelemetryTasks{
311317
Gerrit: gerritClient,
312318
CloudBuild: cloudBuildClient,

internal/task/releasevscodego.go

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
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+
10+
"golang.org/x/build/internal/relui/groups"
11+
"golang.org/x/build/internal/workflow"
12+
wf "golang.org/x/build/internal/workflow"
13+
)
14+
15+
// VSCode extensions and semantic versioning have different understandings of
16+
// release and pre-release.
17+
18+
// From the VSCode extension guidelines:
19+
// - Pre-releases contain the latest changes and use odd-numbered minor versions
20+
// (e.g. v0.45.0).
21+
// - Releases are more stable and use even-numbered minor versions (e.g.
22+
// v0.44.0).
23+
// See: https://code.visualstudio.com/api/working-with-extensions/publishing-extension#prerelease-extensions
24+
25+
// In semantic versioning:
26+
// - Pre-releases use a hyphen and label (e.g. v0.44.0-rc.1).
27+
// - Releases have no hyphen (e.g. v0.44.0).
28+
// See: https://semver.org/spec/v2.0.0.html
29+
30+
// To avoid confusion, vscode-go release flow will use terminology below without
31+
// overloading the term "pre-release":
32+
33+
// - Stable version: VSCode extension's release version (even minor, e.g. v0.44.0)
34+
// - Insider version: VSCode extension's pre-release version (odd minor, e.g. v0.45.0)
35+
// - Release version: Semantic versioning release (no hyphen, e.g. v0.44.0)
36+
// - Pre-release version: Semantic versioning pre-release (with hyphen, e.g. v0.44.0-rc.1)
37+
38+
// ReleaseVSCodeGoTasks implements a set of vscode-go release workflow definitions.
39+
//
40+
// * pre-release workflow: creates a pre-release version of a stable version.
41+
// * release workflow: creates a release version of a stable version.
42+
// * insider workflow: creates a insider version. There are no pre-releases for
43+
// insider versions.
44+
type ReleaseVSCodeGoTasks struct {
45+
Gerrit GerritClient
46+
ApproveAction func(*wf.TaskContext) error
47+
}
48+
49+
var nextVersionParam = wf.ParamDef[string]{
50+
Name: "next version",
51+
ParamType: workflow.ParamType[string]{
52+
HTMLElement: "select",
53+
HTMLSelectOptions: []string{
54+
"next minor",
55+
"next patch",
56+
},
57+
},
58+
}
59+
60+
// NewPrereleaseDefinition create a new workflow definition for vscode-go pre-release.
61+
func (r *ReleaseVSCodeGoTasks) NewPrereleaseDefinition() *wf.Definition {
62+
wd := wf.New(wf.ACL{Groups: []string{groups.ToolsTeam}})
63+
64+
versionBumpStrategy := wf.Param(wd, nextVersionParam)
65+
66+
version := wf.Task1(wd, "find the next pre-release version", r.nextPrereleaseVersion, versionBumpStrategy)
67+
_ = wf.Action1(wd, "await release coordinator's approval", r.approveVersion, version)
68+
69+
return wd
70+
}
71+
72+
// nextPrereleaseVersion determines the next pre-release version for the
73+
// upcoming stable release of vscode-go by examining all existing tags in the
74+
// repository.
75+
//
76+
// The versionBumpStrategy input indicates whether the pre-release should target
77+
// the next minor or next patch version.
78+
func (r *ReleaseVSCodeGoTasks) nextPrereleaseVersion(ctx *wf.TaskContext, versionBumpStrategy string) (semversion, error) {
79+
tags, err := r.Gerrit.ListTags(ctx, "vscode-go")
80+
if err != nil {
81+
return semversion{}, err
82+
}
83+
84+
semv := lastReleasedVersion(tags, true)
85+
switch versionBumpStrategy {
86+
case "next minor":
87+
semv.Minor += 2
88+
semv.Patch = 0
89+
case "next patch":
90+
semv.Patch += 1
91+
default:
92+
return semversion{}, fmt.Errorf("unknown version selection strategy: %q", versionBumpStrategy)
93+
}
94+
95+
// latest to track the latest pre-release for the given semantic version.
96+
latest := 0
97+
for _, v := range tags {
98+
cur, ok := parseSemver(v)
99+
if !ok {
100+
continue
101+
}
102+
103+
if cur.Pre == "" {
104+
continue
105+
}
106+
107+
if cur.Major != semv.Major || cur.Minor != semv.Minor || cur.Patch != semv.Patch {
108+
continue
109+
}
110+
111+
pre, err := cur.prereleaseVersion()
112+
if err != nil {
113+
continue
114+
}
115+
if pre > latest {
116+
latest = pre
117+
}
118+
}
119+
120+
semv.Pre = fmt.Sprintf("rc.%v", latest+1)
121+
return semv, err
122+
}
123+
124+
func lastReleasedVersion(versions []string, onlyStable bool) semversion {
125+
latest := semversion{}
126+
for _, v := range versions {
127+
semv, ok := parseSemver(v)
128+
if !ok {
129+
continue
130+
}
131+
132+
if semv.Pre != "" {
133+
continue
134+
}
135+
136+
if semv.Minor%2 == 0 && onlyStable {
137+
if semv.Minor > latest.Minor {
138+
latest = semv
139+
}
140+
141+
if semv.Minor == latest.Minor && semv.Patch > latest.Patch {
142+
latest = semv
143+
}
144+
}
145+
146+
if semv.Minor%2 == 1 && !onlyStable {
147+
if semv.Minor > latest.Minor {
148+
latest = semv
149+
}
150+
151+
if semv.Minor == latest.Minor && semv.Patch > latest.Patch {
152+
latest = semv
153+
}
154+
}
155+
}
156+
157+
return latest
158+
}
159+
160+
func (r *ReleaseVSCodeGoTasks) approveVersion(ctx *wf.TaskContext, semv semversion) error {
161+
ctx.Printf("The next release candidate will be v%v.%v.%v-%s", semv.Major, semv.Minor, semv.Patch, semv.Pre)
162+
return r.ApproveAction(ctx)
163+
}

internal/task/releasevscodego_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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+
"golang.org/x/build/internal/workflow"
12+
)
13+
14+
func TestNextPrereleaseVersion(t *testing.T) {
15+
tests := []struct {
16+
name string
17+
existingTags []string
18+
versionRule string
19+
wantVersion string
20+
}{
21+
{
22+
name: "v0.44.0 have not released, have no release candidate",
23+
existingTags: []string{"v0.44.0", "v0.43.0", "v0.42.0"},
24+
versionRule: "next minor",
25+
wantVersion: "v0.46.0-rc.1",
26+
},
27+
{
28+
name: "v0.44.0 have not released but already have two release candidate",
29+
existingTags: []string{"v0.44.0-rc.1", "v0.44.0-rc.2", "v0.43.0", "v0.42.0"},
30+
versionRule: "next minor",
31+
wantVersion: "v0.44.0-rc.3",
32+
},
33+
{
34+
name: "v0.44.3 have not released, have no release candidate",
35+
existingTags: []string{"v0.44.2-rc.1", "v0.44.2", "v0.44.1", "v0.44.1-rc.1"},
36+
versionRule: "next patch",
37+
wantVersion: "v0.44.3-rc.1",
38+
},
39+
{
40+
name: "v0.44.3 have not released but already have one release candidate",
41+
existingTags: []string{"v0.44.3-rc.1", "v0.44.2", "v0.44.2-rc.1", "v0.44.1", "v0.44.1-rc.1"},
42+
versionRule: "next patch",
43+
wantVersion: "v0.44.3-rc.2",
44+
},
45+
}
46+
47+
for _, tc := range tests {
48+
t.Run(tc.name, func(t *testing.T) {
49+
vscodego := NewFakeRepo(t, "vscode-go")
50+
commit := vscodego.Commit(map[string]string{
51+
"go.mod": "module github.com/golang/vscode-go\n",
52+
"go.sum": "\n",
53+
})
54+
55+
for _, tag := range tc.existingTags {
56+
vscodego.Tag(tag, commit)
57+
}
58+
59+
gerrit := NewFakeGerrit(t, vscodego)
60+
61+
tasks := &ReleaseVSCodeGoTasks{
62+
Gerrit: gerrit,
63+
}
64+
65+
got, err := tasks.nextPrereleaseVersion(&workflow.TaskContext{Context: context.Background(), Logger: &testLogger{t, ""}}, tc.versionRule)
66+
if err != nil {
67+
t.Fatal(err)
68+
}
69+
70+
want, ok := parseSemver(tc.wantVersion)
71+
if !ok {
72+
t.Fatalf("failed to parse the want version: %q", tc.wantVersion)
73+
}
74+
75+
if want != got {
76+
t.Errorf("nextPrereleaseVersion(%q) = %v but want %v", tc.versionRule, got, want)
77+
}
78+
})
79+
}
80+
}

0 commit comments

Comments
 (0)