mirror of
				https://github.com/openimsdk/open-im-server.git
				synced 2025-10-30 16:02:17 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			320 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			320 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright © 2023 OpenIM. All rights reserved.
 | |
| //
 | |
| // Licensed under the Apache License, Version 2.0 (the "License");
 | |
| // you may not use this file except in compliance with the License.
 | |
| // You may obtain a copy of the License at
 | |
| //
 | |
| //     http://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| // Unless required by applicable law or agreed to in writing, software
 | |
| // distributed under the License is distributed on an "AS IS" BASIS,
 | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| // See the License for the specific language governing permissions and
 | |
| // limitations under the License.
 | |
| 
 | |
| // do a fast type check of kubernetes code, for all platforms.
 | |
| package main
 | |
| 
 | |
| import (
 | |
| 	"flag"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"log"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"sort"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| 
 | |
| 	"golang.org/x/tools/go/packages"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	verbose    = flag.Bool("verbose", false, "print more information")
 | |
| 	cross      = flag.Bool("cross", true, "build for all platforms")
 | |
| 	platforms  = flag.String("platform", "", "comma-separated list of platforms to typecheck")
 | |
| 	timings    = flag.Bool("time", false, "output times taken for each phase")
 | |
| 	defuses    = flag.Bool("defuse", false, "output defs/uses")
 | |
| 	serial     = flag.Bool("serial", false, "don't type check platforms in parallel (equivalent to --parallel=1)")
 | |
| 	parallel   = flag.Int("parallel", 2, "limits how many platforms can be checked in parallel. 0 means no limit.")
 | |
| 	skipTest   = flag.Bool("skip-test", false, "don't type check test code")
 | |
| 	tags       = flag.String("tags", "", "comma-separated list of build tags to apply in addition to go's defaults")
 | |
| 	ignoreDirs = flag.String("ignore-dirs", "", "comma-separated list of directories to ignore in addition to the default hardcoded list including staging, vendor, and hidden dirs")
 | |
| 
 | |
| 	// When processed in order, windows and darwin are early to make
 | |
| 	// interesting OS-based errors happen earlier.
 | |
| 	crossPlatforms = []string{
 | |
| 		"linux/amd64", "windows/386",
 | |
| 		"darwin/amd64", "darwin/arm64",
 | |
| 		"linux/arm", "linux/386",
 | |
| 		"windows/amd64", "linux/arm64",
 | |
| 		"linux/ppc64le", "linux/s390x",
 | |
| 		"windows/arm64",
 | |
| 	}
 | |
| 
 | |
| 	// directories we always ignore
 | |
| 	standardIgnoreDirs = []string{
 | |
| 		// Staging code is symlinked from vendor/k8s.io, and uses import
 | |
| 		// paths as if it were inside of vendor/. It fails typechecking
 | |
| 		// inside of staging/, but works when typechecked as part of vendor/.
 | |
| 		"staging",
 | |
| 		// OS-specific vendor code tends to be imported by OS-specific
 | |
| 		// packages. We recursively typecheck imported vendored packages for
 | |
| 		// each OS, but don't typecheck everything for every OS.
 | |
| 		"vendor",
 | |
| 		"_output",
 | |
| 		// This is a weird one. /testdata/ is *mostly* ignored by Go,
 | |
| 		// and this translates to kubernetes/vendor not working.
 | |
| 		// edit/record.go doesn't compile without gopkg.in/yaml.v2
 | |
| 		// in $GOSRC/$GOROOT (both typecheck and the shell script).
 | |
| 		"pkg/kubectl/cmd/testdata/edit",
 | |
| 		// Tools we use for maintaining the code base but not necessarily
 | |
| 		// ship as part of the release
 | |
| 		"hack/tools",
 | |
| 	}
 | |
| )
 | |
| 
 | |
