github.com/SaurabhDubey-Groww/go-cloud@v0.0.0-20221124105541-b26c29285fd8/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  }