1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package main
20
21 import (
22 "bytes"
23 "cmd/go/internal/str"
24 "flag"
25 "fmt"
26 "internal/txtar"
27 "io/fs"
28 "log"
29 "os"
30 "os/exec"
31 "path/filepath"
32 "strings"
33 )
34
35 func usage() {
36 fmt.Fprintf(os.Stderr, "usage: go run addmod.go path@version...\n")
37 os.Exit(2)
38 }
39
40 var tmpdir string
41
42 func fatalf(format string, args ...any) {
43 os.RemoveAll(tmpdir)
44 log.Fatalf(format, args...)
45 }
46
47 const goCmd = "go"
48
49 func main() {
50 flag.Usage = usage
51 flag.Parse()
52 if flag.NArg() == 0 {
53 usage()
54 }
55
56 log.SetPrefix("addmod: ")
57 log.SetFlags(0)
58
59 var err error
60 tmpdir, err = os.MkdirTemp("", "addmod-")
61 if err != nil {
62 log.Fatal(err)
63 }
64
65 run := func(command string, args ...string) string {
66 cmd := exec.Command(command, args...)
67 cmd.Dir = tmpdir
68 var stderr bytes.Buffer
69 cmd.Stderr = &stderr
70 out, err := cmd.Output()
71 if err != nil {
72 fatalf("%s %s: %v\n%s", command, strings.Join(args, " "), err, stderr.Bytes())
73 }
74 return string(out)
75 }
76
77 gopath := strings.TrimSpace(run("go", "env", "GOPATH"))
78 if gopath == "" {
79 fatalf("cannot find GOPATH")
80 }
81
82 exitCode := 0
83 for _, arg := range flag.Args() {
84 if err := os.WriteFile(filepath.Join(tmpdir, "go.mod"), []byte("module m\n"), 0666); err != nil {
85 fatalf("%v", err)
86 }
87 run(goCmd, "get", "-d", arg)
88 path := arg
89 if i := strings.Index(path, "@"); i >= 0 {
90 path = path[:i]
91 }
92 out := run(goCmd, "list", "-m", "-f={{.Path}} {{.Version}} {{.Dir}}", path)
93 f := strings.Fields(out)
94 if len(f) != 3 {
95 log.Printf("go list -m %s: unexpected output %q", arg, out)
96 exitCode = 1
97 continue
98 }
99 path, vers, dir := f[0], f[1], f[2]
100 mod, err := os.ReadFile(filepath.Join(gopath, "pkg/mod/cache/download", path, "@v", vers+".mod"))
101 if err != nil {
102 log.Printf("%s: %v", arg, err)
103 exitCode = 1
104 continue
105 }
106 info, err := os.ReadFile(filepath.Join(gopath, "pkg/mod/cache/download", path, "@v", vers+".info"))
107 if err != nil {
108 log.Printf("%s: %v", arg, err)
109 exitCode = 1
110 continue
111 }
112
113 a := new(txtar.Archive)
114 title := arg
115 if !strings.Contains(arg, "@") {
116 title += "@" + vers
117 }
118 a.Comment = []byte(fmt.Sprintf("module %s\n\n", title))
119 a.Files = []txtar.File{
120 {Name: ".mod", Data: mod},
121 {Name: ".info", Data: info},
122 }
123 dir = filepath.Clean(dir)
124 err = filepath.WalkDir(dir, func(path string, info fs.DirEntry, err error) error {
125 if !info.Type().IsRegular() {
126 return nil
127 }
128 name := info.Name()
129 if name == "go.mod" || strings.HasSuffix(name, ".go") {
130 data, err := os.ReadFile(path)
131 if err != nil {
132 return err
133 }
134 a.Files = append(a.Files, txtar.File{Name: str.TrimFilePathPrefix(path, dir), Data: data})
135 }
136 return nil
137 })
138 if err != nil {
139 log.Printf("%s: %v", arg, err)
140 exitCode = 1
141 continue
142 }
143
144 data := txtar.Format(a)
145 target := filepath.Join("mod", strings.ReplaceAll(path, "/", "_")+"_"+vers+".txt")
146 if err := os.WriteFile(target, data, 0666); err != nil {
147 log.Printf("%s: %v", arg, err)
148 exitCode = 1
149 continue
150 }
151 }
152 os.RemoveAll(tmpdir)
153 os.Exit(exitCode)
154 }
155
View as plain text