Skip to content

Commit 964fb3c

Browse files
nixprimegvisor-bot
authored andcommitted
Use go:build directives in generated files.
Build constraints are now inferred from go:build directives rather than +build directives. +build directives are still emitted in generated files as required in Go 1.16 and earlier. Note that go/build/constraint was added in Go 1.16, so gVisor now requires Go 1.16. PiperOrigin-RevId: 387240779
1 parent 9a96e00 commit 964fb3c

File tree

11 files changed

+361
-127
lines changed

11 files changed

+361
-127
lines changed

tools/constraintutil/BUILD

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
load("//tools:defs.bzl", "go_library", "go_test")
2+
3+
package(licenses = ["notice"])
4+
5+
go_library(
6+
name = "constraintutil",
7+
srcs = ["constraintutil.go"],
8+
marshal = False,
9+
stateify = False,
10+
visibility = ["//tools:__subpackages__"],
11+
)
12+
13+
go_test(
14+
name = "constraintutil_test",
15+
size = "small",
16+
srcs = ["constraintutil_test.go"],
17+
library = ":constraintutil",
18+
)
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
// Copyright 2021 The gVisor Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package constraintutil provides utilities for working with Go build
16+
// constraints.
17+
package constraintutil
18+
19+
import (
20+
"bufio"
21+
"bytes"
22+
"fmt"
23+
"go/build/constraint"
24+
"io"
25+
"os"
26+
"strings"
27+
)
28+
29+
// FromReader extracts the build constraint from the Go source or assembly file
30+
// whose contents are read by r.
31+
func FromReader(r io.Reader) (constraint.Expr, error) {
32+
// See go/build.parseFileHeader() for the "official" logic that this is
33+
// derived from.
34+
const (
35+
slashStar = "/*"
36+
starSlash = "*/"
37+
gobuildPrefix = "//go:build"
38+
)
39+
s := bufio.NewScanner(r)
40+
var (
41+
inSlashStar = false // between /* and */
42+
haveGobuild = false
43+
e constraint.Expr
44+
)
45+
Lines:
46+
for s.Scan() {
47+
line := bytes.TrimSpace(s.Bytes())
48+
if !inSlashStar && constraint.IsGoBuild(string(line)) {
49+
if haveGobuild {
50+
return nil, fmt.Errorf("multiple go:build directives")
51+
}
52+
haveGobuild = true
53+
var err error
54+
e, err = constraint.Parse(string(line))
55+
if err != nil {
56+
return nil, err
57+
}
58+
}
59+
ThisLine:
60+
for len(line) > 0 {
61+
if inSlashStar {
62+
if i := bytes.Index(line, []byte(starSlash)); i >= 0 {
63+
inSlashStar = false
64+
line = bytes.TrimSpace(line[i+len(starSlash):])
65+
continue ThisLine
66+
}
67+
continue Lines
68+
}
69+
if bytes.HasPrefix(line, []byte("//")) {
70+
continue Lines
71+
}
72+
// Note that if /* appears in the line, but not at the beginning,
73+
// then the line is still non-empty, so skipping this and
74+
// terminating below is correct.
75+
if bytes.HasPrefix(line, []byte(slashStar)) {
76+
inSlashStar = true
77+
line = bytes.TrimSpace(line[len(slashStar):])
78+
continue ThisLine
79+
}
80+
// A non-empty non-comment line terminates scanning for go:build.
81+
break Lines
82+
}
83+
}
84+
return e, s.Err()
85+
}
86+
87+
// FromString extracts the build constraint from the Go source or assembly file
88+
// containing the given data. If no build constraint applies to the file, it
89+
// returns nil.
90+
func FromString(str string) (constraint.Expr, error) {
91+
return FromReader(strings.NewReader(str))
92+
}
93+
94+
// FromFile extracts the build constraint from the Go source or assembly file
95+
// at the given path. If no build constraint applies to the file, it returns
96+
// nil.
97+
func FromFile(path string) (constraint.Expr, error) {
98+
f, err := os.Open(path)
99+
if err != nil {
100+
return nil, err
101+
}
102+
defer f.Close()
103+
return FromReader(f)
104+
}
105+
106+
// Combine returns a constraint.Expr that evaluates to true iff all expressions
107+
// in es evaluate to true. If es is empty, Combine returns nil.
108+
//
109+
// Preconditions: All constraint.Exprs in es are non-nil.
110+
func Combine(es []constraint.Expr) constraint.Expr {
111+
switch len(es) {
112+
case 0:
113+
return nil
114+
case 1:
115+
return es[0]
116+
default:
117+
a := &constraint.AndExpr{es[0], es[1]}
118+
for i := 2; i < len(es); i++ {
119+
a = &constraint.AndExpr{a, es[i]}
120+
}
121+
return a
122+
}
123+
}
124+
125+
// CombineFromFiles returns a build constraint expression that evaluates to
126+
// true iff the build constraints from all of the given Go source or assembly
127+
// files evaluate to true. If no build constraints apply to any of the given
128+
// files, it returns nil.
129+
func CombineFromFiles(paths []string) (constraint.Expr, error) {
130+
var es []constraint.Expr
131+
for _, path := range paths {
132+
e, err := FromFile(path)
133+
if err != nil {
134+
return nil, fmt.Errorf("failed to read build constraints from %q: %v", path, err)
135+
}
136+
if e != nil {
137+
es = append(es, e)
138+
}
139+
}
140+
return Combine(es), nil
141+
}
142+
143+
// Lines returns a string containing build constraint directives for the given
144+
// constraint.Expr, including two trailing newlines, as appropriate for a Go
145+
// source or assembly file. At least a go:build directive will be emitted; if
146+
// the constraint is expressible using +build directives as well, then +build
147+
// directives will also be emitted.
148+
//
149+
// If e is nil, Lines returns the empty string.
150+
func Lines(e constraint.Expr) string {
151+
if e == nil {
152+
return ""
153+
}
154+
155+
var b strings.Builder
156+
b.WriteString("//go:build ")
157+
b.WriteString(e.String())
158+
b.WriteByte('\n')
159+
160+
if pblines, err := constraint.PlusBuildLines(e); err == nil {
161+
for _, line := range pblines {
162+
b.WriteString(line)
163+
b.WriteByte('\n')
164+
}
165+
}
166+
167+
b.WriteByte('\n')
168+
return b.String()
169+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// Copyright 2021 The gVisor Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package constraintutil
16+
17+
import (
18+
"go/build/constraint"
19+
"testing"
20+
)
21+
22+
func TestFileParsing(t *testing.T) {
23+
for _, test := range []struct {
24+
name string
25+
data string
26+
expr string
27+
}{
28+
{
29+
name: "Empty",
30+
},
31+
{
32+
name: "NoConstraint",
33+
data: "// copyright header\n\npackage main",
34+
},
35+
{
36+
name: "ConstraintOnFirstLine",
37+
data: "//go:build amd64\n#include \"textflag.h\"",
38+
expr: "amd64",
39+
},
40+
{
41+
name: "ConstraintAfterSlashSlashComment",
42+
data: "// copyright header\n\n//go:build linux\n\npackage newlib",
43+
expr: "linux",
44+
},
45+
{
46+
name: "ConstraintAfterSlashStarComment",
47+
data: "/*\ncopyright header\n*/\n\n//go:build !race\n\npackage oldlib",
48+
expr: "!race",
49+
},
50+
{
51+
name: "ConstraintInSlashSlashComment",
52+
data: "// blah blah //go:build windows",
53+
},
54+
{
55+
name: "ConstraintInSlashStarComment",
56+
data: "/*\n//go:build windows\n*/",
57+
},
58+
{
59+
name: "ConstraintAfterPackageClause",
60+
data: "package oops\n//go:build race",
61+
},
62+
{
63+
name: "ConstraintAfterCppInclude",
64+
data: "#include \"textflag.h\"\n//go:build arm64",
65+
},
66+
} {
67+
t.Run(test.name, func(t *testing.T) {
68+
e, err := FromString(test.data)
69+
if err != nil {
70+
t.Fatalf("FromString(%q) failed: %v", test.data, err)
71+
}
72+
if e == nil {
73+
if len(test.expr) != 0 {
74+
t.Errorf("FromString(%q): got no constraint, wanted %q", test.data, test.expr)
75+
}
76+
} else {
77+
got := e.String()
78+
if len(test.expr) == 0 {
79+
t.Errorf("FromString(%q): got %q, wanted no constraint", test.data, got)
80+
} else if got != test.expr {
81+
t.Errorf("FromString(%q): got %q, wanted %q", test.data, got, test.expr)
82+
}
83+
}
84+
})
85+
}
86+
}
87+
88+
func TestCombine(t *testing.T) {
89+
for _, test := range []struct {
90+
name string
91+
in []string
92+
out string
93+
}{
94+
{
95+
name: "0",
96+
},
97+
{
98+
name: "1",
99+
in: []string{"amd64 || arm64"},
100+
out: "amd64 || arm64",
101+
},
102+
{
103+
name: "2",
104+
in: []string{"amd64", "amd64 && linux"},
105+
out: "amd64 && amd64 && linux",
106+
},
107+
{
108+
name: "3",
109+
in: []string{"amd64", "amd64 || arm64", "amd64 || riscv64"},
110+
out: "amd64 && (amd64 || arm64) && (amd64 || riscv64)",
111+
},
112+
} {
113+
t.Run(test.name, func(t *testing.T) {
114+
inexprs := make([]constraint.Expr, 0, len(test.in))
115+
for _, estr := range test.in {
116+
line := "//go:build " + estr
117+
e, err := constraint.Parse(line)
118+
if err != nil {
119+
t.Fatalf("constraint.Parse(%q) failed: %v", line, err)
120+
}
121+
inexprs = append(inexprs, e)
122+
}
123+
outexpr := Combine(inexprs)
124+
if outexpr == nil {
125+
if len(test.out) != 0 {
126+
t.Errorf("Combine(%v): got no constraint, wanted %q", test.in, test.out)
127+
}
128+
} else {
129+
got := outexpr.String()
130+
if len(test.out) == 0 {
131+
t.Errorf("Combine(%v): got %q, wanted no constraint", test.in, got)
132+
} else if got != test.out {
133+
t.Errorf("Combine(%v): got %q, wanted %q", test.in, got, test.out)
134+
}
135+
}
136+
})
137+
}
138+
}

tools/go_generics/go_merge/BUILD

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ go_binary(
77
srcs = ["main.go"],
88
visibility = ["//:sandbox"],
99
deps = [
10-
"//tools/tags",
10+
"//tools/constraintutil",
1111
],
1212
)

tools/go_generics/go_merge/main.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,8 @@ import (
2525
"os"
2626
"path/filepath"
2727
"strconv"
28-
"strings"
2928

30-
"gvisor.dev/gvisor/tools/tags"
29+
"gvisor.dev/gvisor/tools/constraintutil"
3130
)
3231

3332
var (
@@ -131,6 +130,12 @@ func main() {
131130
}
132131
f.Decls = newDecls
133132

133+
// Infer build constraints for the output file.
134+
bcexpr, err := constraintutil.CombineFromFiles(flag.Args())
135+
if err != nil {
136+
fatalf("Failed to read build constraints: %v\n", err)
137+
}
138+
134139
// Write the output file.
135140
var buf bytes.Buffer
136141
if err := format.Node(&buf, fset, f); err != nil {
@@ -141,9 +146,7 @@ func main() {
141146
fatalf("opening output: %v\n", err)
142147
}
143148
defer outf.Close()
144-
if t := tags.Aggregate(flag.Args()); len(t) > 0 {
145-
fmt.Fprintf(outf, "%s\n\n", strings.Join(t.Lines(), "\n"))
146-
}
149+
outf.WriteString(constraintutil.Lines(bcexpr))
147150
if _, err := outf.Write(buf.Bytes()); err != nil {
148151
fatalf("write: %v\n", err)
149152
}

tools/go_marshal/gomarshal/BUILD

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,5 @@ go_library(
1818
visibility = [
1919
"//:sandbox",
2020
],
21-
deps = ["//tools/tags"],
21+
deps = ["//tools/constraintutil"],
2222
)

0 commit comments

Comments
 (0)