mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
load("//tools:defs.bzl", "go_library", "go_test")
|
||||
|
||||
package(licenses = ["notice"])
|
||||
|
||||
go_library(
|
||||
name = "constraintutil",
|
||||
srcs = ["constraintutil.go"],
|
||||
marshal = False,
|
||||
stateify = False,
|
||||
visibility = ["//tools:__subpackages__"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "constraintutil_test",
|
||||
size = "small",
|
||||
srcs = ["constraintutil_test.go"],
|
||||
library = ":constraintutil",
|
||||
)
|
||||
@@ -0,0 +1,169 @@
|
||||
// Copyright 2021 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package constraintutil provides utilities for working with Go build
|
||||
// constraints.
|
||||
package constraintutil
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/build/constraint"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// FromReader extracts the build constraint from the Go source or assembly file
|
||||
// whose contents are read by r.
|
||||
func FromReader(r io.Reader) (constraint.Expr, error) {
|
||||
// See go/build.parseFileHeader() for the "official" logic that this is
|
||||
// derived from.
|
||||
const (
|
||||
slashStar = "/*"
|
||||
starSlash = "*/"
|
||||
gobuildPrefix = "//go:build"
|
||||
)
|
||||
s := bufio.NewScanner(r)
|
||||
var (
|
||||
inSlashStar = false // between /* and */
|
||||
haveGobuild = false
|
||||
e constraint.Expr
|
||||
)
|
||||
Lines:
|
||||
for s.Scan() {
|
||||
line := bytes.TrimSpace(s.Bytes())
|
||||
if !inSlashStar && constraint.IsGoBuild(string(line)) {
|
||||
if haveGobuild {
|
||||
return nil, fmt.Errorf("multiple go:build directives")
|
||||
}
|
||||
haveGobuild = true
|
||||
var err error
|
||||
e, err = constraint.Parse(string(line))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
ThisLine:
|
||||
for len(line) > 0 {
|
||||
if inSlashStar {
|
||||
if i := bytes.Index(line, []byte(starSlash)); i >= 0 {
|
||||
inSlashStar = false
|
||||
line = bytes.TrimSpace(line[i+len(starSlash):])
|
||||
continue ThisLine
|
||||
}
|
||||
continue Lines
|
||||
}
|
||||
if bytes.HasPrefix(line, []byte("//")) {
|
||||
continue Lines
|
||||
}
|
||||
// Note that if /* appears in the line, but not at the beginning,
|
||||
// then the line is still non-empty, so skipping this and
|
||||
// terminating below is correct.
|
||||
if bytes.HasPrefix(line, []byte(slashStar)) {
|
||||
inSlashStar = true
|
||||
line = bytes.TrimSpace(line[len(slashStar):])
|
||||
continue ThisLine
|
||||
}
|
||||
// A non-empty non-comment line terminates scanning for go:build.
|
||||
break Lines
|
||||
}
|
||||
}
|
||||
return e, s.Err()
|
||||
}
|
||||
|
||||
// FromString extracts the build constraint from the Go source or assembly file
|
||||
// containing the given data. If no build constraint applies to the file, it
|
||||
// returns nil.
|
||||
func FromString(str string) (constraint.Expr, error) {
|
||||
return FromReader(strings.NewReader(str))
|
||||
}
|
||||
|
||||
// FromFile extracts the build constraint from the Go source or assembly file
|
||||
// at the given path. If no build constraint applies to the file, it returns
|
||||
// nil.
|
||||
func FromFile(path string) (constraint.Expr, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
return FromReader(f)
|
||||
}
|
||||
|
||||
// Combine returns a constraint.Expr that evaluates to true iff all expressions
|
||||
// in es evaluate to true. If es is empty, Combine returns nil.
|
||||
//
|
||||
// Preconditions: All constraint.Exprs in es are non-nil.
|
||||
func Combine(es []constraint.Expr) constraint.Expr {
|
||||
switch len(es) {
|
||||
case 0:
|
||||
return nil
|
||||
case 1:
|
||||
return es[0]
|
||||
default:
|
||||
a := &constraint.AndExpr{es[0], es[1]}
|
||||
for i := 2; i < len(es); i++ {
|
||||
a = &constraint.AndExpr{a, es[i]}
|
||||
}
|
||||
return a
|
||||
}
|
||||
}
|
||||
|
||||
// CombineFromFiles returns a build constraint expression that evaluates to
|
||||
// true iff the build constraints from all of the given Go source or assembly
|
||||
// files evaluate to true. If no build constraints apply to any of the given
|
||||
// files, it returns nil.
|
||||
func CombineFromFiles(paths []string) (constraint.Expr, error) {
|
||||
var es []constraint.Expr
|
||||
for _, path := range paths {
|
||||
e, err := FromFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read build constraints from %q: %v", path, err)
|
||||
}
|
||||
if e != nil {
|
||||
es = append(es, e)
|
||||
}
|
||||
}
|
||||
return Combine(es), nil
|
||||
}
|
||||
|
||||
// Lines returns a string containing build constraint directives for the given
|
||||
// constraint.Expr, including two trailing newlines, as appropriate for a Go
|
||||
// source or assembly file. At least a go:build directive will be emitted; if
|
||||
// the constraint is expressible using +build directives as well, then +build
|
||||
// directives will also be emitted.
|
||||
//
|
||||
// If e is nil, Lines returns the empty string.
|
||||
func Lines(e constraint.Expr) string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteString("//go:build ")
|
||||
b.WriteString(e.String())
|
||||
b.WriteByte('\n')
|
||||
|
||||
if pblines, err := constraint.PlusBuildLines(e); err == nil {
|
||||
for _, line := range pblines {
|
||||
b.WriteString(line)
|
||||
b.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
|
||||
b.WriteByte('\n')
|
||||
return b.String()
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
// Copyright 2021 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package constraintutil
|
||||
|
||||
import (
|
||||
"go/build/constraint"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFileParsing(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
data string
|
||||
expr string
|
||||
}{
|
||||
{
|
||||
name: "Empty",
|
||||
},
|
||||
{
|
||||
name: "NoConstraint",
|
||||
data: "// copyright header\n\npackage main",
|
||||
},
|
||||
{
|
||||
name: "ConstraintOnFirstLine",
|
||||
data: "//go:build amd64\n#include \"textflag.h\"",
|
||||
expr: "amd64",
|
||||
},
|
||||
{
|
||||
name: "ConstraintAfterSlashSlashComment",
|
||||
data: "// copyright header\n\n//go:build linux\n\npackage newlib",
|
||||
expr: "linux",
|
||||
},
|
||||
{
|
||||
name: "ConstraintAfterSlashStarComment",
|
||||
data: "/*\ncopyright header\n*/\n\n//go:build !race\n\npackage oldlib",
|
||||
expr: "!race",
|
||||
},
|
||||
{
|
||||
name: "ConstraintInSlashSlashComment",
|
||||
data: "// blah blah //go:build windows",
|
||||
},
|
||||
{
|
||||
name: "ConstraintInSlashStarComment",
|
||||
data: "/*\n//go:build windows\n*/",
|
||||
},
|
||||
{
|
||||
name: "ConstraintAfterPackageClause",
|
||||
data: "package oops\n//go:build race",
|
||||
},
|
||||
{
|
||||
name: "ConstraintAfterCppInclude",
|
||||
data: "#include \"textflag.h\"\n//go:build arm64",
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
e, err := FromString(test.data)
|
||||
if err != nil {
|
||||
t.Fatalf("FromString(%q) failed: %v", test.data, err)
|
||||
}
|
||||
if e == nil {
|
||||
if len(test.expr) != 0 {
|
||||
t.Errorf("FromString(%q): got no constraint, wanted %q", test.data, test.expr)
|
||||
}
|
||||
} else {
|
||||
got := e.String()
|
||||
if len(test.expr) == 0 {
|
||||
t.Errorf("FromString(%q): got %q, wanted no constraint", test.data, got)
|
||||
} else if got != test.expr {
|
||||
t.Errorf("FromString(%q): got %q, wanted %q", test.data, got, test.expr)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCombine(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
in []string
|
||||
out string
|
||||
}{
|
||||
{
|
||||
name: "0",
|
||||
},
|
||||
{
|
||||
name: "1",
|
||||
in: []string{"amd64 || arm64"},
|
||||
out: "amd64 || arm64",
|
||||
},
|
||||
{
|
||||
name: "2",
|
||||
in: []string{"amd64", "amd64 && linux"},
|
||||
out: "amd64 && amd64 && linux",
|
||||
},
|
||||
{
|
||||
name: "3",
|
||||
in: []string{"amd64", "amd64 || arm64", "amd64 || riscv64"},
|
||||
out: "amd64 && (amd64 || arm64) && (amd64 || riscv64)",
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
inexprs := make([]constraint.Expr, 0, len(test.in))
|
||||
for _, estr := range test.in {
|
||||
line := "//go:build " + estr
|
||||
e, err := constraint.Parse(line)
|
||||
if err != nil {
|
||||
t.Fatalf("constraint.Parse(%q) failed: %v", line, err)
|
||||
}
|
||||
inexprs = append(inexprs, e)
|
||||
}
|
||||
outexpr := Combine(inexprs)
|
||||
if outexpr == nil {
|
||||
if len(test.out) != 0 {
|
||||
t.Errorf("Combine(%v): got no constraint, wanted %q", test.in, test.out)
|
||||
}
|
||||
} else {
|
||||
got := outexpr.String()
|
||||
if len(test.out) == 0 {
|
||||
t.Errorf("Combine(%v): got %q, wanted no constraint", test.in, got)
|
||||
} else if got != test.out {
|
||||
t.Errorf("Combine(%v): got %q, wanted %q", test.in, got, test.out)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,6 @@ go_binary(
|
||||
srcs = ["main.go"],
|
||||
visibility = ["//:sandbox"],
|
||||
deps = [
|
||||
"//tools/tags",
|
||||
"//tools/constraintutil",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -25,9 +25,8 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gvisor.dev/gvisor/tools/tags"
|
||||
"gvisor.dev/gvisor/tools/constraintutil"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -131,6 +130,12 @@ func main() {
|
||||
}
|
||||
f.Decls = newDecls
|
||||
|
||||
// Infer build constraints for the output file.
|
||||
bcexpr, err := constraintutil.CombineFromFiles(flag.Args())
|
||||
if err != nil {
|
||||
fatalf("Failed to read build constraints: %v\n", err)
|
||||
}
|
||||
|
||||
// Write the output file.
|
||||
var buf bytes.Buffer
|
||||
if err := format.Node(&buf, fset, f); err != nil {
|
||||
@@ -141,9 +146,7 @@ func main() {
|
||||
fatalf("opening output: %v\n", err)
|
||||
}
|
||||
defer outf.Close()
|
||||
if t := tags.Aggregate(flag.Args()); len(t) > 0 {
|
||||
fmt.Fprintf(outf, "%s\n\n", strings.Join(t.Lines(), "\n"))
|
||||
}
|
||||
outf.WriteString(constraintutil.Lines(bcexpr))
|
||||
if _, err := outf.Write(buf.Bytes()); err != nil {
|
||||
fatalf("write: %v\n", err)
|
||||
}
|
||||
|
||||
@@ -18,5 +18,5 @@ go_library(
|
||||
visibility = [
|
||||
"//:sandbox",
|
||||
],
|
||||
deps = ["//tools/tags"],
|
||||
deps = ["//tools/constraintutil"],
|
||||
)
|
||||
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"gvisor.dev/gvisor/tools/tags"
|
||||
"gvisor.dev/gvisor/tools/constraintutil"
|
||||
)
|
||||
|
||||
// List of identifiers we use in generated code that may conflict with a
|
||||
@@ -123,16 +123,18 @@ func (g *Generator) writeHeader() error {
|
||||
var b sourceBuffer
|
||||
b.emit("// Automatically generated marshal implementation. See tools/go_marshal.\n\n")
|
||||
|
||||
// Emit build tags.
|
||||
b.emit("// If there are issues with build tag aggregation, see\n")
|
||||
b.emit("// tools/go_marshal/gomarshal/generator.go:writeHeader(). The build tags here\n")
|
||||
b.emit("// come from the input set of files used to generate this file. This input set\n")
|
||||
b.emit("// is filtered based on pre-defined file suffixes related to build tags, see \n")
|
||||
b.emit("// tools/defs.bzl:calculate_sets().\n\n")
|
||||
|
||||
if t := tags.Aggregate(g.inputs); len(t) > 0 {
|
||||
b.emit(strings.Join(t.Lines(), "\n"))
|
||||
b.emit("\n\n")
|
||||
bcexpr, err := constraintutil.CombineFromFiles(g.inputs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bcexpr != nil {
|
||||
// Emit build constraints.
|
||||
b.emit("// If there are issues with build constraint aggregation, see\n")
|
||||
b.emit("// tools/go_marshal/gomarshal/generator.go:writeHeader(). The constraints here\n")
|
||||
b.emit("// come from the input set of files used to generate this file. This input set\n")
|
||||
b.emit("// is filtered based on pre-defined file suffixes related to build constraints,\n")
|
||||
b.emit("// see tools/defs.bzl:calculate_sets().\n\n")
|
||||
b.emit(constraintutil.Lines(bcexpr))
|
||||
}
|
||||
|
||||
// Package header.
|
||||
@@ -553,11 +555,12 @@ func (g *Generator) writeTests(ts []*testGenerator) error {
|
||||
b.reset()
|
||||
b.emit("// Automatically generated marshal tests. See tools/go_marshal.\n\n")
|
||||
|
||||
// Emit build tags.
|
||||
if t := tags.Aggregate(g.inputs); len(t) > 0 {
|
||||
b.emit(strings.Join(t.Lines(), "\n"))
|
||||
b.emit("\n\n")
|
||||
// Emit build constraints.
|
||||
bcexpr, err := constraintutil.CombineFromFiles(g.inputs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.emit(constraintutil.Lines(bcexpr))
|
||||
|
||||
b.emit("package %s\n\n", g.pkg)
|
||||
if err := b.write(g.outputTest); err != nil {
|
||||
|
||||
@@ -6,7 +6,7 @@ go_binary(
|
||||
name = "stateify",
|
||||
srcs = ["main.go"],
|
||||
visibility = ["//:sandbox"],
|
||||
deps = ["//tools/tags"],
|
||||
deps = ["//tools/constraintutil"],
|
||||
)
|
||||
|
||||
bzl_library(
|
||||
|
||||
@@ -28,7 +28,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"gvisor.dev/gvisor/tools/tags"
|
||||
"gvisor.dev/gvisor/tools/constraintutil"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -214,10 +214,13 @@ func main() {
|
||||
// Automated warning.
|
||||
fmt.Fprint(outputFile, "// automatically generated by stateify.\n\n")
|
||||
|
||||
// Emit build tags.
|
||||
if t := tags.Aggregate(flag.Args()); len(t) > 0 {
|
||||
fmt.Fprintf(outputFile, "%s\n\n", strings.Join(t.Lines(), "\n"))
|
||||
// Emit build constraints.
|
||||
bcexpr, err := constraintutil.CombineFromFiles(flag.Args())
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to infer build constraints: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
outputFile.WriteString(constraintutil.Lines(bcexpr))
|
||||
|
||||
// Emit the package name.
|
||||
_, pkg := filepath.Split(*fullPkg)
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
load("//tools:defs.bzl", "go_library")
|
||||
|
||||
package(licenses = ["notice"])
|
||||
|
||||
go_library(
|
||||
name = "tags",
|
||||
srcs = ["tags.go"],
|
||||
marshal = False,
|
||||
stateify = False,
|
||||
visibility = ["//tools:__subpackages__"],
|
||||
)
|
||||
@@ -1,89 +0,0 @@
|
||||
// Copyright 2020 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package tags is a utility for parsing build tags.
|
||||
package tags
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// OrSet is a set of tags on a single line.
|
||||
//
|
||||
// Note that tags may include ",", and we don't distinguish this case in the
|
||||
// logic below. Ideally, this constraints can be split into separate top-level
|
||||
// build tags in order to resolve any issues.
|
||||
type OrSet []string
|
||||
|
||||
// Line returns the line for this or.
|
||||
func (or OrSet) Line() string {
|
||||
return fmt.Sprintf("// +build %s", strings.Join([]string(or), " "))
|
||||
}
|
||||
|
||||
// AndSet is the set of all OrSets.
|
||||
type AndSet []OrSet
|
||||
|
||||
// Lines returns the lines to be printed.
|
||||
func (and AndSet) Lines() (ls []string) {
|
||||
for _, or := range and {
|
||||
ls = append(ls, or.Line())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Join joins this AndSet with another.
|
||||
func (and AndSet) Join(other AndSet) AndSet {
|
||||
return append(and, other...)
|
||||
}
|
||||
|
||||
// Tags returns the unique set of +build tags.
|
||||
//
|
||||
// Derived form the runtime's canBuild.
|
||||
func Tags(file string) (tags AndSet) {
|
||||
data, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
// Check file contents for // +build lines.
|
||||
for _, p := range strings.Split(string(data), "\n") {
|
||||
p = strings.TrimSpace(p)
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
if !strings.HasPrefix(p, "//") {
|
||||
break
|
||||
}
|
||||
if !strings.Contains(p, "+build") {
|
||||
continue
|
||||
}
|
||||
fields := strings.Fields(p[2:])
|
||||
if len(fields) < 1 || fields[0] != "+build" {
|
||||
continue
|
||||
}
|
||||
tags = append(tags, OrSet(fields[1:]))
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
// Aggregate aggregates all tags from a set of files.
|
||||
//
|
||||
// Note that these may be in conflict, in which case the build will fail.
|
||||
func Aggregate(files []string) (tags AndSet) {
|
||||
for _, file := range files {
|
||||
tags = tags.Join(Tags(file))
|
||||
}
|
||||
return tags
|
||||
}
|
||||
Reference in New Issue
Block a user