cuelang.org/go@v0.10.1/internal/mod/modpkgload/pkgload.go (about) 1 package modpkgload 2 3 import ( 4 "context" 5 "fmt" 6 "io/fs" 7 "runtime" 8 "slices" 9 "sort" 10 "strings" 11 "sync/atomic" 12 13 "cuelang.org/go/internal/mod/modimports" 14 "cuelang.org/go/internal/mod/modrequirements" 15 "cuelang.org/go/internal/par" 16 "cuelang.org/go/mod/module" 17 ) 18 19 // Registry represents a module registry, or at least this package's view of it. 20 type Registry interface { 21 // Fetch returns the location of the contents for the given module 22 // version, downloading it if necessary. 23 Fetch(ctx context.Context, m module.Version) (module.SourceLoc, error) 24 } 25 26 // Flags is a set of flags tracking metadata about a package. 27 type Flags int8 28 29 const ( 30 // PkgInAll indicates that the package is in the "all" package pattern, 31 // regardless of whether we are loading the "all" package pattern. 32 // 33 // When the PkgInAll flag and PkgImportsLoaded flags are both set, the caller 34 // who set the last of those flags must propagate the PkgInAll marking to all 35 // of the imports of the marked package. 36 PkgInAll Flags = 1 << iota 37 38 // PkgIsRoot indicates that the package matches one of the root package 39 // patterns requested by the caller. 40 PkgIsRoot 41 42 // PkgFromRoot indicates that the package is in the transitive closure of 43 // imports starting at the roots. (Note that every package marked as PkgIsRoot 44 // is also trivially marked PkgFromRoot.) 45 PkgFromRoot 46 47 // PkgImportsLoaded indicates that the Imports field of a 48 // Pkg have been populated. 49 PkgImportsLoaded 50 ) 51 52 func (f Flags) String() string { 53 var buf strings.Builder 54 set := func(f1 Flags, s string) { 55 if (f & f1) == 0 { 56 return 57 } 58 if buf.Len() > 0 { 59 buf.WriteString(",") 60 } 61 buf.WriteString(s) 62 f &^= f1 63 } 64 set(PkgInAll, "inAll") 65 set(PkgIsRoot, "isRoot") 66 set(PkgFromRoot, "fromRoot") 67 set(PkgImportsLoaded, "importsLoaded") 68 if f != 0 { 69 set(f, fmt.Sprintf("extra%x", int(f))) 70 } 71 return buf.String() 72 } 73 74 // has reports whether all of the flags in cond are set in f. 75 func (f Flags) has(cond Flags) bool { 76 return f&cond == cond 77 } 78 79 type Packages struct { 80 mainModuleVersion module.Version 81 mainModuleLoc module.SourceLoc 82 shouldIncludePkgFile func(pkgPath string, mod module.Version, fsys fs.FS, mf modimports.ModuleFile) bool 83 pkgCache par.Cache[string, *Package] 84 pkgs []*Package 85 rootPkgs []*Package 86 work *par.Queue 87 requirements *modrequirements.Requirements 88 registry Registry 89 } 90 91 type Package struct { 92 // Populated at construction time: 93 path string // import path 94 95 // Populated at construction time and updated by [loader.applyPkgFlags]: 96 flags atomicLoadPkgFlags 97 98 // Populated by [loader.load]. 99 mod module.Version // module providing package 100 locs []module.SourceLoc // location of source code directories 101 err error // error loading package 102 imports []*Package // packages imported by this one 103 inStd bool 104 fromExternal bool 105 altMods []module.Version // modules that could have contained the package but did not 106 107 // Populated by postprocessing in [Packages.buildStacks]: 108 stack *Package // package importing this one in minimal import stack for this pkg 109 } 110 111 func (pkg *Package) ImportPath() string { 112 return pkg.path 113 } 114 115 func (pkg *Package) FromExternalModule() bool { 116 return pkg.fromExternal 117 } 118 119 func (pkg *Package) Locations() []module.SourceLoc { 120 return pkg.locs 121 } 122 123 func (pkg *Package) Error() error { 124 return pkg.err 125 } 126 127 func (pkg *Package) SetError(err error) { 128 pkg.err = err 129 } 130 131 func (pkg *Package) HasFlags(flags Flags) bool { 132 return pkg.flags.has(flags) 133 } 134 135 func (pkg *Package) Imports() []*Package { 136 return pkg.imports 137 } 138 139 func (pkg *Package) Flags() Flags { 140 return pkg.flags.get() 141 } 142 143 func (pkg *Package) Mod() module.Version { 144 return pkg.mod 145 } 146 147 // LoadPackages loads information about all the given packages and the 148 // packages they import, recursively, using modules from the given 149 // requirements to determine which modules they might be obtained from, 150 // and reg to download module contents. 151 // 152 // rootPkgPaths should only contain canonical import paths. 153 // 154 // The shouldIncludePkgFile function is used to determine whether a 155 // given file in a package should be considered to be part of the build. 156 // If it returns true for a package, the file's imports will be followed. 157 // A nil value corresponds to a function that always returns true. 158 // It may be called concurrently. 159 func LoadPackages( 160 ctx context.Context, 161 mainModulePath string, 162 mainModuleLoc module.SourceLoc, 163 rs *modrequirements.Requirements, 164 reg Registry, 165 rootPkgPaths []string, 166 shouldIncludePkgFile func(pkgPath string, mod module.Version, fsys fs.FS, mf modimports.ModuleFile) bool, 167 ) *Packages { 168 pkgs := &Packages{ 169 mainModuleVersion: module.MustNewVersion(mainModulePath, ""), 170 mainModuleLoc: mainModuleLoc, 171 shouldIncludePkgFile: shouldIncludePkgFile, 172 requirements: rs, 173 registry: reg, 174 work: par.NewQueue(runtime.GOMAXPROCS(0)), 175 } 176 inRoots := map[*Package]bool{} 177 pkgs.rootPkgs = make([]*Package, 0, len(rootPkgPaths)) 178 for _, p := range rootPkgPaths { 179 // TODO the original logic didn't add PkgInAll here. Not sure why, 180 // and that might be a lurking problem. 181 if root := pkgs.addPkg(ctx, p, PkgIsRoot|PkgInAll); !inRoots[root] { 182 pkgs.rootPkgs = append(pkgs.rootPkgs, root) 183 inRoots[root] = true 184 } 185 } 186 <-pkgs.work.Idle() 187 pkgs.buildStacks() 188 return pkgs 189 } 190 191 // buildStacks computes minimal import stacks for each package, 192 // for use in error messages. When it completes, packages that 193 // are part of the original root set have pkg.stack == nil, 194 // and other packages have pkg.stack pointing at the next 195 // package up the import stack in their minimal chain. 196 // As a side effect, buildStacks also constructs ld.pkgs, 197 // the list of all packages loaded. 198 func (pkgs *Packages) buildStacks() { 199 for _, pkg := range pkgs.rootPkgs { 200 pkg.stack = pkg // sentinel to avoid processing in next loop 201 pkgs.pkgs = append(pkgs.pkgs, pkg) 202 } 203 for i := 0; i < len(pkgs.pkgs); i++ { // not range: appending to pkgs.pkgs in loop 204 pkg := pkgs.pkgs[i] 205 for _, next := range pkg.imports { 206 if next.stack == nil { 207 next.stack = pkg 208 pkgs.pkgs = append(pkgs.pkgs, next) 209 } 210 } 211 } 212 for _, pkg := range pkgs.rootPkgs { 213 pkg.stack = nil 214 } 215 } 216 217 func (pkgs *Packages) Roots() []*Package { 218 return slices.Clip(pkgs.rootPkgs) 219 } 220 221 func (pkgs *Packages) All() []*Package { 222 return slices.Clip(pkgs.pkgs) 223 } 224 225 // Pkg obtains a given package given its canonical import path. 226 func (pkgs *Packages) Pkg(canonicalPkgPath string) *Package { 227 pkg, _ := pkgs.pkgCache.Get(canonicalPkgPath) 228 return pkg 229 } 230 231 func (pkgs *Packages) addPkg(ctx context.Context, pkgPath string, flags Flags) *Package { 232 pkg := pkgs.pkgCache.Do(pkgPath, func() *Package { 233 pkg := &Package{ 234 path: pkgPath, 235 } 236 pkgs.applyPkgFlags(pkg, flags) 237 238 pkgs.work.Add(func() { pkgs.load(ctx, pkg) }) 239 return pkg 240 }) 241 242 // Ensure the flags apply even if the package already existed. 243 pkgs.applyPkgFlags(pkg, flags) 244 return pkg 245 } 246 247 func (pkgs *Packages) load(ctx context.Context, pkg *Package) { 248 if IsStdlibPackage(pkg.path) { 249 pkg.inStd = true 250 return 251 } 252 pkg.fromExternal = pkg.mod != pkgs.mainModuleVersion 253 pkg.mod, pkg.locs, pkg.altMods, pkg.err = pkgs.importFromModules(ctx, pkg.path) 254 if pkg.err != nil { 255 return 256 } 257 if pkgs.mainModuleVersion.Path() == pkg.mod.Path() { 258 pkgs.applyPkgFlags(pkg, PkgInAll) 259 } 260 ip := module.ParseImportPath(pkg.path) 261 pkgQual := ip.Qualifier 262 if pkgQual == "" { 263 pkg.err = fmt.Errorf("cannot determine package name from import path %q", pkg.path) 264 return 265 } 266 if pkgQual == "_" { 267 pkg.err = fmt.Errorf("_ is not a valid import path qualifier in %q", pkg.path) 268 return 269 } 270 importsMap := make(map[string]bool) 271 foundPackageFile := false 272 excludedPackageFiles := 0 273 for _, loc := range pkg.locs { 274 // Layer an iterator whose yield function keeps track of whether we have seen 275 // a single valid CUE file in the package directory. 276 // Otherwise we would have to iterate twice, causing twice as many io/fs operations. 277 pkgFileIter := func(yield func(modimports.ModuleFile, error) bool) { 278 modimports.PackageFiles(loc.FS, loc.Dir, pkgQual)(func(mf modimports.ModuleFile, err error) bool { 279 if err != nil { 280 return yield(mf, err) 281 } 282 ip1 := ip 283 ip1.Qualifier = mf.Syntax.PackageName() 284 if !pkgs.shouldIncludePkgFile(ip1.String(), pkg.mod, loc.FS, mf) { 285 excludedPackageFiles++ 286 return true 287 } 288 foundPackageFile = true 289 return yield(mf, err) 290 }) 291 } 292 imports, err := modimports.AllImports(pkgFileIter) 293 if err != nil { 294 pkg.err = fmt.Errorf("cannot get imports: %v", err) 295 return 296 } 297 for _, imp := range imports { 298 importsMap[imp] = true 299 } 300 } 301 if !foundPackageFile { 302 if excludedPackageFiles > 0 { 303 pkg.err = fmt.Errorf("no files in package directory with package name %q (%d files were excluded)", pkgQual, excludedPackageFiles) 304 } else { 305 pkg.err = fmt.Errorf("no files in package directory with package name %q", pkgQual) 306 } 307 return 308 } 309 imports := make([]string, 0, len(importsMap)) 310 for imp := range importsMap { 311 imports = append(imports, imp) 312 } 313 sort.Strings(imports) // Make the algorithm deterministic for tests. 314 315 pkg.imports = make([]*Package, 0, len(imports)) 316 var importFlags Flags 317 if pkg.flags.has(PkgInAll) { 318 importFlags = PkgInAll 319 } 320 for _, path := range imports { 321 pkg.imports = append(pkg.imports, pkgs.addPkg(ctx, path, importFlags)) 322 } 323 pkgs.applyPkgFlags(pkg, PkgImportsLoaded) 324 } 325 326 // applyPkgFlags updates pkg.flags to set the given flags and propagate the 327 // (transitive) effects of those flags, possibly loading or enqueueing further 328 // packages as a result. 329 func (pkgs *Packages) applyPkgFlags(pkg *Package, flags Flags) { 330 if flags == 0 { 331 return 332 } 333 334 if flags.has(PkgInAll) { 335 // This package matches a root pattern by virtue of being in "all". 336 flags |= PkgIsRoot 337 } 338 if flags.has(PkgIsRoot) { 339 flags |= PkgFromRoot 340 } 341 342 old := pkg.flags.update(flags) 343 new := old | flags 344 if new == old || !new.has(PkgImportsLoaded) { 345 // We either didn't change the state of pkg, or we don't know anything about 346 // its dependencies yet. Either way, we can't usefully load its test or 347 // update its dependencies. 348 return 349 } 350 351 if new.has(PkgInAll) && !old.has(PkgInAll|PkgImportsLoaded) { 352 // We have just marked pkg with pkgInAll, or we have just loaded its 353 // imports, or both. Now is the time to propagate pkgInAll to the imports. 354 for _, dep := range pkg.imports { 355 pkgs.applyPkgFlags(dep, PkgInAll) 356 } 357 } 358 359 if new.has(PkgFromRoot) && !old.has(PkgFromRoot|PkgImportsLoaded) { 360 for _, dep := range pkg.imports { 361 pkgs.applyPkgFlags(dep, PkgFromRoot) 362 } 363 } 364 } 365 366 // An atomicLoadPkgFlags stores a loadPkgFlags for which individual flags can be 367 // added atomically. 368 type atomicLoadPkgFlags struct { 369 bits atomic.Int32 370 } 371 372 // update sets the given flags in af (in addition to any flags already set). 373 // 374 // update returns the previous flag state so that the caller may determine which 375 // flags were newly-set. 376 func (af *atomicLoadPkgFlags) update(flags Flags) (old Flags) { 377 for { 378 old := af.bits.Load() 379 new := old | int32(flags) 380 if new == old || af.bits.CompareAndSwap(old, new) { 381 return Flags(old) 382 } 383 } 384 } 385 386 func (af *atomicLoadPkgFlags) get() Flags { 387 return Flags(af.bits.Load()) 388 } 389 390 // has reports whether all of the flags in cond are set in af. 391 func (af *atomicLoadPkgFlags) has(cond Flags) bool { 392 return Flags(af.bits.Load())&cond == cond 393 } 394 395 func IsStdlibPackage(pkgPath string) bool { 396 firstElem, _, _ := strings.Cut(pkgPath, "/") 397 return !strings.Contains(firstElem, ".") 398 }