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