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