github.com/bazelbuild/bazel-gazelle@v0.36.1-0.20240520142334-61b277ba6fed/tools/releaser/main.go (about) 1 /* Copyright 2023 The Bazel Authors. All rights reserved. 2 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 */ 15 16 // releaser is a tool for managing part of the process to release a new version of gazelle. 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 // reuse the open workspace file, so first we empty it and rewind 124 err = workspace.Truncate(0) 125 if err != nil { 126 return err 127 } 128 _ /* new offset */, err = workspace.Seek(0, os.SEEK_SET) 129 if err != nil { 130 return err 131 } 132 133 // write the directive-less workspace and update repos 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 // parse the resulting tmp.bzl for deps.bzl and WORKSPACE updates 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 // update deps 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 // append WORKSPACE with directives at the end. 167 // except we cannot append directly because the earlier bazel //:gazelle run modified WORKSPACE 168 // so we truncate and seek to the beginning again before writing all of what we want 169 if verbose { 170 fmt.Println("Append WORKSPACE with directives") 171 } 172 _ /* new offset */, err = workspace.Seek(0, os.SEEK_SET) 173 if err != nil { 174 return err 175 } 176 177 // write the directive-less workspace and update repos 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 // cleanup before final gazelle run 186 // 187 // note that we also have a defer for os.Remove so it gets cleaned up if there are earlier errors. 188 // This defer will throw an error from this point on, but we're swallowing it anyways. 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 // leave some buffering at the end of the bytes 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" /* pkg */, "gazelle_dependencies" /* DefName */) 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" /* pkg */, "gazelle_dependencies" /* DefName */) 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 // We can't actually delete all _maybe's because http_archive uses it too in here! 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 }