github.com/gagliardetto/golang-go@v0.0.0-20201020153340-53909ea70814/cmd/go/not-internal/modcmd/vendor.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  package modcmd
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"os"
    13  	"path/filepath"
    14  	"sort"
    15  	"strings"
    16  
    17  	"github.com/gagliardetto/golang-go/cmd/go/not-internal/base"
    18  	"github.com/gagliardetto/golang-go/cmd/go/not-internal/cfg"
    19  	"github.com/gagliardetto/golang-go/cmd/go/not-internal/imports"
    20  	"github.com/gagliardetto/golang-go/cmd/go/not-internal/modload"
    21  	"github.com/gagliardetto/golang-go/cmd/go/not-internal/work"
    22  
    23  	"golang.org/x/mod/module"
    24  	"golang.org/x/mod/semver"
    25  )
    26  
    27  var cmdVendor = &base.Command{
    28  	UsageLine: "go mod vendor [-v]",
    29  	Short:     "make vendored copy of dependencies",
    30  	Long: `
    31  Vendor resets the main module's vendor directory to include all packages
    32  needed to build and test all the main module's packages.
    33  It does not include test code for vendored packages.
    34  
    35  The -v flag causes vendor to print the names of vendored
    36  modules and packages to standard error.
    37  	`,
    38  	Run: runVendor,
    39  }
    40  
    41  func init() {
    42  	cmdVendor.Flag.BoolVar(&cfg.BuildV, "v", false, "")
    43  	work.AddModCommonFlags(cmdVendor)
    44  }
    45  
    46  func runVendor(cmd *base.Command, args []string) {
    47  	if len(args) != 0 {
    48  		base.Fatalf("go mod vendor: vendor takes no arguments")
    49  	}
    50  	pkgs := modload.LoadVendor()
    51  
    52  	vdir := filepath.Join(modload.ModRoot(), "vendor")
    53  	if err := os.RemoveAll(vdir); err != nil {
    54  		base.Fatalf("go mod vendor: %v", err)
    55  	}
    56  
    57  	modpkgs := make(map[module.Version][]string)
    58  	for _, pkg := range pkgs {
    59  		m := modload.PackageModule(pkg)
    60  		if m == modload.Target {
    61  			continue
    62  		}
    63  		modpkgs[m] = append(modpkgs[m], pkg)
    64  	}
    65  
    66  	includeAllReplacements := false
    67  	isExplicit := map[module.Version]bool{}
    68  	if gv := modload.ModFile().Go; gv != nil && semver.Compare("v"+gv.Version, "v1.14") >= 0 {
    69  		// If the Go version is at least 1.14, annotate all explicit 'require' and
    70  		// 'replace' targets found in the go.mod file so that we can perform a
    71  		// stronger consistency check when -mod=vendor is set.
    72  		for _, r := range modload.ModFile().Require {
    73  			isExplicit[r.Mod] = true
    74  		}
    75  		includeAllReplacements = true
    76  	}
    77  
    78  	var buf bytes.Buffer
    79  	for _, m := range modload.BuildList()[1:] {
    80  		if pkgs := modpkgs[m]; len(pkgs) > 0 || isExplicit[m] {
    81  			line := moduleLine(m, modload.Replacement(m))
    82  			buf.WriteString(line)
    83  			if cfg.BuildV {
    84  				os.Stderr.WriteString(line)
    85  			}
    86  			if isExplicit[m] {
    87  				buf.WriteString("## explicit\n")
    88  				if cfg.BuildV {
    89  					os.Stderr.WriteString("## explicit\n")
    90  				}
    91  			}
    92  			sort.Strings(pkgs)
    93  			for _, pkg := range pkgs {
    94  				fmt.Fprintf(&buf, "%s\n", pkg)
    95  				if cfg.BuildV {
    96  					fmt.Fprintf(os.Stderr, "%s\n", pkg)
    97  				}
    98  				vendorPkg(vdir, pkg)
    99  			}
   100  		}
   101  	}
   102  
   103  	if includeAllReplacements {
   104  		// Record unused and wildcard replacements at the end of the modules.txt file:
   105  		// without access to the complete build list, the consumer of the vendor
   106  		// directory can't otherwise determine that those replacements had no effect.
   107  		for _, r := range modload.ModFile().Replace {
   108  			if len(modpkgs[r.Old]) > 0 {
   109  				// We we already recorded this replacement in the entry for the replaced
   110  				// module with the packages it provides.
   111  				continue
   112  			}
   113  
   114  			line := moduleLine(r.Old, r.New)
   115  			buf.WriteString(line)
   116  			if cfg.BuildV {
   117  				os.Stderr.WriteString(line)
   118  			}
   119  		}
   120  	}
   121  
   122  	if buf.Len() == 0 {
   123  		fmt.Fprintf(os.Stderr, "go: no dependencies to vendor\n")
   124  		return
   125  	}
   126  	if err := ioutil.WriteFile(filepath.Join(vdir, "modules.txt"), buf.Bytes(), 0666); err != nil {
   127  		base.Fatalf("go mod vendor: %v", err)
   128  	}
   129  }
   130  
   131  func moduleLine(m, r module.Version) string {
   132  	b := new(strings.Builder)
   133  	b.WriteString("# ")
   134  	b.WriteString(m.Path)
   135  	if m.Version != "" {
   136  		b.WriteString(" ")
   137  		b.WriteString(m.Version)
   138  	}
   139  	if r.Path != "" {
   140  		b.WriteString(" => ")
   141  		b.WriteString(r.Path)
   142  		if r.Version != "" {
   143  			b.WriteString(" ")
   144  			b.WriteString(r.Version)
   145  		}
   146  	}
   147  	b.WriteString("\n")
   148  	return b.String()
   149  }
   150  
   151  func vendorPkg(vdir, pkg string) {
   152  	realPath := modload.ImportMap(pkg)
   153  	if realPath != pkg && modload.ImportMap(realPath) != "" {
   154  		fmt.Fprintf(os.Stderr, "warning: %s imported as both %s and %s; making two copies.\n", realPath, realPath, pkg)
   155  	}
   156  
   157  	dst := filepath.Join(vdir, pkg)
   158  	src := modload.PackageDir(realPath)
   159  	if src == "" {
   160  		fmt.Fprintf(os.Stderr, "internal error: no pkg for %s -> %s\n", pkg, realPath)
   161  	}
   162  	copyDir(dst, src, matchPotentialSourceFile)
   163  	if m := modload.PackageModule(realPath); m.Path != "" {
   164  		copyMetadata(m.Path, realPath, dst, src)
   165  	}
   166  }
   167  
   168  type metakey struct {
   169  	modPath string
   170  	dst     string
   171  }
   172  
   173  var copiedMetadata = make(map[metakey]bool)
   174  
   175  // copyMetadata copies metadata files from parents of src to parents of dst,
   176  // stopping after processing the src parent for modPath.
   177  func copyMetadata(modPath, pkg, dst, src string) {
   178  	for parent := 0; ; parent++ {
   179  		if copiedMetadata[metakey{modPath, dst}] {
   180  			break
   181  		}
   182  		copiedMetadata[metakey{modPath, dst}] = true
   183  		if parent > 0 {
   184  			copyDir(dst, src, matchMetadata)
   185  		}
   186  		if modPath == pkg {
   187  			break
   188  		}
   189  		pkg = filepath.Dir(pkg)
   190  		dst = filepath.Dir(dst)
   191  		src = filepath.Dir(src)
   192  	}
   193  }
   194  
   195  // metaPrefixes is the list of metadata file prefixes.
   196  // Vendoring copies metadata files from parents of copied directories.
   197  // Note that this list could be arbitrarily extended, and it is longer
   198  // in other tools (such as godep or dep). By using this limited set of
   199  // prefixes and also insisting on capitalized file names, we are trying
   200  // to nudge people toward more agreement on the naming
   201  // and also trying to avoid false positives.
   202  var metaPrefixes = []string{
   203  	"AUTHORS",
   204  	"CONTRIBUTORS",
   205  	"COPYLEFT",
   206  	"COPYING",
   207  	"COPYRIGHT",
   208  	"LEGAL",
   209  	"LICENSE",
   210  	"NOTICE",
   211  	"PATENTS",
   212  }
   213  
   214  // matchMetadata reports whether info is a metadata file.
   215  func matchMetadata(dir string, info os.FileInfo) bool {
   216  	name := info.Name()
   217  	for _, p := range metaPrefixes {
   218  		if strings.HasPrefix(name, p) {
   219  			return true
   220  		}
   221  	}
   222  	return false
   223  }
   224  
   225  // matchPotentialSourceFile reports whether info may be relevant to a build operation.
   226  func matchPotentialSourceFile(dir string, info os.FileInfo) bool {
   227  	if strings.HasSuffix(info.Name(), "_test.go") {
   228  		return false
   229  	}
   230  	if strings.HasSuffix(info.Name(), ".go") {
   231  		f, err := os.Open(filepath.Join(dir, info.Name()))
   232  		if err != nil {
   233  			base.Fatalf("go mod vendor: %v", err)
   234  		}
   235  		defer f.Close()
   236  
   237  		content, err := imports.ReadImports(f, false, nil)
   238  		if err == nil && !imports.ShouldBuild(content, imports.AnyTags()) {
   239  			// The file is explicitly tagged "ignore", so it can't affect the build.
   240  			// Leave it out.
   241  			return false
   242  		}
   243  		return true
   244  	}
   245  
   246  	// We don't know anything about this file, so optimistically assume that it is
   247  	// needed.
   248  	return true
   249  }
   250  
   251  // copyDir copies all regular files satisfying match(info) from src to dst.
   252  func copyDir(dst, src string, match func(dir string, info os.FileInfo) bool) {
   253  	files, err := ioutil.ReadDir(src)
   254  	if err != nil {
   255  		base.Fatalf("go mod vendor: %v", err)
   256  	}
   257  	if err := os.MkdirAll(dst, 0777); err != nil {
   258  		base.Fatalf("go mod vendor: %v", err)
   259  	}
   260  	for _, file := range files {
   261  		if file.IsDir() || !file.Mode().IsRegular() || !match(src, file) {
   262  			continue
   263  		}
   264  		r, err := os.Open(filepath.Join(src, file.Name()))
   265  		if err != nil {
   266  			base.Fatalf("go mod vendor: %v", err)
   267  		}
   268  		w, err := os.Create(filepath.Join(dst, file.Name()))
   269  		if err != nil {
   270  			base.Fatalf("go mod vendor: %v", err)
   271  		}
   272  		if _, err := io.Copy(w, r); err != nil {
   273  			base.Fatalf("go mod vendor: %v", err)
   274  		}
   275  		r.Close()
   276  		if err := w.Close(); err != nil {
   277  			base.Fatalf("go mod vendor: %v", err)
   278  		}
   279  	}
   280  }