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