Source file src/pkg/cmd/go/internal/modload/import.go
1
2
3
4
5 package modload
6
7 import (
8 "bytes"
9 "errors"
10 "fmt"
11 "go/build"
12 "internal/goroot"
13 "os"
14 "path/filepath"
15 "sort"
16 "strings"
17 "time"
18
19 "cmd/go/internal/cfg"
20 "cmd/go/internal/modfetch"
21 "cmd/go/internal/module"
22 "cmd/go/internal/par"
23 "cmd/go/internal/search"
24 "cmd/go/internal/semver"
25 "cmd/go/internal/str"
26 )
27
28 type ImportMissingError struct {
29 ImportPath string
30 Module module.Version
31
32
33
34 newMissingVersion string
35 }
36
37 func (e *ImportMissingError) Error() string {
38 if e.Module.Path == "" {
39 if str.HasPathPrefix(e.ImportPath, "cmd") {
40 return fmt.Sprintf("package %s is not in GOROOT (%s)", e.ImportPath, filepath.Join(cfg.GOROOT, "src", e.ImportPath))
41 }
42 return "cannot find module providing package " + e.ImportPath
43 }
44 return "missing module for import: " + e.Module.Path + "@" + e.Module.Version + " provides " + e.ImportPath
45 }
46
47
48
49
50
51
52
53
54
55
56
57
58 func Import(path string) (m module.Version, dir string, err error) {
59 if strings.Contains(path, "@") {
60 return module.Version{}, "", fmt.Errorf("import path should not have @version")
61 }
62 if build.IsLocalImport(path) {
63 return module.Version{}, "", fmt.Errorf("relative import not supported")
64 }
65 if path == "C" || path == "unsafe" {
66
67 return module.Version{}, "", nil
68 }
69
70
71 if search.IsStandardImportPath(path) &&
72 goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) {
73 if targetInGorootSrc {
74 if dir, ok := dirInModule(path, targetPrefix, ModRoot(), true); ok {
75 return Target, dir, nil
76 }
77 }
78 dir := filepath.Join(cfg.GOROOT, "src", path)
79 return module.Version{}, dir, nil
80 }
81 if str.HasPathPrefix(path, "cmd") {
82 return module.Version{}, "", &ImportMissingError{ImportPath: path}
83 }
84
85
86
87 if cfg.BuildMod == "vendor" {
88 mainDir, mainOK := dirInModule(path, targetPrefix, ModRoot(), true)
89 vendorDir, vendorOK := dirInModule(path, "", filepath.Join(ModRoot(), "vendor"), false)
90 if mainOK && vendorOK {
91 return module.Version{}, "", fmt.Errorf("ambiguous import: found %s in multiple directories:\n\t%s\n\t%s", path, mainDir, vendorDir)
92 }
93
94
95
96 if !vendorOK && mainDir != "" {
97 return Target, mainDir, nil
98 }
99 readVendorList()
100 return vendorMap[path], vendorDir, nil
101 }
102
103
104 var dirs []string
105 var mods []module.Version
106 for _, m := range buildList {
107 if !maybeInModule(path, m.Path) {
108
109 continue
110 }
111 root, isLocal, err := fetch(m)
112 if err != nil {
113
114
115
116
117
118
119 return module.Version{}, "", err
120 }
121 dir, ok := dirInModule(path, m.Path, root, isLocal)
122 if ok {
123 mods = append(mods, m)
124 dirs = append(dirs, dir)
125 }
126 }
127 if len(mods) == 1 {
128 return mods[0], dirs[0], nil
129 }
130 if len(mods) > 0 {
131 var buf bytes.Buffer
132 fmt.Fprintf(&buf, "ambiguous import: found %s in multiple modules:", path)
133 for i, m := range mods {
134 fmt.Fprintf(&buf, "\n\t%s", m.Path)
135 if m.Version != "" {
136 fmt.Fprintf(&buf, " %s", m.Version)
137 }
138 fmt.Fprintf(&buf, " (%s)", dirs[i])
139 }
140 return module.Version{}, "", errors.New(buf.String())
141 }
142
143
144
145 if cfg.BuildMod == "readonly" {
146 return module.Version{}, "", fmt.Errorf("import lookup disabled by -mod=%s", cfg.BuildMod)
147 }
148
149
150
151
152 if modFile != nil {
153 latest := map[string]string{}
154 for _, r := range modFile.Replace {
155 if maybeInModule(path, r.Old.Path) {
156 latest[r.Old.Path] = semver.Max(r.Old.Version, latest[r.Old.Path])
157 }
158 }
159
160 mods = make([]module.Version, 0, len(latest))
161 for p, v := range latest {
162
163
164
165
166
167 if v == "" {
168 if _, pathMajor, ok := module.SplitPathVersion(p); ok && len(pathMajor) > 0 {
169 v = modfetch.PseudoVersion(pathMajor[1:], "", time.Time{}, "000000000000")
170 } else {
171 v = modfetch.PseudoVersion("v0", "", time.Time{}, "000000000000")
172 }
173 }
174 mods = append(mods, module.Version{Path: p, Version: v})
175 }
176
177
178
179 sort.Slice(mods, func(i, j int) bool {
180 return len(mods[i].Path) > len(mods[j].Path)
181 })
182 for _, m := range mods {
183 root, isLocal, err := fetch(m)
184 if err != nil {
185
186 return module.Version{}, "", err
187 }
188 _, ok := dirInModule(path, m.Path, root, isLocal)
189 if ok {
190 return m, "", &ImportMissingError{ImportPath: path, Module: m}
191 }
192 }
193 }
194
195 candidates, err := QueryPackage(path, "latest", Allowed)
196 if err != nil {
197 if errors.Is(err, os.ErrNotExist) {
198
199
200 return module.Version{}, "", &ImportMissingError{ImportPath: path}
201 } else {
202 return module.Version{}, "", err
203 }
204 }
205 m = candidates[0].Mod
206 newMissingVersion := ""
207 for _, c := range candidates {
208 cm := c.Mod
209 for _, bm := range buildList {
210 if bm.Path == cm.Path && semver.Compare(bm.Version, cm.Version) > 0 {
211
212
213
214
215
216
217
218
219 m = cm
220 newMissingVersion = bm.Version
221 break
222 }
223 }
224 }
225 return m, "", &ImportMissingError{ImportPath: path, Module: m, newMissingVersion: newMissingVersion}
226 }
227
228
229
230
231 func maybeInModule(path, mpath string) bool {
232 return mpath == path ||
233 len(path) > len(mpath) && path[len(mpath)] == '/' && path[:len(mpath)] == mpath
234 }
235
236 var haveGoModCache, haveGoFilesCache par.Cache
237
238
239
240
241
242
243
244
245
246
247
248 func dirInModule(path, mpath, mdir string, isLocal bool) (dir string, haveGoFiles bool) {
249
250 if path == mpath {
251 dir = mdir
252 } else if mpath == "" {
253 dir = filepath.Join(mdir, path)
254 } else if len(path) > len(mpath) && path[len(mpath)] == '/' && path[:len(mpath)] == mpath {
255 dir = filepath.Join(mdir, path[len(mpath)+1:])
256 } else {
257 return "", false
258 }
259
260
261
262
263
264
265
266 if isLocal {
267 for d := dir; d != mdir && len(d) > len(mdir); {
268 haveGoMod := haveGoModCache.Do(d, func() interface{} {
269 fi, err := os.Stat(filepath.Join(d, "go.mod"))
270 return err == nil && !fi.IsDir()
271 }).(bool)
272
273 if haveGoMod {
274 return "", false
275 }
276 parent := filepath.Dir(d)
277 if parent == d {
278
279
280 break
281 }
282 d = parent
283 }
284 }
285
286
287
288
289
290
291 haveGoFiles = haveGoFilesCache.Do(dir, func() interface{} {
292 f, err := os.Open(dir)
293 if err != nil {
294 return false
295 }
296 defer f.Close()
297 names, _ := f.Readdirnames(-1)
298 for _, name := range names {
299 if strings.HasSuffix(name, ".go") {
300 info, err := os.Stat(filepath.Join(dir, name))
301 if err == nil && info.Mode().IsRegular() {
302 return true
303 }
304 }
305 }
306 return false
307 }).(bool)
308
309 return dir, haveGoFiles
310 }
311
View as plain text