Files
Jamie LiuandgVisor bot 19238c44db 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
2021-07-27 18:14:05 -07:00

170 lines
4.6 KiB
Go

// 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()
}