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  }