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, vendorOK, _ := dirInModule(path, "", vendorDir, false)
342 if vendorOK {
343 readVendorList(vendorDir)
344
345
346
347
348 mods = append(mods, vendorPkgModule[path])
349 dirs = append(dirs, dir)
350 roots = append(roots, vendorDir)
351 }
352 }
353
354 if len(dirs) > 1 {
355 return module.Version{}, "", "", nil, &AmbiguousImportError{importPath: path, Dirs: dirs}
356 }
357
358 if mainErr != nil {
359 return module.Version{}, "", "", nil, mainErr
360 }
361
362 if len(dirs) == 0 {
363 return module.Version{}, "", "", nil, &ImportMissingError{Path: path}
364 }
365
366 return mods[0], roots[0], dirs[0], nil, nil
367 }
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382 for {
383 var sumErrMods, altMods []module.Version
384 for prefix := path; prefix != "."; prefix = pathpkg.Dir(prefix) {
385 if gover.IsToolchain(prefix) {
386
387 continue
388 }
389 var (
390 v string
391 ok bool
392 )
393 if mg == nil {
394 v, ok = rs.rootSelected(prefix)
395 } else {
396 v, ok = mg.Selected(prefix), true
397 }
398 if !ok || v == "none" {
399 continue
400 }
401 m := module.Version{Path: prefix, Version: v}
402
403 root, isLocal, err := fetch(ctx, m)
404 if err != nil {
405 if sumErr := (*sumMissingError)(nil); errors.As(err, &sumErr) {
406
407
408
409
410
411 sumErrMods = append(sumErrMods, m)
412 continue
413 }
414
415
416
417
418
419
420 return module.Version{}, "", "", nil, err
421 }
422 if dir, ok, err := dirInModule(path, m.Path, root, isLocal); err != nil {
423 return module.Version{}, "", "", nil, err
424 } else if ok {
425 mods = append(mods, m)
426 roots = append(roots, root)
427 dirs = append(dirs, dir)
428 } else {
429 altMods = append(altMods, m)
430 }
431 }
432
433 if len(mods) > 1 {
434
435
436
437 for i := 0; i < len(mods)/2; i++ {
438 j := len(mods) - 1 - i
439 mods[i], mods[j] = mods[j], mods[i]
440 roots[i], roots[j] = roots[j], roots[i]
441 dirs[i], dirs[j] = dirs[j], dirs[i]
442 }
443 return module.Version{}, "", "", nil, &AmbiguousImportError{importPath: path, Dirs: dirs, Modules: mods}
444 }
445
446 if len(sumErrMods) > 0 {
447 for i := 0; i < len(sumErrMods)/2; i++ {
448 j := len(sumErrMods) - 1 - i
449 sumErrMods[i], sumErrMods[j] = sumErrMods[j], sumErrMods[i]
450 }
451 return module.Version{}, "", "", nil, &ImportMissingSumError{
452 importPath: path,
453 mods: sumErrMods,
454 found: len(mods) > 0,
455 }
456 }
457
458 if len(mods) == 1 {
459
460
461
462
463
464
465
466 if !skipModFile && cfg.BuildMod != "vendor" && mods[0].Path != "" && !MainModules.Contains(mods[0].Path) {
467 if _, err := goModSummary(mods[0]); err != nil {
468 return module.Version{}, "", "", nil, err
469 }
470 }
471 return mods[0], roots[0], dirs[0], altMods, nil
472 }
473
474 if mg != nil {
475
476
477 var queryErr error
478 if !HasModRoot() {
479 queryErr = ErrNoModRoot
480 }
481 return module.Version{}, "", "", nil, &ImportMissingError{Path: path, QueryErr: queryErr, isStd: pathIsStd}
482 }
483
484
485
486 mg, err = rs.Graph(ctx)
487 if err != nil {
488
489
490
491
492 return module.Version{}, "", "", nil, err
493 }
494 }
495 }
496
497
498
499
500
501
502 func queryImport(ctx context.Context, path string, rs *Requirements) (module.Version, error) {
503
504
505 var mods []module.Version
506 if MainModules != nil {
507 for mp, mv := range MainModules.HighestReplaced() {
508 if !maybeInModule(path, mp) {
509 continue
510 }
511 if mv == "" {
512
513
514
515
516
517 if _, pathMajor, ok := module.SplitPathVersion(mp); ok && len(pathMajor) > 0 {
518 mv = module.ZeroPseudoVersion(pathMajor[1:])
519 } else {
520 mv = module.ZeroPseudoVersion("v0")
521 }
522 }
523 mg, err := rs.Graph(ctx)
524 if err != nil {
525 return module.Version{}, err
526 }
527 if gover.ModCompare(mp, mg.Selected(mp), mv) >= 0 {
528
529
530 continue
531 }
532 mods = append(mods, module.Version{Path: mp, Version: mv})
533 }
534 }
535
536
537
538 sort.Slice(mods, func(i, j int) bool {
539 return len(mods[i].Path) > len(mods[j].Path)
540 })
541 for _, m := range mods {
542 root, isLocal, err := fetch(ctx, m)
543 if err != nil {
544 if sumErr := (*sumMissingError)(nil); errors.As(err, &sumErr) {
545 return module.Version{}, &ImportMissingSumError{importPath: path}
546 }
547 return module.Version{}, err
548 }
549 if _, ok, err := dirInModule(path, m.Path, root, isLocal); err != nil {
550 return m, err
551 } else if ok {
552 if cfg.BuildMod == "readonly" {
553 return module.Version{}, &ImportMissingError{Path: path, replaced: m}
554 }
555 return m, nil
556 }
557 }
558 if len(mods) > 0 && module.CheckPath(path) != nil {
559
560
561
562 replacement := Replacement(mods[0])
563 return module.Version{}, &PackageNotInModuleError{
564 Mod: mods[0],
565 Query: "latest",
566 Pattern: path,
567 Replacement: replacement,
568 }
569 }
570
571 if search.IsStandardImportPath(path) {
572
573
574
575
576
577
578
579 return module.Version{}, &ImportMissingError{Path: path, isStd: true}
580 }
581
582 if (cfg.BuildMod == "readonly" || cfg.BuildMod == "vendor") && !allowMissingModuleImports {
583
584
585
586
587
588 var queryErr error
589 if cfg.BuildModExplicit {
590 queryErr = fmt.Errorf("import lookup disabled by -mod=%s", cfg.BuildMod)
591 } else if cfg.BuildModReason != "" {
592 queryErr = fmt.Errorf("import lookup disabled by -mod=%s\n\t(%s)", cfg.BuildMod, cfg.BuildModReason)
593 }
594 return module.Version{}, &ImportMissingError{Path: path, QueryErr: queryErr}
595 }
596
597
598
599
600 fmt.Fprintf(os.Stderr, "go: finding module for package %s\n", path)
601
602 mg, err := rs.Graph(ctx)
603 if err != nil {
604 return module.Version{}, err
605 }
606
607 candidates, err := QueryPackages(ctx, path, "latest", mg.Selected, CheckAllowed)
608 if err != nil {
609 if errors.Is(err, fs.ErrNotExist) {
610
611
612 return module.Version{}, &ImportMissingError{Path: path, QueryErr: err}
613 } else {
614 return module.Version{}, err
615 }
616 }
617
618 candidate0MissingVersion := ""
619 for i, c := range candidates {
620 if v := mg.Selected(c.Mod.Path); gover.ModCompare(c.Mod.Path, v, c.Mod.Version) > 0 {
621
622
623
624
625
626
627
628
629 if i == 0 {
630 candidate0MissingVersion = v
631 }
632 continue
633 }
634 return c.Mod, nil
635 }
636 return module.Version{}, &ImportMissingError{
637 Path: path,
638 Module: candidates[0].Mod,
639 newMissingVersion: candidate0MissingVersion,
640 }
641 }
642
643
644
645
646 func maybeInModule(path, mpath string) bool {
647 return mpath == path ||
648 len(path) > len(mpath) && path[len(mpath)] == '/' && path[:len(mpath)] == mpath
649 }
650
651 var (
652 haveGoModCache par.Cache[string, bool]
653 haveGoFilesCache par.ErrCache[string, bool]
654 )
655
656
657
658
659
660
661
662
663
664
665
666
667
668 func dirInModule(path, mpath, mdir string, isLocal bool) (dir string, haveGoFiles bool, err error) {
669
670 if path == mpath {
671 dir = mdir
672 } else if mpath == "" {
673 dir = filepath.Join(mdir, path)
674 } else if len(path) > len(mpath) && path[len(mpath)] == '/' && path[:len(mpath)] == mpath {
675 dir = filepath.Join(mdir, path[len(mpath)+1:])
676 } else {
677 return "", false, nil
678 }
679
680
681
682
683
684
685
686 if isLocal {
687 for d := dir; d != mdir && len(d) > len(mdir); {
688 haveGoMod := haveGoModCache.Do(d, func() bool {
689 fi, err := fsys.Stat(filepath.Join(d, "go.mod"))
690 return err == nil && !fi.IsDir()
691 })
692
693 if haveGoMod {
694 return "", false, nil
695 }
696 parent := filepath.Dir(d)
697 if parent == d {
698
699
700 break
701 }
702 d = parent
703 }
704 }
705
706
707
708
709
710
711 haveGoFiles, err = haveGoFilesCache.Do(dir, func() (bool, error) {
712
713
714
715 if ip, err := modindex.GetPackage(mdir, dir); err == nil {
716 return ip.IsDirWithGoFiles()
717 } else if !errors.Is(err, modindex.ErrNotIndexed) {
718 return false, err
719 }
720 return fsys.IsDirWithGoFiles(dir)
721 })
722
723 return dir, haveGoFiles, err
724 }
725
726
727
728
729
730
731 func fetch(ctx context.Context, mod module.Version) (dir string, isLocal bool, err error) {
732 if modRoot := MainModules.ModRoot(mod); modRoot != "" {
733 return modRoot, true, nil
734 }
735 if r := Replacement(mod); r.Path != "" {
736 if r.Version == "" {
737 dir = r.Path
738 if !filepath.IsAbs(dir) {
739 dir = filepath.Join(replaceRelativeTo(), dir)
740 }
741
742
743
744
745 if _, err := fsys.Stat(dir); err != nil {
746
747
748
749 if os.IsNotExist(err) {
750
751
752
753 err = fmt.Errorf("replacement directory %s does not exist", r.Path)
754 } else {
755 err = fmt.Errorf("replacement directory %s: %w", r.Path, err)
756 }
757 return dir, true, module.VersionError(mod, err)
758 }
759 return dir, true, nil
760 }
761 mod = r
762 }
763
764 if mustHaveSums() && !modfetch.HaveSum(mod) {
765 return "", false, module.VersionError(mod, &sumMissingError{})
766 }
767
768 dir, err = modfetch.Download(ctx, mod)
769 return dir, false, err
770 }
771
772
773
774 func mustHaveSums() bool {
775 return HasModRoot() && cfg.BuildMod == "readonly" && !inWorkspaceMode()
776 }
777
778 type sumMissingError struct {
779 suggestion string
780 }
781
782 func (e *sumMissingError) Error() string {
783 return "missing go.sum entry" + e.suggestion
784 }
785
View as plain text