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//go:build ignore
6// +build ignore
7
8// Addmod adds a module as a txtar archive to the testdata/mod directory.
9//
10// Usage:
11//
12//	go run addmod.go path@version...
13//
14// It should only be used 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// See mod/README for more information.
19package main
20
21import (
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
35func usage() {
36	fmt.Fprintf(os.Stderr, "usage: go run addmod.go path@version...\n")
37	os.Exit(2)
38}
39
40var tmpdir string
41
42func fatalf(format string, args ...any) {
43	os.RemoveAll(tmpdir)
44	log.Fatalf(format, args...)
45}
46
47const goCmd = "go"
48
49func 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