this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

cue/load: better treatment for build tags with respect to modules

This implements a coherent approach to build attributes and tool files
across modules:
- tags in build attributes outside the main module are treated as unset
- tool files (`*_tool.cue`) are not considered except when they are in
packages explicitly mentioned on the command line.

This change required a bit of refactoring inside cue/load in order
that the tag logic could be made aware of which module a package
is part of. It also requires changes to the module tidy logic
so that it also respected the rules.

Fixes #3180.

Signed-off-by: Roger Peppe <rogpeppe@gmail.com>
Change-Id: I125655a9fba43f05f8d75d299d927e0ede566479
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1197160
Reviewed-by: Daniel Martí <mvdan@mvdan.cc>
Unity-Result: CUE porcuepine <cue.porcuepine@gmail.com>
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>

+407 -47
+24
cmd/cue/cmd/testdata/script/modtidy_excluded_files_error.txtar
··· 1 + ! exec cue mod tidy 2 + cmp stderr expect-stderr 3 + 4 + -- expect-stderr -- 5 + failed to resolve "test.example/d1": no files in package directory with package name "d1" (1 files were excluded) 6 + -- cue.mod/module.cue -- 7 + module: "test.example/main" 8 + language: version: "v0.9.2" 9 + 10 + -- main.cue -- 11 + package main 12 + 13 + import "test.example/d1" 14 + 15 + x: d1 16 + 17 + -- _registry/test.example_d1_v0.0.1/cue.mod/module.cue -- 18 + module: "test.example/d1" 19 + language: version: "v0.9.2" 20 + 21 + -- _registry/test.example_d1_v0.0.1/x.cue -- 22 + @if(notused) 23 + 24 + package d1
+127
cmd/cue/cmd/testdata/script/modtidy_with_build_attrs.txtar
··· 1 + # Check that, for the purposes of `cue mod tidy`, build attributes are always considered 2 + # to be enabled in the main module but disabled in dependencies. 3 + exec cue mod tidy 4 + cmp cue.mod/module.cue want-module 5 + 6 + # Note that test.example.d3 should _not_ appear 7 + # in the dependencies because it's guarded by a build 8 + # tag that should be considered to be false. 9 + ! grep test.example/d3 want-module 10 + 11 + # On the other hand, d4 _should_ appear in the 12 + # dependencies, because it's guarded by the negation 13 + # of a build tag. 14 + grep test.example/d4 want-module 15 + 16 + exec cue eval 17 + cmp stdout want-stdout-1 18 + exec cue eval -t prod 19 + cmp stdout want-stdout-2 20 + ! exec cue eval -t notknown 21 + stderr 'tag "notknown" not used in any file' 22 + 23 + -- want-module -- 24 + module: "main.org" 25 + language: { 26 + version: "v0.9.2" 27 + } 28 + deps: { 29 + "test.example/d1@v0": { 30 + v: "v0.0.1" 31 + default: true 32 + } 33 + "test.example/d2@v0": { 34 + v: "v0.0.1" 35 + default: true 36 + } 37 + "test.example/d4@v0": { 38 + v: "v0.0.1" 39 + default: true 40 + } 41 + } 42 + -- want-stdout-1 -- 43 + prod: false 44 + x: { 45 + self: "test.example/d2" 46 + } 47 + -- want-stdout-2 -- 48 + prod: true 49 + x: { 50 + self: "test.example/d1" 51 + prodenabled: false 52 + y: { 53 + self: "test.example/d4" 54 + } 55 + } 56 + -- cue.mod/module.cue -- 57 + module: "main.org" 58 + language: { 59 + version: "v0.9.2" 60 + } 61 + -- foo_prod.cue -- 62 + @if(prod) 63 + 64 + package foo 65 + 66 + import "test.example/d1" 67 + 68 + prod: true 69 + x: d1 70 + -- foo_nonprod.cue -- 71 + @if(!prod) 72 + 73 + package foo 74 + 75 + import "test.example/d2" 76 + 77 + prod: false 78 + x: d2 79 + -- _registry/test.example_d1_v0.0.1/cue.mod/module.cue -- 80 + module: "test.example/d1" 81 + language: version: "v0.9.2" 82 + 83 + -- _registry/test.example_d1_v0.0.1/x.cue -- 84 + 85 + @if(prod) 86 + 87 + package d1 88 + 89 + import "test.example/d3" 90 + 91 + self: "test.example/d1" 92 + prodenabled: true 93 + y: d3 94 + 95 + -- _registry/test.example_d1_v0.0.1/y.cue -- 96 + 97 + @if(!prod) 98 + 99 + package d1 100 + 101 + import "test.example/d4" 102 + 103 + self: "test.example/d1" 104 + prodenabled: false 105 + y: d4 106 + 107 + -- _registry/test.example_d2_v0.0.1/cue.mod/module.cue -- 108 + module: "test.example/d2" 109 + language: version: "v0.9.2" 110 + 111 + -- _registry/test.example_d2_v0.0.1/x.cue -- 112 + package d2 113 + self: "test.example/d2" 114 + 115 + -- _registry/test.example_d3_v0.0.1/cue.mod/module.cue -- 116 + module: "test.example/d3" 117 + language: version: "v0.9.2" 118 + -- _registry/test.example_d3_v0.0.1/x.cue -- 119 + package d3 120 + self: "test.example/d3" 121 + 122 + -- _registry/test.example_d4_v0.0.1/cue.mod/module.cue -- 123 + module: "test.example/d4" 124 + language: version: "v0.9.2" 125 + -- _registry/test.example_d4_v0.0.1/x.cue -- 126 + package d4 127 + self: "test.example/d4"
+21 -18
cue/load/import.go
··· 365 365 } 366 366 367 367 func (l *loader) newInstance(pos token.Pos, p importPath) *build.Instance { 368 - dir, err := l.absDirFromImportPath(pos, p) 368 + dir, modPath, err := l.absDirFromImportPath(pos, p) 369 369 i := l.cfg.Context.NewInstance(dir, l.loadFunc) 370 370 i.Err = errors.Append(i.Err, err) 371 371 i.Dir = dir ··· 380 380 i.DisplayPath = string(p) 381 381 i.ImportPath = string(p) 382 382 i.Root = l.cfg.ModuleRoot 383 - i.Module = l.cfg.Module 383 + i.Module = modPath 384 384 385 385 return i 386 386 } ··· 389 389 // and a package name. The root directory must be set. 390 390 // 391 391 // The returned directory may not exist. 392 - func (l *loader) absDirFromImportPath(pos token.Pos, p importPath) (string, errors.Error) { 393 - dir, err := l.absDirFromImportPath1(pos, p) 392 + func (l *loader) absDirFromImportPath(pos token.Pos, p importPath) (dir string, modPath string, _ errors.Error) { 393 + dir, modPath, err := l.absDirFromImportPath1(pos, p) 394 394 if err != nil { 395 395 // Any error trying to determine the package location 396 396 // is a PackageError. 397 - return "", l.errPkgf([]token.Pos{pos}, "%s", err.Error()) 397 + return "", "", l.errPkgf([]token.Pos{pos}, "%s", err.Error()) 398 398 } 399 - return dir, nil 399 + return dir, modPath, nil 400 400 } 401 401 402 - func (l *loader) absDirFromImportPath1(pos token.Pos, p importPath) (absDir string, err error) { 402 + func (l *loader) absDirFromImportPath1(pos token.Pos, p importPath) (absDir string, modPath string, err error) { 403 403 if p == "" { 404 - return "", fmt.Errorf("empty import path") 404 + return "", "", fmt.Errorf("empty import path") 405 405 } 406 406 if l.cfg.ModuleRoot == "" { 407 - return "", fmt.Errorf("cannot import %q (root undefined)", p) 407 + return "", "", fmt.Errorf("cannot import %q (root undefined)", p) 408 408 } 409 409 if isStdlibPackage(string(p)) { 410 - return "", fmt.Errorf("standard library import path %q cannot be imported as a CUE package", p) 410 + return "", "", fmt.Errorf("standard library import path %q cannot be imported as a CUE package", p) 411 411 } 412 412 // Extract the package name. 413 413 parts := module.ParseImportPath(string(p)) 414 414 unqualified := parts.Unqualified().String() 415 415 if l.cfg.Registry != nil { 416 416 if l.pkgs == nil { 417 - return "", fmt.Errorf("imports are unavailable because there is no cue.mod/module.cue file") 417 + return "", "", fmt.Errorf("imports are unavailable because there is no cue.mod/module.cue file") 418 418 } 419 419 // TODO predicate registry-aware lookup on module.cue-declared CUE version? 420 420 ··· 426 426 // should we not be using either the original path or the canonical path? 427 427 // The unqualified import path should only be used for filepath.FromSlash further below. 428 428 if pkg == nil { 429 - return "", fmt.Errorf("no dependency found for package %q", unqualified) 429 + return "", "", fmt.Errorf("no dependency found for package %q", unqualified) 430 430 } 431 431 if err := pkg.Error(); err != nil { 432 - return "", fmt.Errorf("cannot find package %q: %v", unqualified, err) 432 + return "", "", fmt.Errorf("cannot find package %q: %v", unqualified, err) 433 433 } 434 434 if mv := pkg.Mod(); mv.IsLocal() { 435 435 // It's a local package that's present inside one or both of the gen, usr or pkg ··· 440 440 } else { 441 441 locs := pkg.Locations() 442 442 if len(locs) > 1 { 443 - return "", fmt.Errorf("package %q unexpectedly found in multiple locations", unqualified) 443 + return "", "", fmt.Errorf("package %q unexpectedly found in multiple locations", unqualified) 444 444 } 445 445 if len(locs) == 0 { 446 - return "", fmt.Errorf("no location found for package %q", unqualified) 446 + return "", "", fmt.Errorf("no location found for package %q", unqualified) 447 447 } 448 448 var err error 449 449 absDir, err = absPathForSourceLoc(locs[0]) 450 450 if err != nil { 451 - return "", fmt.Errorf("cannot determine source directory for package %q: %v", unqualified, err) 451 + return "", "", fmt.Errorf("cannot determine source directory for package %q: %v", unqualified, err) 452 452 } 453 453 } 454 - return absDir, nil 454 + return absDir, pkg.Mod().Path(), nil 455 455 } 456 456 457 457 // Determine the directory without using the registry. ··· 459 459 sub := filepath.FromSlash(unqualified) 460 460 switch hasPrefix := strings.HasPrefix(unqualified, l.cfg.Module); { 461 461 case hasPrefix && len(sub) == len(l.cfg.Module): 462 + modPath = l.cfg.Module 462 463 absDir = l.cfg.ModuleRoot 463 464 464 465 case hasPrefix && unqualified[len(l.cfg.Module)] == '/': 466 + modPath = l.cfg.Module 465 467 absDir = filepath.Join(l.cfg.ModuleRoot, sub[len(l.cfg.Module)+1:]) 466 468 467 469 default: 470 + modPath = "local" 468 471 absDir = filepath.Join(GenPath(l.cfg.ModuleRoot), sub) 469 472 } 470 - return absDir, err 473 + return absDir, modPath, err 471 474 } 472 475 473 476 func absPathForSourceLoc(loc module.SourceLoc) (string, error) {
+34 -3
cue/load/instances.go
··· 17 17 import ( 18 18 "context" 19 19 "fmt" 20 + "io/fs" 20 21 "sort" 21 22 "strconv" 23 + "strings" 22 24 23 25 "cuelang.org/go/cue/ast" 24 26 "cuelang.org/go/cue/build" 25 27 "cuelang.org/go/internal/cueexperiment" 26 28 "cuelang.org/go/internal/filetypes" 29 + "cuelang.org/go/internal/mod/modimports" 27 30 "cuelang.org/go/internal/mod/modpkgload" 28 31 "cuelang.org/go/internal/mod/modrequirements" 29 32 "cuelang.org/go/mod/module" ··· 102 105 if err != nil { 103 106 return []*build.Instance{c.newErrInstance(err)} 104 107 } 105 - pkgs, err := loadPackages(ctx, c, synCache, expandedPaths, otherFiles) 108 + pkgs, err := loadPackages(ctx, c, synCache, expandedPaths, otherFiles, tg) 106 109 if err != nil { 107 110 return []*build.Instance{c.newErrInstance(err)} 108 111 } ··· 169 172 170 173 // loadPackages returns packages loaded from the given package list and also 171 174 // including imports from the given build files. 172 - func loadPackages(ctx context.Context, cfg *Config, synCache *syntaxCache, pkgs []resolvedPackageArg, otherFiles []*build.File) (*modpkgload.Packages, error) { 175 + func loadPackages( 176 + ctx context.Context, 177 + cfg *Config, 178 + synCache *syntaxCache, 179 + pkgs []resolvedPackageArg, 180 + otherFiles []*build.File, 181 + tg *tagger, 182 + ) (*modpkgload.Packages, error) { 173 183 if cfg.Registry == nil || cfg.modFile == nil || cfg.modFile.Module == "" { 174 184 return nil, nil 175 185 } 186 + mainModPath := cfg.modFile.QualifiedModule() 176 187 reqs := modrequirements.NewRequirements( 177 - cfg.modFile.QualifiedModule(), 188 + mainModPath, 178 189 cfg.Registry, 179 190 cfg.modFile.DepVersions(), 180 191 cfg.modFile.DefaultMajorVersions(), ··· 224 235 reqs, 225 236 cfg.Registry, 226 237 pkgPathSlice, 238 + func(pkgPath string, mod module.Version, fsys fs.FS, mf modimports.ModuleFile) bool { 239 + if !cfg.Tools && strings.HasSuffix(mf.FilePath, "_tool.cue") { 240 + return false 241 + } 242 + var tagIsSet func(string) bool 243 + if mod.Path() == mainModPath || pkgPaths[pkgPath] { 244 + tagIsSet = tg.tagIsSet 245 + } else { 246 + // The file is outside the main module and isn't mentioned explicitly 247 + // on the command line, so treat all build tag keys as unset. 248 + tagIsSet = func(string) bool { 249 + return false 250 + } 251 + } 252 + if err := shouldBuildFile(mf.Syntax, tagIsSet); err != nil { 253 + // Later build logic should pick up and report the same error. 254 + return false 255 + } 256 + return true 257 + }, 227 258 ), nil 228 259 }
+9 -1
cue/load/loader_common.go
··· 248 248 } 249 249 250 250 if !fp.c.AllCUEFiles { 251 - if err := shouldBuildFile(pf, fp.tagger.tagIsSet); err != nil { 251 + tagIsSet := fp.tagger.tagIsSet 252 + if p.Module != fp.c.Module { 253 + // The file is outside the main module so treat all build tag keys as unset. 254 + // TODO also consider packages explicitly specified on the command line. 255 + tagIsSet = func(string) bool { 256 + return false 257 + } 258 + } 259 + if err := shouldBuildFile(pf, tagIsSet); err != nil { 252 260 if !errors.Is(err, errExclude) { 253 261 fp.err = errors.Append(fp.err, err) 254 262 }
+42 -6
cue/load/loader_test.go
··· 132 132 root: $CWD/testdata/testmod 133 133 dir: "" 134 134 display:""`}, { 135 - name: "NoPackageName", 135 + name: "NoMatchingPackageName", 136 136 cfg: dirCfg, 137 137 args: []string{"./anon"}, 138 138 want: `err: build constraints exclude all CUE files in ./anon: ··· 185 185 args: []string{"mod.test/test/hello:nonexist"}, 186 186 want: `err: cannot find package "mod.test/test/hello": no files in package directory with package name "nonexist" 187 187 path: mod.test/test/hello:nonexist 188 - module: mod.test/test@v0 188 + module: "" 189 189 root: $CWD/testdata/testmod 190 190 dir: "" 191 191 display:mod.test/test/hello:nonexist`, ··· 229 229 want: `err: cannot determine package name for "foo.com/bad-identifier"; set it explicitly with ':' 230 230 cannot find package "foo.com/bad-identifier": cannot find module providing package foo.com/bad-identifier 231 231 path: foo.com/bad-identifier 232 - module: mod.test/test@v0 232 + module: "" 233 233 root: $CWD/testdata/testmod 234 234 dir: "" 235 235 display:foo.com/bad-identifier`, ··· 239 239 args: []string{"nonexisting"}, 240 240 want: `err: standard library import path "nonexisting" cannot be imported as a CUE package 241 241 path: nonexisting 242 - module: mod.test/test@v0 242 + module: "" 243 243 root: $CWD/testdata/testmod 244 244 dir: "" 245 245 display:nonexisting`, ··· 249 249 args: []string{"strconv"}, 250 250 want: `err: standard library import path "strconv" cannot be imported as a CUE package 251 251 path: strconv 252 - module: mod.test/test@v0 252 + module: "" 253 253 root: $CWD/testdata/testmod 254 254 dir: "" 255 255 display:strconv`, ··· 503 503 display:. 504 504 files: 505 505 $CWD/testdata/testmod/anon.cue 506 - $CWD/testdata/testmod/multi5/nopackage.cue`}} 506 + $CWD/testdata/testmod/multi5/nopackage.cue`}, { 507 + // Check that imports are only considered from files 508 + // that match the build paths. 509 + name: "BuildTagsWithImports#1", 510 + cfg: &Config{ 511 + Dir: filepath.Join(testdataDir, "tagswithimports"), 512 + Tags: []string{"prod"}, 513 + }, 514 + args: []string{"."}, 515 + want: `path: mod.test/test/tagswithimports@v0 516 + module: mod.test/test@v0 517 + root: $CWD/testdata/testmod 518 + dir: $CWD/testdata/testmod/tagswithimports 519 + display:. 520 + files: 521 + $CWD/testdata/testmod/tagswithimports/prod.cue 522 + imports: 523 + mod.test/test/hello:test: $CWD/testdata/testmod/test.cue $CWD/testdata/testmod/hello/test.cue 524 + mod.test/test/sub: $CWD/testdata/testmod/sub/sub.cue`}, { 525 + // Check that imports are only considered from files 526 + // that match the build paths. When we don't have the prod 527 + // tag, the bad import path mentioned in testdata/testmod/tagswithimports/nonprod.cue 528 + // surfaces in the errors. 529 + name: "BuildTagsWithImports#2", 530 + cfg: &Config{ 531 + Dir: filepath.Join(testdataDir, "tagswithimports"), 532 + }, 533 + args: []string{"."}, 534 + want: `err: mod.test/test/tagswithimports@v0: import failed: cannot find package "bad-import.example/foo": cannot find module providing package bad-import.example/foo: 535 + $CWD/testdata/testmod/tagswithimports/nonprod.cue:5:8 536 + path: mod.test/test/tagswithimports@v0 537 + module: mod.test/test@v0 538 + root: $CWD/testdata/testmod 539 + dir: $CWD/testdata/testmod/tagswithimports 540 + display:. 541 + files: 542 + $CWD/testdata/testmod/tagswithimports/nonprod.cue`}} 507 543 tdtest.Run(t, testCases, func(t *tdtest.T, tc *loadTest) { 508 544 pkgs := Instances(tc.args, tc.cfg) 509 545
+7
cue/load/testdata/testmod/tagswithimports/nonprod.cue
··· 1 + @if(!prod) 2 + 3 + package tagswithimports 4 + 5 + import "bad-import.example/foo" 6 + 7 + x: foo
+7
cue/load/testdata/testmod/tagswithimports/prod.cue
··· 1 + @if(prod) 2 + 3 + package tagswithimports 4 + 5 + import "mod.test/test/hello:test" 6 + 7 + x: hello
+68
internal/mod/modload/testdata/tidy/with-tools.txtar
··· 1 + # Test a scenario where there are _tool.cue files, both 2 + # in the main module and in the dependencies. 3 + 4 + -- tidy-check-error -- 5 + module is not tidy: missing dependency providing package test.example/foo 6 + -- want -- 7 + module: "main.org" 8 + language: { 9 + version: "v0.9.2" 10 + } 11 + deps: { 12 + "test.example/foo@v0": { 13 + v: "v0.0.1" 14 + default: true 15 + } 16 + "test.example/toolonly@v0": { 17 + v: "v0.0.1" 18 + default: true 19 + } 20 + } 21 + -- cue.mod/module.cue -- 22 + module: "main.org" 23 + language: { 24 + version: "v0.9.2" 25 + } 26 + 27 + -- main.cue -- 28 + package main 29 + import "test.example/foo" 30 + 31 + x: foo 32 + 33 + -- main_tool.cue -- 34 + package main 35 + import "test.example/toolonly" 36 + 37 + x: toolonly 38 + 39 + -- _registry/test.example_foo_v0.0.1/cue.mod/module.cue -- 40 + module: "test.example/foo" 41 + language: version: "v0.10.0" 42 + -- _registry/test.example_foo_v0.0.1/foo.cue -- 43 + package foo 44 + 45 + -- _registry/test.example_foo_v0.0.1/foo_tool.cue -- 46 + package foo 47 + 48 + import "test.example/nextleveltool" 49 + 50 + x: nextleveltool 51 + 52 + -- _registry/test.example_toolonly_v0.0.1/cue.mod/module.cue -- 53 + module: "test.example/toolonly" 54 + language: version: "v0.10.0" 55 + 56 + -- _registry/test.example_toolonly_v0.0.1/x.cue -- 57 + package toolonly 58 + 59 + -- _registry/test.example_nextleveltool_v0.0.1/cue.mod/module.cue -- 60 + // Note: this module should _not_ be included because it's only imported 61 + // as result of the foo_tool.cue file inside the dependency test.example/foo, 62 + // not from the main module. 63 + 64 + module: "test.example/nextleveltool" 65 + language: version: "v0.10.0" 66 + 67 + -- _registry/test.example_nextleveltool_v0.0.1/x.cue -- 68 + package nextleveltool
+25 -1
internal/mod/modload/tidy.go
··· 10 10 "path" 11 11 "runtime" 12 12 "slices" 13 + "strings" 13 14 15 + "cuelang.org/go/internal/buildattr" 14 16 "cuelang.org/go/internal/mod/modimports" 15 17 "cuelang.org/go/internal/mod/modpkgload" 16 18 "cuelang.org/go/internal/mod/modrequirements" ··· 165 167 return mf 166 168 } 167 169 170 + // shouldIncludePkgFile reports whether a file from a package should be included 171 + // for dependency-analysis purposes. 172 + // 173 + // In general a file should always be considered unless it's a _tool.cue file 174 + // that's not in the main module. 175 + func (ld *loader) shouldIncludePkgFile(pkgPath string, mod module.Version, fsys fs.FS, mf modimports.ModuleFile) bool { 176 + inMainModule := mod.Path() == ld.mainModule.Path() 177 + if strings.HasSuffix(mf.FilePath, "_tool.cue") { 178 + // _tool.cue files are only considered when they are part of the main module. 179 + return inMainModule 180 + } 181 + ok, _, err := buildattr.ShouldBuildFile(mf.Syntax, func(string) bool { 182 + // Keys of build attributes are considered always true when they're 183 + // in the main module and false otherwise. 184 + return inMainModule 185 + }) 186 + if err != nil { 187 + return false 188 + } 189 + return ok 190 + } 191 + 168 192 func (ld *loader) resolveDependencies(ctx context.Context, rootPkgPaths []string, rs *modrequirements.Requirements) (*modrequirements.Requirements, *modpkgload.Packages, error) { 169 193 for { 170 194 logf("---- LOADING from requirements %q", rs.RootModules()) 171 - pkgs := modpkgload.LoadPackages(ctx, ld.mainModule.Path(), ld.mainModuleLoc, rs, ld.registry, rootPkgPaths) 195 + pkgs := modpkgload.LoadPackages(ctx, ld.mainModule.Path(), ld.mainModuleLoc, rs, ld.registry, rootPkgPaths, ld.shouldIncludePkgFile) 172 196 if ld.checkTidy { 173 197 for _, pkg := range pkgs.All() { 174 198 err := pkg.Error()
+39 -18
internal/mod/modpkgload/pkgload.go
··· 3 3 import ( 4 4 "context" 5 5 "fmt" 6 + "io/fs" 6 7 "runtime" 7 8 "slices" 8 9 "sort" ··· 76 77 } 77 78 78 79 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 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 87 89 } 88 90 89 91 type Package struct { ··· 148 150 // and reg to download module contents. 149 151 // 150 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. 151 159 func LoadPackages( 152 160 ctx context.Context, 153 161 mainModulePath string, ··· 155 163 rs *modrequirements.Requirements, 156 164 reg Registry, 157 165 rootPkgPaths []string, 166 + shouldIncludePkgFile func(pkgPath string, mod module.Version, fsys fs.FS, mf modimports.ModuleFile) bool, 158 167 ) *Packages { 159 168 pkgs := &Packages{ 160 - mainModuleVersion: module.MustNewVersion(mainModulePath, ""), 161 - mainModuleLoc: mainModuleLoc, 162 - requirements: rs, 163 - registry: reg, 164 - work: par.NewQueue(runtime.GOMAXPROCS(0)), 169 + mainModuleVersion: module.MustNewVersion(mainModulePath, ""), 170 + mainModuleLoc: mainModuleLoc, 171 + shouldIncludePkgFile: shouldIncludePkgFile, 172 + requirements: rs, 173 + registry: reg, 174 + work: par.NewQueue(runtime.GOMAXPROCS(0)), 165 175 } 166 176 inRoots := map[*Package]bool{} 167 177 pkgs.rootPkgs = make([]*Package, 0, len(rootPkgPaths)) ··· 247 257 if pkgs.mainModuleVersion.Path() == pkg.mod.Path() { 248 258 pkgs.applyPkgFlags(pkg, PkgInAll) 249 259 } 250 - pkgQual := module.ParseImportPath(pkg.path).Qualifier 260 + ip := module.ParseImportPath(pkg.path) 261 + pkgQual := ip.Qualifier 251 262 if pkgQual == "" { 252 263 pkg.err = fmt.Errorf("cannot determine package name from import path %q", pkg.path) 253 264 return ··· 258 269 } 259 270 importsMap := make(map[string]bool) 260 271 foundPackageFile := false 272 + excludedPackageFiles := 0 261 273 for _, loc := range pkg.locs { 262 274 // Layer an iterator whose yield function keeps track of whether we have seen 263 275 // a single valid CUE file in the package directory. 264 276 // Otherwise we would have to iterate twice, causing twice as many io/fs operations. 265 277 pkgFileIter := func(yield func(modimports.ModuleFile, error) bool) { 266 - yield2 := func(mf modimports.ModuleFile, err error) bool { 278 + modimports.PackageFiles(loc.FS, loc.Dir, pkgQual)(func(mf modimports.ModuleFile, err error) bool { 279 + ip1 := ip 280 + ip1.Qualifier = mf.Syntax.PackageName() 281 + if !pkgs.shouldIncludePkgFile(ip1.String(), pkg.mod, loc.FS, mf) { 282 + excludedPackageFiles++ 283 + return true 284 + } 267 285 foundPackageFile = err == nil 268 286 return yield(mf, err) 269 - } 270 - modimports.PackageFiles(loc.FS, loc.Dir, pkgQual)(yield2) 287 + }) 271 288 } 272 289 imports, err := modimports.AllImports(pkgFileIter) 273 290 if err != nil { ··· 279 296 } 280 297 } 281 298 if !foundPackageFile { 282 - pkg.err = fmt.Errorf("no files in package directory with package name %q", pkgQual) 299 + if excludedPackageFiles > 0 { 300 + pkg.err = fmt.Errorf("no files in package directory with package name %q (%d files were excluded)", pkgQual, excludedPackageFiles) 301 + } else { 302 + pkg.err = fmt.Errorf("no files in package directory with package name %q", pkgQual) 303 + } 283 304 return 284 305 } 285 306 imports := make([]string, 0, len(importsMap))
+4
internal/mod/modpkgload/pkgload_test.go
··· 14 14 "github.com/google/go-cmp/cmp" 15 15 "golang.org/x/tools/txtar" 16 16 17 + "cuelang.org/go/internal/mod/modimports" 17 18 "cuelang.org/go/internal/mod/modrequirements" 18 19 "cuelang.org/go/internal/txtarfs" 19 20 "cuelang.org/go/mod/modfile" ··· 63 64 initialRequirements, 64 65 reg, 65 66 rootPackages, 67 + func(pkgPath string, mod module.Version, fsys fs.FS, mf modimports.ModuleFile) bool { 68 + return true 69 + }, 66 70 ) 67 71 for _, pkg := range pkgs.All() { 68 72 printf("%s\n", pkg.ImportPath())