k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/typecheck/main.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // do a fast type check of kubernetes code, for all platforms. 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 // When processed in order, windows and darwin are early to make 47 // interesting OS-based errors happen earlier. 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 // Recursively import all deps and flatten to one list. 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 // ignore list errors (e.g. doesn't exist) 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 // to avoid confusing interleaved logs 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 // block until there's room for this task 233 throttle <- 1 234 defer func() { 235 // indicate this task is done 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 // Special case CGo errors which may depend on headers we 248 // don't have. 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 }