diff --git a/buildtags/buildtags.go b/buildtags/buildtags.go index 977c6d2..fc03634 100644 --- a/buildtags/buildtags.go +++ b/buildtags/buildtags.go @@ -34,6 +34,7 @@ import ( type Interface interface { ConstraintsConvertable fmt.GoStringer + Evaluate(v map[string]bool) bool Validate() error } @@ -66,6 +67,15 @@ func (cs Constraints) Validate() error { } return nil } + +func (cs Constraints) Evaluate(v map[string]bool) bool { + r := true + for _, c := range cs { + r = r && c.Evaluate(v) + } + return r +} + func (cs Constraints) GoString() string { s := "" for _, c := range cs { @@ -86,6 +96,14 @@ func (c Constraint) Validate() error { return nil } +func (c Constraint) Evaluate(v map[string]bool) bool { + r := false + for _, o := range c { + r = r || o.Evaluate(v) + } + return r +} + func (c Constraint) GoString() string { s := "// +build" for _, o := range c { @@ -107,6 +125,14 @@ func (o Option) Validate() error { return nil } +func (o Option) Evaluate(v map[string]bool) bool { + r := true + for _, t := range o { + r = r && t.Evaluate(v) + } + return r +} + func (o Option) GoString() string { var ts []string for _, t := range o { @@ -119,22 +145,24 @@ func (t Term) ToConstraints() Constraints { return t.ToOption().ToConstraints() func (t Term) ToConstraint() Constraint { return t.ToOption().ToConstraint() } func (t Term) ToOption() Option { return Option{t} } -func (t Term) Validate() error { - name := string(t) +func (t Term) IsNegated() bool { return strings.HasPrefix(string(t), "!") } +func (t Term) Name() string { + return strings.TrimPrefix(string(t), "!") +} + +func (t Term) Validate() error { // Reference: https://github.com/golang/go/blob/204a8f55dc2e0ac8d27a781dab0da609b98560da/src/cmd/go/internal/imports/build.go#L110-L112 // // if strings.HasPrefix(name, "!!") { // bad syntax, reject always // return false // } // - if strings.HasPrefix(name, "!!") { + if strings.HasPrefix(string(t), "!!") { return errors.New("at most one '!' allowed") } - name = strings.TrimPrefix(name, "!") - - if len(name) == 0 { + if len(t.Name()) == 0 { return errors.New("empty tag name") } @@ -148,7 +176,7 @@ func (t Term) Validate() error { // } // } // - for _, c := range name { + for _, c := range t.Name() { if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' { return fmt.Errorf("character '%c' disallowed in tags", c) } @@ -157,6 +185,10 @@ func (t Term) Validate() error { return nil } +func (t Term) Evaluate(v map[string]bool) bool { + return (t.Validate() == nil) && (v[t.Name()] == !t.IsNegated()) +} + func (t Term) GoString() string { return string(t) } func Not(ident string) Term { @@ -202,3 +234,11 @@ func ParseConstraint(expr string) (Constraint, error) { } return c, nil } + +func SetTags(names ...string) map[string]bool { + v := map[string]bool{} + for _, n := range names { + v[n] = true + } + return v +} diff --git a/buildtags/buildtags_test.go b/buildtags/buildtags_test.go index 4c11b5b..daccd9e 100644 --- a/buildtags/buildtags_test.go +++ b/buildtags/buildtags_test.go @@ -65,10 +65,7 @@ func TestParseConstraintRoundTrip(t *testing.T) { "linux,386 darwin,!cgo", } for _, expr := range exprs { - c, err := ParseConstraint(expr) - if err != nil { - t.Fatalf("error parsing expression %q: %q", expr, err) - } + c := AssertParseConstraint(t, expr) got := c.GoString() expect := "// +build " + expr + "\n" if got != expect { @@ -84,3 +81,62 @@ func TestParseConstraintError(t *testing.T) { t.Fatalf("expected error parsing %q", expr) } } + +func TestEvaluate(t *testing.T) { + cases := []struct { + Constraint Interface + Values map[string]bool + Expect bool + }{ + {Term("a"), SetTags("a"), true}, + {Term("!a"), SetTags("a"), false}, + {Term("!a"), SetTags(), true}, + {Term("inval-id"), SetTags("inval-id"), false}, + + {Opt(Term("a"), Term("b")), SetTags(), false}, + {Opt(Term("a"), Term("b")), SetTags("a"), false}, + {Opt(Term("a"), Term("b")), SetTags("b"), false}, + {Opt(Term("a"), Term("b")), SetTags("a", "b"), true}, + {Opt(Term("a"), Term("b-a-d")), SetTags("a", "b-a-d"), false}, + + { + Any(Opt(Term("linux"), Term("386")), Opt("darwin", Not("cgo"))), + SetTags("linux", "386"), + true, + }, + { + Any(Opt(Term("linux"), Term("386")), Opt("darwin", Not("cgo"))), + SetTags("darwin"), + true, + }, + { + Any(Opt(Term("linux"), Term("386")), Opt("darwin", Not("cgo"))), + SetTags("linux", "darwin", "cgo"), + false, + }, + + { + And(Any(Term("linux"), Term("darwin")), Term("386")), + SetTags("darwin", "386"), + true, + }, + } + for _, c := range cases { + got := c.Constraint.Evaluate(c.Values) + if c.Constraint.Validate() != nil && got { + t.Fatal("invalid expressions must evaluate false") + } + if got != c.Expect { + t.Errorf("%#v evaluated with %#v got %v expect %v", c.Constraint, c.Values, got, c.Expect) + } + } +} + +func AssertParseConstraint(t *testing.T, expr string) Constraint { + t.Helper() + c, err := ParseConstraint(expr) + if err != nil { + t.Fatalf("error parsing expression %q: %q", expr, err) + } + return c +}