github.com/yorinasub17/go-cloud@v0.27.40/internal/releasehelper/releasehelper.go (about) 1 // Copyright 2019 The Go Cloud Development Kit Authors 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 // https://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 // Helper tool for creating new releases of the Go CDK. Run without arguments 16 // or with 'help' for details. 17 package main 18 19 import ( 20 "bufio" 21 "encoding/json" 22 "fmt" 23 "log" 24 "os" 25 "os/exec" 26 "path/filepath" 27 "regexp" 28 "strings" 29 ) 30 31 var helpText string = ` 32 Helper tool for creating new releases of the Go CDK. 33 34 Automates the modifications required in the project's 35 go.mod files to create an test new releases. 36 37 The tool processes all modules listed in the 'allmodules' 38 file. For each module it handles all dependencies on 39 other gocloud.dev modules. 40 41 Run it from the root directory of the repository, as follows: 42 43 $ %s <command> 44 45 Where command is one of the following: 46 47 addreplace adds 'replace' directives to point to local versions 48 for testing. 49 50 dropreplace removes these directives. 51 52 setversion <version> 53 sets 'required' version of modules to a given version formatted 54 as vX.Y.Z 55 56 tag <version> 57 runs 'git tag <module>/<version>' for all CDK modules 58 59 help prints this usage message 60 ` 61 62 func printHelp() { 63 _, binName := filepath.Split(os.Args[0]) 64 fmt.Fprintf(os.Stderr, helpText, binName) 65 fmt.Fprintln(os.Stderr) 66 } 67 68 // cmdCheck invokes the command given in s, echoing the invocation to stdout. 69 // It checks that the command was successful and returns its standard output. 70 // If the command returned a non-0 status, log.Fatal is invoked. 71 func cmdCheck(s string) []byte { 72 fmt.Printf(" -> %s\n", s) 73 fields := strings.Fields(s) 74 if len(fields) < 1 { 75 log.Fatal(`Expected "command <arguments>"`) 76 } 77 b, err := exec.Command(fields[0], fields[1:]...).Output() 78 if exiterr, ok := err.(*exec.ExitError); ok { 79 log.Fatalf("%s; stderr: %s\n", err, string(exiterr.Stderr)) 80 } else if err != nil { 81 log.Fatal("exec.Command", err) 82 } 83 return b 84 } 85 86 // GoMod holds "go mod" parameters, taken from "go mod help edit", in order to parse the JSON 87 // output of `go mod edit -json`. 88 type GoMod struct { 89 Module Module 90 Go string 91 Require []Require 92 Exclude []Module 93 Replace []Replace 94 } 95 96 // Module represents a Go module. 97 type Module struct { 98 Path string 99 Version string 100 } 101 102 // Require represents a required module. 103 type Require struct { 104 Path string 105 Version string 106 Indirect bool 107 } 108 109 // Replace represents a replace directive. 110 type Replace struct { 111 Old Module 112 New Module 113 } 114 115 // parseModuleInfo parses module information from a go.mod file at path. 116 func parseModuleInfo(path string) GoMod { 117 rawJson := cmdCheck("go mod edit -json " + path) 118 var modInfo GoMod 119 err := json.Unmarshal(rawJson, &modInfo) 120 if err != nil { 121 log.Fatal(err) 122 } 123 return modInfo 124 } 125 126 // runOnGomod processes a single go.mod file (located in directory 'path'). 127 // Each require in the go.mod file is processed with reqHandler, a callback 128 // function. It's called with these arguments: 129 // 130 // gomodPath - path to the go.mod file where this 'require' was found 131 // mod - name of the module being 'require'd 132 // modPath - mod's location in the filesystem relative to 133 // the go.mod 'require'ing it 134 func runOnGomod(path string, reqHandler func(gomodPath, mod, modPath string)) { 135 gomodPath := filepath.Join(path, "go.mod") 136 fmt.Println("Processing", gomodPath) 137 modInfo := parseModuleInfo(gomodPath) 138 139 base := "gocloud.dev" 140 141 for _, r := range modInfo.Require { 142 // Find requirements on modules within the gocloud.dev tree. 143 if strings.HasPrefix(r.Path, base) { 144 // Find the relative path from 'path' and the module required here. 145 var reqPath string 146 if r.Path == base { 147 reqPath = "." 148 } else { 149 reqPath = strings.TrimPrefix(r.Path, base+"/") 150 } 151 rel, err := filepath.Rel(path, reqPath) 152 if err != nil { 153 log.Fatal(err) 154 } 155 // When path is '.', filepath.Rel will append a /. to the result and we 156 // may get paths like ../../. 157 if strings.HasSuffix(rel, "/.") { 158 rel, _ = filepath.Split(rel) 159 } 160 161 reqHandler(gomodPath, r.Path, rel) 162 } 163 } 164 } 165 166 func gomodAddReplace(path string) { 167 runOnGomod(path, func(gomodPath, mod, modPath string) { 168 cmdCheck(fmt.Sprintf("go mod edit -replace=%s=%s %s", mod, modPath, gomodPath)) 169 }) 170 } 171 172 func gomodDropReplace(path string) { 173 runOnGomod(path, func(gomodPath, mod, modPath string) { 174 cmdCheck(fmt.Sprintf("go mod edit -dropreplace=%s %s", mod, gomodPath)) 175 }) 176 } 177 178 func gomodSetVersion(path string, v string) { 179 runOnGomod(path, func(gomodPath, mod, modPath string) { 180 cmdCheck(fmt.Sprintf("go mod edit -require=%s@%s %s", mod, v, gomodPath)) 181 }) 182 } 183 184 func gomodTag(path string, v string) { 185 var tagName string 186 if path == "." { 187 tagName = v 188 } else { 189 tagName = filepath.Join(path, v) 190 } 191 cmdCheck(fmt.Sprintf("git tag %s", tagName)) 192 } 193 194 func validSemanticVersion(v string) bool { 195 match, err := regexp.MatchString(`v\d+\.\d+\.\d+`, v) 196 if err != nil { 197 return false 198 } 199 return match 200 } 201 202 func main() { 203 if len(os.Args) < 2 { 204 printHelp() 205 os.Exit(0) 206 } 207 208 var gomodHandler func(path string) 209 switch os.Args[1] { 210 case "help": 211 printHelp() 212 os.Exit(0) 213 case "addreplace": 214 gomodHandler = gomodAddReplace 215 case "dropreplace": 216 gomodHandler = gomodDropReplace 217 case "setversion": 218 if len(os.Args) < 3 || !validSemanticVersion(os.Args[2]) { 219 printHelp() 220 os.Exit(1) 221 } 222 gomodHandler = func(path string) { 223 gomodSetVersion(path, os.Args[2]) 224 } 225 case "tag": 226 if len(os.Args) < 3 || !validSemanticVersion(os.Args[2]) { 227 printHelp() 228 os.Exit(1) 229 } 230 gomodHandler = func(path string) { 231 gomodTag(path, os.Args[2]) 232 } 233 default: 234 printHelp() 235 os.Exit(1) 236 } 237 238 f, err := os.Open("allmodules") 239 if err != nil { 240 log.Fatal(err) 241 } 242 243 input := bufio.NewScanner(f) 244 input.Split(bufio.ScanLines) 245 for input.Scan() { 246 if len(input.Text()) > 0 && !strings.HasPrefix(input.Text(), "#") { 247 fields := strings.Fields(input.Text()) 248 if len(fields) != 2 { 249 log.Fatalf("want 2 fields, got '%s'\n", input.Text()) 250 } 251 // "tag" only runs if the released field is "yes". Other commands run 252 // for every line. 253 if os.Args[1] != "tag" || fields[1] == "yes" { 254 gomodHandler(fields[0]) 255 } 256 } 257 } 258 259 if input.Err() != nil { 260 log.Fatal(input.Err()) 261 } 262 }