feat: check build constraints in filenames and comments (#144)
This commit is contained in:
committed by
Ludovic Fernandez
parent
d055747bef
commit
10a8312d2c
13
_test/build0.go
Normal file
13
_test/build0.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// A test program
|
||||
|
||||
// +build darwin,linux !arm
|
||||
// +build go1.12 !go1.13
|
||||
|
||||
package main
|
||||
|
||||
func main() {
|
||||
println("hello world")
|
||||
}
|
||||
|
||||
// Output:
|
||||
// hello world
|
||||
@@ -310,17 +310,17 @@ func (interp *Interpreter) firstToken(src string) token.Token {
|
||||
return tok
|
||||
}
|
||||
|
||||
// Note: no type analysis is performed at this stage, it is done in pre-order processing
|
||||
// of CFG, in order to accommodate forward type declarations
|
||||
// Note: no type analysis is performed at this stage, it is done in pre-order
|
||||
// processing of CFG, in order to accommodate forward type declarations
|
||||
|
||||
// ast parses src string containing Go code and generates the corresponding AST.
|
||||
// The package name and the AST root node are returned.
|
||||
func (interp *Interpreter) ast(src, name string) (string, *Node, error) {
|
||||
var inFunc bool
|
||||
|
||||
// Allow incremental parsing of declarations or statements, by inserting them in a pseudo
|
||||
// file package or function.
|
||||
// Those statements or declarations will be always evaluated in the global scope
|
||||
// Allow incremental parsing of declarations or statements, by inserting
|
||||
// them in a pseudo file package or function. Those statements or
|
||||
// declarations will be always evaluated in the global scope
|
||||
switch interp.firstToken(src) {
|
||||
case token.PACKAGE:
|
||||
// nothing to do
|
||||
@@ -331,6 +331,10 @@ func (interp *Interpreter) ast(src, name string) (string, *Node, error) {
|
||||
src = "package _; func _() {" + src + "}"
|
||||
}
|
||||
|
||||
if !interp.buildOk(name, src) {
|
||||
return "", nil, nil // skip source not matching build constraints
|
||||
}
|
||||
|
||||
f, err := parser.ParseFile(interp.fset, name, src, 0)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
|
||||
149
interp/build.go
Normal file
149
interp/build.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package interp
|
||||
|
||||
import (
|
||||
"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 {
|
||||
// Extract comments before the first clause
|
||||
f, err := parser.ParseFile(interp.fset, name, src, parser.PackageClauseOnly|parser.ParseComments)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
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) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// buildLineOk returns true if line is not a build constraint or
|
||||
// if build constraint is satisfied
|
||||
func buildLineOk(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 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// buildOptionOk return true if all comma separated tags match, false otherwise
|
||||
func buildOptionOk(tag string) bool {
|
||||
// in option, evaluate the AND of individual tags
|
||||
for _, t := range strings.Split(tag, ",") {
|
||||
if !buildTagOk(t) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var (
|
||||
goos = runtime.GOOS
|
||||
goarch = runtime.GOARCH
|
||||
goversion = goNumVersion()
|
||||
)
|
||||
|
||||
// buildTagOk returns true if a build tag matches, false otherwise
|
||||
// if first character is !, result is negated
|
||||
func buildTagOk(s string) (r bool) {
|
||||
not := s[0] == '!'
|
||||
if not {
|
||||
s = s[1:]
|
||||
}
|
||||
switch {
|
||||
case s == goos:
|
||||
r = true
|
||||
case s == 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
|
||||
}
|
||||
}
|
||||
if not {
|
||||
r = !r
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// goNumVersion returns the go minor version number
|
||||
func goNumVersion() int {
|
||||
v := strings.Split(runtime.Version(), ".")
|
||||
n, _ := strconv.Atoi(v[1])
|
||||
return n
|
||||
}
|
||||
|
||||
// skipFile returns true if file should be skipped
|
||||
func skipFile(p string) bool {
|
||||
if !strings.HasSuffix(p, ".go") {
|
||||
return true
|
||||
}
|
||||
p = strings.TrimSuffix(path.Base(p), ".go")
|
||||
if strings.HasSuffix(p, "_test") {
|
||||
return true
|
||||
}
|
||||
i := strings.Index(p, "_")
|
||||
if i < 0 {
|
||||
return false
|
||||
}
|
||||
a := strings.Split(p[i+1:], "_")
|
||||
last := len(a) - 1
|
||||
if last1 := last - 1; last1 >= 0 && a[last1] == goos && a[last] == goarch {
|
||||
return false
|
||||
}
|
||||
if s := a[last]; s != goos && s != goarch && knownOs[s] || knownArch[s] {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var knownOs = map[string]bool{
|
||||
"aix": true,
|
||||
"android": true,
|
||||
"darwin": true,
|
||||
"dragonfly": true,
|
||||
"freebsd": true,
|
||||
"js": true,
|
||||
"linux": true,
|
||||
"nacl": true,
|
||||
"netbsd": true,
|
||||
"openbsd": true,
|
||||
"plan9": true,
|
||||
"solaris": true,
|
||||
"windows": true,
|
||||
}
|
||||
|
||||
var knownArch = map[string]bool{
|
||||
"386": true,
|
||||
"amd64": true,
|
||||
"amd64p32": true,
|
||||
"arm": true,
|
||||
"arm64": true,
|
||||
"mips": true,
|
||||
"mips64": true,
|
||||
"mips64le": true,
|
||||
"mipsle": true,
|
||||
"ppc64": true,
|
||||
"ppc64le": true,
|
||||
"s390x": true,
|
||||
"wasm": true,
|
||||
}
|
||||
74
interp/build_test.go
Normal file
74
interp/build_test.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package interp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testBuild struct {
|
||||
src string
|
||||
res bool
|
||||
}
|
||||
|
||||
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 }()
|
||||
|
||||
tests := []testBuild{
|
||||
{"// +build linux", true},
|
||||
{"// +build windows", false},
|
||||
{"// +build go1.11", true},
|
||||
{"// +build !go1.12", true},
|
||||
{"// +build go1.12", false},
|
||||
{"// +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(Opt{})
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
src := test.src + "\npackage x"
|
||||
t.Run("", func(t *testing.T) {
|
||||
if r := i.buildOk("", src); r != test.res {
|
||||
t.Errorf("got %v, want %v", r, test.res)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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 }()
|
||||
|
||||
tests := []testBuild{
|
||||
{"foo/bar_linux_amd64.go", false},
|
||||
{"foo/bar.go", false},
|
||||
{"bar.go", false},
|
||||
{"bar_linux.go", false},
|
||||
{"bar_maix.go", false},
|
||||
{"bar_mlinux.go", false},
|
||||
{"bar_aix_foo.go", false},
|
||||
{"bar_aix_s390x.go", true},
|
||||
{"bar_aix_amd64.go", true},
|
||||
{"bar_linux_arm.go", true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.src, func(t *testing.T) {
|
||||
if r := skipFile(test.src); r != test.res {
|
||||
t.Errorf("got %v, want %v", r, test.res)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -194,7 +194,7 @@ func (i *Interpreter) Eval(src string) (reflect.Value, error) {
|
||||
|
||||
// Parse source to AST
|
||||
pkgName, root, err := i.ast(src, i.Name)
|
||||
if err != nil {
|
||||
if err != nil || root == nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
|
||||
@@ -28,10 +28,7 @@ func (i *Interpreter) importSrcFile(rPath, path, alias string) error {
|
||||
// Parse source files
|
||||
for _, file := range files {
|
||||
name := file.Name()
|
||||
if len(name) <= 3 || name[len(name)-3:] != ".go" {
|
||||
continue
|
||||
}
|
||||
if len(name) > 8 && name[len(name)-8:] == "_test.go" {
|
||||
if skipFile(name) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -45,6 +42,9 @@ func (i *Interpreter) importSrcFile(rPath, path, alias string) error {
|
||||
if pname, root, err = i.ast(string(buf), name); err != nil {
|
||||
return err
|
||||
}
|
||||
if root == nil {
|
||||
continue
|
||||
}
|
||||
if pkgName == "" {
|
||||
pkgName = pname
|
||||
} else if pkgName != pname {
|
||||
|
||||
Reference in New Issue
Block a user