interp/build: support custom build constraints.
This commit is contained in:
committed by
Traefiker Bot
parent
ee81ee7fea
commit
458e8e911a
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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"))
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user