...

Source file src/pkg/cmd/go/internal/modcmd/vendor.go

     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		"cmd/go/internal/base"
    18		"cmd/go/internal/cfg"
    19		"cmd/go/internal/imports"
    20		"cmd/go/internal/modload"
    21		"cmd/go/internal/module"
    22	)
    23	
    24	var cmdVendor = &base.Command{
    25		UsageLine: "go mod vendor [-v]",
    26		Short:     "make vendored copy of dependencies",
    27		Long: `
    28	Vendor resets the main module's vendor directory to include all packages
    29	needed to build and test all the main module's packages.
    30	It does not include test code for vendored packages.
    31	
    32	The -v flag causes vendor to print the names of vendored
    33	modules and packages to standard error.
    34		`,
    35		Run: runVendor,
    36	}
    37	
    38	func init() {
    39		cmdVendor.Flag.BoolVar(&cfg.BuildV, "v", false, "")
    40	}
    41	
    42	func runVendor(cmd *base.Command, args []string) {
    43		if len(args) != 0 {
    44			base.Fatalf("go mod vendor: vendor takes no arguments")
    45		}
    46		pkgs := modload.LoadVendor()
    47	
    48		vdir := filepath.Join(modload.ModRoot(), "vendor")
    49		if err := os.RemoveAll(vdir); err != nil {
    50			base.Fatalf("go mod vendor: %v", err)
    51		}
    52	
    53		modpkgs := make(map[module.Version][]string)
    54		for _, pkg := range pkgs {
    55			m := modload.PackageModule(pkg)
    56			if m == modload.Target {
    57				continue
    58			}
    59			modpkgs[m] = append(modpkgs[m], pkg)
    60		}
    61	
    62		var buf bytes.Buffer
    63		for _, m := range modload.BuildList()[1:] {
    64			if pkgs := modpkgs[m]; len(pkgs) > 0 {
    65				repl := ""
    66				if r := modload.Replacement(m); r.Path != "" {
    67					repl = " => " + r.Path
    68					if r.Version != "" {
    69						repl += " " + r.Version
    70					}
    71				}
    72				fmt.Fprintf(&buf, "# %s %s%s\n", m.Path, m.Version, repl)
    73				if cfg.BuildV {
    74					fmt.Fprintf(os.Stderr, "# %s %s%s\n", m.Path, m.Version, repl)
    75				}
    76				sort.Strings(pkgs)
    77				for _, pkg := range pkgs {
    78					fmt.Fprintf(&buf, "%s\n", pkg)
    79					if cfg.BuildV {
    80						fmt.Fprintf(os.Stderr, "%s\n", pkg)
    81					}
    82					vendorPkg(vdir, pkg)
    83				}
    84			}
    85		}
    86		if buf.Len() == 0 {
    87			fmt.Fprintf(os.Stderr, "go: no dependencies to vendor\n")
    88			return
    89		}
    90		if err := ioutil.WriteFile(filepath.Join(vdir, "modules.txt"), buf.Bytes(), 0666); err != nil {
    91			base.Fatalf("go mod vendor: %v", err)
    92		}
    93	}
    94	
    95	func vendorPkg(vdir, pkg string) {
    96		realPath := modload.ImportMap(pkg)
    97		if realPath != pkg && modload.ImportMap(realPath) != "" {
    98			fmt.Fprintf(os.Stderr, "warning: %s imported as both %s and %s; making two copies.\n", realPath, realPath, pkg)
    99		}
   100	
   101		dst := filepath.Join(vdir, pkg)
   102		src := modload.PackageDir(realPath)
   103		if src == "" {
   104			fmt.Fprintf(os.Stderr, "internal error: no pkg for %s -> %s\n", pkg, realPath)
   105		}
   106		copyDir(dst, src, matchPotentialSourceFile)
   107		if m := modload.PackageModule(realPath); m.Path != "" {
   108			copyMetadata(m.Path, realPath, dst, src)
   109		}
   110	}
   111	
   112	type metakey struct {
   113		modPath string
   114		dst     string
   115	}
   116	
   117	var copiedMetadata = make(map[metakey]bool)
   118	
   119	// copyMetadata copies metadata files from parents of src to parents of dst,
   120	// stopping after processing the src parent for modPath.
   121	func copyMetadata(modPath, pkg, dst, src string) {
   122		for parent := 0; ; parent++ {
   123			if copiedMetadata[metakey{modPath, dst}] {
   124				break
   125			}
   126			copiedMetadata[metakey{modPath, dst}] = true
   127			if parent > 0 {
   128				copyDir(dst, src, matchMetadata)
   129			}
   130			if modPath == pkg {
   131				break
   132			}
   133			pkg = filepath.Dir(pkg)
   134			dst = filepath.Dir(dst)
   135			src = filepath.Dir(src)
   136		}
   137	}
   138	
   139	// metaPrefixes is the list of metadata file prefixes.
   140	// Vendoring copies metadata files from parents of copied directories.
   141	// Note that this list could be arbitrarily extended, and it is longer
   142	// in other tools (such as godep or dep). By using this limited set of
   143	// prefixes and also insisting on capitalized file names, we are trying
   144	// to nudge people toward more agreement on the naming
   145	// and also trying to avoid false positives.
   146	var metaPrefixes = []string{
   147		"AUTHORS",
   148		"CONTRIBUTORS",
   149		"COPYLEFT",
   150		"COPYING",
   151		"COPYRIGHT",
   152		"LEGAL",
   153		"LICENSE",
   154		"NOTICE",
   155		"PATENTS",
   156	}
   157	
   158	// matchMetadata reports whether info is a metadata file.
   159	func matchMetadata(dir string, info os.FileInfo) bool {
   160		name := info.Name()
   161		for _, p := range metaPrefixes {
   162			if strings.HasPrefix(name, p) {
   163				return true
   164			}
   165		}
   166		return false
   167	}
   168	
   169	// matchPotentialSourceFile reports whether info may be relevant to a build operation.
   170	func matchPotentialSourceFile(dir string, info os.FileInfo) bool {
   171		if strings.HasSuffix(info.Name(), "_test.go") {
   172			return false
   173		}
   174		if strings.HasSuffix(info.Name(), ".go") {
   175			f, err := os.Open(filepath.Join(dir, info.Name()))
   176			if err != nil {
   177				base.Fatalf("go mod vendor: %v", err)
   178			}
   179			defer f.Close()
   180	
   181			content, err := imports.ReadImports(f, false, nil)
   182			if err == nil && !imports.ShouldBuild(content, imports.AnyTags()) {
   183				// The file is explicitly tagged "ignore", so it can't affect the build.
   184				// Leave it out.
   185				return false
   186			}
   187			return true
   188		}
   189	
   190		// We don't know anything about this file, so optimistically assume that it is
   191		// needed.
   192		return true
   193	}
   194	
   195	// copyDir copies all regular files satisfying match(info) from src to dst.
   196	func copyDir(dst, src string, match func(dir string, info os.FileInfo) bool) {
   197		files, err := ioutil.ReadDir(src)
   198		if err != nil {
   199			base.Fatalf("go mod vendor: %v", err)
   200		}
   201		if err := os.MkdirAll(dst, 0777); err != nil {
   202			base.Fatalf("go mod vendor: %v", err)
   203		}
   204		for _, file := range files {
   205			if file.IsDir() || !file.Mode().IsRegular() || !match(src, file) {
   206				continue
   207			}
   208			r, err := os.Open(filepath.Join(src, file.Name()))
   209			if err != nil {
   210				base.Fatalf("go mod vendor: %v", err)
   211			}
   212			w, err := os.Create(filepath.Join(dst, file.Name()))
   213			if err != nil {
   214				base.Fatalf("go mod vendor: %v", err)
   215			}
   216			if _, err := io.Copy(w, r); err != nil {
   217				base.Fatalf("go mod vendor: %v", err)
   218			}
   219			r.Close()
   220			if err := w.Close(); err != nil {
   221				base.Fatalf("go mod vendor: %v", err)
   222			}
   223		}
   224	}
   225	

View as plain text