...

Source file src/k8s.io/kubernetes/test/typecheck/main.go

Documentation: k8s.io/kubernetes/test/typecheck

     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  }
   266  

View as plain text