feature: add support for custom build tags

This commit is contained in:
Marc Vertes
2019-10-11 16:02:05 +02:00
committed by Traefiker Bot
parent 2765478137
commit 0b4dcbf7bb
11 changed files with 99 additions and 15 deletions

3
_test/ct/ct1.go Normal file
View File

@@ -0,0 +1,3 @@
package ct
func init() { println("hello from ct1") }

5
_test/ct/ct2.go Normal file
View File

@@ -0,0 +1,5 @@
// +build !dummy
package ct
func init() { println("hello from ct2") }

5
_test/ct/ct3.go Normal file
View File

@@ -0,0 +1,5 @@
// +build dummy
package ct
func init() { println("hello from ct3") }

15
_test/tag0.go Normal file
View File

@@ -0,0 +1,15 @@
// The following comment line has the same effect as 'go run -tags=dummy'
//yaegi:tags dummy
package main
import _ "github.com/containous/yaegi/_test/ct"
func main() {
println("bye")
}
// Output:
// hello from ct1
// hello from ct3
// bye

View File

@@ -18,7 +18,10 @@ at global level in an implicit main package.
Options:
-i
start an interactive REPL after file execution
start an interactive REPL after file execution.
-tags tag,list
a comma-separated list of build tags to consider satisfied during
the interpretation.
Debugging support (may be removed at any time):
YAEGI_AST_DOT=1
@@ -43,7 +46,9 @@ import (
func main() {
var interactive bool
var tags string
flag.BoolVar(&interactive, "i", false, "start an interactive REPL")
flag.StringVar(&tags, "tags", "", "set a list of build tags")
flag.Usage = func() {
fmt.Println("Usage:", os.Args[0], "[options] [script] [args]")
fmt.Println("Options:")
@@ -53,7 +58,7 @@ func main() {
args := flag.Args()
log.SetFlags(log.Lshortfile)
i := interp.New(interp.Options{GoPath: build.Default.GOPATH})
i := interp.New(interp.Options{GoPath: build.Default.GOPATH, BuildTags: strings.Split(tags, ",")})
i.Use(stdlib.Symbols)
i.Use(interp.Symbols)

View File

@@ -320,6 +320,7 @@ func (interp *Interpreter) firstToken(src string) token.Token {
func (interp *Interpreter) ast(src, name string) (string, *node, error) {
inRepl := name == ""
var inFunc bool
var mode parser.Mode
// Allow incremental parsing of declarations or statements, by inserting
// them in a pseudo file package or function. Those statements or
@@ -334,17 +335,21 @@ func (interp *Interpreter) ast(src, name string) (string, *node, error) {
inFunc = true
src = "package main; func main() {" + src + "}"
}
// Parse comments in REPL mode, to allow tag setting
mode |= parser.ParseComments
}
if ok, err := interp.buildOk(interp.context, name, src); !ok || err != nil {
if ok, err := interp.buildOk(&interp.context, name, src); !ok || err != nil {
return "", nil, err // skip source not matching build constraints
}
f, err := parser.ParseFile(interp.fset, name, src, 0)
f, err := parser.ParseFile(interp.fset, name, src, mode)
if err != nil {
return "", nil, err
}
setYaegiTags(&interp.context, f.Comments)
var root *node
var anc astNode
var st nodestack

View File

@@ -1,6 +1,7 @@
package interp
import (
"go/ast"
"go/build"
"go/parser"
"path"
@@ -11,7 +12,7 @@ import (
// buildOk returns true if a file or script matches build constraints
// as specified in https://golang.org/pkg/go/build/#hdr-Build_Constraints.
// An error from parser is returned as well.
func (interp *Interpreter) buildOk(ctx build.Context, name, src string) (bool, error) {
func (interp *Interpreter) buildOk(ctx *build.Context, name, src string) (bool, error) {
// Extract comments before the first clause
f, err := parser.ParseFile(interp.fset, name, src, parser.PackageClauseOnly|parser.ParseComments)
if err != nil {
@@ -25,12 +26,13 @@ func (interp *Interpreter) buildOk(ctx build.Context, name, src string) (bool, e
}
}
}
setYaegiTags(ctx, f.Comments)
return true, nil
}
// buildLineOk returns true if line is not a build constraint or
// if build constraint is satisfied
func buildLineOk(ctx build.Context, line string) (ok bool) {
func buildLineOk(ctx *build.Context, line string) (ok bool) {
if len(line) < 7 || line[:7] != "+build " {
return true
}
@@ -45,7 +47,7 @@ func buildLineOk(ctx build.Context, line string) (ok bool) {
}
// buildOptionOk return true if all comma separated tags match, false otherwise
func buildOptionOk(ctx build.Context, 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(ctx, t) {
@@ -57,7 +59,7 @@ func buildOptionOk(ctx build.Context, tag string) bool {
// buildTagOk returns true if a build tag matches, false otherwise
// if first character is !, result is negated
func buildTagOk(ctx build.Context, s string) (r bool) {
func buildTagOk(ctx *build.Context, s string) (r bool) {
not := s[0] == '!'
if not {
s = s[1:]
@@ -82,6 +84,25 @@ func buildTagOk(ctx build.Context, s string) (r bool) {
return
}
// setYaegiTags scans a comment group for "yaegi:tags tag1 tag2 ..." lines
// and adds the corresponding tags to the interpreter build tags.
func setYaegiTags(ctx *build.Context, comments []*ast.CommentGroup) {
for _, g := range comments {
for _, line := range strings.Split(strings.TrimSpace(g.Text()), "\n") {
if len(line) < 11 || line[:11] != "yaegi:tags " {
continue
}
tags := strings.Split(strings.TrimSpace(line[10:]), " ")
for _, tag := range tags {
if !contains(ctx.BuildTags, tag) {
ctx.BuildTags = append(ctx.BuildTags, tag)
}
}
}
}
}
func contains(tags []string, tag string) bool {
for _, t := range tags {
if t == tag {
@@ -92,7 +113,7 @@ func contains(tags []string, tag string) bool {
}
// goMinorVersion returns the go minor version number
func goMinorVersion(ctx build.Context) int {
func goMinorVersion(ctx *build.Context) int {
current := ctx.ReleaseTags[len(ctx.ReleaseTags)-1]
v := strings.Split(current, ".")
@@ -108,7 +129,7 @@ func goMinorVersion(ctx build.Context) int {
}
// skipFile returns true if file should be skipped
func skipFile(ctx build.Context, p string) bool {
func skipFile(ctx *build.Context, p string) bool {
if !strings.HasSuffix(p, ".go") {
return true
}

View File

@@ -44,7 +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(ctx, "", src); r != test.res {
if r, _ := i.buildOk(&ctx, "", src); r != test.res {
t.Errorf("got %v, want %v", r, test.res)
}
})
@@ -74,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(ctx, test.src); r != test.res {
if r := skipFile(&ctx, test.src); r != test.res {
t.Errorf("got %v, want %v", r, test.res)
}
})
@@ -106,7 +106,7 @@ func Test_goMinorVersion(t *testing.T) {
for _, test := range tests {
test := test
t.Run(test.desc, func(t *testing.T) {
minor := goMinorVersion(test.context)
minor := goMinorVersion(&test.context)
if minor != test.expected {
t.Errorf("got %v, want %v", minor, test.expected)

View File

@@ -4,6 +4,31 @@ Package interp provides a complete Go interpreter
For the Go language itself, refer to the official Go specification
https://golang.org/ref/spec.
Custom build tags
Custom build tags allow to control which files in imported source
packages are interpreted, in the same way as the "-tags" option of the
"go build" command. Setting a custom build tag spans globally for all
future imports of the session.
A build tag is a line comment that begins
// yaegi:tags
that lists the build constraints to be satisfied by the further
imports of source packages.
For example the following custom build tag
// yaegi:tags noasm
Will ensure that an import of a package will exclude files containing
// +build !noasm
And include files containing
// +build noasm
*/
package interp

View File

@@ -104,7 +104,7 @@ func TestInterpConsistencyBuild(t *testing.T) {
bin := filepath.Join(dir, strings.TrimSuffix(file.Name(), ".go"))
cmdBuild := exec.Command("go", "build", "-o", bin, filePath)
cmdBuild := exec.Command("go", "build", "-tags=dummy", "-o", bin, filePath)
outBuild, err := cmdBuild.CombinedOutput()
if err != nil {
t.Log(string(outBuild))

View File

@@ -49,7 +49,7 @@ func (interp *Interpreter) importSrc(rPath, path, alias string) error {
// Parse source files
for _, file := range files {
name := file.Name()
if skipFile(interp.context, name) {
if skipFile(&interp.context, name) {
continue
}