1 package modpkgload
2
3 import (
4 "context"
5 "fmt"
6 "runtime"
7 "slices"
8 "sort"
9 "strings"
10 "sync/atomic"
11
12 "cuelang.org/go/internal/mod/modimports"
13 "cuelang.org/go/internal/mod/modrequirements"
14 "cuelang.org/go/internal/par"
15 "cuelang.org/go/mod/module"
16 )
17
18
19 type Registry interface {
20
21
22 Fetch(ctx context.Context, m module.Version) (module.SourceLoc, error)
23 }
24
25
26 type Flags int8
27
28 const (
29
30
31
32
33
34
35 PkgInAll Flags = 1 << iota
36
37
38
39 PkgIsRoot
40
41
42
43
44 PkgFromRoot
45
46
47
48 PkgImportsLoaded
49 )
50
51 func (f Flags) String() string {
52 var buf strings.Builder
53 set := func(f1 Flags, s string) {
54 if (f & f1) == 0 {
55 return
56 }
57 if buf.Len() > 0 {
58 buf.WriteString(",")
59 }
60 buf.WriteString(s)
61 f &^= f1
62 }
63 set(PkgInAll, "inAll")
64 set(PkgIsRoot, "isRoot")
65 set(PkgFromRoot, "fromRoot")
66 set(PkgImportsLoaded, "importsLoaded")
67 if f != 0 {
68 set(f, fmt.Sprintf("extra%x", int(f)))
69 }
70 return buf.String()
71 }
72
73
74 func (f Flags) has(cond Flags) bool {
75 return f&cond == cond
76 }
77
78 type Packages struct {
79 mainModuleVersion module.Version
80 mainModuleLoc module.SourceLoc
81 pkgCache par.Cache[string, *Package]
82 pkgs []*Package
83 rootPkgs []*Package
84 work *par.Queue
85 requirements *modrequirements.Requirements
86 registry Registry
87 }
88
89 type Package struct {
90
91 path string
92
93
94 flags atomicLoadPkgFlags
95
96
97 mod module.Version
98 locs []module.SourceLoc
99 err error
100 imports []*Package
101 inStd bool
102 fromExternal bool
103 altMods []module.Version
104
105
106 stack *Package
107 }
108
109 func (pkg *Package) ImportPath() string {
110 return pkg.path
111 }
112
113 func (pkg *Package) FromExternalModule() bool {
114 return pkg.fromExternal
115 }
116
117 func (pkg *Package) Locations() []module.SourceLoc {
118 return pkg.locs
119 }
120
121 func (pkg *Package) Error() error {
122 return pkg.err
123 }
124
125 func (pkg *Package) SetError(err error) {
126 pkg.err = err
127 }
128
129 func (pkg *Package) HasFlags(flags Flags) bool {
130 return pkg.flags.has(flags)
131 }
132
133 func (pkg *Package) Imports() []*Package {
134 return pkg.imports
135 }
136
137 func (pkg *Package) Flags() Flags {
138 return pkg.flags.get()
139 }
140
141 func (pkg *Package) Mod() module.Version {
142 return pkg.mod
143 }
144
145
146
147
148
149 func LoadPackages(
150 ctx context.Context,
151 mainModulePath string,
152 mainModuleLoc module.SourceLoc,
153 rs *modrequirements.Requirements,
154 reg Registry,
155 rootPkgPaths []string,
156 ) *Packages {
157 pkgs := &Packages{
158 mainModuleVersion: module.MustNewVersion(mainModulePath, ""),
159 mainModuleLoc: mainModuleLoc,
160 requirements: rs,
161 registry: reg,
162 work: par.NewQueue(runtime.GOMAXPROCS(0)),
163 }
164 inRoots := map[*Package]bool{}
165 pkgs.rootPkgs = make([]*Package, 0, len(rootPkgPaths))
166 for _, p := range rootPkgPaths {
167
168
169 if root := pkgs.addPkg(ctx, p, PkgIsRoot|PkgInAll); !inRoots[root] {
170 pkgs.rootPkgs = append(pkgs.rootPkgs, root)
171 inRoots[root] = true
172 }
173 }
174 <-pkgs.work.Idle()
175 pkgs.buildStacks()
176 return pkgs
177 }
178
179
180
181
182
183
184
185
186 func (pkgs *Packages) buildStacks() {
187 for _, pkg := range pkgs.rootPkgs {
188 pkg.stack = pkg
189 pkgs.pkgs = append(pkgs.pkgs, pkg)
190 }
191 for i := 0; i < len(pkgs.pkgs); i++ {
192 pkg := pkgs.pkgs[i]
193 for _, next := range pkg.imports {
194 if next.stack == nil {
195 next.stack = pkg
196 pkgs.pkgs = append(pkgs.pkgs, next)
197 }
198 }
199 }
200 for _, pkg := range pkgs.rootPkgs {
201 pkg.stack = nil
202 }
203 }
204
205 func (pkgs *Packages) Roots() []*Package {
206 return slices.Clip(pkgs.rootPkgs)
207 }
208
209 func (pkgs *Packages) All() []*Package {
210 return slices.Clip(pkgs.pkgs)
211 }
212
213 func (pkgs *Packages) Pkg(pkgPath string) *Package {
214 pkg, _ := pkgs.pkgCache.Get(pkgPath)
215 return pkg
216 }
217
218 func (pkgs *Packages) addPkg(ctx context.Context, pkgPath string, flags Flags) *Package {
219 pkg := pkgs.pkgCache.Do(pkgPath, func() *Package {
220 pkg := &Package{
221 path: pkgPath,
222 }
223 pkgs.applyPkgFlags(ctx, pkg, flags)
224
225 pkgs.work.Add(func() { pkgs.load(ctx, pkg) })
226 return pkg
227 })
228
229
230 pkgs.applyPkgFlags(ctx, pkg, flags)
231 return pkg
232 }
233
234 func (pkgs *Packages) load(ctx context.Context, pkg *Package) {
235 if IsStdlibPackage(pkg.path) {
236 pkg.inStd = true
237 return
238 }
239 pkg.fromExternal = pkg.mod != pkgs.mainModuleVersion
240 pkg.mod, pkg.locs, pkg.altMods, pkg.err = pkgs.importFromModules(ctx, pkg.path)
241 if pkg.err != nil {
242 return
243 }
244 if pkgs.mainModuleVersion.Path() == pkg.mod.Path() {
245 pkgs.applyPkgFlags(ctx, pkg, PkgInAll)
246 }
247 pkgQual := module.ParseImportPath(pkg.path).Qualifier
248 importsMap := make(map[string]bool)
249 for _, loc := range pkg.locs {
250 imports, err := modimports.AllImports(modimports.PackageFiles(loc.FS, loc.Dir, pkgQual))
251 if err != nil {
252 pkg.err = fmt.Errorf("cannot get imports: %v", err)
253 return
254 }
255 for _, imp := range imports {
256 importsMap[imp] = true
257 }
258 }
259 imports := make([]string, 0, len(importsMap))
260 for imp := range importsMap {
261 imports = append(imports, imp)
262 }
263 sort.Strings(imports)
264
265 pkg.imports = make([]*Package, 0, len(imports))
266 var importFlags Flags
267 if pkg.flags.has(PkgInAll) {
268 importFlags = PkgInAll
269 }
270 for _, path := range imports {
271 pkg.imports = append(pkg.imports, pkgs.addPkg(ctx, path, importFlags))
272 }
273 pkgs.applyPkgFlags(ctx, pkg, PkgImportsLoaded)
274 }
275
276
277
278
279 func (pkgs *Packages) applyPkgFlags(ctx context.Context, pkg *Package, flags Flags) {
280 if flags == 0 {
281 return
282 }
283
284 if flags.has(PkgInAll) {
285
286 flags |= PkgIsRoot
287 }
288 if flags.has(PkgIsRoot) {
289 flags |= PkgFromRoot
290 }
291
292 old := pkg.flags.update(flags)
293 new := old | flags
294 if new == old || !new.has(PkgImportsLoaded) {
295
296
297
298 return
299 }
300
301 if new.has(PkgInAll) && !old.has(PkgInAll|PkgImportsLoaded) {
302
303
304 for _, dep := range pkg.imports {
305 pkgs.applyPkgFlags(ctx, dep, PkgInAll)
306 }
307 }
308
309 if new.has(PkgFromRoot) && !old.has(PkgFromRoot|PkgImportsLoaded) {
310 for _, dep := range pkg.imports {
311 pkgs.applyPkgFlags(ctx, dep, PkgFromRoot)
312 }
313 }
314 }
315
316
317
318 type atomicLoadPkgFlags struct {
319 bits atomic.Int32
320 }
321
322
323
324
325
326 func (af *atomicLoadPkgFlags) update(flags Flags) (old Flags) {
327 for {
328 old := af.bits.Load()
329 new := old | int32(flags)
330 if new == old || af.bits.CompareAndSwap(old, new) {
331 return Flags(old)
332 }
333 }
334 }
335
336 func (af *atomicLoadPkgFlags) get() Flags {
337 return Flags(af.bits.Load())
338 }
339
340
341 func (af *atomicLoadPkgFlags) has(cond Flags) bool {
342 return Flags(af.bits.Load())&cond == cond
343 }
344
345 func IsStdlibPackage(pkgPath string) bool {
346 firstElem, _, _ := strings.Cut(pkgPath, "/")
347 return !strings.Contains(firstElem, ".")
348 }
349
View as plain text