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