Skip to content

Commit 5b76343

Browse files
committed
go/build: prefer //go:build over // +build lines
Part of //go:build change (#41184). See https://golang.org/design/draft-gobuild - Reject files with multiple //go:build lines. - If a file has both //go:build and // +build lines, only use the //go:build line. - Otherwise fall back to // +build lines - Use go/build/constraint for parsing both //go:build and // +build lines. For Go 1.17. Change-Id: I32e2404d8ce266230f767718dc7cc24e77b425e8 Reviewed-on: https://go-review.googlesource.com/c/go/+/240607 Trust: Russ Cox <[email protected]> Run-TryBot: Russ Cox <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Jay Conrod <[email protected]>
1 parent a8942d2 commit 5b76343

File tree

3 files changed

+127
-102
lines changed

3 files changed

+127
-102
lines changed

src/go/build/build.go

Lines changed: 66 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"errors"
1010
"fmt"
1111
"go/ast"
12+
"go/build/constraint"
1213
"go/doc"
1314
"go/token"
1415
exec "internal/execabs"
@@ -1423,7 +1424,7 @@ func (ctxt *Context) matchFile(dir, name string, allTags map[string]bool, binary
14231424
// Look for +build comments to accept or reject the file.
14241425
ok, sawBinaryOnly, err := ctxt.shouldBuild(info.header, allTags)
14251426
if err != nil {
1426-
return nil, err
1427+
return nil, fmt.Errorf("%s: %v", name, err)
14271428
}
14281429
if !ok && !ctxt.UseAllFiles {
14291430
return nil, nil
@@ -1459,11 +1460,12 @@ var (
14591460
bSlashSlash = []byte(slashSlash)
14601461
bStarSlash = []byte(starSlash)
14611462
bSlashStar = []byte(slashStar)
1463+
bPlusBuild = []byte("+build")
14621464

14631465
goBuildComment = []byte("//go:build")
14641466

14651467
errGoBuildWithoutBuild = errors.New("//go:build comment without // +build comment")
1466-
errMultipleGoBuild = errors.New("multiple //go:build comments") // unused in Go 1.(N-1)
1468+
errMultipleGoBuild = errors.New("multiple //go:build comments")
14671469
)
14681470

14691471
func isGoBuildComment(line []byte) bool {
@@ -1498,53 +1500,50 @@ var binaryOnlyComment = []byte("//go:binary-only-package")
14981500
// shouldBuild reports whether the file should be built
14991501
// and whether a //go:binary-only-package comment was found.
15001502
func (ctxt *Context) shouldBuild(content []byte, allTags map[string]bool) (shouldBuild, binaryOnly bool, err error) {
1501-
1502-
// Pass 1. Identify leading run of // comments and blank lines,
1503+
// Identify leading run of // comments and blank lines,
15031504
// which must be followed by a blank line.
15041505
// Also identify any //go:build comments.
15051506
content, goBuild, sawBinaryOnly, err := parseFileHeader(content)
15061507
if err != nil {
15071508
return false, false, err
15081509
}
15091510

1510-
// Pass 2. Process each +build line in the run.
1511-
p := content
1512-
shouldBuild = true
1513-
sawBuild := false
1514-
for len(p) > 0 {
1515-
line := p
1516-
if i := bytes.IndexByte(line, '\n'); i >= 0 {
1517-
line, p = line[:i], p[i+1:]
1518-
} else {
1519-
p = p[len(p):]
1520-
}
1521-
line = bytes.TrimSpace(line)
1522-
if !bytes.HasPrefix(line, bSlashSlash) {
1523-
continue
1511+
// If //go:build line is present, it controls.
1512+
// Otherwise fall back to +build processing.
1513+
switch {
1514+
case goBuild != nil:
1515+
x, err := constraint.Parse(string(goBuild))
1516+
if err != nil {
1517+
return false, false, fmt.Errorf("parsing //go:build line: %v", err)
15241518
}
1525-
line = bytes.TrimSpace(line[len(bSlashSlash):])
1526-
if len(line) > 0 && line[0] == '+' {
1527-
// Looks like a comment +line.
1528-
f := strings.Fields(string(line))
1529-
if f[0] == "+build" {
1530-
sawBuild = true
1531-
ok := false
1532-
for _, tok := range f[1:] {
1533-
if ctxt.match(tok, allTags) {
1534-
ok = true
1535-
}
1536-
}
1537-
if !ok {
1519+
shouldBuild = ctxt.eval(x, allTags)
1520+
1521+
default:
1522+
shouldBuild = true
1523+
p := content
1524+
for len(p) > 0 {
1525+
line := p
1526+
if i := bytes.IndexByte(line, '\n'); i >= 0 {
1527+
line, p = line[:i], p[i+1:]
1528+
} else {
1529+
p = p[len(p):]
1530+
}
1531+
line = bytes.TrimSpace(line)
1532+
if !bytes.HasPrefix(line, bSlashSlash) || !bytes.Contains(line, bPlusBuild) {
1533+
continue
1534+
}
1535+
text := string(line)
1536+
if !constraint.IsPlusBuild(text) {
1537+
continue
1538+
}
1539+
if x, err := constraint.Parse(text); err == nil {
1540+
if !ctxt.eval(x, allTags) {
15381541
shouldBuild = false
15391542
}
15401543
}
15411544
}
15421545
}
15431546

1544-
if goBuild != nil && !sawBuild {
1545-
return false, false, errGoBuildWithoutBuild
1546-
}
1547-
15481547
return shouldBuild, sawBinaryOnly, nil
15491548
}
15501549

@@ -1580,7 +1579,7 @@ Lines:
15801579
}
15811580

15821581
if !inSlashStar && isGoBuildComment(line) {
1583-
if false && goBuild != nil { // enabled in Go 1.N
1582+
if goBuild != nil {
15841583
return nil, nil, false, errMultipleGoBuild
15851584
}
15861585
goBuild = line
@@ -1649,7 +1648,7 @@ func (ctxt *Context) saveCgo(filename string, di *Package, cg *ast.CommentGroup)
16491648
if len(cond) > 0 {
16501649
ok := false
16511650
for _, c := range cond {
1652-
if ctxt.match(c, nil) {
1651+
if ctxt.matchAuto(c, nil) {
16531652
ok = true
16541653
break
16551654
}
@@ -1831,50 +1830,44 @@ func splitQuoted(s string) (r []string, err error) {
18311830
return args, err
18321831
}
18331832

1834-
// match reports whether the name is one of:
1833+
// matchAuto interprets text as either a +build or //go:build expression (whichever works),
1834+
// reporting whether the expression matches the build context.
18351835
//
1836+
// matchAuto is only used for testing of tag evaluation
1837+
// and in #cgo lines, which accept either syntax.
1838+
func (ctxt *Context) matchAuto(text string, allTags map[string]bool) bool {
1839+
if strings.ContainsAny(text, "&|()") {
1840+
text = "//go:build " + text
1841+
} else {
1842+
text = "// +build " + text
1843+
}
1844+
x, err := constraint.Parse(text)
1845+
if err != nil {
1846+
return false
1847+
}
1848+
return ctxt.eval(x, allTags)
1849+
}
1850+
1851+
func (ctxt *Context) eval(x constraint.Expr, allTags map[string]bool) bool {
1852+
return x.Eval(func(tag string) bool { return ctxt.matchTag(tag, allTags) })
1853+
}
1854+
1855+
// matchTag reports whether the name is one of:
1856+
//
1857+
// cgo (if cgo is enabled)
18361858
// $GOOS
18371859
// $GOARCH
1838-
// cgo (if cgo is enabled)
1839-
// !cgo (if cgo is disabled)
18401860
// ctxt.Compiler
1841-
// !ctxt.Compiler
1861+
// linux (if GOOS = android)
1862+
// solaris (if GOOS = illumos)
18421863
// tag (if tag is listed in ctxt.BuildTags or ctxt.ReleaseTags)
1843-
// !tag (if tag is not listed in ctxt.BuildTags or ctxt.ReleaseTags)
1844-
// a comma-separated list of any of these
18451864
//
1846-
func (ctxt *Context) match(name string, allTags map[string]bool) bool {
1847-
if name == "" {
1848-
if allTags != nil {
1849-
allTags[name] = true
1850-
}
1851-
return false
1852-
}
1853-
if i := strings.Index(name, ","); i >= 0 {
1854-
// comma-separated list
1855-
ok1 := ctxt.match(name[:i], allTags)
1856-
ok2 := ctxt.match(name[i+1:], allTags)
1857-
return ok1 && ok2
1858-
}
1859-
if strings.HasPrefix(name, "!!") { // bad syntax, reject always
1860-
return false
1861-
}
1862-
if strings.HasPrefix(name, "!") { // negation
1863-
return len(name) > 1 && !ctxt.match(name[1:], allTags)
1864-
}
1865-
1865+
// It records all consulted tags in allTags.
1866+
func (ctxt *Context) matchTag(name string, allTags map[string]bool) bool {
18661867
if allTags != nil {
18671868
allTags[name] = true
18681869
}
18691870

1870-
// Tags must be letters, digits, underscores or dots.
1871-
// Unlike in Go identifiers, all digits are fine (e.g., "386").
1872-
for _, c := range name {
1873-
if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
1874-
return false
1875-
}
1876-
}
1877-
18781871
// special tags
18791872
if ctxt.CgoEnabled && name == "cgo" {
18801873
return true
@@ -1946,10 +1939,10 @@ func (ctxt *Context) goodOSArchFile(name string, allTags map[string]bool) bool {
19461939
}
19471940
n := len(l)
19481941
if n >= 2 && knownOS[l[n-2]] && knownArch[l[n-1]] {
1949-
return ctxt.match(l[n-1], allTags) && ctxt.match(l[n-2], allTags)
1942+
return ctxt.matchTag(l[n-1], allTags) && ctxt.matchTag(l[n-2], allTags)
19501943
}
19511944
if n >= 1 && (knownOS[l[n-1]] || knownArch[l[n-1]]) {
1952-
return ctxt.match(l[n-1], allTags)
1945+
return ctxt.matchTag(l[n-1], allTags)
19531946
}
19541947
return true
19551948
}

0 commit comments

Comments
 (0)