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