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