Skip to content

Commit e00557b

Browse files
committed
internal/buildattr: implement @ignore attributes
This adds support for an `@ignore()` attribute to enable a file to be unconditionally ignored. Unlike `@if` build attributes, a file with an `@ignore()` attribute is always ignored when calculating dependencies, for example when running `cue mod tidy`. We decided against following Go's lead here, which would have been `@if(ignore)` because there is no existing convention like Go had, and because this is sufficiently different from regular build tags that it seems like it justifies another attribute. Also because `@ignore()` seems to read a bit better than `@if(ignore)`. Fixes #2962. Signed-off-by: Roger Peppe <[email protected]> Change-Id: I7ee84cea2ffce6765bd7667feb78c4d44e0254ea Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1198249 TryBot-Result: CUEcueckoo <[email protected]> Reviewed-by: Daniel Martí <[email protected]> Unity-Result: CUE porcuepine <[email protected]>
1 parent d502843 commit e00557b

File tree

6 files changed

+84
-61
lines changed

6 files changed

+84
-61
lines changed

cmd/cue/cmd/help.go

+15-1
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,8 @@ Injecting files
581581
A "build" attribute defines a boolean expression that causes a file
582582
to only be included in a build if its expression evaluates to true.
583583
There may only be a single @if attribute per file and it must
584-
appear before a package clause.
584+
appear before a package clause, or before any CUE declarations
585+
if there is no package clause.
585586
586587
The expression is a subset of CUE consisting only of identifiers
587588
and the operators &&, ||, !, where identifiers refer to tags
@@ -595,6 +596,19 @@ if the user includes the flag "-t prod" on the command line.
595596
596597
package foo
597598
599+
Ignoring files
600+
601+
An "ignore" attribute causes a file to be unconditionally excluded
602+
from a build. The @ignore attribute must appear before a package
603+
clause or before any other CUE syntax if there is no package clause.
604+
605+
For example:
606+
607+
@ignore()
608+
609+
// This file will be excluded for all purposes.
610+
611+
package foo
598612
599613
Injecting values
600614

cmd/cue/cmd/testdata/script/modtidy_with_build_attrs.txtar

