1
16
17
18 package main
19
20 import (
21 "flag"
22 "fmt"
23 "io"
24 "log"
25 "os"
26 "sort"
27 "strings"
28 "sync"
29 "time"
30
31 "golang.org/x/tools/go/packages"
32 )
33
34 var (
35 verbose = flag.Bool("verbose", false, "print more information")
36 cross = flag.Bool("cross", true, "build for all platforms")
37 platforms = flag.String("platform", "", "comma-separated list of platforms to typecheck")
38 timings = flag.Bool("time", false, "output times taken for each phase")
39 defuses = flag.Bool("defuse", false, "output defs/uses")
40 serial = flag.Bool("serial", false, "don't type check platforms in parallel (equivalent to --parallel=1)")
41 parallel = flag.Int("parallel", 2, "limits how many platforms can be checked in parallel. 0 means no limit.")
42 skipTest = flag.Bool("skip-test", false, "don't type check test code")
43 tags = flag.String("tags", "", "comma-separated list of build tags to apply in addition to go's defaults")
44 ignorePatterns = flag.String("ignore", "", "comma-separated list of Go patterns to ignore")
45
46
47
48 crossPlatforms = []string{
49 "linux/amd64", "windows/386",
50 "darwin/amd64", "darwin/arm64",
51 "linux/arm", "linux/386",
52 "windows/amd64", "linux/arm64",
53 "linux/ppc64le", "linux/s390x",
54 "windows/arm64",
55 }
56 )
57
58 func newConfig(platform string) *packages.Config {
59 platSplit := strings.Split(platform, "/")
60 goos, goarch := platSplit[0], platSplit[1]
61 mode := packages.NeedName | packages.NeedFiles | packages.NeedTypes | packages.NeedSyntax | packages.NeedDeps | packages.NeedImports | packages.NeedModule
62 if *defuses {
63 mode = mode | packages.NeedTypesInfo
64 }
65 env := append(os.Environ(),
66 "CGO_ENABLED=1",
67 fmt.Sprintf("GOOS=%s", goos),
68 fmt.Sprintf("GOARCH=%s", goarch))
69 tagstr := "selinux"
70 if *tags != "" {
71 tagstr = tagstr + "," + *tags
72 }
73 flags := []string{"-tags", tagstr}
74
75 return &packages.Config{
76 Mode: mode,
77 Env: env,
78 BuildFlags: flags,
79 Tests: !(*skipTest),
80 }
81 }
82
83 func verify(plat string, patterns []string, ignore map[string]bool) ([]string, error) {
84 errors := []packages.Error{}
85 start := time.Now()
86 config := newConfig(plat)
87
88 pkgs, err := packages.Load(config, patterns...)
89 if err != nil {
90 return nil, err
91 }
92
93
94 allMap := map[string]*packages.Package{}
95 for _, pkg := range pkgs {
96 if ignore[pkg.PkgPath] {
97 continue
98 }
99 if *verbose {
100 serialFprintf(os.Stdout, "pkg %q has %d GoFiles\n", pkg.PkgPath, len(pkg.GoFiles))
101 }
102 accumulate(pkg, allMap)
103 }
104 keys := make([]string, 0, len(allMap))
105 for k := range allMap {
106 keys = append(keys, k)
107 }
108 sort.Strings(keys)
109 allList := make([]*packages.Package, 0, len(keys))
110 for _, k := range keys {
111 allList = append(allList, allMap[k])
112 }
113
114 for _, pkg := range allList {
115 if len(pkg.GoFiles) > 0 {
116 if len(pkg.Errors) > 0 && (pkg.PkgPath == "main" || strings.Contains(pkg.PkgPath, ".")) {
117 errors = append(errors, pkg.Errors...)
118 }
119 }
120 if *defuses {
121 for id, obj := range pkg.TypesInfo.Defs {
122 serialFprintf(os.Stdout, "%s: %q defines %v\n",
123 pkg.Fset.Position(id.Pos()), id.Name, obj)
124 }
125 for id, obj := range pkg.TypesInfo.Uses {
126 serialFprintf(os.Stdout, "%s: %q uses %v\n",
127 pkg.Fset.Position(id.Pos()), id.Name, obj)
128 }
129 }
130 }
131 if *timings {
132 serialFprintf(os.Stdout, "%s took %.1fs\n", plat, time.Since(start).Seconds())
133 }
134 return dedup(errors), nil
135 }
136
137 func accumulate(pkg *packages.Package, allMap map[string]*packages.Package) {
138 allMap[pkg.PkgPath] = pkg
139 for _, imp := range pkg.Imports {
140 if allMap[imp.PkgPath] != nil {
141 continue
142 }
143 if *verbose {
144 serialFprintf(os.Stdout, "pkg %q imports %q\n", pkg.PkgPath, imp.PkgPath)
145 }
146 accumulate(imp, allMap)
147 }
148 }
149
150 func dedup(errors []packages.Error) []string {
151 ret := []string{}
152
153 m := map[string]bool{}
154 for _, e := range errors {
155 es := e.Error()
156 if !m[es] {
157 ret = append(ret, es)
158 m[es] = true
159 }
160 }
161 return ret
162 }
163
164 var outMu sync.Mutex
165
166 func serialFprintf(w io.Writer, format string, a ...interface{}) {
167 outMu.Lock()
168 defer outMu.Unlock()
169 _, _ = fmt.Fprintf(w, format, a...)
170 }
171
172 func resolvePkgs(patterns ...string) (map[string]bool, error) {
173 config := &packages.Config{
174 Mode: packages.NeedName,
175 }
176 pkgs, err := packages.Load(config, patterns...)
177 if err != nil {
178 return nil, err
179 }
180 paths := map[string]bool{}
181 for _, p := range pkgs {
182
183 if len(p.Errors) == 0 {
184 paths[p.PkgPath] = true
185 }
186 }
187 return paths, nil
188 }
189
190 func main() {
191 flag.Parse()
192 args := flag.Args()
193
194 if *verbose {
195 *serial = true
196 }
197
198 if len(args) == 0 {
199 args = append(args, "./...")
200 }
201
202 ignore := []string{}
203 if *ignorePatterns != "" {
204 ignore = append(ignore, strings.Split(*ignorePatterns, ",")...)
205 }
206 ignorePkgs, err := resolvePkgs(ignore...)
207 if err != nil {
208 log.Fatalf("failed to resolve ignored packages: %v", err)
209 }
210
211 plats := crossPlatforms[:]
212 if *platforms != "" {
213 plats = strings.Split(*platforms, ",")
214 } else if !*cross {
215 plats = plats[:1]
216 }
217
218 var wg sync.WaitGroup
219 var failMu sync.Mutex
220 failed := false
221
222 if *serial {
223 *parallel = 1
224 } else if *parallel == 0 {
225 *parallel = len(plats)
226 }
227 throttle := make(chan int, *parallel)
228
229 for _, plat := range plats {
230 wg.Add(1)
231 go func(plat string) {
232
233 throttle <- 1
234 defer func() {
235
236 <-throttle
237 }()
238
239 f := false
240 serialFprintf(os.Stdout, "type-checking %s\n", plat)
241 errors, err := verify(plat, args, ignorePkgs)
242 if err != nil {
243 serialFprintf(os.Stderr, "ERROR(%s): failed to verify: %v\n", plat, err)
244 f = true
245 } else if len(errors) > 0 {
246 for _, e := range errors {
247
248
249 if !strings.HasSuffix(e, "could not import C (no metadata for C)") {
250 f = true
251 serialFprintf(os.Stderr, "ERROR(%s): %s\n", plat, e)
252 }
253 }
254 }
255 failMu.Lock()
256 failed = failed || f
257 failMu.Unlock()
258 wg.Done()
259 }(plat)
260 }
261 wg.Wait()
262 if failed {
263 os.Exit(1)
264 }
265 }
266
View as plain text