8
8
"errors"
9
9
"fmt"
10
10
"slices"
11
+ "strconv"
11
12
"strings"
12
13
"time"
13
14
@@ -34,7 +35,9 @@ func (r *ReleaseGoplsTasks) NewDefinition() *wf.Definition {
34
35
semversion := wf .Task1 (wd , "validating input version" , r .isValidVersion , version )
35
36
branchCreated := wf .Action1 (wd , "creating new branch if minor release" , r .createBranchIfMinor , semversion )
36
37
changeID := wf .Task2 (wd , "updating branch's codereview.cfg" , r .updateCodeReviewConfig , semversion , reviewers , wf .After (branchCreated ))
37
- _ = wf .Action1 (wd , "await config CL submission" , r .AwaitSubmission , changeID )
38
+ submitted := wf .Action1 (wd , "await config CL submission" , r .AwaitSubmission , changeID )
39
+ changeID = wf .Task2 (wd , "updating gopls' x/tools dependency" , r .updateXToolsDependency , semversion , reviewers , wf .After (submitted ))
40
+ _ = wf .Action1 (wd , "await gopls' x/tools dependency CL submission" , r .AwaitSubmission , changeID )
38
41
return wd
39
42
}
40
43
@@ -73,10 +76,29 @@ func (r *ReleaseGoplsTasks) createBranchIfMinor(ctx *wf.TaskContext, semv semver
73
76
return err
74
77
}
75
78
76
- // updateCodeReviewConfig ensures codereview.cfg contains the expected
77
- // configuration .
79
+ // openCL checks if an open CL with the given title exists in the specified
80
+ // branch .
78
81
//
79
- // It returns the change ID, or "" if the CL was not created.
82
+ // It returns an empty string if no such CL is found, otherwise it returns the
83
+ // CL's change ID.
84
+ func (r * ReleaseGoplsTasks ) openCL (ctx * wf.TaskContext , branch , title string ) (string , error ) {
85
+ // Query for an existing pending config CL, to avoid duplication.
86
+ query := fmt .
Sprintf (
`message:%q status:open owner:[email protected] repo:tools branch:%q -age:7d` ,
title ,
branch )
87
+ changes , err := r .Gerrit .QueryChanges (ctx , query )
88
+ if err != nil {
89
+ return "" , err
90
+ }
91
+ if len (changes ) == 0 {
92
+ return "" , nil
93
+ }
94
+
95
+ return changes [0 ].ChangeID , nil
96
+ }
97
+
98
+ // updateCodeReviewConfig checks if codereview.cfg has the desired configuration.
99
+ //
100
+ // It returns the change ID required to update the config if changes are needed,
101
+ // otherwise it returns an empty string indicating no update is necessary.
80
102
func (r * ReleaseGoplsTasks ) updateCodeReviewConfig (ctx * wf.TaskContext , semv semversion , reviewers []string ) (string , error ) {
81
103
const configFile = "codereview.cfg"
82
104
const configFmt = `issuerepo: golang/go
@@ -87,15 +109,13 @@ parent-branch: master
87
109
branch := goplsReleaseBranchName (semv )
88
110
clTitle := fmt .Sprintf ("all: update %s for %s" , configFile , branch )
89
111
90
- // Query for an existing pending config CL, to avoid duplication.
91
- query := fmt .
Sprintf (
`message:%q status:open owner:[email protected] repo:tools branch:%q -age:7d` ,
clTitle ,
branch )
92
- changes , err := r .Gerrit .QueryChanges (ctx , query )
112
+ openCL , err := r .openCL (ctx , branch , clTitle )
93
113
if err != nil {
94
- return "" , err
114
+ return "" , fmt . Errorf ( "failed to find the open CL of title %q in branch %q: %w" , clTitle , branch , err )
95
115
}
96
- if len ( changes ) > 0 {
97
- ctx .Printf ("not creating CL: found existing CL %d " , changes [ 0 ]. ChangeNumber )
98
- return changes [ 0 ]. ChangeID , nil
116
+ if openCL != "" {
117
+ ctx .Printf ("not creating CL: found existing CL %s " , openCL )
118
+ return openCL , nil
99
119
}
100
120
101
121
head , err := r .Gerrit .ReadBranchHead (ctx , "tools" , branch )
@@ -128,6 +148,110 @@ parent-branch: master
128
148
return r .Gerrit .CreateAutoSubmitChange (ctx , changeInput , reviewers , files )
129
149
}
130
150
151
+ // nextPrerelease go through the tags in tools repo that matches with the given
152
+ // version and find the next pre-release version.
153
+ func (r * ReleaseGoplsTasks ) nextPrerelease (ctx * wf.TaskContext , semv semversion ) (int , error ) {
154
+ tags , err := r .Gerrit .ListTags (ctx , "tools" )
155
+ if err != nil {
156
+ return - 1 , fmt .Errorf ("failed to list tags for tools repo: %w" , err )
157
+ }
158
+
159
+ max := 0
160
+ for _ , tag := range tags {
161
+ v , ok := strings .CutPrefix (tag , "gopls/" )
162
+ if ! ok {
163
+ continue
164
+ }
165
+ cur , ok := parseSemver (v )
166
+ if ! ok {
167
+ continue
168
+ }
169
+ if cur .Major != semv .Major || cur .Minor != semv .Minor || cur .Patch != semv .Patch {
170
+ continue
171
+ }
172
+ pre , err := cur .prereleaseVersion ()
173
+ if err != nil {
174
+ continue
175
+ }
176
+
177
+ if pre > max {
178
+ max = pre
179
+ }
180
+ }
181
+
182
+ return max + 1 , nil
183
+ }
184
+
185
+ // updateXToolsDependency ensures gopls sub module have the correct x/tools
186
+ // version as dependency.
187
+ //
188
+ // It returns the change ID, or "" if the CL was not created.
189
+ func (r * ReleaseGoplsTasks ) updateXToolsDependency (ctx * wf.TaskContext , semv semversion , reviewers []string ) (string , error ) {
190
+ const scriptFmt = `cp gopls/go.mod gopls/go.mod.before
191
+ cp gopls/go.sum gopls/go.sum.before
192
+ cd gopls
193
+ go mod edit -dropreplace=golang.org/x/tools
194
+ go get -u golang.org/x/tools@%s
195
+ go mod tidy -compat=1.19
196
+ `
197
+
198
+ pre , err := r .nextPrerelease (ctx , semv )
199
+ if err != nil {
200
+ return "" , fmt .Errorf ("failed to find the next prerelease version: %w" , err )
201
+ }
202
+
203
+ branch := goplsReleaseBranchName (semv )
204
+ clTitle := fmt .Sprintf ("gopls: update go.mod for v%v.%v.%v-pre.%v" , semv .Major , semv .Minor , semv .Patch , pre )
205
+ openCL , err := r .openCL (ctx , branch , clTitle )
206
+ if err != nil {
207
+ return "" , fmt .Errorf ("failed to find the open CL of title %q in branch %q: %w" , clTitle , branch , err )
208
+ }
209
+ if openCL != "" {
210
+ ctx .Printf ("not creating CL: found existing CL %s" , openCL )
211
+ return openCL , nil
212
+ }
213
+
214
+ outputFiles := []string {"gopls/go.mod.before" , "gopls/go.mod" , "gopls/go.sum.before" , "gopls/go.sum" }
215
+ // TODO(hxjiang): Replacing branch with the latest non-pinned commit in the
216
+ // release branch. Rationale:
217
+ // 1. Module proxy might return an outdated commit when using the branch name
218
+ // (to be confirmed with samthanawalla@).
219
+ // 2. Pinning x/tools using the latest commit from a branch isn't idempotent.
220
+ // It's best to avoid pinning x/tools to a version that's effectively another
221
+ // pin.
222
+ build , err := r .CloudBuild .RunScript (ctx , fmt .Sprintf (scriptFmt , branch ), "tools" , outputFiles )
223
+ if err != nil {
224
+ return "" , err
225
+ }
226
+
227
+ outputs , err := buildToOutputs (ctx , r .CloudBuild , build )
228
+ if err != nil {
229
+ return "" , err
230
+ }
231
+
232
+ changedFiles := map [string ]string {}
233
+ for i := 0 ; i < len (outputFiles ); i += 2 {
234
+ before , after := outputs [outputFiles [i ]], outputs [outputFiles [i + 1 ]]
235
+ if before != after {
236
+ changedFiles [outputFiles [i + 1 ]] = after
237
+ }
238
+ }
239
+
240
+ // Skip CL creation as nothing changed.
241
+ if len (changedFiles ) == 0 {
242
+ return "" , nil
243
+ }
244
+
245
+ changeInput := gerrit.ChangeInput {
246
+ Project : "tools" ,
247
+ Branch : branch ,
248
+ Subject : fmt .Sprintf ("%s\n \n This is an automated CL which updates the go.mod go.sum." , clTitle ),
249
+ }
250
+
251
+ ctx .Printf ("creating auto-submit change under branch %q in x/tools repo." , branch )
252
+ return r .Gerrit .CreateAutoSubmitChange (ctx , changeInput , reviewers , changedFiles )
253
+ }
254
+
131
255
// AwaitSubmission waits for the CL with the given change ID to be submitted.
132
256
//
133
257
// The return value is the submitted commit hash, or "" if changeID is "".
@@ -179,6 +303,24 @@ func parseSemver(v string) (_ semversion, ok bool) {
179
303
return parsed , ok
180
304
}
181
305
306
+ func (s * semversion ) prereleaseVersion () (int , error ) {
307
+ parts := strings .Split (s .Pre , "." )
308
+ if len (parts ) == 1 {
309
+ return - 1 , fmt .Errorf (`prerelease version does not contain any "."` )
310
+ }
311
+
312
+ if len (parts ) > 2 {
313
+ return - 1 , fmt .Errorf (`prerelease version contains %v "."` , len (parts )- 1 )
314
+ }
315
+
316
+ pre , err := strconv .Atoi (parts [1 ])
317
+ if err != nil {
318
+ return - 1 , fmt .Errorf ("failed to convert prerelease version to int %q: %w" , pre , err )
319
+ }
320
+
321
+ return pre , nil
322
+ }
323
+
182
324
// possibleGoplsVersions identifies suitable versions for the upcoming release
183
325
// based on the current tags in the repo.
184
326
func (r * ReleaseGoplsTasks ) possibleGoplsVersions (ctx * wf.TaskContext ) ([]string , error ) {
0 commit comments