-17
Original file line numberDiff line numberDiff line change
@@ -41,32 +41,15 @@ deps: {
4141
v: "v0.0.1"
4242
default: true
4343
}
44-
"test.example/d7@v0": {
45-
v: "v0.0.1"
46-
default: true
47-
}
48-
"test.example/d8@v0": {
49-
v: "v0.0.1"
50-
default: true
51-
}
5244
}
5345
-- want-stdout-1 --
5446
prod: false
55-
ignore: {
56-
self: "test.example/d7"
57-
}
5847
x: {
5948
self: "test.example/d2"
6049
}
6150
-- want-stdout-2 --
6251
prod: true
63-
ignore: {
64-
self: "test.example/d7"
65-
}
6652
x: {
67-
ignore: {
68-
self: "test.example/d8"
69-
}
7053
self: "test.example/d1"
7154
prodenabled: false
7255
y: {

cue/load/tags.go

+6-3
Original file line numberDiff line numberDiff line change
@@ -358,9 +358,12 @@ func shouldBuildFile(f *ast.File, tagIsSet func(key string) bool) errors.Error {
358358
if err != nil {
359359
return err
360360
}
361-
if !ok {
362-
_, body := attr.Split()
361+
if ok {
362+
return nil
363+
}
364+
if key, body := attr.Split(); key == "if" {
363365
return excludeError{errors.Newf(attr.Pos(), "@if(%s) did not match", body)}
366+
} else {
367+
return excludeError{errors.Newf(attr.Pos(), "@ignore() attribute found")}
364368
}
365-
return nil
366369
}

internal/buildattr/buildattr.go

+29-17
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,21 @@ import (
99
"cuelang.org/go/cue/token"
1010
)
1111

12+
// ShouldIgnoreFile reports whether a File contains an @ignore() file-level
13+
// attribute and hence should be ignored.
14+
func ShouldIgnoreFile(f *ast.File) bool {
15+
ignore, _, _ := getBuildAttr(f)
16+
return ignore
17+
}
18+
1219
// ShouldBuildFile reports whether a File should be included based on its
1320
// attributes. It uses tagIsSet to determine whether a given attribute
1421
// key should be treated as set.
1522
//
1623
// It also returns the build attribute if one was found.
1724
func ShouldBuildFile(f *ast.File, tagIsSet func(key string) bool) (bool, *ast.Attribute, errors.Error) {
18-
a, err := getBuildAttr(f)
19-
if err != nil {
25+
ignore, a, err := getBuildAttr(f)
26+
if ignore || err != nil {
2027
return false, a, err
2128
}
2229
if a == nil {
@@ -37,28 +44,33 @@ func ShouldBuildFile(f *ast.File, tagIsSet func(key string) bool) (bool, *ast.At
3744
return include, a, nil
3845
}
3946

40-
func getBuildAttr(f *ast.File) (*ast.Attribute, errors.Error) {
41-
var a *ast.Attribute
47+
func getBuildAttr(f *ast.File) (ignore bool, a *ast.Attribute, err errors.Error) {
4248
for _, d := range f.Decls {
4349
switch x := d.(type) {
4450
case *ast.Attribute:
45-
key, _ := x.Split()
46-
if key != "if" {
47-
continue
51+
switch key, _ := x.Split(); key {
52+
case "ignore":
53+
return true, x, nil
54+
case "if":
55+
if a != nil {
56+
err := errors.Newf(d.Pos(), "multiple @if attributes")
57+
err = errors.Append(err,
58+
errors.Newf(a.Pos(), "previous declaration here"))
59+
return false, a, err
60+
}
61+
a = x
4862
}
49-
if a != nil {
50-
err := errors.Newf(d.Pos(), "multiple @if attributes")
51-
err = errors.Append(err,
52-
errors.Newf(a.Pos(), "previous declaration here"))
53-
return a, err
54-
}
55-
a = x
56-
5763
case *ast.Package:
58-
return a, nil
64+
return false, a, nil
65+
case *ast.CommentGroup:
66+
default:
67+
// If it's anything else, then we know we won't see a package
68+
// clause so avoid scanning more than we need to (this
69+
// could be a large file with no package clause)
70+
return false, a, nil
5971
}
6072
}
61-
return a, nil
73+
return false, a, nil
6274
}
6375

6476
func shouldInclude(expr ast.Expr, tagIsSet func(key string) bool) (bool, errors.Error) {

internal/buildattr/buildattr_test.go

+14-19
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,8 @@ package something
265265
266266
package something
267267
`,
268-
wantOK: true,
268+
wantOK: false,
269+
wantAttr: "@ignore()",
269270
}, {
270271
testName: "IgnoreWithBuildAttrs",
271272
syntax: `
@@ -274,12 +275,14 @@ package something
274275
275276
package something
276277
`,
277-
wantOK: false,
278-
wantTagCalls: map[string]bool{
279-
"blah": true,
280-
},
281-
wantAttr: "@if(blah)",
278+
wantOK: false,
279+
wantAttr: "@ignore()",
282280
}, {
281+
// It's arguable whether multiple @if attributes
282+
// should be an error when there's an @ignore
283+
// attribute, but it's easily worked around by
284+
// putting the @ignore attribute first, which should
285+
// be fairly intuitive.
283286
testName: "IgnoreWithMultipleEarlierIfs",
284287
syntax: `
285288
@if(foo)
@@ -304,31 +307,23 @@ multiple @if attributes:
304307
305308
package something
306309
`,
307-
wantOK: false,
308-
wantError: `previous declaration here:
309-
testfile.cue:3:1
310-
multiple @if attributes:
311-
testfile.cue:4:1
312-
`,
313-
wantAttr: "@if(foo)",
310+
wantOK: false,
311+
wantAttr: "@ignore()",
314312
}, {
315313
testName: "IgnoreWithoutPackageClause",
316314
syntax: `
317315
@ignore()
318316
a: 5
319317
`,
320-
wantOK: true,
318+
wantOK: false,
319+
wantAttr: "@ignore()",
321320
}, {
322321
testName: "IfAfterDeclaration",
323322
syntax: `
324323
a: 1
325324
@if(foo)
326325
`,
327-
wantOK: false,
328-
wantTagCalls: map[string]bool{
329-
"foo": true,
330-
},
331-
wantAttr: "@if(foo)",
326+
wantOK: true,
332327
}}
333328

334329
func TestShouldBuildFile(t *testing.T) {

internal/mod/modload/tidy.go

+20-4
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,10 @@ func tidy(ctx context.Context, fsys fs.FS, modRoot string, reg Registry, checkTi
6666
}
6767
// TODO check that module path is well formed etc
6868
origRs := modrequirements.NewRequirements(mf.QualifiedModule(), reg, mf.DepVersions(), mf.DefaultMajorVersions())
69-
// Note: we can just ignore build tags and the fact that we might
69+
// Note: we can ignore build tags and the fact that we might
7070
// have _tool.cue and _test.cue files, because we want to include
71-
// all of them.
72-
rootPkgPaths, err := modimports.AllImports(modimports.AllModuleFiles(fsys, modRoot))
71+
// all of those, but we do need to consider @ignore() attributes.
72+
rootPkgPaths, err := modimports.AllImports(withoutIgnoredFiles(modimports.AllModuleFiles(fsys, modRoot)))
7373
if err != nil {
7474
return nil, err
7575
}
@@ -175,7 +175,11 @@ func modfileFromRequirements(old *modfile.File, rs *modrequirements.Requirements
175175
//
176176
// In general a file should always be considered unless it's a _tool.cue file
177177
// that's not in the main module.
178-
func (ld *loader) shouldIncludePkgFile(pkgPath string, mod module.Version, fsys fs.FS, mf modimports.ModuleFile) bool {
178+
func (ld *loader) shouldIncludePkgFile(pkgPath string, mod module.Version, fsys fs.FS, mf modimports.ModuleFile) (_ok bool) {
179+
if buildattr.ShouldIgnoreFile(mf.Syntax) {
180+
// The file is marked to be explicitly ignored.
181+
return false
182+
}
179183
if mod.Path() == ld.mainModule.Path() {
180184
// All files in the main module are considered.
181185
return true
@@ -716,6 +720,18 @@ func (ld *loader) spotCheckRoots(ctx context.Context, rs *modrequirements.Requir
716720
return true
717721
}
718722

723+
func withoutIgnoredFiles(iter func(func(modimports.ModuleFile, error) bool)) func(func(modimports.ModuleFile, error) bool) {
724+
return func(yield func(modimports.ModuleFile, error) bool) {
725+
// TODO for mf, err := range iter {
726+
iter(func(mf modimports.ModuleFile, err error) bool {
727+
if err == nil && buildattr.ShouldIgnoreFile(mf.Syntax) {
728+
return true
729+
}
730+
return yield(mf, err)
731+
})
732+
}
733+
}
734+
719735
func logf(f string, a ...any) {
720736
if logging {
721737
log.Printf(f, a...)

0 commit comments

Comments
 (0)