1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package imports
20
21 import (
22 "bytes"
23 "errors"
24 "fmt"
25 "go/build/constraint"
26 "strings"
27 "unicode"
28 )
29
30 var (
31 bSlashSlash = []byte("//")
32 bStarSlash = []byte("*/")
33 bSlashStar = []byte("/*")
34 bPlusBuild = []byte("+build")
35
36 goBuildComment = []byte("//go:build")
37
38 errGoBuildWithoutBuild = errors.New("//go:build comment without // +build comment")
39 errMultipleGoBuild = errors.New("multiple //go:build comments")
40 )
41
42 func isGoBuildComment(line []byte) bool {
43 if !bytes.HasPrefix(line, goBuildComment) {
44 return false
45 }
46 line = bytes.TrimSpace(line)
47 rest := line[len(goBuildComment):]
48 return len(rest) == 0 || len(bytes.TrimSpace(rest)) < len(rest)
49 }
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70 func ShouldBuild(content []byte, tags map[string]bool) bool {
71
72
73
74 content, goBuild, _, err := parseFileHeader(content)
75 if err != nil {
76 return false
77 }
78
79
80
81 var shouldBuild bool
82 switch {
83 case goBuild != nil:
84 x, err := constraint.Parse(string(goBuild))
85 if err != nil {
86 return false
87 }
88 shouldBuild = eval(x, tags, true)
89
90 default:
91 shouldBuild = true
92 p := content
93 for len(p) > 0 {
94 line := p
95 if i := bytes.IndexByte(line, '\n'); i >= 0 {
96 line, p = line[:i], p[i+1:]
97 } else {
98 p = p[len(p):]
99 }
100 line = bytes.TrimSpace(line)
101 if !bytes.HasPrefix(line, bSlashSlash) || !bytes.Contains(line, bPlusBuild) {
102 continue
103 }
104 text := string(line)
105 if !constraint.IsPlusBuild(text) {
106 continue
107 }
108 if x, err := constraint.Parse(text); err == nil {
109 if !eval(x, tags, true) {
110 shouldBuild = false
111 }
112 }
113 }
114 }
115
116 return shouldBuild
117 }
118
119 func parseFileHeader(content []byte) (trimmed, goBuild []byte, sawBinaryOnly bool, err error) {
120 end := 0
121 p := content
122 ended := false
123 inSlashStar := false
124
125 Lines:
126 for len(p) > 0 {
127 line := p
128 if i := bytes.IndexByte(line, '\n'); i >= 0 {
129 line, p = line[:i], p[i+1:]
130 } else {
131 p = p[len(p):]
132 }
133 line = bytes.TrimSpace(line)
134 if len(line) == 0 && !ended {
135
136
137
138
139
140
141
142
143 end = len(content) - len(p)
144 continue Lines
145 }
146 if !bytes.HasPrefix(line, bSlashSlash) {
147 ended = true
148 }
149
150 if !inSlashStar && isGoBuildComment(line) {
151 if goBuild != nil {
152 return nil, nil, false, errMultipleGoBuild
153 }
154 goBuild = line
155 }
156
157 Comments:
158 for len(line) > 0 {
159 if inSlashStar {
160 if i := bytes.Index(line, bStarSlash); i >= 0 {
161 inSlashStar = false
162 line = bytes.TrimSpace(line[i+len(bStarSlash):])
163 continue Comments
164 }
165 continue Lines
166 }
167 if bytes.HasPrefix(line, bSlashSlash) {
168 continue Lines
169 }
170 if bytes.HasPrefix(line, bSlashStar) {
171 inSlashStar = true
172 line = bytes.TrimSpace(line[len(bSlashStar):])
173 continue Comments
174 }
175
176 break Lines
177 }
178 }
179
180 return content[:end], goBuild, sawBinaryOnly, nil
181 }
182
183
184
185
186
187
188 func matchTag(name string, tags map[string]bool, prefer bool) bool {
189
190
191 for _, c := range name {
192 if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
193 return false
194 }
195 }
196
197 if tags["*"] && name != "" && name != "ignore" {
198
199
200
201
202 return prefer
203 }
204
205 have := tags[name]
206 if name == "linux" {
207 have = have || tags["android"]
208 }
209 if name == "solaris" {
210 have = have || tags["illumos"]
211 }
212 if name == "darwin" {
213 have = have || tags["ios"]
214 }
215 return have
216 }
217
218
219
220
221
222 func eval(x constraint.Expr, tags map[string]bool, prefer bool) bool {
223 switch x := x.(type) {
224 case *constraint.TagExpr:
225 return matchTag(x.Tag, tags, prefer)
226 case *constraint.NotExpr:
227 return !eval(x.X, tags, !prefer)
228 case *constraint.AndExpr:
229 return eval(x.X, tags, prefer) && eval(x.Y, tags, prefer)
230 case *constraint.OrExpr:
231 return eval(x.X, tags, prefer) || eval(x.Y, tags, prefer)
232 }
233 panic(fmt.Sprintf("unexpected constraint expression %T", x))
234 }
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255 func MatchFile(name string, tags map[string]bool) bool {
256 if tags["*"] {
257 return true
258 }
259 if dot := strings.Index(name, "."); dot != -1 {
260 name = name[:dot]
261 }
262
263
264
265
266
267
268
269
270 i := strings.Index(name, "_")
271 if i < 0 {
272 return true
273 }
274 name = name[i:]
275
276 l := strings.Split(name, "_")
277 if n := len(l); n > 0 && l[n-1] == "test" {
278 l = l[:n-1]
279 }
280 n := len(l)
281 if n >= 2 && KnownOS[l[n-2]] && KnownArch[l[n-1]] {
282 return matchTag(l[n-2], tags, true) && matchTag(l[n-1], tags, true)
283 }
284 if n >= 1 && KnownOS[l[n-1]] {
285 return matchTag(l[n-1], tags, true)
286 }
287 if n >= 1 && KnownArch[l[n-1]] {
288 return matchTag(l[n-1], tags, true)
289 }
290 return true
291 }
292
293 var KnownOS = map[string]bool{
294 "aix": true,
295 "android": true,
296 "darwin": true,
297 "dragonfly": true,
298 "freebsd": true,
299 "hurd": true,
300 "illumos": true,
301 "ios": true,
302 "js": true,
303 "linux": true,
304 "nacl": true,
305 "netbsd": true,
306 "openbsd": true,
307 "plan9": true,
308 "solaris": true,
309 "windows": true,
310 "zos": true,
311 }
312
313 var KnownArch = map[string]bool{
314 "386": true,
315 "amd64": true,
316 "amd64p32": true,
317 "arm": true,
318 "armbe": true,
319 "arm64": true,
320 "arm64be": true,
321 "ppc64": true,
322 "ppc64le": true,
323 "mips": true,
324 "mipsle": true,
325 "mips64": true,
326 "mips64le": true,
327 "mips64p32": true,
328 "mips64p32le": true,
329 "ppc": true,
330 "riscv": true,
331 "riscv64": true,
332 "s390": true,
333 "s390x": true,
334 "sparc": true,
335 "sparc64": true,
336 "wasm": true,
337 }
338
View as plain text