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://golang.org/ref/mod#go-mod-vendor for more about 'go mod vendor'.
53 `,
54 Run: runVendor,
55 }
56
57 var vendorE bool
58 var vendorO string
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 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
122
123
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
136
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
193
194
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
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
215 continue
216 }
217 replacementWritten[r.Old] = true
218 rNew := modload.Replacement(r.Old)
219 if rNew == (module.Version{}) {
220
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
248 func 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
271 func 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
279
280
281
282
283
284
285
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
300
301
302
303
304
305
306
307
308 var multiplePackageError *build.MultiplePackageError
309 var noGoError *build.NoGoError
310 if err != nil {
311 if errors.As(err, &noGoError) {
312 return
313 } else if !errors.As(err, &multiplePackageError) {
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
322
323
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
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
371 type metakey struct {
372 modPath string
373 dst string
374 }
375
376 var copiedMetadata = make(map[metakey]bool)
377
378
379
380 func 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
399
400
401
402
403
404
405 var metaPrefixes = []string{
406 "AUTHORS",
407 "CONTRIBUTORS",
408 "COPYLEFT",
409 "COPYING",
410 "COPYRIGHT",
411 "LEGAL",
412 "LICENSE",
413 "NOTICE",
414 "PATENTS",
415 }
416
417
418 func 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
429 func 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
436
437
438
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
452
453 return false
454 }
455 return true
456 }
457
458
459
460 return true
461 }
462
463
464 func 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
498
499
500
501 func 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
View as plain text