// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package constraint implements parsing and evaluation of build constraint lines. // See https://golang.org/cmd/go/#hdr-Build_constraints for documentation about build constraints themselves. // // This package parses both the original “// +build” syntax and the “//go:build” syntax that was added in Go 1.17. // See https://golang.org/design/draft-gobuild for details about the “//go:build” syntax.
package constraint import ( ) // An Expr is a build tag constraint expression. // The underlying concrete type is *[AndExpr], *[OrExpr], *[NotExpr], or *[TagExpr]. type Expr interface { // String returns the string form of the expression, // using the boolean syntax used in //go:build lines. String() string // Eval reports whether the expression evaluates to true. // It calls ok(tag) as needed to find out whether a given build tag // is satisfied by the current build configuration. Eval(ok func(tag string) bool) bool // The presence of an isExpr method explicitly marks the type as an Expr. // Only implementations in this package should be used as Exprs. isExpr() } // A TagExpr is an [Expr] for the single tag Tag. type TagExpr struct { Tag string // for example, “linux” or “cgo” } func ( *TagExpr) () {} func ( *TagExpr) ( func( string) bool) bool { return (.Tag) } func ( *TagExpr) () string { return .Tag } func tag( string) Expr { return &TagExpr{} } // A NotExpr represents the expression !X (the negation of X). type NotExpr struct { X Expr } func ( *NotExpr) () {} func ( *NotExpr) ( func( string) bool) bool { return !.X.Eval() } func ( *NotExpr) () string { := .X.String() switch .X.(type) { case *AndExpr, *OrExpr: = "(" + + ")" } return "!" + } func not( Expr) Expr { return &NotExpr{} } // An AndExpr represents the expression X && Y. type AndExpr struct { X, Y Expr } func ( *AndExpr) () {} func ( *AndExpr) ( func( string) bool) bool { // Note: Eval both, to make sure ok func observes all tags. := .X.Eval() := .Y.Eval() return && } func ( *AndExpr) () string { return andArg(.X) + " && " + andArg(.Y) } func andArg( Expr) string { := .String() if , := .(*OrExpr); { = "(" + + ")" } return } func and(, Expr) Expr { return &AndExpr{, } } // An OrExpr represents the expression X || Y. type OrExpr struct { X, Y Expr } func ( *OrExpr) () {} func ( *OrExpr) ( func( string) bool) bool { // Note: Eval both, to make sure ok func observes all tags. := .X.Eval() := .Y.Eval() return || } func ( *OrExpr) () string { return orArg(.X) + " || " + orArg(.Y) } func orArg( Expr) string { := .String() if , := .(*AndExpr); { = "(" + + ")" } return } func or(, Expr) Expr { return &OrExpr{, } } // A SyntaxError reports a syntax error in a parsed build expression. type SyntaxError struct { Offset int // byte offset in input where error was detected Err string // description of error } func ( *SyntaxError) () string { return .Err } var errNotConstraint = errors.New("not a build constraint") // Parse parses a single build constraint line of the form “//go:build ...” or “// +build ...” // and returns the corresponding boolean expression. func ( string) (Expr, error) { if , := splitGoBuild(); { return parseExpr() } if , := splitPlusBuild(); { return parsePlusBuildExpr(), nil } return nil, errNotConstraint } // IsGoBuild reports whether the line of text is a “//go:build” constraint. // It only checks the prefix of the text, not that the expression itself parses. func ( string) bool { , := splitGoBuild() return } // splitGoBuild splits apart the leading //go:build prefix in line from the build expression itself. // It returns "", false if the input is not a //go:build line or if the input contains multiple lines. func splitGoBuild( string) ( string, bool) { // A single trailing newline is OK; otherwise multiple lines are not. if len() > 0 && [len()-1] == '\n' { = [:len()-1] } if strings.Contains(, "\n") { return "", false } if !strings.HasPrefix(, "//go:build") { return "", false } = strings.TrimSpace() = [len("//go:build"):] // If strings.TrimSpace finds more to trim after removing the //go:build prefix, // it means that the prefix was followed by a space, making this a //go:build line // (as opposed to a //go:buildsomethingelse line). // If line is empty, we had "//go:build" by itself, which also counts. := strings.TrimSpace() if len() == len() && != "" { return "", false } return , true } // An exprParser holds state for parsing a build expression. type exprParser struct { s string // input string i int // next read location in s tok string // last token read isTag bool pos int // position (start) of last token } // parseExpr parses a boolean build tag expression. func parseExpr( string) ( Expr, error) { defer func() { if := recover(); != nil { if , := .(*SyntaxError); { = return } panic() // unreachable unless parser has a bug } }() := &exprParser{s: } = .or() if .tok != "" { panic(&SyntaxError{Offset: .pos, Err: "unexpected token " + .tok}) } return , nil } // or parses a sequence of || expressions. // On entry, the next input token has not yet been lexed. // On exit, the next input token has been lexed and is in p.tok. func ( *exprParser) () Expr { := .and() for .tok == "||" { = or(, .and()) } return } // and parses a sequence of && expressions. // On entry, the next input token has not yet been lexed. // On exit, the next input token has been lexed and is in p.tok. func ( *exprParser) () Expr { := .not() for .tok == "&&" { = and(, .not()) } return } // not parses a ! expression. // On entry, the next input token has not yet been lexed. // On exit, the next input token has been lexed and is in p.tok. func ( *exprParser) () Expr { .lex() if .tok == "!" { .lex() if .tok == "!" { panic(&SyntaxError{Offset: .pos, Err: "double negation not allowed"}) } return not(.atom()) } return .atom() } // atom parses a tag or a parenthesized expression. // On entry, the next input token HAS been lexed. // On exit, the next input token has been lexed and is in p.tok. func ( *exprParser) () Expr { // first token already in p.tok if .tok == "(" { := .pos defer func() { if := recover(); != nil { if , := .(*SyntaxError); && .Err == "unexpected end of expression" { .Err = "missing close paren" } panic() } }() := .or() if .tok != ")" { panic(&SyntaxError{Offset: , Err: "missing close paren"}) } .lex() return } if !.isTag { if .tok == "" { panic(&SyntaxError{Offset: .pos, Err: "unexpected end of expression"}) } panic(&SyntaxError{Offset: .pos, Err: "unexpected token " + .tok}) } := .tok .lex() return tag() } // lex finds and consumes the next token in the input stream. // On return, p.tok is set to the token text, // p.isTag reports whether the token was a tag, // and p.pos records the byte offset of the start of the token in the input stream. // If lex reaches the end of the input, p.tok is set to the empty string. // For any other syntax error, lex panics with a SyntaxError. func ( *exprParser) () { .isTag = false for .i < len(.s) && (.s[.i] == ' ' || .s[.i] == '\t') { .i++ } if .i >= len(.s) { .tok = "" .pos = .i return } switch .s[.i] { case '(', ')', '!': .pos = .i .i++ .tok = .s[.pos:.i] return case '&', '|': if .i+1 >= len(.s) || .s[.i+1] != .s[.i] { panic(&SyntaxError{Offset: .i, Err: "invalid syntax at " + string(rune(.s[.i]))}) } .pos = .i .i += 2 .tok = .s[.pos:.i] return } := .s[.i:] for , := range { if !unicode.IsLetter() && !unicode.IsDigit() && != '_' && != '.' { = [:] break } } if == "" { , := utf8.DecodeRuneInString(.s[.i:]) panic(&SyntaxError{Offset: .i, Err: "invalid syntax at " + string()}) } .pos = .i .i += len() .tok = .s[.pos:.i] .isTag = true } // IsPlusBuild reports whether the line of text is a “// +build” constraint. // It only checks the prefix of the text, not that the expression itself parses. func ( string) bool { , := splitPlusBuild() return } // splitPlusBuild splits apart the leading // +build prefix in line from the build expression itself. // It returns "", false if the input is not a // +build line or if the input contains multiple lines. func splitPlusBuild( string) ( string, bool) { // A single trailing newline is OK; otherwise multiple lines are not. if len() > 0 && [len()-1] == '\n' { = [:len()-1] } if strings.Contains(, "\n") { return "", false } if !strings.HasPrefix(, "//") { return "", false } = [len("//"):] // Note the space is optional; "//+build" is recognized too. = strings.TrimSpace() if !strings.HasPrefix(, "+build") { return "", false } = [len("+build"):] // If strings.TrimSpace finds more to trim after removing the +build prefix, // it means that the prefix was followed by a space, making this a +build line // (as opposed to a +buildsomethingelse line). // If line is empty, we had "// +build" by itself, which also counts. := strings.TrimSpace() if len() == len() && != "" { return "", false } return , true } // parsePlusBuildExpr parses a legacy build tag expression (as used with “// +build”). func parsePlusBuildExpr( string) Expr { var Expr for , := range strings.Fields() { var Expr for , := range strings.Split(, ",") { var Expr var bool if strings.HasPrefix(, "!!") || == "!" { = tag("ignore") } else { if strings.HasPrefix(, "!") { = true = [len("!"):] } if isValidTag() { = tag() } else { = tag("ignore") } if { = not() } } if == nil { = } else { = and(, ) } } if == nil { = } else { = or(, ) } } if == nil { = tag("ignore") } return } // isValidTag reports whether the word is a valid build tag. // Tags must be letters, digits, underscores or dots. // Unlike in Go identifiers, all digits are fine (e.g., "386"). func isValidTag( string) bool { if == "" { return false } for , := range { if !unicode.IsLetter() && !unicode.IsDigit() && != '_' && != '.' { return false } } return true } var errComplex = errors.New("expression too complex for // +build lines") // PlusBuildLines returns a sequence of “// +build” lines that evaluate to the build expression x. // If the expression is too complex to convert directly to “// +build” lines, PlusBuildLines returns an error. func ( Expr) ([]string, error) { // Push all NOTs to the expression leaves, so that //go:build !(x && y) can be treated as !x || !y. // This rewrite is both efficient and commonly needed, so it's worth doing. // Essentially all other possible rewrites are too expensive and too rarely needed. = pushNot(, false) // Split into AND of ORs of ANDs of literals (tag or NOT tag). var [][][]Expr for , := range appendSplitAnd(nil, ) { var [][]Expr for , := range appendSplitOr(nil, ) { var []Expr for , := range appendSplitAnd(nil, ) { switch .(type) { case *TagExpr, *NotExpr: = append(, ) default: return nil, errComplex } } = append(, ) } = append(, ) } // If all the ORs have length 1 (no actual OR'ing going on), // push the top-level ANDs to the bottom level, so that we get // one // +build line instead of many. := 0 for , := range { if < len() { = len() } } if == 1 { var []Expr for , := range { = append(, [0]...) } = [][][]Expr{{}} } // Prepare the +build lines. var []string for , := range { := "// +build" for , := range { := "" for , := range { if > 0 { += "," } += .String() } += " " + } = append(, ) } return , nil } // pushNot applies DeMorgan's law to push negations down the expression, // so that only tags are negated in the result. // (It applies the rewrites !(X && Y) => (!X || !Y) and !(X || Y) => (!X && !Y).) func pushNot( Expr, bool) Expr { switch x := .(type) { default: // unreachable return case *NotExpr: if , := .X.(*TagExpr); && ! { return } return (.X, !) case *TagExpr: if { return &NotExpr{X: } } return case *AndExpr: := (.X, ) := (.Y, ) if { return or(, ) } if == .X && == .Y { return } return and(, ) case *OrExpr: := (.X, ) := (.Y, ) if { return and(, ) } if == .X && == .Y { return } return or(, ) } } // appendSplitAnd appends x to list while splitting apart any top-level && expressions. // For example, appendSplitAnd({W}, X && Y && Z) = {W, X, Y, Z}. func appendSplitAnd( []Expr, Expr) []Expr { if , := .(*AndExpr); { = (, .X) = (, .Y) return } return append(, ) } // appendSplitOr appends x to list while splitting apart any top-level || expressions. // For example, appendSplitOr({W}, X || Y || Z) = {W, X, Y, Z}. func appendSplitOr( []Expr, Expr) []Expr { if , := .(*OrExpr); { = (, .X) = (, .Y) return } return append(, ) }