github.com/HaHadaxigua/yaegi@v1.0.1/interp/build.go (about)

     1  package interp
     2  
     3  import (
     4  	"go/ast"
     5  	"go/build"
     6  	"go/parser"
     7  	"path"
     8  	"path/filepath"
     9  	"strconv"
    10  	"strings"
    11  )
    12  
    13  // buildOk returns true if a file or script matches build constraints
    14  // as specified in https://golang.org/pkg/go/build/#hdr-Build_Constraints.
    15  // An error from parser is returned as well.
    16  func (interp *Interpreter) buildOk(ctx *build.Context, name, src string) (bool, error) {
    17  	// Extract comments before the first clause
    18  	f, err := parser.ParseFile(interp.fset, name, src, parser.PackageClauseOnly|parser.ParseComments)
    19  	if err != nil {
    20  		return false, err
    21  	}
    22  	for _, g := range f.Comments {
    23  		// in file, evaluate the AND of multiple line build constraints
    24  		for _, line := range strings.Split(strings.TrimSpace(g.Text()), "\n") {
    25  			if !buildLineOk(ctx, line) {
    26  				return false, nil
    27  			}
    28  		}
    29  	}
    30  	setYaegiTags(ctx, f.Comments)
    31  	return true, nil
    32  }
    33  
    34  // buildLineOk returns true if line is not a build constraint or
    35  // if build constraint is satisfied.
    36  func buildLineOk(ctx *build.Context, line string) (ok bool) {
    37  	if len(line) < 7 || line[:7] != "+build " {
    38  		return true
    39  	}
    40  	// In line, evaluate the OR of space-separated options
    41  	options := strings.Split(strings.TrimSpace(line[6:]), " ")
    42  	for _, o := range options {
    43  		if ok = buildOptionOk(ctx, o); ok {
    44  			break
    45  		}
    46  	}
    47  	return ok
    48  }
    49  
    50  // buildOptionOk return true if all comma separated tags match, false otherwise.
    51  func buildOptionOk(ctx *build.Context, tag string) bool {
    52  	// in option, evaluate the AND of individual tags
    53  	for _, t := range strings.Split(tag, ",") {
    54  		if !buildTagOk(ctx, t) {
    55  			return false
    56  		}
    57  	}
    58  	return true
    59  }
    60  
    61  // buildTagOk returns true if a build tag matches, false otherwise
    62  // if first character is !, result is negated.
    63  func buildTagOk(ctx *build.Context, s string) (r bool) {
    64  	not := s[0] == '!'
    65  	if not {
    66  		s = s[1:]
    67  	}
    68  	switch {
    69  	case contains(ctx.BuildTags, s):
    70  		r = true
    71  	case s == ctx.GOOS:
    72  		r = true
    73  	case s == ctx.GOARCH:
    74  		r = true
    75  	case len(s) > 4 && s[:4] == "go1.":
    76  		if n, err := strconv.Atoi(s[4:]); err != nil {
    77  			r = false
    78  		} else {
    79  			r = goMinorVersion(ctx) >= n
    80  		}
    81  	}
    82  	if not {
    83  		r = !r
    84  	}
    85  	return
    86  }
    87  
    88  // setYaegiTags scans a comment group for "yaegi:tags tag1 tag2 ..." lines
    89  // and adds the corresponding tags to the interpreter build tags.
    90  func setYaegiTags(ctx *build.Context, comments []*ast.CommentGroup) {
    91  	for _, g := range comments {
    92  		for _, line := range strings.Split(strings.TrimSpace(g.Text()), "\n") {
    93  			if len(line) < 11 || line[:11] != "yaegi:tags " {
    94  				continue
    95  			}
    96  
    97  			tags := strings.Split(strings.TrimSpace(line[10:]), " ")
    98  			for _, tag := range tags {
    99  				if !contains(ctx.BuildTags, tag) {
   100  					ctx.BuildTags = append(ctx.BuildTags, tag)
   101  				}
   102  			}
   103  		}
   104  	}
   105  }
   106  
   107  func contains(tags []string, tag string) bool {
   108  	for _, t := range tags {
   109  		if t == tag {
   110  			return true
   111  		}
   112  	}
   113  	return false
   114  }
   115  
   116  // goMinorVersion returns the go minor version number.
   117  func goMinorVersion(ctx *build.Context) int {
   118  	current := ctx.ReleaseTags[len(ctx.ReleaseTags)-1]
   119  
   120  	v := strings.Split(current, ".")
   121  	if len(v) < 2 {
   122  		panic("unsupported Go version: " + current)
   123  	}
   124  
   125  	m, err := strconv.Atoi(v[1])
   126  	if err != nil {
   127  		panic("unsupported Go version: " + current)
   128  	}
   129  	return m
   130  }
   131  
   132  // skipFile returns true if file should be skipped.
   133  func skipFile(ctx *build.Context, p string, skipTest bool) bool {
   134  	if !strings.HasSuffix(p, ".go") {
   135  		return true
   136  	}
   137  	p = strings.TrimSuffix(path.Base(p), ".go")
   138  	if pp := filepath.Base(p); strings.HasPrefix(pp, "_") || strings.HasPrefix(pp, ".") {
   139  		return true
   140  	}
   141  	if skipTest && strings.HasSuffix(p, "_test") {
   142  		return true
   143  	}
   144  	i := strings.Index(p, "_")
   145  	if i < 0 {
   146  		return false
   147  	}
   148  	a := strings.Split(p[i+1:], "_")
   149  	last := len(a) - 1
   150  	if last1 := last - 1; last1 >= 0 && a[last1] == ctx.GOOS && a[last] == ctx.GOARCH {
   151  		return false
   152  	}
   153  	if s := a[last]; s != ctx.GOOS && s != ctx.GOARCH && knownOs[s] || knownArch[s] {
   154  		return true
   155  	}
   156  	return false
   157  }
   158  
   159  var knownOs = map[string]bool{
   160  	"aix":       true,
   161  	"android":   true,
   162  	"darwin":    true,
   163  	"dragonfly": true,
   164  	"freebsd":   true,
   165  	"illumos":   true,
   166  	"ios":       true,
   167  	"js":        true,
   168  	"linux":     true,
   169  	"netbsd":    true,
   170  	"openbsd":   true,
   171  	"plan9":     true,
   172  	"solaris":   true,
   173  	"windows":   true,
   174  }
   175  
   176  var knownArch = map[string]bool{
   177  	"386":      true,
   178  	"amd64":    true,
   179  	"arm":      true,
   180  	"arm64":    true,
   181  	"loong64":  true,
   182  	"mips":     true,
   183  	"mips64":   true,
   184  	"mips64le": true,
   185  	"mipsle":   true,
   186  	"ppc64":    true,
   187  	"ppc64le":  true,
   188  	"s390x":    true,
   189  	"wasm":     true,
   190  }