gitee.com/wgliang/goreporter@v0.0.0-20180902115603-df1b20f7c5d0/linters/depend/depend.go (about)

     1  package depend
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"go/build"
     7  	"io/ioutil"
     8  	"os"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"runtime"
    12  	"strings"
    13  
    14  	"github.com/golang/glog"
    15  )
    16  
    17  var (
    18  	pkgs   map[string]*build.Package
    19  	ids    map[string]int
    20  	nextId int
    21  
    22  	ignored = map[string]bool{
    23  		"C": true,
    24  	}
    25  	ignoredPrefixes []string
    26  	// ignore packages in the Go standard library
    27  	ignoreStdlib   = true
    28  	delveGoroot    = false
    29  	ignorePrefixes = ""
    30  	// a comma-separated list of packages to ignore
    31  	ignorePackages = ""
    32  	// a comma-separated list of build tags to consider satisified during the build
    33  	tagList    = ""
    34  	horizontal = true
    35  	// include test packages
    36  	includeTests = false
    37  
    38  	buildTags    []string
    39  	buildContext = build.Default
    40  
    41  	vendors []string
    42  )
    43  
    44  func Depend(path, expect string) string {
    45  	ignorePackages = expect
    46  	vendors = getVendorlist(path)
    47  	// add root vendor
    48  	vendors = append(vendors, "vendor")
    49  	pkgs = make(map[string]*build.Package)
    50  	ids = make(map[string]int)
    51  
    52  	args := []string{path}
    53  
    54  	if len(args) != 1 {
    55  		glog.Errorln("need one package name to process")
    56  		return ""
    57  	}
    58  
    59  	if ignorePrefixes != "" {
    60  		if runtime.GOOS == `windows` {
    61  			ignorePrefixes = strings.Replace(ignorePrefixes, "/", `\`, -1)
    62  		}
    63  		ignoredPrefixes = strings.Split(ignorePrefixes, ",")
    64  	}
    65  	if ignorePackages != "" {
    66  		for _, p := range strings.Split(ignorePackages, ",") {
    67  			ignored[p] = true
    68  		}
    69  	}
    70  	if tagList != "" {
    71  		buildTags = strings.Split(tagList, ",")
    72  	}
    73  	buildContext.BuildTags = buildTags
    74  
    75  	cwd, err := os.Getwd()
    76  	if err != nil {
    77  		glog.Errorf("failed to get cwd: %s", err)
    78  		return ""
    79  	}
    80  	if err := processPackage(cwd, strings.Replace(args[0], `\`, "/", -1), path); err != nil {
    81  		glog.Errorln(err)
    82  		return ""
    83  	}
    84  
    85  	graph := "digraph godep {"
    86  	// fmt.Println("digraph godep {")
    87  	if horizontal {
    88  		// fmt.Println(`rankdir="LR"`)
    89  		graph += `rankdir="LR"`
    90  	}
    91  	for pkgName, pkg := range pkgs {
    92  		pkgId := getId(pkgName)
    93  
    94  		if isIgnored(pkg) {
    95  			continue
    96  		}
    97  
    98  		var color string
    99  		if pkg.Goroot {
   100  			color = "palegreen"
   101  		} else if len(pkg.CgoFiles) > 0 {
   102  			color = "darkgoldenrod1"
   103  		} else {
   104  			color = "paleturquoise"
   105  		}
   106  		graph += fmt.Sprintf("%d [label=\"%s\" style=\"filled\" color=\"%s\"];\n", pkgId, pkgName, color)
   107  		// fmt.Printf("%d [label=\"%s\" style=\"filled\" color=\"%s\"];\n", pkgId, pkgName, color)
   108  
   109  		// Don't render imports from packages in Goroot
   110  		if pkg.Goroot && !delveGoroot {
   111  			continue
   112  		}
   113  
   114  		for _, imp := range getImports(pkg) {
   115  			impPkg := pkgs[imp]
   116  			if impPkg == nil || isIgnored(impPkg) {
   117  				continue
   118  			}
   119  
   120  			impId := getId(imp)
   121  			graph += fmt.Sprintf("%d -> %d;\n", pkgId, impId)
   122  			// fmt.Printf("%d -> %d;\n", pkgId, impId)
   123  		}
   124  	}
   125  	graph += `}`
   126  
   127  	err = ioutil.WriteFile("graph.gv", []byte(graph), 0666)
   128  	if err != nil {
   129  		glog.Errorln(err)
   130  	}
   131  
   132  	// convert file formate
   133  	cmdsvg := exec.Command("dot", "-Tsvg", "-o", "pkgdep.svg", "graph.gv")
   134  	var outsvg bytes.Buffer
   135  	cmdsvg.Stdout = &outsvg
   136  	cmdsvg.Stderr = os.Stderr
   137  	err = cmdsvg.Run()
   138  	if err != nil {
   139  		glog.Errorln(err)
   140  	}
   141  
   142  	svg, err := ioutil.ReadFile("pkgdep.svg")
   143  	if err != nil {
   144  		glog.Errorln(err)
   145  	}
   146  
   147  	err = os.Remove("pkgdep.svg")
   148  	if err != nil {
   149  		glog.Errorln(err)
   150  	}
   151  
   152  	err = os.Remove("graph.gv")
   153  	if err != nil {
   154  		glog.Errorln(err)
   155  	}
   156  
   157  	return string(svg)
   158  }
   159  
   160  func processPackage(root string, pkgName, path string) error {
   161  	if ignored[pkgName] {
   162  		return nil
   163  	}
   164  	var err error
   165  	if !build.IsLocalImport(pkgName) {
   166  		root, err = filepath.Abs(path)
   167  		if err != nil {
   168  			return fmt.Errorf("failed to convert path to absolute path %s ", err)
   169  		}
   170  	}
   171  
   172  	pkg, err := buildContext.Import(pkgName, root, 0)
   173  	if err != nil {
   174  		flag := false
   175  		for i := 0; i < len(vendors); i++ {
   176  			pkg, err = buildContext.Import(vendors[i]+string(filepath.Separator)+pkgName, root, 0)
   177  			if err == nil {
   178  				flag = true
   179  				break
   180  			}
   181  		}
   182  		if !flag {
   183  			return fmt.Errorf("failed to import %s: %s", pkgName, err)
   184  		}
   185  	}
   186  
   187  	if isIgnored(pkg) {
   188  		return nil
   189  	}
   190  
   191  	pkgs[pkg.ImportPath] = pkg
   192  
   193  	// Don't worry about dependencies for stdlib packages
   194  	if pkg.Goroot && !delveGoroot {
   195  		return nil
   196  	}
   197  
   198  	for _, imp := range getImports(pkg) {
   199  		if _, ok := pkgs[imp]; !ok {
   200  			if err := processPackage(root, imp, path); err != nil {
   201  				return err
   202  			}
   203  		}
   204  	}
   205  	return nil
   206  }
   207  
   208  func getImports(pkg *build.Package) []string {
   209  	allImports := pkg.Imports
   210  	if includeTests {
   211  		allImports = append(allImports, pkg.TestImports...)
   212  		allImports = append(allImports, pkg.XTestImports...)
   213  	}
   214  	var imports []string
   215  	found := make(map[string]struct{})
   216  	for _, imp := range allImports {
   217  		if imp == pkg.ImportPath {
   218  			// Don't draw a self-reference when foo_test depends on foo.
   219  			continue
   220  		}
   221  		if _, ok := found[imp]; ok {
   222  			continue
   223  		}
   224  		found[imp] = struct{}{}
   225  		imports = append(imports, imp)
   226  	}
   227  	return imports
   228  }
   229  
   230  func getId(name string) int {
   231  	id, ok := ids[name]
   232  	if !ok {
   233  		id = nextId
   234  		nextId++
   235  		ids[name] = id
   236  	}
   237  	return id
   238  }
   239  
   240  func hasPrefixes(s string, prefixes []string) bool {
   241  	for _, p := range prefixes {
   242  		if strings.HasPrefix(s, p) {
   243  			return true
   244  		}
   245  	}
   246  	return false
   247  }
   248  
   249  func hasPackage(pkgpath string) bool {
   250  	for k, _ := range ignored {
   251  		if strings.Contains(pkgpath, k) {
   252  			return true
   253  		}
   254  	}
   255  	return false
   256  }
   257  
   258  func isIgnored(pkg *build.Package) bool {
   259  	return ignored[pkg.ImportPath] || (pkg.Goroot && ignoreStdlib) || hasPrefixes(pkg.ImportPath, ignoredPrefixes) || hasPackage(pkg.ImportPath)
   260  }
   261  
   262  func debug(args ...interface{}) {
   263  	fmt.Fprintln(os.Stderr, args...)
   264  }
   265  
   266  func debugf(s string, args ...interface{}) {
   267  	fmt.Fprintf(os.Stderr, s, args...)
   268  }
   269  
   270  func getVendorlist(path string) []string {
   271  	vendors := make([]string, 0)
   272  	err := filepath.Walk(path, func(path string, f os.FileInfo, err error) error {
   273  		if f == nil {
   274  			return err
   275  		}
   276  		if !f.IsDir() {
   277  			return nil
   278  		}
   279  		if strings.HasSuffix(path, "vendor") && path != "" {
   280  			vendors = append(vendors, PackageAbsPath(path))
   281  		}
   282  		return nil
   283  	})
   284  	if err != nil {
   285  		glog.Errorf("filepath.Walk() returned %v\n", err)
   286  	}
   287  	return vendors
   288  }
   289  
   290  func PackageAbsPath(path string) (packagePath string) {
   291  	_, err := os.Stat(path)
   292  	if err != nil {
   293  		glog.Errorln("package path is invalid")
   294  		return ""
   295  	}
   296  	absPath, err := filepath.Abs(path)
   297  	if err != nil {
   298  		glog.Errorln(err)
   299  	}
   300  	packagePathIndex := strings.Index(absPath, "src")
   301  	if -1 != packagePathIndex {
   302  		packagePath = absPath[(packagePathIndex + 4):]
   303  	}
   304  
   305  	return packagePath
   306  }