1
15
16
17 package main
18
19 import (
20 "bufio"
21 "bytes"
22 "context"
23 "errors"
24 "flag"
25 "fmt"
26 "github.com/bazelbuild/bazel-gazelle/rule"
27 bzl "github.com/bazelbuild/buildtools/build"
28 "io"
29 "os"
30 "os/exec"
31 "os/signal"
32 "path"
33 "strconv"
34 "strings"
35 )
36
37 func main() {
38 ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
39 defer cancel()
40 if err := run(ctx, os.Stderr); err != nil {
41 fmt.Fprintln(os.Stderr, err)
42 os.Exit(1)
43 }
44 }
45
46 func run(ctx context.Context, stderr *os.File) error {
47 var (
48 verbose bool
49 goVersion string
50 repoRoot string
51 )
52
53 flag.BoolVar(&verbose, "verbose", false, "increase verbosity")
54 flag.BoolVar(&verbose, "v", false, "increase verbosity (shorthand)")
55 flag.StringVar(&goVersion, "go_version", "", "go version for go.mod")
56 flag.StringVar(&repoRoot, "repo_root", os.Getenv("BUILD_WORKSPACE_DIRECTORY"), "root directory of Gazelle repo")
57 flag.Usage = func() {
58 fmt.Fprint(flag.CommandLine.Output(), `usage: bazel run //tools/releaser -- -go_version <version>
59
60 This utility is intended to handle many of the steps to release a new version.
61
62 `)
63 flag.PrintDefaults()
64 }
65
66 flag.Parse()
67
68 var goVersionArgs []string
69 if goVersion != "" {
70 versionParts := strings.Split(goVersion, ".")
71 if len(versionParts) < 2 {
72 flag.Usage()
73 return errors.New("please provide a valid Go version")
74 }
75 if minorVersion, err := strconv.Atoi(versionParts[1]); err != nil {
76 return fmt.Errorf("%q is not a valid Go version", goVersion)
77 } else if minorVersion > 0 {
78 versionParts[1] = strconv.Itoa(minorVersion - 1)
79 }
80 goVersionArgs = append(goVersionArgs, "-go", goVersion, "-compat", strings.Join(versionParts, "."))
81 }
82
83 workspacePath := path.Join(repoRoot, "WORKSPACE")
84 depsPath := path.Join(repoRoot, "deps.bzl")
85 _tmpBzl := "tmp.bzl"
86 tmpBzlPath := path.Join(repoRoot, _tmpBzl)
87
88 if verbose {
89 fmt.Println("Running initial go update commands")
90 }
91 initialCommands := []struct {
92 cmd string
93 args []string
94 }{
95 {cmd: "go", args: []string{"get", "-t", "-u", "./..."}},
96 {cmd: "go", args: append([]string{"mod", "tidy"}, goVersionArgs...)},
97 {cmd: "go", args: []string{"mod", "vendor"}},
98 {cmd: "find", args: []string{"vendor", "-name", "BUILD.bazel", "-delete"}},
99 }
100 for _, c := range initialCommands {
101 cmd := exec.CommandContext(ctx, c.cmd, c.args...)
102 cmd.Dir = repoRoot
103 if out, err := cmd.CombinedOutput(); err != nil {
104 fmt.Println(string(out))
105 return err
106 }
107 }
108
109 workspace, err := os.OpenFile(workspacePath, os.O_RDWR, 0644)
110 if err != nil {
111 return err
112 }
113 defer workspace.Close()
114
115 if verbose {
116 fmt.Println("Preparing temporary WORKSPACE without gazelle directives.")
117 }
118 workspaceWithoutDirectives, err := getWorkspaceWithoutDirectives(workspace)
119 if err != nil {
120 return err
121 }
122
123
124 err = workspace.Truncate(0)
125 if err != nil {
126 return err
127 }
128 _ , err = workspace.Seek(0, os.SEEK_SET)
129 if err != nil {
130 return err
131 }
132
133
134 if _, err := workspace.Write(workspaceWithoutDirectives); err != nil {
135 return err
136 }
137
138 if verbose {
139 fmt.Println("Running update-repos outputting to temporary file.")
140 }
141 cmd := exec.CommandContext(ctx, "bazel", "run", "//:gazelle", "--", "update-repos", "-from_file=go.mod", fmt.Sprintf("-to_macro=%s%%gazelle_dependencies", _tmpBzl))
142 cmd.Dir = os.Getenv("BUILD_WORKSPACE_DIRECTORY")
143 if out, err := cmd.CombinedOutput(); err != nil {
144 fmt.Println(string(out))
145 return err
146 }
147 defer os.Remove(tmpBzlPath)
148
149
150 if verbose {
151 fmt.Println("Parsing temporary bzl file to prepare deps.bzl and WORKSPACE modifications.")
152 }
153 maybeRules, workspaceDirectives, err := readFromTmp(tmpBzlPath)
154 if err != nil {
155 return err
156 }
157
158
159 if verbose {
160 fmt.Println("Writing new deps.bzl")
161 }
162 if err := updateDepsBzlWithRules(depsPath, maybeRules); err != nil {
163 return err
164 }
165
166
167
168
169 if verbose {
170 fmt.Println("Append WORKSPACE with directives")
171 }
172 _ , err = workspace.Seek(0, os.SEEK_SET)
173 if err != nil {
174 return err
175 }
176
177
178 if _, err := workspace.Write(workspaceWithoutDirectives); err != nil {
179 return err
180 }
181 if _, err := workspace.Write(workspaceDirectives); err != nil {
182 return err
183 }
184
185
186
187
188
189 if verbose {
190 fmt.Println("Cleaning up temporary files")
191 }
192 if err := os.Remove(tmpBzlPath); err != nil {
193 return err
194 }
195
196 if verbose {
197 fmt.Println("Running final gazelle run, and copying some language specific build files.")
198 }
199 cmd = exec.CommandContext(ctx, "bazel", "run", "//:gazelle")
200 cmd.Dir = repoRoot
201 if out, err := cmd.CombinedOutput(); err != nil {
202 fmt.Println(string(out))
203 return err
204 }
205
206 cmd = exec.CommandContext(ctx, "bazel", "build",
207 "//language/go:std_package_list",
208 "//language/proto:known_go_imports",
209 "//language/proto:known_imports",
210 "//language/proto:known_proto_imports",
211 )
212 cmd.Dir = repoRoot
213 if out, err := cmd.CombinedOutput(); err != nil {
214 fmt.Println(string(out))
215 return err
216 }
217
218 generatedFiles := []string{
219 "language/go/std_package_list.go",
220 "language/proto/known_go_imports.go",
221 "language/proto/known_imports.go",
222 "language/proto/known_proto_imports.go",
223 }
224 for _, f := range generatedFiles {
225 if err := updateFile(repoRoot, f); err != nil {
226 return err
227 }
228 }
229
230 if verbose {
231 fmt.Println("Release prepared.")
232 }
233 return nil
234 }
235
236 func updateFile(repoRoot, filePath string) error {
237 destPath := path.Join(repoRoot, filePath)
238 dest, err := os.Create(destPath)
239 if err != nil {
240 return err
241 }
242 srcPath := path.Join(repoRoot, "bazel-bin", filePath)
243 src, err := os.Open(srcPath)
244 if err != nil {
245 return err
246 }
247 _, err = io.Copy(dest, src)
248 return err
249 }
250
251 func getWorkspaceWithoutDirectives(workspace io.Reader) ([]byte, error) {
252 workspaceScanner := bufio.NewScanner(workspace)
253 var workspaceWithoutDirectives bytes.Buffer
254 for workspaceScanner.Scan() {
255 currentLine := workspaceScanner.Text()
256 if strings.HasPrefix(currentLine, "# gazelle:repository go_repository") {
257 continue
258 }
259 _, err := workspaceWithoutDirectives.WriteString(currentLine + "\n")
260 if err != nil {
261 return nil, err
262 }
263 }
264
265 _, err := workspaceWithoutDirectives.WriteString("\n\n")
266 if err != nil {
267 return nil, err
268 }
269 return workspaceWithoutDirectives.Bytes(), workspaceScanner.Err()
270 }
271
272 func readFromTmp(tmpBzlPath string) ([]*rule.Rule, []byte, error) {
273 workspaceDirectivesBuff := new(bytes.Buffer)
274 var rules []*rule.Rule
275 tmpBzl, err := rule.LoadMacroFile(tmpBzlPath, "tmp" , "gazelle_dependencies" )
276 if err != nil {
277 return nil, nil, err
278 }
279 for _, r := range tmpBzl.Rules {
280 maybeRule := rule.NewRule("_maybe", r.Name())
281 maybeRule.AddArg(&bzl.Ident{
282 Name: r.Kind(),
283 })
284
285 for _, k := range r.AttrKeys() {
286 maybeRule.SetAttr(k, r.Attr(k))
287 }
288
289 var suffix string
290 rules = append(rules, maybeRule)
291 fmt.Fprintf(workspaceDirectivesBuff, "# gazelle:repository go_repository name=%s importpath=%s%s\n",
292 r.Name(),
293 r.AttrString("importpath"),
294 suffix,
295 )
296 }
297 return rules, workspaceDirectivesBuff.Bytes(), nil
298 }
299
300 func updateDepsBzlWithRules(depsPath string, maybeRules []*rule.Rule) error {
301 depsBzl, err := rule.LoadMacroFile(depsPath, "deps" , "gazelle_dependencies" )
302 if err != nil {
303 return err
304 }
305
306 for _, r := range depsBzl.Rules {
307 if r.Kind() == "_maybe" && len(r.Args()) == 1 {
308
309 if ident, ok := r.Args()[0].(*bzl.Ident); ok && ident.Name == "go_repository" {
310 r.Delete()
311 }
312 }
313 }
314
315 for _, r := range maybeRules {
316 r.Insert(depsBzl)
317 }
318
319 return depsBzl.Save(depsPath)
320 }
321
View as plain text