interp/build: support custom build constraints.

This commit is contained in:
Ludovic Fernandez
2019-07-31 18:00:05 +02:00
committed by Traefiker Bot
parent ee81ee7fea
commit 458e8e911a
5 changed files with 55 additions and 72 deletions

View File

@@ -333,7 +333,7 @@ func (interp *Interpreter) ast(src, name string) (string, *node, error) {
src = "package main; func main() {" + src + "}"
}
if !interp.buildOk(name, src) {
if !interp.buildOk(interp.context, name, src) {
return "", nil, nil // skip source not matching build constraints
}

View File

@@ -4,14 +4,13 @@ import (
"go/build"
"go/parser"
"path"
"runtime"
"strconv"
"strings"
)
// buildOk returns true if a file or script matches build constraints
// as specified in https://golang.org/pkg/go/build/#hdr-Build_Constraints
func (interp *Interpreter) buildOk(name, src string) bool {
func (interp *Interpreter) buildOk(ctx build.Context, name, src string) bool {
// Extract comments before the first clause
f, err := parser.ParseFile(interp.fset, name, src, parser.PackageClauseOnly|parser.ParseComments)
if err != nil {
@@ -20,7 +19,7 @@ func (interp *Interpreter) buildOk(name, src string) bool {
for _, g := range f.Comments {
// in file, evaluate the AND of multiple line build constraints
for _, line := range strings.Split(strings.TrimSpace(g.Text()), "\n") {
if !buildLineOk(line) {
if !buildLineOk(ctx, line) {
return false
}
}
@@ -30,14 +29,14 @@ func (interp *Interpreter) buildOk(name, src string) bool {
// buildLineOk returns true if line is not a build constraint or
// if build constraint is satisfied
func buildLineOk(line string) (ok bool) {
func buildLineOk(ctx build.Context, line string) (ok bool) {
if len(line) < 7 || line[:7] != "+build " {
return true
}
// In line, evaluate the OR of space-separated options
options := strings.Split(strings.TrimSpace(line[6:]), " ")
for _, o := range options {
if ok = buildOptionOk(o); ok {
if ok = buildOptionOk(ctx, o); ok {
break
}
}
@@ -45,39 +44,35 @@ func buildLineOk(line string) (ok bool) {
}
// buildOptionOk return true if all comma separated tags match, false otherwise
func buildOptionOk(tag string) bool {
func buildOptionOk(ctx build.Context, tag string) bool {
// in option, evaluate the AND of individual tags
for _, t := range strings.Split(tag, ",") {
if !buildTagOk(t) {
if !buildTagOk(ctx, t) {
return false
}
}
return true
}
var (
goos = runtime.GOOS
goarch = runtime.GOARCH
goversion = goMinorVersion(build.Default)
)
// buildTagOk returns true if a build tag matches, false otherwise
// if first character is !, result is negated
func buildTagOk(s string) (r bool) {
func buildTagOk(ctx build.Context, s string) (r bool) {
not := s[0] == '!'
if not {
s = s[1:]
}
switch {
case s == goos:
case contains(ctx.BuildTags, s):
r = true
case s == goarch:
case s == ctx.GOOS:
r = true
case s == ctx.GOARCH:
r = true
case len(s) > 4 && s[:4] == "go1.":
if n, err := strconv.Atoi(s[4:]); err != nil {
r = false
} else {
r = goversion >= n
r = goMinorVersion(ctx) >= n
}
}
if not {
@@ -86,6 +81,15 @@ func buildTagOk(s string) (r bool) {
return
}
func contains(tags []string, tag string) bool {
for _, t := range tags {
if t == tag {
return true
}
}
return false
}
// goMinorVersion returns the go minor version number
func goMinorVersion(ctx build.Context) int {
current := ctx.ReleaseTags[len(ctx.ReleaseTags)-1]
@@ -103,7 +107,7 @@ func goMinorVersion(ctx build.Context) int {
}
// skipFile returns true if file should be skipped
func skipFile(p string) bool {
func skipFile(ctx build.Context, p string) bool {
if !strings.HasSuffix(p, ".go") {
return true
}
@@ -117,10 +121,10 @@ func skipFile(p string) bool {
}
a := strings.Split(p[i+1:], "_")
last := len(a) - 1
if last1 := last - 1; last1 >= 0 && a[last1] == goos && a[last] == goarch {
if last1 := last - 1; last1 >= 0 && a[last1] == ctx.GOOS && a[last] == ctx.GOARCH {
return false
}
if s := a[last]; s != goos && s != goarch && knownOs[s] || knownArch[s] {
if s := a[last]; s != ctx.GOOS && s != ctx.GOARCH && knownOs[s] || knownArch[s] {
return true
}
return false

View File

@@ -2,7 +2,6 @@ package interp
import (
"go/build"
"math"
"testing"
)
@@ -13,9 +12,12 @@ type testBuild struct {
func TestBuildTag(t *testing.T) {
// Assume a specific OS, arch and go version no matter the real underlying system
oo, oa, ov := goos, goarch, goversion
goos, goarch, goversion = "linux", "amd64", 11
defer func() { goos, goarch, goversion = oo, oa, ov }()
ctx := build.Context{
GOARCH: "amd64",
GOOS: "linux",
BuildTags: []string{"foo"},
ReleaseTags: []string{"go1.11"},
}
tests := []testBuild{
{"// +build linux", true},
@@ -32,6 +34,9 @@ func TestBuildTag(t *testing.T) {
{"// +build linux\n// +build amd64", true},
{"// +build linux\n\n\n// +build amd64", true},
{"// +build linux\n// +build i386", false},
{"// +build foo", true},
{"// +build !foo", false},
{"// +build bar", false},
}
i := New(Options{})
@@ -39,42 +44,7 @@ func TestBuildTag(t *testing.T) {
test := test
src := test.src + "\npackage x"
t.Run(test.src, func(t *testing.T) {
if r := i.buildOk("", src); r != test.res {
t.Errorf("got %v, want %v", r, test.res)
}
})
}
}
func TestBuildTagDevel(t *testing.T) {
// Assume a specific OS, arch and go version no matter the real underlying system
oo, oa, ov := goos, goarch, goversion
goos, goarch, goversion = "linux", "amd64", math.MaxInt16
defer func() { goos, goarch, goversion = oo, oa, ov }()
tests := []testBuild{
{"// +build linux", true},
{"// +build windows", false},
{"// +build go1.11", true},
{"// +build !go1.12", false},
{"// +build go1.12", true},
{"// +build !go1.10", false},
{"// +build go1.9", true},
{"// +build ignore", false},
{"// +build linux,amd64", true},
{"// +build linux,i386", false},
{"// +build linux,i386 go1.11", true},
{"// +build linux\n// +build amd64", true},
{"// +build linux\n\n\n// +build amd64", true},
{"// +build linux\n// +build i386", false},
}
i := New(Options{})
for _, test := range tests {
test := test
src := test.src + "\npackage x"
t.Run(test.src, func(t *testing.T) {
if r := i.buildOk("", src); r != test.res {
if r := i.buildOk(ctx, "", src); r != test.res {
t.Errorf("got %v, want %v", r, test.res)
}
})
@@ -83,9 +53,10 @@ func TestBuildTagDevel(t *testing.T) {
func TestBuildFile(t *testing.T) {
// Assume a specific OS, arch and go pattern no matter the real underlying system
oo, oa := goos, goarch
goos, goarch = "linux", "amd64"
defer func() { goos, goarch = oo, oa }()
ctx := build.Context{
GOARCH: "amd64",
GOOS: "linux",
}
tests := []testBuild{
{"foo/bar_linux_amd64.go", false},
@@ -103,7 +74,7 @@ func TestBuildFile(t *testing.T) {
for _, test := range tests {
test := test
t.Run(test.src, func(t *testing.T) {
if r := skipFile(test.src); r != test.res {
if r := skipFile(ctx, test.src); r != test.res {
t.Errorf("got %v, want %v", r, test.res)
}
})

View File

@@ -3,6 +3,7 @@ package interp
import (
"bufio"
"fmt"
"go/build"
"go/scanner"
"go/token"
"os"
@@ -58,10 +59,10 @@ type Exports map[string]map[string]reflect.Value
// opt stores interpreter options
type opt struct {
astDot bool // display AST graph (debug)
cfgDot bool // display CFG graph (debug)
noRun bool // compile, but do not run
goPath string // custom GOPATH
astDot bool // display AST graph (debug)
cfgDot bool // display CFG graph (debug)
noRun bool // compile, but do not run
context build.Context // build context: GOPATH, build constraints
}
// Interpreter contains global resources and state
@@ -118,12 +119,14 @@ func (n *node) Walk(in func(n *node) bool, out func(n *node)) {
type Options struct {
// GoPath sets GOPATH for the interpreter
GoPath string
// BuildTags sets build constraints for the interpreter
BuildTags []string
}
// New returns a new interpreter
func New(options Options) *Interpreter {
i := Interpreter{
opt: opt{goPath: options.GoPath},
opt: opt{context: build.Default},
fset: token.NewFileSet(),
universe: initUniverse(),
scopes: map[string]*scope{},
@@ -131,6 +134,11 @@ func New(options Options) *Interpreter {
frame: &frame{data: []reflect.Value{}},
}
i.opt.context.GOPATH = options.GoPath
if len(options.BuildTags) > 0 {
i.opt.context.BuildTags = options.BuildTags
}
// AstDot activates AST graph display for the interpreter
i.opt.astDot, _ = strconv.ParseBool(os.Getenv("YAEGI_AST_DOT"))

View File

@@ -22,7 +22,7 @@ func (interp *Interpreter) importSrcFile(rPath, path, alias string) error {
rPath = "."
}
dir = filepath.Join(filepath.Dir(interp.Name), rPath, path)
} else if dir, rPath, err = pkgDir(interp.goPath, rPath, path); err != nil {
} else if dir, rPath, err = pkgDir(interp.context.GOPATH, rPath, path); err != nil {
return err
}
@@ -40,7 +40,7 @@ func (interp *Interpreter) importSrcFile(rPath, path, alias string) error {
// Parse source files
for _, file := range files {
name := file.Name()
if skipFile(name) {
if skipFile(interp.context, name) {
continue
}