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