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 5package modcmd 6 7import ( 8 "bytes" 9 "context" 10 "errors" 11 "fmt" 12 "go/build" 13 "io" 14 "io/fs" 15 "os" 16 "path" 17 "path/filepath" 18 "sort" 19 "strings" 20 21 "cmd/go/internal/base" 22 "cmd/go/internal/cfg" 23 "cmd/go/internal/fsys" 24 "cmd/go/internal/gover" 25 "cmd/go/internal/imports" 26 "cmd/go/internal/load" 27 "cmd/go/internal/modload" 28 "cmd/go/internal/str" 29 30 "golang.org/x/mod/module" 31) 32 33var cmdVendor = &base.Command{ 34 UsageLine: "go mod vendor [-e] [-v] [-o outdir]", 35 Short: "make vendored copy of dependencies", 36 Long: ` 37Vendor resets the main module's vendor directory to include all packages 38needed to build and test all the main module's packages. 39It does not include test code for vendored packages. 40 41The -v flag causes vendor to print the names of vendored 42modules and packages to standard error. 43 44The -e flag causes vendor to attempt to proceed despite errors 45encountered while loading packages. 46 47The -o flag causes vendor to create the vendor directory at the given 48path instead of "vendor". The go command can only use a vendor directory 49named "vendor" within the module root directory, so this flag is 50primarily useful for other tools. 51 52See https://golang.org/ref/mod#go-mod-vendor for more about 'go mod vendor'. 53 `, 54 Run: runVendor, 55} 56 57var vendorE bool // if true, report errors but proceed anyway 58var vendorO string // if set, overrides the default output directory 59 60func init() { 61 cmdVendor.Flag.BoolVar(&cfg.BuildV, "v", false, "") 62 cmdVendor.Flag.BoolVar(&vendorE, "e", false, "") 63 cmdVendor.Flag.StringVar(&vendorO, "o", "", "") 64 base.AddChdirFlag(&cmdVendor.Flag) 65 base.AddModCommonFlags(&cmdVendor.Flag) 66} 67 68func runVendor(ctx context.Context, cmd *base.Command, args []string) { 69 modload.InitWorkfile() 70 if modload.WorkFilePath() != "" { 71 base.Fatalf("go: 'go mod vendor' cannot be run in workspace mode. Run 'go work vendor' to vendor the workspace or set 'GOWORK=off' to exit workspace mode.") 72 } 73 RunVendor(ctx, vendorE, vendorO, args) 74} 75 76func RunVendor(ctx context.Context, vendorE bool, vendorO string, args []string) { 77 if len(args) != 0 { 78 base.Fatalf("go: 'go mod vendor' accepts no arguments") 79 } 80 modload.ForceUseModules = true 81 modload.RootMode = modload.NeedRoot 82 83 loadOpts := modload.PackageOpts{ 84 Tags: imports.AnyTags(), 85 VendorModulesInGOROOTSrc: true, 86 ResolveMissingImports: true, 87 UseVendorAll: true, 88 AllowErrors: vendorE, 89 SilenceMissingStdImports: true, 90 } 91 _, pkgs := modload.LoadPackages(ctx, loadOpts, "all") 92 93 var vdir string 94 switch { 95 case filepath.IsAbs(vendorO): 96 vdir = vendorO 97 case vendorO != "": 98 vdir = filepath.Join(base.Cwd(), vendorO) 99 default: 100 vdir = filepath.Join(modload.VendorDir()) 101 } 102 if err := os.RemoveAll(vdir); err != nil { 103 base.Fatal(err) 104 } 105 106 modpkgs := make(map[module.Version][]string) 107 for _, pkg := range pkgs { 108 m := modload.PackageModule(pkg) 109 if m.Path == "" || modload.MainModules.Contains(m.Path) { 110 continue 111 } 112 modpkgs[m] = append(modpkgs[m], pkg) 113 } 114 checkPathCollisions(modpkgs) 115 116 includeAllReplacements := false 117 includeGoVersions := false 118 isExplicit := map[module.Version]bool{} 119 gv := modload.MainModules.GoVersion() 120 if gover.Compare(gv, "1.14") >= 0 && (modload.FindGoWork(base.Cwd()) != "" || modload.ModFile().Go != nil) { 121 // If the Go version is at least 1.14, annotate all explicit 'require' and 122 // 'replace' targets found in the go.mod file so that we can perform a 123 // stronger consistency check when -mod=vendor is set. 124 for _, m := range modload.MainModules.Versions() { 125 if modFile := modload.MainModules.ModFile(m); modFile != nil { 126 for _, r := range modFile.Require { 127 isExplicit[r.Mod] = true 128 } 129 } 130 131 } 132 includeAllReplacements = true 133 } 134 if gover.Compare(gv, "1.17") >= 0 { 135 // If the Go version is at least 1.17, annotate all modules with their 136 // 'go' version directives. 137 includeGoVersions = true 138 } 139 140 var vendorMods []module.Version 141 for m := range isExplicit { 142 vendorMods = append(vendorMods, m) 143 } 144 for m := range modpkgs { 145 if !isExplicit[m] { 146 vendorMods = append(vendorMods, m) 147 } 148 } 149 gover.ModSort(vendorMods) 150 151 var ( 152 buf bytes.Buffer 153 w io.Writer = &buf 154 ) 155 if cfg.BuildV { 156 w = io.MultiWriter(&buf, os.Stderr) 157 } 158 159 if modload.MainModules.WorkFile() != nil { 160 fmt.Fprintf(w, "## workspace\n") 161 } 162 163 replacementWritten := make(map[module.Version]bool) 164 for _, m := range vendorMods { 165 replacement := modload.Replacement(m) 166 line := moduleLine(m, replacement) 167 replacementWritten[m] = true 168 io.WriteString(w, line) 169 170 goVersion := "" 171 if includeGoVersions { 172 goVersion = modload.ModuleInfo(ctx, m.Path).GoVersion 173 } 174 switch { 175 case isExplicit[m] && goVersion != "": 176 fmt.Fprintf(w, "## explicit; go %s\n", goVersion) 177 case isExplicit[m]: 178 io.WriteString(w, "## explicit\n") 179 case goVersion != "": 180 fmt.Fprintf(w, "## go %s\n", goVersion) 181 } 182 183 pkgs := modpkgs[m] 184 sort.Strings(pkgs) 185 for _, pkg := range pkgs { 186 fmt.Fprintf(w, "%s\n", pkg) 187 vendorPkg(vdir, pkg) 188 } 189 } 190 191 if includeAllReplacements { 192 // Record unused and wildcard replacements at the end of the modules.txt file: 193 // without access to the complete build list, the consumer of the vendor 194 // directory can't otherwise determine that those replacements had no effect. 195 for _, m := range modload.MainModules.Versions() { 196 if workFile := modload.MainModules.WorkFile(); workFile != nil { 197 for _, r := range workFile.Replace { 198 if replacementWritten[r.Old] { 199 // We already recorded this replacement. 200 continue 201 } 202 replacementWritten[r.Old] = true 203 204 line := moduleLine(r.Old, r.New) 205 buf.WriteString(line) 206 if cfg.BuildV { 207 os.Stderr.WriteString(line) 208 } 209 } 210 } 211 if modFile := modload.MainModules.ModFile(m); modFile != nil { 212 for _, r := range modFile.Replace { 213 if replacementWritten[r.Old] { 214 // We already recorded this replacement. 215 continue 216 } 217 replacementWritten[r.Old] = true 218 rNew := modload.Replacement(r.Old) 219 if rNew == (module.Version{}) { 220 // There is no replacement. Don't try to write it. 221 continue 222 } 223 224 line := moduleLine(r.Old, rNew) 225 buf.WriteString(line) 226 if cfg.BuildV { 227 os.Stderr.WriteString(line) 228 } 229 } 230 } 231 } 232 } 233 234 if buf.Len() == 0 { 235 fmt.Fprintf(os.Stderr, "go: no dependencies to vendor\n") 236 return 237 } 238 239 if err := os.MkdirAll(vdir, 0777); err != nil { 240 base.Fatal(err) 241 } 242 243 if err := os.WriteFile(filepath.Join(vdir, "modules.txt"), buf.Bytes(), 0666); err != nil { 244 base.Fatal(err) 245 } 246} 247 248func moduleLine(m, r module.Version) string { 249 b := new(strings.Builder) 250 b.WriteString("# ") 251 b.WriteString(m.Path) 252 if m.Version != "" { 253 b.WriteString(" ") 254 b.WriteString(m.Version) 255 } 256 if r.Path != "" { 257 if str.HasFilePathPrefix(filepath.Clean(r.Path), "vendor") { 258 base.Fatalf("go: replacement path %s inside vendor directory", r.Path) 259 } 260 b.WriteString(" => ") 261 b.WriteString(r.Path) 262 if r.Version != "" { 263 b.WriteString(" ") 264 b.WriteString(r.Version) 265 } 266 } 267 b.WriteString("\n") 268 return b.String() 269} 270 271func vendorPkg(vdir, pkg string) { 272 src, realPath, _ := modload.Lookup("", false, pkg) 273 if src == "" { 274 base.Errorf("internal error: no pkg for %s\n", pkg) 275 return 276 } 277 if realPath != pkg { 278 // TODO(#26904): Revisit whether this behavior still makes sense. 279 // This should actually be impossible today, because the import map is the 280 // identity function for packages outside of the standard library. 281 // 282 // Part of the purpose of the vendor directory is to allow the packages in 283 // the module to continue to build in GOPATH mode, and GOPATH-mode users 284 // won't know about replacement aliasing. How important is it to maintain 285 // compatibility? 286 fmt.Fprintf(os.Stderr, "warning: %s imported as both %s and %s; making two copies.\n", realPath, realPath, pkg) 287 } 288 289 copiedFiles := make(map[string]bool) 290 dst := filepath.Join(vdir, pkg) 291 copyDir(dst, src, matchPotentialSourceFile, copiedFiles) 292 if m := modload.PackageModule(realPath); m.Path != "" { 293 copyMetadata(m.Path, realPath, dst, src, copiedFiles) 294 } 295 296 ctx := build.Default 297 ctx.UseAllFiles = true 298 bp, err := ctx.ImportDir(src, build.IgnoreVendor) 299 // Because UseAllFiles is set on the build.Context, it's possible ta get 300 // a MultiplePackageError on an otherwise valid package: the package could 301 // have different names for GOOS=windows and GOOS=mac for example. On the 302 // other hand if there's a NoGoError, the package might have source files 303 // specifying "//go:build ignore" those packages should be skipped because 304 // embeds from ignored files can't be used. 305 // TODO(#42504): Find a better way to avoid errors from ImportDir. We'll 306 // need to figure this out when we switch to PackagesAndErrors as per the 307 // TODO above. 308 var multiplePackageError *build.MultiplePackageError 309 var noGoError *build.NoGoError 310 if err != nil { 311 if errors.As(err, &noGoError) { 312 return // No source files in this package are built. Skip embeds in ignored files. 313 } else if !errors.As(err, &multiplePackageError) { // multiplePackageErrors are OK, but others are not. 314 base.Fatalf("internal error: failed to find embedded files of %s: %v\n", pkg, err) 315 } 316 } 317 var embedPatterns []string 318 if gover.Compare(modload.MainModules.GoVersion(), "1.22") >= 0 { 319 embedPatterns = bp.EmbedPatterns 320 } else { 321 // Maintain the behavior of https://github.com/golang/go/issues/63473 322 // so that we continue to agree with older versions of the go command 323 // about the contents of vendor directories in existing modules 324 embedPatterns = str.StringList(bp.EmbedPatterns, bp.TestEmbedPatterns, bp.XTestEmbedPatterns) 325 } 326 embeds, err := load.ResolveEmbed(bp.Dir, embedPatterns) 327 if err != nil { 328 format := "go: resolving embeds in %s: %v\n" 329 if vendorE { 330 fmt.Fprintf(os.Stderr, format, pkg, err) 331 } else { 332 base.Errorf(format, pkg, err) 333 } 334 return 335 } 336 for _, embed := range embeds { 337 embedDst := filepath.Join(dst, embed) 338 if copiedFiles[embedDst] { 339 continue 340 } 341 342 // Copy the file as is done by copyDir below. 343 err := func() error { 344 r, err := os.Open(filepath.Join(src, embed)) 345 if err != nil { 346 return err 347 } 348 if err := os.MkdirAll(filepath.Dir(embedDst), 0777); err != nil { 349 return err 350 } 351 w, err := os.Create(embedDst) 352 if err != nil { 353 return err 354 } 355 if _, err := io.Copy(w, r); err != nil { 356 return err 357 } 358 r.Close() 359 return w.Close() 360 }() 361 if err != nil { 362 if vendorE { 363 fmt.Fprintf(os.Stderr, "go: %v\n", err) 364 } else { 365 base.Error(err) 366 } 367 } 368 } 369} 370 371type metakey struct { 372 modPath string 373 dst string 374} 375 376var copiedMetadata = make(map[metakey]bool) 377 378// copyMetadata copies metadata files from parents of src to parents of dst, 379// stopping after processing the src parent for modPath. 380func copyMetadata(modPath, pkg, dst, src string, copiedFiles map[string]bool) { 381 for parent := 0; ; parent++ { 382 if copiedMetadata[metakey{modPath, dst}] { 383 break 384 } 385 copiedMetadata[metakey{modPath, dst}] = true 386 if parent > 0 { 387 copyDir(dst, src, matchMetadata, copiedFiles) 388 } 389 if modPath == pkg { 390 break 391 } 392 pkg = path.Dir(pkg) 393 dst = filepath.Dir(dst) 394 src = filepath.Dir(src) 395 } 396} 397 398// metaPrefixes is the list of metadata file prefixes. 399// Vendoring copies metadata files from parents of copied directories. 400// Note that this list could be arbitrarily extended, and it is longer 401// in other tools (such as godep or dep). By using this limited set of 402// prefixes and also insisting on capitalized file names, we are trying 403// to nudge people toward more agreement on the naming 404// and also trying to avoid false positives. 405var metaPrefixes = []string{ 406 "AUTHORS", 407 "CONTRIBUTORS", 408 "COPYLEFT", 409 "COPYING", 410 "COPYRIGHT", 411 "LEGAL", 412 "LICENSE", 413 "NOTICE", 414 "PATENTS", 415} 416 417// matchMetadata reports whether info is a metadata file. 418func matchMetadata(dir string, info fs.DirEntry) bool { 419 name := info.Name() 420 for _, p := range metaPrefixes { 421 if strings.HasPrefix(name, p) { 422 return true 423 } 424 } 425 return false 426} 427 428// matchPotentialSourceFile reports whether info may be relevant to a build operation. 429func matchPotentialSourceFile(dir string, info fs.DirEntry) bool { 430 if strings.HasSuffix(info.Name(), "_test.go") { 431 return false 432 } 433 if info.Name() == "go.mod" || info.Name() == "go.sum" { 434 if gv := modload.MainModules.GoVersion(); gover.Compare(gv, "1.17") >= 0 { 435 // As of Go 1.17, we strip go.mod and go.sum files from dependency modules. 436 // Otherwise, 'go' commands invoked within the vendor subtree may misidentify 437 // an arbitrary directory within the vendor tree as a module root. 438 // (See https://golang.org/issue/42970.) 439 return false 440 } 441 } 442 if strings.HasSuffix(info.Name(), ".go") { 443 f, err := fsys.Open(filepath.Join(dir, info.Name())) 444 if err != nil { 445 base.Fatal(err) 446 } 447 defer f.Close() 448 449 content, err := imports.ReadImports(f, false, nil) 450 if err == nil && !imports.ShouldBuild(content, imports.AnyTags()) { 451 // The file is explicitly tagged "ignore", so it can't affect the build. 452 // Leave it out. 453 return false 454 } 455 return true 456 } 457 458 // We don't know anything about this file, so optimistically assume that it is 459 // needed. 460 return true 461} 462 463// copyDir copies all regular files satisfying match(info) from src to dst. 464func copyDir(dst, src string, match func(dir string, info fs.DirEntry) bool, copiedFiles map[string]bool) { 465 files, err := os.ReadDir(src) 466 if err != nil { 467 base.Fatal(err) 468 } 469 if err := os.MkdirAll(dst, 0777); err != nil { 470 base.Fatal(err) 471 } 472 for _, file := range files { 473 if file.IsDir() || !file.Type().IsRegular() || !match(src, file) { 474 continue 475 } 476 copiedFiles[file.Name()] = true 477 r, err := os.Open(filepath.Join(src, file.Name())) 478 if err != nil { 479 base.Fatal(err) 480 } 481 dstPath := filepath.Join(dst, file.Name()) 482 copiedFiles[dstPath] = true 483 w, err := os.Create(dstPath) 484 if err != nil { 485 base.Fatal(err) 486 } 487 if _, err := io.Copy(w, r); err != nil { 488 base.Fatal(err) 489 } 490 r.Close() 491 if err := w.Close(); err != nil { 492 base.Fatal(err) 493 } 494 } 495} 496 497// checkPathCollisions will fail if case-insensitive collisions are present. 498// The reason why we do this check in go mod vendor is to keep consistency 499// with go build. If modifying, consider changing load() in 500// src/cmd/go/internal/load/pkg.go 501func checkPathCollisions(modpkgs map[module.Version][]string) { 502 var foldPath = make(map[string]string, len(modpkgs)) 503 for m := range modpkgs { 504 fold := str.ToFold(m.Path) 505 if other := foldPath[fold]; other == "" { 506 foldPath[fold] = m.Path 507 } else if other != m.Path { 508 base.Fatalf("go.mod: case-insensitive import collision: %q and %q", m.Path, other) 509 } 510 } 511} 512