Skip to content

Commit d7fe890

Browse files
h9jianggopherbot
authored andcommitted
internal/task: add new selection input to gopls pre-release flow
Coordinator have three options to run the pre-release flow: * next minor or next patch, the flow will interpret the user intention. * use explicit version, the user must input the version explicitly. A local relui screenshot is at https://go.dev/issue/57643#issuecomment-2310730081 For golang/go#57643 Change-Id: I9621da87f248c79597641162682fb2d108bc19f9 Reviewed-on: https://go-review.googlesource.com/c/build/+/608415 Auto-Submit: Hongxiang Jiang <[email protected]> Reviewed-by: Robert Findley <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 8ac64cc commit d7fe890

File tree

3 files changed

+220
-73
lines changed

3 files changed

+220
-73
lines changed

internal/task/releasegopls.go

Lines changed: 73 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,27 @@ type ReleaseGoplsTasks struct {
3333
func (r *ReleaseGoplsTasks) NewPrereleaseDefinition() *wf.Definition {
3434
wd := wf.New(wf.ACL{Groups: []string{groups.ToolsTeam}})
3535

36-
// TODO(hxjiang): provide potential release versions in the relui where the
37-
// coordinator can choose which version to release instead of manual input.
38-
version := wf.Param(wd, wf.ParamDef[string]{Name: "version"})
36+
// versionBumpStrategy specifies the desired release type: next minor or next
37+
// patch.
38+
// This should be the default choice for most releases.
39+
versionBumpStrategy := wf.Param(wd, nextVersionParam)
40+
// inputVersion allows manual override of the version, bypassing the version
41+
// bump strategy.
42+
// Use with caution.
43+
inputVersion := wf.Param(wd, wf.ParamDef[string]{Name: "explicit version (optional)"})
3944
reviewers := wf.Param(wd, reviewersParam)
4045

41-
semv := wf.Task1(wd, "validating input version", r.isValidReleaseVersion, version)
42-
prerelease := wf.Task1(wd, "find the pre-release version", r.nextPrerelease, semv)
46+
semv := wf.Task2(wd, "determine the version", r.determineVersion, inputVersion, versionBumpStrategy)
47+
prerelease := wf.Task1(wd, "find the pre-release version", r.nextPrereleaseVersion, semv)
4348
approved := wf.Action2(wd, "wait for release coordinator approval", r.approveVersion, semv, prerelease)
4449

4550
issue := wf.Task1(wd, "create release git issue", r.createReleaseIssue, semv, wf.After(approved))
46-
branchCreated := wf.Action1(wd, "creating new branch if minor release", r.createBranchIfMinor, semv, wf.After(issue))
51+
branchCreated := wf.Action1(wd, "create new branch if minor release", r.createBranchIfMinor, semv, wf.After(issue))
4752

48-
configChangeID := wf.Task3(wd, "updating branch's codereview.cfg", r.updateCodeReviewConfig, semv, reviewers, issue, wf.After(branchCreated))
53+
configChangeID := wf.Task3(wd, "update branch's codereview.cfg", r.updateCodeReviewConfig, semv, reviewers, issue, wf.After(branchCreated))
4954
configCommit := wf.Task1(wd, "await config CL submission", clAwaiter{r.Gerrit}.awaitSubmission, configChangeID)
5055

51-
dependencyChangeID := wf.Task4(wd, "updating gopls' x/tools dependency", r.updateXToolsDependency, semv, prerelease, reviewers, issue, wf.After(configCommit))
56+
dependencyChangeID := wf.Task4(wd, "update gopls' x/tools dependency", r.updateXToolsDependency, semv, prerelease, reviewers, issue, wf.After(configCommit))
5257
dependencyCommit := wf.Task1(wd, "await gopls' x/tools dependency CL submission", clAwaiter{r.Gerrit}.awaitSubmission, dependencyChangeID)
5358

5459
verified := wf.Action1(wd, "verify installing latest gopls using release branch dependency commit", r.verifyGoplsInstallation, dependencyCommit)
@@ -61,6 +66,58 @@ func (r *ReleaseGoplsTasks) NewPrereleaseDefinition() *wf.Definition {
6166
return wd
6267
}
6368

69+
// determineVersion returns the release version based on coordinator inputs.
70+
//
71+
// Returns the specified input version if provided; otherwise, interpret a new
72+
// version based on the version bumping strategy.
73+
func (r *ReleaseGoplsTasks) determineVersion(ctx *wf.TaskContext, inputVersion, versionBumpStrategy string) (semversion, error) {
74+
switch versionBumpStrategy {
75+
case "use explicit version":
76+
if inputVersion == "" {
77+
return semversion{}, fmt.Errorf("the input version should not be empty when choosing explicit version release")
78+
}
79+
if err := r.isValidReleaseVersion(ctx, inputVersion); err != nil {
80+
return semversion{}, err
81+
}
82+
semv, ok := parseSemver(inputVersion)
83+
if !ok {
84+
return semversion{}, fmt.Errorf("input version %q can not be parsed as semantic version", inputVersion)
85+
}
86+
return semv, nil
87+
case "next minor", "next patch":
88+
return r.interpretNextRelease(ctx, versionBumpStrategy)
89+
default:
90+
return semversion{}, fmt.Errorf("unknown version selection strategy: %q", versionBumpStrategy)
91+
}
92+
}
93+
94+
func (r *ReleaseGoplsTasks) interpretNextRelease(ctx *wf.TaskContext, versionBumpStrategy string) (semversion, error) {
95+
tags, err := r.Gerrit.ListTags(ctx, "tools")
96+
if err != nil {
97+
return semversion{}, err
98+
}
99+
100+
var versions []string
101+
for _, tag := range tags {
102+
if v, ok := strings.CutPrefix(tag, "gopls/"); ok {
103+
versions = append(versions, v)
104+
}
105+
}
106+
107+
version := latestVersion(versions, isReleaseVersion)
108+
switch versionBumpStrategy {
109+
case "next minor":
110+
version.Minor += 1
111+
version.Patch = 0
112+
case "next patch":
113+
version.Patch += 1
114+
default:
115+
return semversion{}, fmt.Errorf("unknown version selection strategy: %q", versionBumpStrategy)
116+
}
117+
118+
return version, nil
119+
}
120+
64121
func (r *ReleaseGoplsTasks) approveVersion(ctx *wf.TaskContext, semv semversion, pre string) error {
65122
ctx.Printf("The next release candidate will be v%v.%v.%v-%s", semv.Major, semv.Minor, semv.Patch, pre)
66123
return r.ApproveAction(ctx)
@@ -230,9 +287,9 @@ parent-branch: master
230287
return r.Gerrit.CreateAutoSubmitChange(ctx, changeInput, reviewers, files)
231288
}
232289

233-
// nextPrerelease inspects the tags in tools repo that match with the given
234-
// version and find the next prerelease version.
235-
func (r *ReleaseGoplsTasks) nextPrerelease(ctx *wf.TaskContext, semv semversion) (string, error) {
290+
// nextPrereleaseVersion inspects the tags in tools repo that match with the given
291+
// version and finds the next prerelease version.
292+
func (r *ReleaseGoplsTasks) nextPrereleaseVersion(ctx *wf.TaskContext, semv semversion) (string, error) {
236293
cur, err := currentGoplsPrerelease(ctx, r.Gerrit, semv)
237294
if err != nil {
238295
return "", err
@@ -410,22 +467,21 @@ func (r *ReleaseGoplsTasks) mailAnnouncement(ctx *wf.TaskContext, semv semversio
410467
return r.SendMail(r.AnnounceMailHeader, content)
411468
}
412469

413-
func (r *ReleaseGoplsTasks) isValidReleaseVersion(ctx *wf.TaskContext, ver string) (semversion, error) {
470+
func (r *ReleaseGoplsTasks) isValidReleaseVersion(ctx *wf.TaskContext, ver string) error {
414471
if !semver.IsValid(ver) {
415-
return semversion{}, fmt.Errorf("the input %q version does not follow semantic version schema", ver)
472+
return fmt.Errorf("the input %q version does not follow semantic version schema", ver)
416473
}
417474

418475
versions, err := r.possibleGoplsVersions(ctx)
419476
if err != nil {
420-
return semversion{}, fmt.Errorf("failed to get latest Gopls version tags from x/tool: %w", err)
477+
return fmt.Errorf("failed to get latest Gopls version tags from x/tool: %w", err)
421478
}
422479

423480
if !slices.Contains(versions, ver) {
424-
return semversion{}, fmt.Errorf("the input %q is not next version of any existing versions", ver)
481+
return fmt.Errorf("the input %q is not next version of any existing versions", ver)
425482
}
426483

427-
semver, _ := parseSemver(ver)
428-
return semver, nil
484+
return nil
429485
}
430486

431487
// semversion is a parsed semantic version.

internal/task/releasegopls_test.go

Lines changed: 108 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,62 @@ import (
1717
"golang.org/x/build/internal/workflow"
1818
)
1919

20+
func TestInterpretNextRelease(t *testing.T) {
21+
tests := []struct {
22+
name string
23+
tags []string
24+
bump string
25+
want semversion
26+
}{
27+
{
28+
name: "next minor version of v0.0.0 is v0.1.0",
29+
tags: []string{"gopls/v0.0.0"},
30+
bump: "next minor",
31+
want: semversion{Major: 0, Minor: 1, Patch: 0},
32+
},
33+
{
34+
name: "pre-release versions should be ignored",
35+
tags: []string{"gopls/v0.0.0", "gopls/v0.1.0-pre.1", "gopls/v0.1.0-pre.2"},
36+
bump: "next minor",
37+
want: semversion{Major: 0, Minor: 1, Patch: 0},
38+
},
39+
{
40+
name: "next patch version of v0.2.2 is v0.2.3",
41+
tags: []string{"gopls/0.1.1", "gopls/0.2.0", "gopls/0.2.1", "gopls/v0.2.2"},
42+
bump: "next patch",
43+
want: semversion{Major: 0, Minor: 2, Patch: 3},
44+
},
45+
}
46+
47+
for _, tc := range tests {
48+
t.Run(tc.name, func(t *testing.T) {
49+
tools := NewFakeRepo(t, "tools")
50+
commit := tools.Commit(map[string]string{
51+
"go.mod": "module golang.org/x/tools\n",
52+
"go.sum": "\n",
53+
})
54+
55+
for _, tag := range tc.tags {
56+
tools.Tag(tag, commit)
57+
}
58+
59+
gerrit := NewFakeGerrit(t, tools)
60+
61+
tasks := &ReleaseGoplsTasks{
62+
Gerrit: gerrit,
63+
}
64+
65+
got, err := tasks.interpretNextRelease(&workflow.TaskContext{Context: context.Background(), Logger: &testLogger{t, ""}}, tc.bump)
66+
if err != nil {
67+
t.Fatalf("interpretNextRelease(%q) should not return error, but return %v", tc.bump, err)
68+
}
69+
if tc.want != got {
70+
t.Errorf("interpretNextRelease(%q) = %v, want %v", tc.bump, tc.want, got)
71+
}
72+
})
73+
}
74+
}
75+
2076
func TestPossibleGoplsVersions(t *testing.T) {
2177
tests := []struct {
2278
name string
@@ -367,7 +423,7 @@ func TestNextPrerelease(t *testing.T) {
367423
if !ok {
368424
t.Fatalf("parseSemver(%q) should success", tc.version)
369425
}
370-
got, err := tasks.nextPrerelease(&workflow.TaskContext{Context: ctx, Logger: &testLogger{t, ""}}, semv)
426+
got, err := tasks.nextPrereleaseVersion(&workflow.TaskContext{Context: ctx, Logger: &testLogger{t, ""}}, semv)
371427
if err != nil {
372428
t.Fatalf("nextPrerelease(%q) should not return error: %v", tc.version, err)
373429
}
@@ -501,30 +557,32 @@ func TestGoplsPrereleaseFlow(t *testing.T) {
501557
// For each entry, a new commit is created, and if the entry is
502558
// non empty that commit is tagged with the entry value.
503559
commitTags []string
504-
config string
505-
semv semversion
560+
// If set, create the release branch before starting the workflow.
561+
createBranch bool
562+
config string
563+
semv semversion
506564
// fields below are the desired states.
507565
wantVersion string
508566
wantConfig string
509567
wantCommits int
510568
}{
511569
{
512-
name: "update all three file through two commits",
513-
// create release branch with one commit without any tag.
514-
commitTags: []string{""},
515-
config: " ",
516-
semv: semversion{Major: 0, Minor: 1, Patch: 0},
517-
wantVersion: "v0.1.0-pre.1",
570+
name: "update all three file through two commits",
571+
commitTags: []string{"gopls/v0.0.0"},
572+
createBranch: true,
573+
config: " ",
574+
semv: semversion{Major: 0, Minor: 1, Patch: 0},
575+
wantVersion: "v0.1.0-pre.1",
518576
wantConfig: `issuerepo: golang/go
519577
branch: gopls-release-branch.0.1
520578
parent-branch: master
521579
`,
522580
wantCommits: 2,
523581
},
524582
{
525-
name: "codereview.cfg already have expected content, update go.mod and go.sum with one commit",
526-
// create release branch with one commit without any tag.
527-
commitTags: []string{""},
583+
name: "codereview.cfg already have expected content, update go.mod and go.sum with one commit",
584+
commitTags: []string{"gopls/v0.0.0"},
585+
createBranch: true,
528586
config: `issuerepo: golang/go
529587
branch: gopls-release-branch.0.1
530588
parent-branch: master
@@ -538,24 +596,25 @@ parent-branch: master
538596
wantCommits: 1,
539597
},
540598
{
541-
name: "create the branch for minor version",
542-
commitTags: nil, // no release branch
543-
config: ` `,
544-
semv: semversion{Major: 0, Minor: 12, Patch: 0},
545-
wantVersion: "v0.12.0-pre.1",
599+
name: "create the branch for minor version",
600+
commitTags: []string{"gopls/v0.11.0"},
601+
createBranch: false,
602+
config: ` `,
603+
semv: semversion{Major: 0, Minor: 12, Patch: 0},
604+
wantVersion: "v0.12.0-pre.1",
546605
wantConfig: `issuerepo: golang/go
547606
branch: gopls-release-branch.0.12
548607
parent-branch: master
549608
`,
550609
wantCommits: 2,
551610
},
552611
{
553-
name: "workflow should increment the pre-release number to 4",
554-
// create release branch with three commits with tags.
555-
commitTags: []string{"gopls/v0.8.3-pre.1", "gopls/v0.8.3-pre.2", "gopls/v0.8.3-pre.3"},
556-
config: " ",
557-
semv: semversion{Major: 0, Minor: 8, Patch: 3},
558-
wantVersion: "v0.8.3-pre.4",
612+
name: "workflow should increment the pre-release number to 4",
613+
commitTags: []string{"gopls/v0.8.2", "gopls/v0.8.3-pre.1", "gopls/v0.8.3-pre.2", "gopls/v0.8.3-pre.3"},
614+
createBranch: true,
615+
config: " ",
616+
semv: semversion{Major: 0, Minor: 8, Patch: 3},
617+
wantVersion: "v0.8.3-pre.4",
559618
wantConfig: `issuerepo: golang/go
560619
branch: gopls-release-branch.0.8
561620
parent-branch: master
@@ -565,7 +624,7 @@ parent-branch: master
565624
}
566625

567626
for _, tc := range testcases {
568-
t.Run(tc.name, func(t *testing.T) {
627+
runTestWithInput := func(input map[string]any) {
569628
ctx, cancel := context.WithCancel(context.Background())
570629
defer cancel()
571630

@@ -575,21 +634,14 @@ parent-branch: master
575634
"gopls/go.sum": "\n",
576635
"codereview.cfg": tc.config,
577636
})
578-
// These tags make sure the input version is the next version of some
579-
// released version, in order to pass the version validation step.
580-
tools.Tag(fmt.Sprintf("gopls/v%v.%v.%v", tc.semv.Major, tc.semv.Minor, tc.semv.Patch-1), beforeHead)
581-
tools.Tag(fmt.Sprintf("gopls/v%v.%v.%v", tc.semv.Major, tc.semv.Minor-1, tc.semv.Patch), beforeHead)
582-
tools.Tag(fmt.Sprintf("gopls/v%v.%v.%v", tc.semv.Major-1, tc.semv.Minor, tc.semv.Patch), beforeHead)
583-
584-
// Create the release branch and make a few commits to the release branch.
637+
// Create the release branch and make a few commits to the master branch.
585638
// Var beforeHead is used to track the commit of release branch's head
586639
// before trigger the gopls pre-release run. If we do not need to create a
587640
// release branch, beforeHead will point to the initial commit in the
588641
// master branch.
589642
if len(tc.commitTags) != 0 {
590-
tools.Branch(goplsReleaseBranchName(tc.semv), beforeHead)
591643
for i, tag := range tc.commitTags {
592-
commit := tools.CommitOnBranch(goplsReleaseBranchName(tc.semv), map[string]string{
644+
commit := tools.CommitOnBranch("master", map[string]string{
593645
"README.md": fmt.Sprintf("THIS IS READ ME FOR %v.", i),
594646
})
595647
beforeHead = commit
@@ -599,6 +651,10 @@ parent-branch: master
599651
}
600652
}
601653

654+
if tc.createBranch {
655+
tools.Branch(goplsReleaseBranchName(tc.semv), beforeHead)
656+
}
657+
602658
gerrit := NewFakeGerrit(t, tools)
603659

604660
// fakeGo handles multiple arguments in gopls pre-release flow.
@@ -664,10 +720,7 @@ esac`, tc.wantVersion)
664720
}
665721

666722
wd := tasks.NewPrereleaseDefinition()
667-
w, err := workflow.Start(wd, map[string]interface{}{
668-
reviewersParam.Name: []string(nil),
669-
"version": fmt.Sprintf("v%v.%v.%v", tc.semv.Major, tc.semv.Minor, tc.semv.Patch),
670-
})
723+
w, err := workflow.Start(wd, input)
671724
if err != nil {
672725
t.Fatal(err)
673726
}
@@ -741,6 +794,24 @@ esac`, tc.wantVersion)
741794
if info.Revision != afterHead {
742795
t.Errorf("the pre-release tag points to commit %s, should point to the head commit of release branch %s", info.Revision, afterHead)
743796
}
797+
}
798+
t.Run("manual input version: "+tc.name, func(t *testing.T) {
799+
runTestWithInput(map[string]any{
800+
reviewersParam.Name: []string(nil),
801+
"explicit version (optional)": fmt.Sprintf("v%v.%v.%v", tc.semv.Major, tc.semv.Minor, tc.semv.Patch),
802+
"next version": "use explicit version",
803+
})
804+
})
805+
versionBump := "next patch"
806+
if tc.semv.Patch == 0 {
807+
versionBump = "next minor"
808+
}
809+
t.Run("interpret version "+versionBump+" : "+tc.name, func(t *testing.T) {
810+
runTestWithInput(map[string]any{
811+
reviewersParam.Name: []string(nil),
812+
"explicit version (optional)": "",
813+
"next version": versionBump,
814+
})
744815
})
745816
}
746817
}

0 commit comments

Comments
 (0)