1
2
3
4
5 package modload
6
7 import (
8 "context"
9 "errors"
10 "fmt"
11 "go/build"
12 "io/fs"
13 "os"
14 pathpkg "path"
15 "path/filepath"
16 "sort"
17 "strings"
18
19 "cmd/go/internal/cfg"
20 "cmd/go/internal/fsys"
21 "cmd/go/internal/gover"
22 "cmd/go/internal/modfetch"
23 "cmd/go/internal/modindex"
24 "cmd/go/internal/par"
25 "cmd/go/internal/search"
26 "cmd/go/internal/str"
27
28 "golang.org/x/mod/module"
29 )
30
31 type ImportMissingError struct {
32 Path string
33 Module module.Version
34 QueryErr error
35
36 ImportingMainModule module.Version
37
38
39
40
41
42 isStd bool
43
44
45
46 importerGoVersion string
47
48
49
50 replaced module.Version
51
52
53
54 newMissingVersion string
55 }
56
57 func (e *ImportMissingError) Error() string {
58 if e.Module.Path == "" {
59 if e.isStd {
60 msg := fmt.Sprintf("package %s is not in std (%s)", e.Path, filepath.Join(cfg.GOROOT, "src", e.Path))
61 if e.importerGoVersion != "" {
62 msg += fmt.Sprintf("\nnote: imported by a module that requires go %s", e.importerGoVersion)
63 }
64 return msg
65 }
66 if e.QueryErr != nil && e.QueryErr != ErrNoModRoot {
67 return fmt.Sprintf("cannot find module providing package %s: %v", e.Path, e.QueryErr)
68 }
69 if cfg.BuildMod == "mod" || (cfg.BuildMod == "readonly" && allowMissingModuleImports) {
70 return "cannot find module providing package " + e.Path
71 }
72
73 if e.replaced.Path != "" {
74 suggestArg := e.replaced.Path
75 if !module.IsZeroPseudoVersion(e.replaced.Version) {
76 suggestArg = e.replaced.String()
77 }
78 return fmt.Sprintf("module %s provides package %s and is replaced but not required; to add it:\n\tgo get %s", e.replaced.Path, e.Path, suggestArg)
79 }
80
81 message := fmt.Sprintf("no required module provides package %s", e.Path)
82 if e.QueryErr != nil {
83 return fmt.Sprintf("%s: %v", message, e.QueryErr)
84 }
85 if e.ImportingMainModule.Path != "" && e.ImportingMainModule != MainModules.ModContainingCWD() {
86 return fmt.Sprintf("%s; to add it:\n\tcd %s\n\tgo get %s", message, MainModules.ModRoot(e.ImportingMainModule), e.Path)
87 }
88 return fmt.Sprintf("%s; to add it:\n\tgo get %s", message, e.Path)
89 }
90
91 if e.newMissingVersion != "" {
92 return fmt.Sprintf("package %s provided by %s at latest version %s but not at required version %s", e.Path, e.Module.Path, e.Module.Version, e.newMissingVersion)
93 }
94
95 return fmt.Sprintf("missing module for import: %s@%s provides %s", e.Module.Path, e.Module.Version, e.Path)
96 }
97
98 func (e *ImportMissingError) Unwrap() error {
99 return e.QueryErr
100 }
101
102 func (e *ImportMissingError) ImportPath() string {
103 return e.Path
104 }
105
106
107
108
109 type AmbiguousImportError struct {
110 importPath string
111 Dirs []string
112 Modules []module.Version
113 }
114
115 func (e *AmbiguousImportError) ImportPath() string {
116 return e.importPath
117 }
118
119 func (e *AmbiguousImportError) Error() string {
120 locType := "modules"
121 if len(e.Modules) == 0 {
122 locType = "directories"
123 }
124
125 var buf strings.Builder
126 fmt.Fprintf(&buf, "ambiguous import: found package %s in multiple %s:", e.importPath, locType)
127
128 for i, dir := range e.Dirs {
129 buf.WriteString("\n\t")
130 if i < len(e.Modules) {
131 m := e.Modules[i]
132 buf.WriteString(m.Path)
133 if m.Version != "" {
134 fmt.Fprintf(&buf, " %s", m.Version)
135 }
136 fmt.Fprintf(&buf, " (%s)", dir)
137 } else {
138 buf.WriteString(dir)
139 }
140 }
141
142 return buf.String()
143 }
144
145
146
147
148 type DirectImportFromImplicitDependencyError struct {
149 ImporterPath string
150 ImportedPath string
151 Module module.Version
152 }
153
154 func (e *DirectImportFromImplicitDependencyError) Error() string {
155 return fmt.Sprintf("package %s imports %s from implicitly required module; to add missing requirements, run:\n\tgo get %s@%s", e.ImporterPath, e.ImportedPath, e.Module.Path, e.Module.Version)
156 }
157
158 func (e *DirectImportFromImplicitDependencyError) ImportPath() string {
159 return e.ImporterPath
160 }
161
162
163
164
165
166
167
168
169
170
171 type ImportMissingSumError struct {
172 importPath string
173 found bool
174 mods []module.Version
175 importer, importerVersion string
176 importerIsTest bool
177 }
178
179 func (e *ImportMissingSumError) Error() string {
180 var importParen string
181 if e.importer != "" {
182 importParen = fmt.Sprintf(" (imported by %s)", e.importer)
183 }
184 var message string
185 if e.found {
186 message = fmt.Sprintf("missing go.sum entry needed to verify package %s%s is provided by exactly one module", e.importPath, importParen)
187 } else {
188 message = fmt.Sprintf("missing go.sum entry for module providing package %s%s", e.importPath, importParen)
189 }
190 var hint string
191 if e.importer == "" {
192
193
194
195 if len(e.mods) > 0 {
196 args := make([]string, len(e.mods))
197 for i, mod := range e.mods {
198 args[i] = mod.Path
199 }
200 hint = fmt.Sprintf("; to add:\n\tgo mod download %s", strings.Join(args, " "))
201 }
202 } else {
203
204
205 tFlag := ""
206 if e.importerIsTest {
207 tFlag = " -t"
208 }
209 version := ""
210 if e.importerVersion != "" {
211 version = "@" + e.importerVersion
212 }
213 hint = fmt.Sprintf("; to add:\n\tgo get%s %s%s", tFlag, e.importer, version)
214 }
215 return message + hint
216 }
217
218 func (e *ImportMissingSumError) ImportPath() string {
219 return e.importPath
220 }
221
222 type invalidImportError struct {
223 importPath string
224 err error
225 }
226
227 func (e *invalidImportError) ImportPath() string {
228 return e.importPath
229 }
230
231 func (e *invalidImportError) Error() string {
232 return e.err.Error()
233 }
234
235 func (e *invalidImportError) Unwrap() error {
236 return e.err
237 }
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265 func importFromModules(ctx context.Context, path string, rs *Requirements, mg *ModuleGraph, skipModFile bool) (m module.Version, modroot, dir string, altMods []module.Version, err error) {
266 invalidf := func(format string, args ...interface{}) (module.Version, string, string, []module.Version, error) {
267 return module.Version{}, "", "", nil, &invalidImportError{
268 importPath: path,
269 err: fmt.Errorf(format, args...),
270 }
271 }
272
273 if strings.Contains(path, "@") {
274 return invalidf("import path %q should not have @version", path)
275 }
276 if build.IsLocalImport(path) {
277 return invalidf("%q is relative, but relative import paths are not supported in module mode", path)
278 }
279 if filepath.IsAbs(path) {
280 return invalidf("%q is not a package path; see 'go help packages'", path)
281 }
282 if search.IsMetaPackage(path) {
283 return invalidf("%q is not an importable package; see 'go help packages'", path)
284 }
285
286 if path == "C" {
287
288 return module.Version{}, "", "", nil, nil
289 }
290
291 if err := module.CheckImportPath(path); err != nil {
292 return module.Version{}, "", "", nil, &invalidImportError{importPath: path, err: err}
293 }
294
295
296 var dirs, roots []string
297 var mods []module.Version
298
299
300 pathIsStd := search.IsStandardImportPath(path)
301 if pathIsStd && modindex.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) {
302 for _, mainModule := range MainModules.Versions() {
303 if MainModules.InGorootSrc(mainModule) {
304 if dir, ok, err := dirInModule(path, MainModules.PathPrefix(mainModule), MainModules.ModRoot(mainModule), true); err != nil {
305 return module.Version{}, MainModules.ModRoot(mainModule), dir, nil, err
306 } else if ok {
307 return mainModule, MainModules.ModRoot(mainModule), dir, nil, nil
308 }
309 }
310 }
311 dir := filepath.Join(cfg.GOROOTsrc, path)
312 modroot = cfg.GOROOTsrc
313 if str.HasPathPrefix(path, "cmd") {
314 modroot = filepath.Join(cfg.GOROOTsrc, "cmd")
315 }
316 dirs = append(dirs, dir)
317 roots = append(roots, modroot)
318 mods = append(mods, module.Version{})
319 }
320
321
322 if cfg.BuildMod == "vendor" {
323 var mainErr error
324 for _, mainModule := range MainModules.Versions() {
325 modRoot := MainModules.ModRoot(mainModule)
326 if modRoot != "" {
327 dir, mainOK, err := dirInModule(path, MainModules.PathPrefix(mainModule), modRoot, true)
328 if mainErr == nil {
329 mainErr = err
330 }
331 if mainOK {
332 mods = append(mods, mainModule)
333 dirs = append(dirs, dir)
334 roots = append(roots, modRoot)
335 }
336 }
337 }
338
339 if HasModRoot() {
340 vendorDir := VendorDir()
341 dir, inVendorDir, _ := dirInModule(path, "", vendorDir, false)
342 if inVendorDir {
343 readVendorList(vendorDir)
344
345
346
347 _, ok := vendorPkgModule[path]
348 if ok || (gover.Compare(MainModules.GoVersion(), gover.ExplicitModulesTxtImportVersion) < 0) {
349 mods = append(mods, vendorPkgModule[path])
350 dirs = append(dirs, dir)
351 roots = append(roots, vendorDir)
352 } else {
353 subCommand := "mod"
354 if inWorkspaceMode() {
355 subCommand = "work"
356 }
357 fmt.Fprintf(os.Stderr, "go: ignoring package %s which exists in the vendor directory but is missing from vendor/modules.txt. To sync the vendor directory run go %s vendor.\n", path, subCommand)
358 }
359 }
360 }
361
362 if len(dirs) > 1 {
363 return module.Version{}, "", "", nil, &AmbiguousImportError{importPath: path, Dirs: dirs}
364 }
365
366 if mainErr != nil {
367 return module.Version{}, "", "", nil, mainErr
368 }
369
370 if len(mods) == 0 {
371 return module.Version{}, "", "", nil, &ImportMissingError{Path: path}
372 }
373
374 return mods[0], roots[0], dirs[0], nil, nil
375 }
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390 for {
391 var sumErrMods, altMods []module.Version
392 for prefix := path; prefix != "."; prefix = pathpkg.Dir(prefix) {
393 if gover.IsToolchain(prefix) {
394
395 continue
396 }
397 var (
398 v string
399 ok bool
400 )
401 if mg == nil {
402 v, ok = rs.rootSelected(prefix)
403 } else {
404 v, ok = mg.Selected(prefix), true
405 }
406 if !ok || v == "none" {
407 continue
408 }
409 m := module.Version{Path: prefix, Version: v}
410
411 root, isLocal, err := fetch(ctx, m)
412 if err != nil {
413 if sumErr := (*sumMissingError)(nil); errors.As(err, &sumErr) {
414
415
416
417
418
419 sumErrMods = append(sumErrMods, m)
420 continue
421 }
422
423
424
425
426
427
428 return module.Version{}, "", "", nil, err
429 }
430 if dir, ok, err := dirInModule(path, m.Path, root, isLocal); err != nil {
431 return module.Version{}, "", "", nil, err
432 } else if ok {
433 mods = append(mods, m)
434 roots = append(roots, root)
435 dirs = append(dirs, dir)
436 } else {
437 altMods = append(altMods, m)
438 }
439 }
440
441 if len(mods) > 1 {
442
443
444
445 for i := 0; i < len(mods)/2; i++ {
446 j := len(mods) - 1 - i
447 mods[i], mods[j] = mods[j], mods[i]
448 roots[i], roots[j] = roots[j], roots[i]
449 dirs[i], dirs[j] = dirs[j], dirs[i]
450 }
451 return module.Version{}, "", "", nil, &AmbiguousImportError{importPath: path, Dirs: dirs, Modules: mods}
452 }
453
454 if len(sumErrMods) > 0 {
455 for i := 0; i < len(sumErrMods)/2; i++ {
456 j := len(sumErrMods) - 1 - i
457 sumErrMods[i], sumErrMods[j] = sumErrMods[j], sumErrMods[i]
458 }
459 return module.Version{}, "", "", nil, &ImportMissingSumError{
460 importPath: path,
461 mods: sumErrMods,
462 found: len(mods) > 0,
463 }
464 }
465
466 if len(mods) == 1 {
467
468
469
470
471
472
473
474 if !skipModFile && cfg.BuildMod != "vendor" && mods[0].Path != "" && !MainModules.Contains(mods[0].Path) {
475 if _, err := goModSummary(mods[0]); err != nil {
476 return module.Version{}, "", "", nil, err
477 }
478 }
479 return mods[0], roots[0], dirs[0], altMods, nil
480 }
481
482 if mg != nil {
483
484
485 var queryErr error
486 if !HasModRoot() {
487 queryErr = ErrNoModRoot
488 }
489 return module.Version{}, "", "", nil, &ImportMissingError{Path: path, QueryErr: queryErr, isStd: pathIsStd}
490 }
491
492
493
494 mg, err = rs.Graph(ctx)
495 if err != nil {
496
497
498
499
500 return module.Version{}, "", "", nil, err
501 }
502 }
503 }
504
505
506
507
508
509
510 func queryImport(ctx context.Context, path string, rs *Requirements) (module.Version, error) {
511
512
513 var mods []module.Version
514 if MainModules != nil {
515 for mp, mv := range MainModules.HighestReplaced() {
516 if !maybeInModule(path, mp) {
517 continue
518 }
519 if mv == "" {
520
521
522
523
524
525 if _, pathMajor, ok := module.SplitPathVersion(mp); ok && len(pathMajor) > 0 {
526 mv = module.ZeroPseudoVersion(pathMajor[1:])
527 } else {
528 mv = module.ZeroPseudoVersion("v0")
529 }
530 }
531 mg, err := rs.Graph(ctx)
532 if err != nil {
533 return module.Version{}, err
534 }
535 if gover.ModCompare(mp, mg.Selected(mp), mv) >= 0 {
536
537
538 continue
539 }
540 mods = append(mods, module.Version{Path: mp, Version: mv})
541 }
542 }
543
544
545
546 sort.Slice(mods, func(i, j int) bool {
547 return len(mods[i].Path) > len(mods[j].Path)
548 })
549 for _, m := range mods {
550 root, isLocal, err := fetch(ctx, m)
551 if err != nil {
552 if sumErr := (*sumMissingError)(nil); errors.As(err, &sumErr) {
553 return module.Version{}, &ImportMissingSumError{importPath: path}
554 }
555 return module.Version{}, err
556 }
557 if _, ok, err := dirInModule(path, m.Path, root, isLocal); err != nil {
558 return m, err
559 } else if ok {
560 if cfg.BuildMod == "readonly" {
561 return module.Version{}, &ImportMissingError{Path: path, replaced: m}
562 }
563 return m, nil
564 }
565 }
566 if len(mods) > 0 && module.CheckPath(path) != nil {
567
568
569
570 replacement := Replacement(mods[0])
571 return module.Version{}, &PackageNotInModuleError{
572 Mod: mods[0],
573 Query: "latest",
574 Pattern: path,
575 Replacement: replacement,
576 }
577 }
578
579 if search.IsStandardImportPath(path) {
580
581
582
583
584
585
586
587 return module.Version{}, &ImportMissingError{Path: path, isStd: true}
588 }
589
590 if (cfg.BuildMod == "readonly" || cfg.BuildMod == "vendor") && !allowMissingModuleImports {
591
592
593
594
595
596 var queryErr error
597 if cfg.BuildModExplicit {
598 queryErr = fmt.Errorf("import lookup disabled by -mod=%s", cfg.BuildMod)
599 } else if cfg.BuildModReason != "" {
600 queryErr = fmt.Errorf("import lookup disabled by -mod=%s\n\t(%s)", cfg.BuildMod, cfg.BuildModReason)
601 }
602 return module.Version{}, &ImportMissingError{Path: path, QueryErr: queryErr}
603 }
604
605
606
607
608 fmt.Fprintf(os.Stderr, "go: finding module for package %s\n", path)
609
610 mg, err := rs.Graph(ctx)
611 if err != nil {
612 return module.Version{}, err
613 }
614
615 candidates, err := QueryPackages(ctx, path, "latest", mg.Selected, CheckAllowed)
616 if err != nil {
617 if errors.Is(err, fs.ErrNotExist) {
618
619
620 return module.Version{}, &ImportMissingError{Path: path, QueryErr: err}
621 } else {
622 return module.Version{}, err
623 }
624 }
625
626 candidate0MissingVersion := ""
627 for i, c := range candidates {
628 if v := mg.Selected(c.Mod.Path); gover.ModCompare(c.Mod.Path, v, c.Mod.Version) > 0 {
629
630
631
632
633
634
635
636
637 if i == 0 {
638 candidate0MissingVersion = v
639 }
640 continue
641 }
642 return c.Mod, nil
643 }
644 return module.Version{}, &ImportMissingError{
645 Path: path,
646 Module: candidates[0].Mod,
647 newMissingVersion: candidate0MissingVersion,
648 }
649 }
650
651
652
653
654 func maybeInModule(path, mpath string) bool {
655 return mpath == path ||
656 len(path) > len(mpath) && path[len(mpath)] == '/' && path[:len(mpath)] == mpath
657 }
658
659 var (
660 haveGoModCache par.Cache[string, bool]
661 haveGoFilesCache par.ErrCache[string, bool]
662 )
663
664
665
666
667
668
669
670
671
672
673
674
675
676 func dirInModule(path, mpath, mdir string, isLocal bool) (dir string, haveGoFiles bool, err error) {
677
678 if path == mpath {
679 dir = mdir
680 } else if mpath == "" {
681 dir = filepath.Join(mdir, path)
682 } else if len(path) > len(mpath) && path[len(mpath)] == '/' && path[:len(mpath)] == mpath {
683 dir = filepath.Join(mdir, path[len(mpath)+1:])
684 } else {
685 return "", false, nil
686 }
687
688
689
690
691
692
693
694 if isLocal {
695 for d := dir; d != mdir && len(d) > len(mdir); {
696 haveGoMod := haveGoModCache.Do(d, func() bool {
697 fi, err := fsys.Stat(filepath.Join(d, "go.mod"))
698 return err == nil && !fi.IsDir()
699 })
700
701 if haveGoMod {
702 return "", false, nil
703 }
704 parent := filepath.Dir(d)
705 if parent == d {
706
707
708 break
709 }
710 d = parent
711 }
712 }
713
714
715
716
717
718
719 haveGoFiles, err = haveGoFilesCache.Do(dir, func() (bool, error) {
720
721
722
723 if ip, err := modindex.GetPackage(mdir, dir); err == nil {
724 return ip.IsDirWithGoFiles()
725 } else if !errors.Is(err, modindex.ErrNotIndexed) {
726 return false, err
727 }
728 return fsys.IsDirWithGoFiles(dir)
729 })
730
731 return dir, haveGoFiles, err
732 }
733
734
735
736
737
738
739 func fetch(ctx context.Context, mod module.Version) (dir string, isLocal bool, err error) {
740 if modRoot := MainModules.ModRoot(mod); modRoot != "" {
741 return modRoot, true, nil
742 }
743 if r := Replacement(mod); r.Path != "" {
744 if r.Version == "" {
745 dir = r.Path
746 if !filepath.IsAbs(dir) {
747 dir = filepath.Join(replaceRelativeTo(), dir)
748 }
749
750
751
752
753 if _, err := fsys.Stat(dir); err != nil {
754
755
756
757 if os.IsNotExist(err) {
758
759
760
761 err = fmt.Errorf("replacement directory %s does not exist", r.Path)
762 } else {
763 err = fmt.Errorf("replacement directory %s: %w", r.Path, err)
764 }
765 return dir, true, module.VersionError(mod, err)
766 }
767 return dir, true, nil
768 }
769 mod = r
770 }
771
772 if mustHaveSums() && !modfetch.HaveSum(mod) {
773 return "", false, module.VersionError(mod, &sumMissingError{})
774 }
775
776 dir, err = modfetch.Download(ctx, mod)
777 return dir, false, err
778 }
779
780
781
782 func mustHaveSums() bool {
783 return HasModRoot() && cfg.BuildMod == "readonly" && !inWorkspaceMode()
784 }
785
786 type sumMissingError struct {
787 suggestion string
788 }
789
790 func (e *sumMissingError) Error() string {
791 return "missing go.sum entry" + e.suggestion
792 }
793
View as plain text