feature: add support for custom build tags
This commit is contained in:
committed by
Traefiker Bot
parent
2765478137
commit
0b4dcbf7bb
3
_test/ct/ct1.go
Normal file
3
_test/ct/ct1.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package ct
|
||||
|
||||
func init() { println("hello from ct1") }
|
||||
5
_test/ct/ct2.go
Normal file
5
_test/ct/ct2.go
Normal file
@@ -0,0 +1,5 @@
|
||||
// +build !dummy
|
||||
|
||||
package ct
|
||||
|
||||
func init() { println("hello from ct2") }
|
||||
5
_test/ct/ct3.go
Normal file
5
_test/ct/ct3.go
Normal file
@@ -0,0 +1,5 @@
|
||||
// +build dummy
|
||||
|
||||
package ct
|
||||
|
||||
func init() { println("hello from ct3") }
|
||||
15
_test/tag0.go
Normal file
15
_test/tag0.go
Normal 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
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user