github.com/rogpeppe/go-internal@v1.12.1-0.20240509064211-c8567cf8e95f/cmd/txtar-addmod/addmod.go (about) 1 // Copyright 2018 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // The txtar-addmod command adds a module as a txtar archive to the a testdata module directory 6 // as understood by the goproxytest package (see https://godoc.org/github.com/rogpeppe/go-internal/goproxytest). 7 // 8 // Usage: 9 // 10 // txtar-addmod dir path@version... 11 // 12 // where dir is the directory to add the module to. 13 // 14 // In general, it's intended to be used only for very small modules - we do not want to check 15 // very large files into testdata/mod. 16 // 17 // It is acceptable to edit the archive afterward to remove or shorten files. 18 package main 19 20 import ( 21 "bytes" 22 "flag" 23 "fmt" 24 "log" 25 "os" 26 "os/exec" 27 "path/filepath" 28 "strings" 29 30 "golang.org/x/mod/module" 31 "golang.org/x/tools/txtar" 32 ) 33 34 func usage() { 35 fmt.Fprintf(os.Stderr, "usage: txtar-addmod dir path@version...\n") 36 flag.PrintDefaults() 37 38 fmt.Fprintf(os.Stderr, ` 39 The txtar-addmod command adds a module as a txtar archive to the 40 testdata module directory as understood by the goproxytest package 41 (see https://godoc.org/github.com/rogpeppe/go-internal/goproxytest). 42 43 The dir argument names to directory to add the module to. If dir is "-", 44 the result will instead be written to the standard output in a form 45 suitable for embedding directly into a testscript txtar file, with each 46 file prefixed with the ".gomodproxy" directory. 47 48 In general, txtar-addmod is intended to be used only for very small 49 modules - we do not want to check very large files into testdata/mod. 50 51 It is acceptable to edit the archive afterward to remove or shorten files. 52 `) 53 os.Exit(2) 54 } 55 56 var tmpdir string 57 58 func fatalf(format string, args ...interface{}) { 59 os.RemoveAll(tmpdir) 60 log.Fatalf(format, args...) 61 } 62 63 const goCmd = "go" 64 65 func main() { 66 os.Exit(main1()) 67 } 68 69 var allFiles = flag.Bool("all", false, "include all source files") 70 71 func main1() int { 72 flag.Usage = usage 73 flag.Parse() 74 if flag.NArg() < 2 { 75 usage() 76 } 77 targetDir := flag.Arg(0) 78 modules := flag.Args()[1:] 79 80 log.SetPrefix("txtar-addmod: ") 81 log.SetFlags(0) 82 83 var err error 84 tmpdir, err = os.MkdirTemp("", "txtar-addmod-") 85 if err != nil { 86 log.Fatal(err) 87 } 88 89 run := func(command string, args ...string) string { 90 cmd := exec.Command(command, args...) 91 cmd.Dir = tmpdir 92 var stderr bytes.Buffer 93 cmd.Stderr = &stderr 94 out, err := cmd.Output() 95 if err != nil { 96 fatalf("%s %s: %v\n%s", command, strings.Join(args, " "), err, stderr.Bytes()) 97 } 98 return string(out) 99 } 100 101 gopath := strings.TrimSpace(run("go", "env", "GOPATH")) 102 if gopath == "" { 103 fatalf("cannot find GOPATH") 104 } 105 106 exitCode := 0 107 for _, arg := range modules { 108 if err := os.WriteFile(filepath.Join(tmpdir, "go.mod"), []byte("module m\n"), 0o666); err != nil { 109 fatalf("%v", err) 110 } 111 run(goCmd, "get", "-d", arg) 112 path := arg 113 if i := strings.Index(path, "@"); i >= 0 { 114 path = path[:i] 115 } 116 out := run(goCmd, "list", "-m", "-f={{.Path}} {{.Version}} {{.Dir}}", path) 117 f := strings.Fields(out) 118 if len(f) != 3 { 119 log.Printf("go list -m %s: unexpected output %q", arg, out) 120 exitCode = 1 121 continue 122 } 123 path, vers, dir := f[0], f[1], f[2] 124 125 encpath, err := module.EscapePath(path) 126 if err != nil { 127 log.Printf("failed to encode path %q: %v", path, err) 128 continue 129 } 130 path = encpath 131 132 mod, err := os.ReadFile(filepath.Join(gopath, "pkg/mod/cache/download", path, "@v", vers+".mod")) 133 if err != nil { 134 log.Printf("%s: %v", arg, err) 135 exitCode = 1 136 continue 137 } 138 info, err := os.ReadFile(filepath.Join(gopath, "pkg/mod/cache/download", path, "@v", vers+".info")) 139 if err != nil { 140 log.Printf("%s: %v", arg, err) 141 exitCode = 1 142 continue 143 } 144 145 a := new(txtar.Archive) 146 title := arg 147 if !strings.Contains(arg, "@") { 148 title += "@" + vers 149 } 150 dir = filepath.Clean(dir) 151 modDir := strings.ReplaceAll(path, "/", "_") + "_" + vers 152 filePrefix := "" 153 if targetDir == "-" { 154 filePrefix = ".gomodproxy/" + modDir + "/" 155 } else { 156 // No comment if we're writing to stdout. 157 a.Comment = []byte(fmt.Sprintf("module %s\n\n", title)) 158 } 159 a.Files = []txtar.File{ 160 {Name: filePrefix + ".mod", Data: mod}, 161 {Name: filePrefix + ".info", Data: info}, 162 } 163 err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { 164 if err != nil { 165 return err 166 } 167 if !info.Mode().IsRegular() { 168 return nil 169 } 170 // TODO: skip dirs like "testdata" or "_foo" unless -all 171 // is given? 172 name := info.Name() 173 switch { 174 case *allFiles: 175 case name == "go.mod": 176 case strings.HasSuffix(name, ".go"): 177 default: 178 // the name is not in the whitelist, and we're 179 // not including all files via -all 180 return nil 181 } 182 data, err := os.ReadFile(path) 183 if err != nil { 184 return err 185 } 186 a.Files = append(a.Files, txtar.File{ 187 Name: filePrefix + strings.TrimPrefix(path, dir+string(filepath.Separator)), 188 Data: data, 189 }) 190 return nil 191 }) 192 if err != nil { 193 log.Printf("%s: %v", arg, err) 194 exitCode = 1 195 continue 196 } 197 198 data := txtar.Format(a) 199 if targetDir == "-" { 200 if _, err := os.Stdout.Write(data); err != nil { 201 log.Printf("cannot write output: %v", err) 202 exitCode = 1 203 break 204 } 205 } else { 206 if err := os.WriteFile(filepath.Join(targetDir, modDir+".txtar"), data, 0o666); err != nil { 207 log.Printf("%s: %v", arg, err) 208 exitCode = 1 209 continue 210 } 211 } 212 } 213 os.RemoveAll(tmpdir) 214 return exitCode 215 }