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 }