Skip to content

Commit

Permalink
internal/task: add workflow to create vscode-go stable release candidate
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
h9jiang authored and gopherbot committed Aug 26, 2024
1 parent 7d24b5e commit 9ff3bee
Show file tree
Hide file tree
Showing 3 changed files with 249 additions and 0 deletions.
6 changes: 6 additions & 0 deletions cmd/relui/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,12 @@ func main() {
}
dh.RegisterDefinition("Update x/crypto NSS root bundle", bundleTasks.NewDefinition())

releaseVSCodeGoTasks := task.ReleaseVSCodeGoTasks{
Gerrit: gerritClient,
ApproveAction: relui.ApproveActionDep(dbPool),
}
dh.RegisterDefinition("Create a vscode-go release candidate", releaseVSCodeGoTasks.NewPrereleaseDefinition())

tagTelemetryTasks := &task.TagTelemetryTasks{
Gerrit: gerritClient,
CloudBuild: cloudBuildClient,
Expand Down
163 changes: 163 additions & 0 deletions internal/task/releasevscodego.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package task

import (
"fmt"

"golang.org/x/build/internal/relui/groups"
"golang.org/x/build/internal/workflow"
wf "golang.org/x/build/internal/workflow"
)

// VSCode extensions and semantic versioning have different understandings of
// release and pre-release.

// From the VSCode extension guidelines:
// - Pre-releases contain the latest changes and use odd-numbered minor versions
// (e.g. v0.45.0).
// - Releases are more stable and use even-numbered minor versions (e.g.
// v0.44.0).
// See: https://code.visualstudio.com/api/working-with-extensions/publishing-extension#prerelease-extensions

// In semantic versioning:
// - Pre-releases use a hyphen and label (e.g. v0.44.0-rc.1).
// - Releases have no hyphen (e.g. v0.44.0).
// See: https://semver.org/spec/v2.0.0.html

// To avoid confusion, vscode-go release flow will use terminology below without
// overloading the term "pre-release":

// - Stable version: VSCode extension's release version (even minor, e.g. v0.44.0)
// - Insider version: VSCode extension's pre-release version (odd minor, e.g. v0.45.0)
// - Release version: Semantic versioning release (no hyphen, e.g. v0.44.0)
// - Pre-release version: Semantic versioning pre-release (with hyphen, e.g. v0.44.0-rc.1)

// ReleaseVSCodeGoTasks implements a set of vscode-go release workflow definitions.
//
// * pre-release workflow: creates a pre-release version of a stable version.
// * release workflow: creates a release version of a stable version.
// * insider workflow: creates a insider version. There are no pre-releases for
// insider versions.
type ReleaseVSCodeGoTasks struct {
Gerrit GerritClient
ApproveAction func(*wf.TaskContext) error
}

var nextVersionParam = wf.ParamDef[string]{
Name: "next version",
ParamType: workflow.ParamType[string]{
HTMLElement: "select",
HTMLSelectOptions: []string{
"next minor",
"next patch",
},
},
}

// NewPrereleaseDefinition create a new workflow definition for vscode-go pre-release.
func (r *ReleaseVSCodeGoTasks) NewPrereleaseDefinition() *wf.Definition {
wd := wf.New(wf.ACL{Groups: []string{groups.ToolsTeam}})

versionBumpStrategy := wf.Param(wd, nextVersionParam)

version := wf.Task1(wd, "find the next pre-release version", r.nextPrereleaseVersion, versionBumpStrategy)
_ = wf.Action1(wd, "await release coordinator's approval", r.approveVersion, version)

return wd
}

// nextPrereleaseVersion determines the next pre-release version for the
// upcoming stable release of vscode-go by examining all existing tags in the
// repository.
//
// The versionBumpStrategy input indicates whether the pre-release should target
// the next minor or next patch version.
func (r *ReleaseVSCodeGoTasks) nextPrereleaseVersion(ctx *wf.TaskContext, versionBumpStrategy string) (semversion, error) {
tags, err := r.Gerrit.ListTags(ctx, "vscode-go")
if err != nil {
return semversion{}, err
}

semv := lastReleasedVersion(tags, true)
switch versionBumpStrategy {
case "next minor":
semv.Minor += 2
semv.Patch = 0
case "next patch":
semv.Patch += 1
default:
return semversion{}, fmt.Errorf("unknown version selection strategy: %q", versionBumpStrategy)
}

// latest to track the latest pre-release for the given semantic version.
latest := 0
for _, v := range tags {
cur, ok := parseSemver(v)
if !ok {
continue
}

if cur.Pre == "" {
continue
}

if cur.Major != semv.Major || cur.Minor != semv.Minor || cur.Patch != semv.Patch {
continue
}

pre, err := cur.prereleaseVersion()
if err != nil {
continue
}
if pre > latest {
latest = pre
}
}

semv.Pre = fmt.Sprintf("rc.%v", latest+1)
return semv, err
}

func lastReleasedVersion(versions []string, onlyStable bool) semversion {
latest := semversion{}
for _, v := range versions {
semv, ok := parseSemver(v)
if !ok {
continue
}

if semv.Pre != "" {
continue
}

if semv.Minor%2 == 0 && onlyStable {
if semv.Minor > latest.Minor {
latest = semv
}

if semv.Minor == latest.Minor && semv.Patch > latest.Patch {
latest = semv
}
}

if semv.Minor%2 == 1 && !onlyStable {
if semv.Minor > latest.Minor {
latest = semv
}

if semv.Minor == latest.Minor && semv.Patch > latest.Patch {
latest = semv
}
}
}

return latest
}

func (r *ReleaseVSCodeGoTasks) approveVersion(ctx *wf.TaskContext, semv semversion) error {
ctx.Printf("The next release candidate will be v%v.%v.%v-%s", semv.Major, semv.Minor, semv.Patch, semv.Pre)
return r.ApproveAction(ctx)
}
80 changes: 80 additions & 0 deletions internal/task/releasevscodego_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package task

import (
"context"
"testing"

"golang.org/x/build/internal/workflow"
)

func TestNextPrereleaseVersion(t *testing.T) {
tests := []struct {
name string
existingTags []string
versionRule string
wantVersion string
}{
{
name: "v0.44.0 have not released, have no release candidate",
existingTags: []string{"v0.44.0", "v0.43.0", "v0.42.0"},
versionRule: "next minor",
wantVersion: "v0.46.0-rc.1",
},
{
name: "v0.44.0 have not released but already have two release candidate",
existingTags: []string{"v0.44.0-rc.1", "v0.44.0-rc.2", "v0.43.0", "v0.42.0"},
versionRule: "next minor",
wantVersion: "v0.44.0-rc.3",
},
{
name: "v0.44.3 have not released, have no release candidate",
existingTags: []string{"v0.44.2-rc.1", "v0.44.2", "v0.44.1", "v0.44.1-rc.1"},
versionRule: "next patch",
wantVersion: "v0.44.3-rc.1",
},
{
name: "v0.44.3 have not released but already have one release candidate",
existingTags: []string{"v0.44.3-rc.1", "v0.44.2", "v0.44.2-rc.1", "v0.44.1", "v0.44.1-rc.1"},
versionRule: "next patch",
wantVersion: "v0.44.3-rc.2",
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
vscodego := NewFakeRepo(t, "vscode-go")
commit := vscodego.Commit(map[string]string{
"go.mod": "module github.com/golang/vscode-go\n",
"go.sum": "\n",
})

for _, tag := range tc.existingTags {
vscodego.Tag(tag, commit)
}

gerrit := NewFakeGerrit(t, vscodego)

tasks := &ReleaseVSCodeGoTasks{
Gerrit: gerrit,
}

got, err := tasks.nextPrereleaseVersion(&workflow.TaskContext{Context: context.Background(), Logger: &testLogger{t, ""}}, tc.versionRule)
if err != nil {
t.Fatal(err)
}

want, ok := parseSemver(tc.wantVersion)
if !ok {
t.Fatalf("failed to parse the want version: %q", tc.wantVersion)
}

if want != got {
t.Errorf("nextPrereleaseVersion(%q) = %v but want %v", tc.versionRule, got, want)
}
})
}
}

0 comments on commit 9ff3bee

Please sign in to comment.