1 package modpkgload
2
3 import (
4 "context"
5 "errors"
6 "fmt"
7 "io/fs"
8 "path"
9 "path/filepath"
10 "slices"
11 "strings"
12
13 "cuelang.org/go/internal/mod/modrequirements"
14 "cuelang.org/go/mod/module"
15 )
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 func (pkgs *Packages) importFromModules(ctx context.Context, pkgPath string) (m module.Version, pkgLocs []module.SourceLoc, altMods []module.Version, err error) {
33 fail := func(err error) (module.Version, []module.SourceLoc, []module.Version, error) {
34 return module.Version{}, []module.SourceLoc(nil), nil, err
35 }
36 failf := func(format string, args ...interface{}) (module.Version, []module.SourceLoc, []module.Version, error) {
37 return fail(fmt.Errorf(format, args...))
38 }
39
40
41
42 pathParts := module.ParseImportPath(pkgPath)
43 pkgPathOnly := pathParts.Path
44
45 if filepath.IsAbs(pkgPathOnly) || path.IsAbs(pkgPathOnly) {
46 return failf("%q is not a package path", pkgPath)
47 }
48
49
50
51
52 if err := module.CheckImportPath(pkgPath); err != nil {
53 return fail(err)
54 }
55
56
57 var locs [][]module.SourceLoc
58 var mods []module.Version
59 var mg *modrequirements.ModuleGraph
60 localPkgLocs, err := pkgs.findLocalPackage(pkgPathOnly)
61 if err != nil {
62 return fail(err)
63 }
64 if len(localPkgLocs) > 0 {
65 mods = append(mods, module.MustNewVersion("local", ""))
66 locs = append(locs, localPkgLocs)
67 }
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82 for {
83 var altMods []module.Version
84
85
86 for prefix := pkgPathOnly; prefix != "."; prefix = path.Dir(prefix) {
87 var (
88 v string
89 ok bool
90 )
91 pkgVersion := pathParts.Version
92 if pkgVersion == "" {
93 if pkgVersion, _ = pkgs.requirements.DefaultMajorVersion(prefix); pkgVersion == "" {
94 continue
95 }
96 }
97 prefixPath := prefix + "@" + pkgVersion
98 if mg == nil {
99 v, ok = pkgs.requirements.RootSelected(prefixPath)
100 } else {
101 v, ok = mg.Selected(prefixPath), true
102 }
103 if !ok || v == "none" {
104 continue
105 }
106 m, err := module.NewVersion(prefixPath, v)
107 if err != nil {
108
109
110 continue
111 }
112 mloc, isLocal, err := pkgs.fetch(ctx, m)
113 if err != nil {
114
115
116
117
118
119
120 return fail(fmt.Errorf("cannot fetch %v: %v", m, err))
121 }
122 if loc, ok, err := locInModule(pkgPathOnly, prefix, mloc, isLocal); err != nil {
123 return fail(fmt.Errorf("cannot find package: %v", err))
124 } else if ok {
125 mods = append(mods, m)
126 locs = append(locs, []module.SourceLoc{loc})
127 } else {
128 altMods = append(altMods, m)
129 }
130 }
131
132 if len(mods) > 1 {
133
134
135
136 slices.Reverse(mods)
137 slices.Reverse(locs)
138 return fail(&AmbiguousImportError{ImportPath: pkgPath, Locations: locs, Modules: mods})
139 }
140
141 if len(mods) == 1 {
142
143 return mods[0], locs[0], altMods, nil
144 }
145
146 if mg != nil {
147
148
149 return fail(&ImportMissingError{Path: pkgPath})
150 }
151
152
153
154 mg, err = pkgs.requirements.Graph(ctx)
155 if err != nil {
156
157
158
159
160 return fail(fmt.Errorf("cannot expand module graph: %v", err))
161 }
162 }
163 }
164
165
166
167
168
169
170
171
172
173
174
175
176
177 func locInModule(pkgPath, mpath string, mloc module.SourceLoc, isLocal bool) (loc module.SourceLoc, haveCUEFiles bool, err error) {
178 loc.FS = mloc.FS
179
180
181 if pkgPath == mpath {
182 loc = mloc
183 } else if len(pkgPath) > len(mpath) && pkgPath[len(mpath)] == '/' && pkgPath[:len(mpath)] == mpath {
184 loc.Dir = path.Join(mloc.Dir, pkgPath[len(mpath)+1:])
185 } else {
186 return module.SourceLoc{}, false, nil
187 }
188
189
190
191
192
193 if isLocal {
194 for d := loc.Dir; d != mloc.Dir && len(d) > len(mloc.Dir); {
195 _, err := fs.Stat(mloc.FS, path.Join(d, "cue.mod/module.cue"))
196
197 haveCUEMod := err == nil
198 if haveCUEMod {
199 return module.SourceLoc{}, false, nil
200 }
201 parent := path.Dir(d)
202 if parent == d {
203
204
205 break
206 }
207 d = parent
208 }
209 }
210
211
212
213
214 haveCUEFiles, err = isDirWithCUEFiles(loc)
215 if err != nil {
216 return module.SourceLoc{}, false, err
217 }
218 return loc, haveCUEFiles, err
219 }
220
221 var localPkgDirs = []string{"cue.mod/gen", "cue.mod/usr", "cue.mod/pkg"}
222
223 func (pkgs *Packages) findLocalPackage(pkgPath string) ([]module.SourceLoc, error) {
224 var locs []module.SourceLoc
225 for _, d := range localPkgDirs {
226 loc := pkgs.mainModuleLoc
227 loc.Dir = path.Join(loc.Dir, d, pkgPath)
228 ok, err := isDirWithCUEFiles(loc)
229 if err != nil {
230 return nil, err
231 }
232 if ok {
233 locs = append(locs, loc)
234 }
235 }
236 return locs, nil
237 }
238
239 func isDirWithCUEFiles(loc module.SourceLoc) (bool, error) {
240
241
242
243
244 fi, err := fs.Stat(loc.FS, loc.Dir)
245 if err != nil {
246 if !errors.Is(err, fs.ErrNotExist) {
247 return false, err
248 }
249 return false, nil
250 }
251 if !fi.IsDir() {
252 return false, nil
253 }
254 entries, err := fs.ReadDir(loc.FS, loc.Dir)
255 if err != nil {
256 return false, err
257 }
258 for _, e := range entries {
259 if strings.HasSuffix(e.Name(), ".cue") && e.Type().IsRegular() {
260 return true, nil
261 }
262 }
263 return false, nil
264 }
265
266
267
268
269
270
271 func (pkgs *Packages) fetch(ctx context.Context, mod module.Version) (loc module.SourceLoc, isLocal bool, err error) {
272 if mod == pkgs.mainModuleVersion {
273 return pkgs.mainModuleLoc, true, nil
274 }
275
276 loc, err = pkgs.registry.Fetch(ctx, mod)
277 return loc, false, err
278 }
279
280
281
282
283 type AmbiguousImportError struct {
284 ImportPath string
285 Locations [][]module.SourceLoc
286 Modules []module.Version
287 }
288
289 func (e *AmbiguousImportError) Error() string {
290 locType := "modules"
291 if len(e.Modules) == 0 {
292 locType = "locations"
293 }
294
295 var buf strings.Builder
296 fmt.Fprintf(&buf, "ambiguous import: found package %s in multiple %s:", e.ImportPath, locType)
297
298 for i, loc := range e.Locations {
299 buf.WriteString("\n\t")
300 if i < len(e.Modules) {
301 m := e.Modules[i]
302 buf.WriteString(m.Path())
303 if m.Version() != "" {
304 fmt.Fprintf(&buf, " %s", m.Version())
305 }
306
307 fmt.Fprintf(&buf, " (%s)", loc[0].Dir)
308 } else {
309 buf.WriteString(loc[0].Dir)
310 }
311 }
312
313 return buf.String()
314 }
315
316
317 type ImportMissingError struct {
318 Path string
319 }
320
321 func (e *ImportMissingError) Error() string {
322 return "cannot find module providing package " + e.Path
323 }
324
View as plain text