1
2
3
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 "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
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://go.dev/ref/mod#go-mod-vendor for more about 'go mod vendor'.
53 `,
54 Run: runVendor,
55 }
56
57 var (
58 vendorE bool
59 vendorO string
60 )
61
62 func init() {
63 cmdVendor.Flag.BoolVar(&cfg.BuildV, "v", false, "print the names of packages as they are processed")
64 cmdVendor.Flag.BoolVar(&vendorE, "e", false, "report errors but proceed anyway")
65 cmdVendor.Flag.StringVar(&vendorO, "o", "", "the output `directory` to write vendor modules to")
66 base.AddChdirFlag(&cmdVendor.Flag)
67 base.AddModCommonFlags(&cmdVendor.Flag)
68 }
69
70 func runVendor(ctx context.Context, cmd *base.Command, args []string) {
71 moduleLoader := modload.NewLoader()
72 moduleLoader.InitWorkfile()
73 if modload.WorkFilePath(moduleLoader) != "" {
74 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.")
75 }
76 RunVendor(moduleLoader, ctx, vendorE, vendorO, args)
77 }
78
79 func RunVendor(ld *modload.Loader, ctx context.Context, vendorE bool, vendorO string, args []string) {
80 if len(args) != 0 {
81 base.Fatalf("go: 'go mod vendor' accepts no arguments")
82 }
83 ld.ForceUseModules = true
84 ld.RootMode = modload.NeedRoot
85
86 loadOpts := modload.PackageOpts{
87 Tags: imports.AnyTags(),
88 VendorModulesInGOROOTSrc: true,
89 ResolveMissingImports: true,
90 UseVendorAll: true,
91 AllowErrors: vendorE,
92 SilenceMissingStdImports: true,
93 }
94 _, pkgs := modload.LoadPackages(ld, ctx, loadOpts, "all")
95
96 var vdir string
97 switch {
98 case filepath.IsAbs(vendorO):
99 vdir = vendorO
100 case vendorO != "":
101 vdir = filepath.Join(base.Cwd(), vendorO)
102 default:
103 vdir = filepath.Join(modload.VendorDir(ld))
104 }
105 if err := os.RemoveAll(vdir); err != nil {
106 base.Fatal(err)
107 }
108
109 modpkgs := make(map[module.Version][]string)
110 for _, pkg := range pkgs {
111 m := ld.PackageModule(pkg)
112 if m.Path == "" || ld.MainModules.Contains(m.Path) {
113 continue
114 }
115 modpkgs[m] = append(modpkgs[m], pkg)
116 }
117 checkPathCollisions(modpkgs)
118
119 includeAllReplacements := false
120 includeGoVersions := false
121 isExplicit := map[module.Version]bool{}
122 gv := ld.MainModules.GoVersion(ld)
123 if gover.Compare(gv, "1.14") >= 0 && (ld.FindGoWork(base.Cwd()) != "" || modload.ModFile(ld).Go != nil) {
124
125
126
127 for _, m := range ld.MainModules.Versions() {
128 if modFile := ld.MainModules.ModFile(m); modFile != nil {
129 for _, r := range modFile.Require {
130 isExplicit[r.Mod] = true
131 }
132 }
133 }
134 includeAllReplacements = true
135 }
136 if gover.Compare(gv, "1.17") >= 0 {
137
138
139 includeGoVersions = true
140 }
141
142 var vendorMods []module.Version
143 for m := range isExplicit {
144 vendorMods = append(vendorMods, m)
145 }
146 for m := range modpkgs {
147 if !isExplicit[m] {
148 vendorMods = append(vendorMods, m)
149 }
150 }
151 gover.ModSort(vendorMods)
152
153 var (
154 buf bytes.Buffer
155 w io.Writer = &buf
156 )
157 if cfg.BuildV {
158 w = io.MultiWriter(&buf, os.Stderr)
159 }
160
161 if ld.MainModules.WorkFile() != nil {
162 fmt.Fprintf(w, "## workspace\n")
163 }
164
165 replacementWritten := make(map[module.Version]bool)
166 for _, m := range vendorMods {
167 replacement := modload.Replacement(ld, m)
168 line := moduleLine(m, replacement)
169 replacementWritten[m] = true
170 io.WriteString(w, line)
171
172 goVersion := ""
173 if includeGoVersions {
174 goVersion = modload.ModuleInfo(ld, ctx, m.Path).GoVersion
175 }
176 switch {
177 case isExplicit[m] && goVersion != "":
178 fmt.Fprintf(w, "## explicit; go %s\n", goVersion)
179 case isExplicit[m]:
180 io.WriteString(w, "## explicit\n")
181 case goVersion != "":
182 fmt.Fprintf(w, "## go %s\n", goVersion)
183 }
184
185 pkgs := modpkgs[m]
186 sort.Strings(pkgs)
187 for _, pkg := range pkgs {
188 fmt.Fprintf(w, "%s\n", pkg)
189 vendorPkg(ld, vdir, pkg)
190 }
191 }
192
193 if includeAllReplacements {
194
195
196
197 for _, m := range ld.MainModules.Versions() {
198 if workFile := ld.MainModules.WorkFile(); workFile != nil {
199 for _, r := range workFile.Replace {
200 if replacementWritten[r.Old] {
201
202 continue
203 }
204 replacementWritten[r.Old] = true
205
206 line := moduleLine(r.Old, r.New)
207 buf.WriteString(line)
208 if cfg.BuildV {
209 os.Stderr.WriteString(line)
210 }
211 }
212 }
213 if modFile := ld.MainModules.ModFile(m); modFile != nil {
214 for _, r := range modFile.Replace {
215 if replacementWritten[r.Old] {
216
217 continue
218 }
219 replacementWritten[r.Old] = true
220 rNew := modload.Replacement(ld, r.Old)
221 if rNew == (module.Version{}) {
222
223 continue
224 }
225
226 line := moduleLine(r.Old, rNew)
227 buf.WriteString(line)
228 if cfg.BuildV {
229 os.Stderr.WriteString(line)
230 }
231 }
232 }
233 }
234 }
235
236 if buf.Len() == 0 {
237 fmt.Fprintf(os.Stderr, "go: no dependencies to vendor\n")
238 return
239 }
240
241 if err := os.MkdirAll(vdir, 0777); err != nil {
242 base.Fatal(err)
243 }
244
245 if err := os.WriteFile(filepath.Join(vdir, "modules.txt"), buf.Bytes(), 0666); err != nil {
246 base.Fatal(err)
247 }
248 }
249
250 func moduleLine(m, r module.Version) string {
251 b := new(strings.Builder)
252 b.WriteString("# ")
253 b.WriteString(m.Path)
254 if m.Version != "" {
255 b.WriteString(" ")
256 b.WriteString(m.Version)
257 }
258 if r.Path != "" {
259 if str.HasFilePathPrefix(filepath.Clean(r.Path), "vendor") {
260 base.Fatalf("go: replacement path %s inside vendor directory", r.Path)
261 }
262 b.WriteString(" => ")
263 b.WriteString(r.Path)
264 if r.Version != "" {
265 b.WriteString(" ")
266 b.WriteString(r.Version)
267 }
268 }
269 b.WriteString("\n")
270 return b.String()
271 }
272
273 func vendorPkg(s *modload.Loader, vdir, pkg string) {
274 src, realPath, _ := modload.Lookup(s, "", false, pkg)
275 if src == "" {
276 base.Errorf("internal error: no pkg for %s\n", pkg)
277 return
278 }
279 if realPath != pkg {
280
281
282
283
284
285
286
287
288 fmt.Fprintf(os.Stderr, "warning: %s imported as both %s and %s; making two copies.\n", realPath, realPath, pkg)
289 }
290
291 copiedFiles := make(map[string]bool)
292 dst := filepath.Join(vdir, pkg)
293 matcher := func(dir string, info fs.DirEntry) bool {
294 goVersion := s.MainModules.GoVersion(s)
295 return matchPotentialSourceFile(dir, info, goVersion)
296 }
297 copyDir(dst, src, matcher, copiedFiles)
298 if m := s.PackageModule(realPath); m.Path != "" {
299 copyMetadata(m.Path, realPath, dst, src, copiedFiles)
300 }
301
302 ctx := build.Default
303 ctx.UseAllFiles = true
304 bp, err := ctx.ImportDir(src, build.IgnoreVendor)
305
306
307
308
309
310
311
312
313
314 var multiplePackageError *build.MultiplePackageError
315 var noGoError *build.NoGoError
316 if err != nil {
317 if errors.As(err, &noGoError) {
318 return
319 } else if !errors.As(err, &multiplePackageError) {
320 base.Fatalf("internal error: failed to find embedded files of %s: %v\n", pkg, err)
321 }
322 }
323 var embedPatterns []string
324 if gover.Compare(s.MainModules.GoVersion(s), "1.22") >= 0 {
325 embedPatterns = bp.EmbedPatterns
326 } else {
327
328
329
330 embedPatterns = str.StringList(bp.EmbedPatterns, bp.TestEmbedPatterns, bp.XTestEmbedPatterns)
331 }
332 embeds, err := load.ResolveEmbed(bp.Dir, embedPatterns)
333 if err != nil {
334 format := "go: resolving embeds in %s: %v\n"
335 if vendorE {
336 fmt.Fprintf(os.Stderr, format, pkg, err)
337 } else {
338 base.Errorf(format, pkg, err)
339 }
340 return
341 }
342 for _, embed := range embeds {
343 embedDst := filepath.Join(dst, embed)
344 if copiedFiles[embedDst] {
345 continue
346 }
347
348
349 err := func() error {
350 r, err := os.Open(filepath.Join(src, embed))
351 if err != nil {
352 return err
353 }
354 if err := os.MkdirAll(filepath.Dir(embedDst), 0777); err != nil {
355 return err
356 }
357 w, err := os.Create(embedDst)
358 if err != nil {
359 return err
360 }
361 if _, err := io.Copy(w, r); err != nil {
362 return err
363 }
364 r.Close()
365 return w.Close()
366 }()
367 if err != nil {
368 if vendorE {
369 fmt.Fprintf(os.Stderr, "go: %v\n", err)
370 } else {
371 base.Error(err)
372 }
373 }
374 }
375 }
376
377 type metakey struct {
378 modPath string
379 dst string
380 }
381
382 var copiedMetadata = make(map[metakey]bool)
383
384
385
386 func copyMetadata(modPath, pkg, dst, src string, copiedFiles map[string]bool) {
387 for parent := 0; ; parent++ {
388 if copiedMetadata[metakey{modPath, dst}] {
389 break
390 }
391 copiedMetadata[metakey{modPath, dst}] = true
392 if parent > 0 {
393 copyDir(dst, src, matchMetadata, copiedFiles)
394 }
395 if modPath == pkg {
396 break
397 }
398 pkg = path.Dir(pkg)
399 dst = filepath.Dir(dst)
400 src = filepath.Dir(src)
401 }
402 }
403
404
405
406
407
408
409
410
411 var metaPrefixes = []string{
412 "AUTHORS",
413 "CONTRIBUTORS",
414 "COPYLEFT",
415 "COPYING",
416 "COPYRIGHT",
417 "LEGAL",
418 "LICENSE",
419 "NOTICE",
420 "PATENTS",
421 }
422
423
424 func matchMetadata(dir string, info fs.DirEntry) bool {
425 name := info.Name()
426 for _, p := range metaPrefixes {
427 if strings.HasPrefix(name, p) {
428 return true
429 }
430 }
431 return false
432 }
433
434
435 func matchPotentialSourceFile(dir string, info fs.DirEntry, goVersion string) bool {
436 if strings.HasSuffix(info.Name(), "_test.go") {
437 return false
438 }
439 if info.Name() == "go.mod" || info.Name() == "go.sum" {
440 if gover.Compare(goVersion, "1.17") >= 0 {
441
442
443
444
445 return false
446 }
447 }
448 if strings.HasSuffix(info.Name(), ".go") {
449 f, err := fsys.Open(filepath.Join(dir, info.Name()))
450 if err != nil {
451 base.Fatal(err)
452 }
453 defer f.Close()
454
455 content, err := imports.ReadImports(f, false, nil)
456 if err == nil && !imports.ShouldBuild(content, imports.AnyTags()) {
457
458
459 return false
460 }
461 return true
462 }
463
464
465
466 return true
467 }
468
469
470 func copyDir(dst, src string, match func(dir string, info fs.DirEntry) bool, copiedFiles map[string]bool) {
471 files, err := os.ReadDir(src)
472 if err != nil {
473 base.Fatal(err)
474 }
475 if err := os.MkdirAll(dst, 0777); err != nil {
476 base.Fatal(err)
477 }
478 for _, file := range files {
479 if file.IsDir() || !file.Type().IsRegular() || !match(src, file) {
480 continue
481 }
482 copiedFiles[file.Name()] = true
483 r, err := os.Open(filepath.Join(src, file.Name()))
484 if err != nil {
485 base.Fatal(err)
486 }
487 dstPath := filepath.Join(dst, file.Name())
488 copiedFiles[dstPath] = true
489 w, err := os.Create(dstPath)
490 if err != nil {
491 base.Fatal(err)
492 }
493 if _, err := io.Copy(w, r); err != nil {
494 base.Fatal(err)
495 }
496 r.Close()
497 if err := w.Close(); err != nil {
498 base.Fatal(err)
499 }
500 }
501 }
502
503
504
505
506
507 func checkPathCollisions(modpkgs map[module.Version][]string) {
508 foldPath := make(map[string]string, len(modpkgs))
509 for m := range modpkgs {
510 fold := str.ToFold(m.Path)
511 if other := foldPath[fold]; other == "" {
512 foldPath[fold] = m.Path
513 } else if other != m.Path {
514 base.Fatalf("go.mod: case-insensitive import collision: %q and %q", m.Path, other)
515 }
516 }
517 }
518
View as plain text