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 }