-
Notifications
You must be signed in to change notification settings - Fork 6
/
buildtag.go
155 lines (139 loc) · 3.78 KB
/
buildtag.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package internal
import (
"bufio"
"bytes"
"fmt"
"go/ast"
"go/build/constraint"
"io"
)
// invertCffConstraint takes the address of a parsed
// build tag (// +build) or constraint (//go:build)
// and modifies it in-place to invert all instances of the "cff" tag.
func invertCffConstraint(exp *constraint.Expr) {
switch ex := (*exp).(type) {
case *constraint.AndExpr:
invertCffConstraint(&ex.X)
invertCffConstraint(&ex.Y)
case *constraint.NotExpr:
// Special-case: If "X" in "!X" is "cff",
// just remove the "!".
if t, ok := ex.X.(*constraint.TagExpr); ok && t.Tag == "cff" {
*exp = ex.X
return
}
invertCffConstraint(&ex.X)
case *constraint.OrExpr:
invertCffConstraint(&ex.X)
invertCffConstraint(&ex.Y)
case *constraint.TagExpr:
if ex.Tag == "cff" {
*exp = &constraint.NotExpr{X: ex}
}
}
}
// hasCffTag reports whether a constraint contains the 'cff' tag.
//
// Note that this does not evaluate whether the constraint evaluates to true,
// only that it exists.
// This allows it to work with partial knowledge. e.g., if someone has:
//
// //go:build (cff && foo) || (cff && !bar)
//
// This function does not need to know whether 'foo' is enabled or whether
// 'bar' is disabled because we don't care at this point in the program.
// We can assume that the constraint for 'cff' evaluates to true because the
// package loader wouldn't have picked up this file otherwise.
func hasCffTag(exp constraint.Expr) (found bool) {
exp.Eval(func(tag string) bool {
if tag == "cff" {
found = true
return true
}
return false
})
return found
}
// fileHasCffTag reports whether a file has the 'cff' tag in its constraints.
func fileHasCffTag(f *ast.File) bool {
for _, group := range f.Comments {
// Ignore comments after the package clause
// because build constraints are allowed only before that.
if group.Pos() >= f.Package {
return false
}
for _, c := range group.List {
exp, err := constraint.Parse(c.Text)
if err != nil {
continue // not a constraint
}
if hasCffTag(exp) {
return true
}
}
}
return false
}
// writeInvertedCffTag writes the provided byte slice to the io.Writer,
// accounting for any "cff" build constraints in the byte slice
// by inverting them.
func writeInvertedCffTag(w io.Writer, bs []byte) error {
// This reduces the unnecessary if err != nil statements below.
errw := stickyErrWriter{W: w}
w = &errw
// For each line before the package clause,
// if it's a build constraint that contains "cff", invert "cff".
scan := bufio.NewScanner(bytes.NewReader(bs))
for scan.Scan() {
line := scan.Text()
isGoBuild := constraint.IsGoBuild(line)
isPlusBuild := constraint.IsPlusBuild(line)
if !isGoBuild && !isPlusBuild {
fmt.Fprintln(w, line)
continue
}
expr, err := constraint.Parse(line)
if err != nil {
// Leave invalid constraints unchanged.
fmt.Fprintln(w, line)
continue
}
invertCffConstraint(&expr)
if isGoBuild {
fmt.Fprintf(w, "//go:build %v\n", expr.String())
continue
}
lines, err := constraint.PlusBuildLines(expr)
if err != nil {
// This is not possible in production cases
// because if we could parse it from build tags,
// we can turn it back into build tags.
// So we won't really see coverage here.
fmt.Fprintln(w, line)
continue
}
for _, tagline := range lines {
fmt.Fprintln(w, tagline)
}
}
return errw.Err
}
// stickyErrWriter is a Writer that never returns an error,
// but records it internally.
// We use this to reduce boilerplate while writing.
type stickyErrWriter struct {
W io.Writer
Err error
}
func (w *stickyErrWriter) Write(bs []byte) (int, error) {
// Already failed. Pretend this worked.
if w.Err != nil {
return len(bs), nil
}
n, err := w.W.Write(bs)
if err != nil {
w.Err = err
return len(bs), nil
}
return n, nil
}