| func newConfig(platform string) *packages.Config {
 | |
| 	platSplit := strings.Split(platform, "/")
 | |
| 	goos, goarch := platSplit[0], platSplit[1]
 | |
| 	mode := packages.NeedName | packages.NeedFiles | packages.NeedTypes | packages.NeedSyntax | packages.NeedDeps | packages.NeedImports | packages.NeedModule
 | |
| 	if *defuses {
 | |
| 		mode = mode | packages.NeedTypesInfo
 | |
| 	}
 | |
| 	env := append(os.Environ(),
 | |
| 		"CGO_ENABLED=1",
 | |
| 		fmt.Sprintf("GOOS=%s", goos),
 | |
| 		fmt.Sprintf("GOARCH=%s", goarch))
 | |
| 	tagstr := "selinux"
 | |
| 	if *tags != "" {
 | |
| 		tagstr = tagstr + "," + *tags
 | |
| 	}
 | |
| 	flags := []string{"-tags", tagstr}
 | |
| 
 | |
| 	return &packages.Config{
 | |
| 		Mode:       mode,
 | |
| 		Env:        env,
 | |
| 		BuildFlags: flags,
 | |
| 		Tests:      !(*skipTest),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type collector struct {
 | |
| 	dirs       []string
 | |
| 	ignoreDirs []string
 | |
| }
 | |
| 
 | |
| func newCollector(ignoreDirs string) collector {
 | |
| 	c := collector{
 | |
| 		ignoreDirs: append([]string(nil), standardIgnoreDirs...),
 | |
| 	}
 | |
| 	if ignoreDirs != "" {
 | |
| 		c.ignoreDirs = append(c.ignoreDirs, strings.Split(ignoreDirs, ",")...)
 | |
| 	}
 | |
| 	return c
 | |
| }
 | |
| 
 | |
| func (c *collector) walk(roots []string) error {
 | |
| 	for _, root := range roots {
 | |
| 		err := filepath.Walk(root, c.handlePath)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	sort.Strings(c.dirs)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // handlePath walks the filesystem recursively, collecting directories,
 | |
| // ignoring some unneeded directories (hidden/vendored) that are handled
 | |
| // specially later.
 | |
| func (c *collector) handlePath(path string, info os.FileInfo, err error) error {
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if info.IsDir() {
 | |
| 		name := info.Name()
 | |
| 		// Ignore hidden directories (.git, .cache, etc)
 | |
| 		if (len(name) > 1 && (name[0] == '.' || name[0] == '_')) || name == "testdata" {
 | |
| 			if *verbose {
 | |
| 				fmt.Printf("DBG: skipping dir %s\n", path)
 | |
| 			}
 | |
| 			return filepath.SkipDir
 | |
| 		}
 | |
| 		for _, dir := range c.ignoreDirs {
 | |
| 			if path == dir {
 | |
| 				if *verbose {
 | |
| 					fmt.Printf("DBG: ignoring dir %s\n", path)
 | |
| 				}
 | |
| 				return filepath.SkipDir
 | |
| 			}
 | |
| 		}
 | |
| 		// Make dirs into relative pkg names.
 | |
| 		// NOTE: can't use filepath.Join because it elides the leading "./"
 | |
| 		pkg := path
 | |
| 		if !strings.HasPrefix(pkg, "./") {
 | |
| 			pkg = "./" + pkg
 | |
| 		}
 | |
| 		c.dirs = append(c.dirs, pkg)
 | |
| 		if *verbose {
 | |
| 			fmt.Printf("DBG: added dir %s\n", path)
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (c *collector) verify(plat string) ([]string, error) {
 | |
| 	errors := []packages.Error{}
 | |
| 	start := time.Now()
 | |
| 	config := newConfig(plat)
 | |
| 
 | |
| 	rootPkgs, err := packages.Load(config, c.dirs...)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	// Recursively import all deps and flatten to one list.
 | |
| 	allMap := map[string]*packages.Package{}
 | |
| 	for _, pkg := range rootPkgs {
 | |
| 		if *verbose {
 | |
| 			serialFprintf(os.Stdout, "pkg %q has %d GoFiles\n", pkg.PkgPath, len(pkg.GoFiles))
 | |
| 		}
 | |
| 		allMap[pkg.PkgPath] = pkg
 | |
| 		if len(pkg.Imports) > 0 {
 | |
| 			for _, imp := range pkg.Imports {
 | |
| 				if *verbose {
 | |
| 					serialFprintf(os.Stdout, "pkg %q imports %q\n", pkg.PkgPath, imp.PkgPath)
 | |
| 				}
 | |
| 				allMap[imp.PkgPath] = imp
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	keys := make([]string, 0, len(allMap))
 | |
| 	for k := range allMap {
 | |
| 		keys = append(keys, k)
 | |
| 	}
 | |
| 	sort.Strings(keys)
 | |
| 	allList := make([]*packages.Package, 0, len(keys))
 | |
| 	for _, k := range keys {
 | |
| 		allList = append(allList, allMap[k])
 | |
| 	}
 | |
| 
 | |
| 	for _, pkg := range allList {
 | |
| 		if len(pkg.GoFiles) > 0 {
 | |
| 			if len(pkg.Errors) > 0 && (pkg.PkgPath == "main" || strings.Contains(pkg.PkgPath, ".")) {
 | |
| 				errors = append(errors, pkg.Errors...)
 | |
| 			}
 | |
| 		}
 | |
| 		if *defuses {
 | |
| 			for id, obj := range pkg.TypesInfo.Defs {
 | |
| 				serialFprintf(os.Stdout, "%s: %q defines %v\n",
 | |
| 					pkg.Fset.Position(id.Pos()), id.Name, obj)
 | |
| 			}
 | |
| 			for id, obj := range pkg.TypesInfo.Uses {
 | |
| 				serialFprintf(os.Stdout, "%s: %q uses %v\n",
 | |
| 					pkg.Fset.Position(id.Pos()), id.Name, obj)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if *timings {
 | |
| 		serialFprintf(os.Stdout, "%s took %.1fs\n", plat, time.Since(start).Seconds())
 | |
| 	}
 | |
| 	return dedup(errors), nil
 | |
| }
 | |
| 
 | |
| func dedup(errors []packages.Error) []string {
 | |
| 	ret := []string{}
 | |
| 
 | |
| 	m := map[string]bool{}
 | |
| 	for _, e := range errors {
 | |
| 		es := e.Error()
 | |
| 		if !m[es] {
 | |
| 			ret = append(ret, es)
 | |
| 			m[es] = true
 | |
| 		}
 | |
| 	}
 | |
| 	return ret
 | |
| }
 | |
| 
 | |
| var outMu sync.Mutex
 | |
| 
 | |
| func serialFprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
 | |
| 	outMu.Lock()
 | |
| 	defer outMu.Unlock()
 | |
| 	return fmt.Fprintf(w, format, a...)
 | |
| }
 | |
| 
 | |
| func main() {
 | |
| 	flag.Parse()
 | |
| 	args := flag.Args()
 | |
| 
 | |
| 	if *verbose {
 | |
| 		*serial = true // to avoid confusing interleaved logs
 | |
| 	}
 | |
| 
 | |
| 	if len(args) == 0 {
 | |
| 		args = append(args, ".")
 | |
| 	}
 | |
| 
 | |
| 	c := newCollector(*ignoreDirs)
 | |
| 
 | |
| 	if err := c.walk(args); err != nil {
 | |
| 		log.Fatalf("Error walking: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	plats := crossPlatforms[:]
 | |
| 	if *platforms != "" {
 | |
| 		plats = strings.Split(*platforms, ",")
 | |
| 	} else if !*cross {
 | |
| 		plats = plats[:1]
 | |
| 	}
 | |
| 
 | |
| 	var wg sync.WaitGroup
 | |
| 	var failMu sync.Mutex
 | |
| 	failed := false
 | |
| 
 | |
| 	if *serial {
 | |
| 		*parallel = 1
 | |
| 	} else if *parallel == 0 {
 | |
| 		*parallel = len(plats)
 | |
| 	}
 | |
| 	throttle := make(chan int, *parallel)
 | |
| 
 | |
| 	for _, plat := range plats {
 | |
| 		wg.Add(1)
 | |
| 		go func(plat string) {
 | |
| 			// block until there's room for this task
 | |
| 			throttle <- 1
 | |
| 			defer func() {
 | |
| 				// indicate this task is done
 | |
| 				<-throttle
 | |
| 			}()
 | |
| 
 | |
| 			f := false
 | |
| 			serialFprintf(os.Stdout, "type-checking %s\n", plat)
 | |
| 			errors, err := c.verify(plat)
 | |
| 			if err != nil {
 | |
| 				serialFprintf(os.Stderr, "ERROR(%s): failed to verify: %v\n", plat, err)
 | |
| 				f = true
 | |
| 			} else if len(errors) > 0 {
 | |
| 				for _, e := range errors {
 | |
| 					// Special case CGo errors which may depend on headers we
 | |
| 					// don't have.
 | |
| 					if !strings.HasSuffix(e, "could not import C (no metadata for C)") {
 | |
| 						f = true
 | |
| 						serialFprintf(os.Stderr, "ERROR(%s): %s\n", plat, e)
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 			failMu.Lock()
 | |
| 			failed = failed || f
 | |
| 			failMu.Unlock()
 | |
| 			wg.Done()
 | |
| 		}(plat)
 | |
| 	}
 | |
| 	wg.Wait()
 | |
| 	if failed {
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| } |