├── .gitignore ├── depgraph ├── testdata │ ├── net.jpg │ └── gen.sh ├── depgraph_test.go └── depgraph.go ├── go.mod ├── LICENSE ├── graphviz.go ├── graphviz_dot.go ├── go.sum ├── go_dep_search.go └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | go_dep_search 2 | -------------------------------------------------------------------------------- /depgraph/testdata/net.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiniu/go_dep_search/HEAD/depgraph/testdata/net.jpg -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ma6174/go_dep_search 2 | 3 | go 1.16 4 | 5 | require github.com/goccy/go-graphviz v0.0.9 6 | -------------------------------------------------------------------------------- /depgraph/testdata/gen.sh: -------------------------------------------------------------------------------- 1 | root@b7e158d83ff2:/go# go version 2 | go version go1.12.5 linux/amd64 3 | root@b7e158d83ff2:/usr/local/go/src# go list -json all > ~/go1.12.5_deps.json 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ma Weiwei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /graphviz.go: -------------------------------------------------------------------------------- 1 | // +build graphviz 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | "path/filepath" 9 | "strings" 10 | 11 | "github.com/goccy/go-graphviz" 12 | "github.com/goccy/go-graphviz/cgraph" 13 | ) 14 | 15 | func resultToSvg(result map[string][]string) { 16 | g := graphviz.New() 17 | defer g.Close() 18 | graph, err := g.Graph() 19 | if err != nil { 20 | log.Panicln(err) 21 | } 22 | defer graph.Close() 23 | graph.SetRankDir(cgraph.LRRank) 24 | for from, tos := range result { 25 | fromNode, err := graph.CreateNode(from) 26 | if err != nil { 27 | log.Panicln(err) 28 | } 29 | fromNode.SetStyle(cgraph.BoldNodeStyle) 30 | for _, to := range tos { 31 | toNode, err := graph.CreateNode(to) 32 | if err != nil { 33 | log.Panicln(err) 34 | } 35 | toNode.SetStyle(cgraph.BoldNodeStyle) 36 | edge, err := graph.CreateEdge(from, fromNode, toNode) 37 | if err != nil { 38 | log.Panicln(err) 39 | } 40 | edge.SetURL("#" + from + "->" + to) 41 | edge.SetStyle(cgraph.BoldEdgeStyle) 42 | } 43 | } 44 | format := graphviz.Format(strings.Trim(filepath.Ext(*graphResultFile), ".")) 45 | err = g.RenderFilename(graph, format, *graphResultFile) 46 | if err != nil { 47 | log.Panicln(err) 48 | } 49 | fmt.Println("result saved to " + *graphResultFile) 50 | } 51 | -------------------------------------------------------------------------------- /graphviz_dot.go: -------------------------------------------------------------------------------- 1 | // +build !graphviz 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | ) 12 | 13 | func write(f *os.File, data string) { 14 | _, err := f.WriteString(data) 15 | if err != nil { 16 | log.Panicln(err) 17 | } 18 | } 19 | 20 | func createHeader(f *os.File) { 21 | write(f, fmt.Sprintln("digraph \"\" {")) 22 | write(f, fmt.Sprintln("\tgraph [ rankdir=LR ];")) 23 | } 24 | 25 | func createNode(name string, f *os.File, nodes map[string]struct{}) { 26 | if _, ok := nodes[name]; ok { 27 | return 28 | } 29 | write(f, fmt.Sprintf("\t\"%s\"\t[ style=bold ];\n", name)) 30 | nodes[name] = struct{}{} 31 | } 32 | 33 | func createLine(from, to string, f *os.File) { 34 | format := "\t\"%s\" -> \"%s\" [ key=\"%s\", URL=\"#%s->%s\", style=bold ];\n" 35 | write(f, fmt.Sprintf(format, from, to, from, from, to)) 36 | } 37 | 38 | func createFooter(f *os.File) { 39 | write(f, fmt.Sprintln("}")) 40 | } 41 | 42 | func resultToSvg(result map[string][]string) { 43 | fn := *graphResultFile 44 | if !strings.HasSuffix(fn, ".dot") { 45 | fn += ".dot" 46 | } 47 | f, err := os.Create(fn) 48 | if err != nil { 49 | log.Panicln("open result file failed", result, err) 50 | } 51 | defer f.Close() 52 | 53 | createHeader(f) 54 | nodes := make(map[string]struct{}) 55 | for from, tos := range result { 56 | createNode(from, f, nodes) 57 | for _, to := range tos { 58 | createNode(to, f, nodes) 59 | createLine(from, to, f) 60 | } 61 | } 62 | createFooter(f) 63 | 64 | err = f.Close() 65 | if err != nil { 66 | log.Panicln(err) 67 | } 68 | 69 | if *graphResultFile == fn { 70 | fmt.Println("result saved to " + *graphResultFile) 71 | } else { 72 | fmt.Println("\ninstall graphviz and run the following command to generate result file:") 73 | format := strings.Trim(filepath.Ext(*graphResultFile), ".") 74 | fmt.Printf("dot -T%v %v -o %v\n\n", format, fn, *graphResultFile) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/corona10/goimagehash v1.0.2 h1:pUfB0LnsJASMPGEZLj7tGY251vF+qLGqOgEP4rUs6kA= 2 | github.com/corona10/goimagehash v1.0.2/go.mod h1:/l9umBhvcHQXVtQO1V6Gp1yD20STawkhRnnX0D1bvVI= 3 | github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= 4 | github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 5 | github.com/goccy/go-graphviz v0.0.9 h1:s/FMMJ1Joj6La3S5ApO3Jk2cwM4LpXECC2muFx3IPQQ= 6 | github.com/goccy/go-graphviz v0.0.9/go.mod h1:wXVsXxmyMQU6TN3zGRttjNn3h+iCAS7xQFC6TlNvLhk= 7 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= 8 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 9 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 10 | github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 h1:BvoENQQU+fZ9uukda/RzCAL/191HHwJA5b13R6diVlY= 11 | github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= 12 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 13 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 14 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 15 | golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 16 | golang.org/x/image v0.0.0-20200119044424-58c23975cae1 h1:5h3ngYt7+vXCDZCup/HkCQgW5XwmSvR/nA2JmJ0RErg= 17 | golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 18 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 19 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 20 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 21 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 22 | -------------------------------------------------------------------------------- /depgraph/depgraph_test.go: -------------------------------------------------------------------------------- 1 | package depgraph 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestDepGraph(t *testing.T) { 9 | f, err := os.Open("testdata/go1.12.5_deps.json") 10 | if err != nil { 11 | panic(err) 12 | } 13 | dg, err := LoadDeps(f) 14 | if err != nil { 15 | panic(err) 16 | } 17 | if !dg.Exists("fmt") { 18 | t.Error("fmt should exists") 19 | } 20 | if dg.Exists("fmtxxxxxxx") { 21 | t.Error("fmtxxxxxxx should not exists") 22 | } 23 | mains := map[string]bool{ 24 | "cmd/vet": true, 25 | "fmt": false, 26 | } 27 | for k, v := range mains { 28 | if dg.IsMainPackage(k) != v { 29 | t.Error(k, v) 30 | } 31 | } 32 | chains := dg.SearchChain("fmt") 33 | if len(chains) <= 0 { 34 | t.Error("chains empty") 35 | } 36 | for _, chain := range chains { 37 | if chain[0] != "main" || chain[len(chain)-1] != "fmt" { 38 | t.Error("result error", chain) 39 | } 40 | for _, v := range chain { 41 | if v == "..." { 42 | t.Error("should not contains ...") 43 | } 44 | } 45 | } 46 | // SearchGraph 47 | result := dg.SearchGraph("net/http", "net") 48 | if len(result) != 8 { 49 | t.Error("expect 8, real:", len(result)) 50 | } 51 | if len(result["net/http"]) != 7 { 52 | t.Error("expect 7, real:", len(result["net/http"])) 53 | } 54 | if len(result["net"]) != 0 { 55 | t.Error("expect 0, real:", len(result["net"])) 56 | } 57 | for _, rr := range result["net/http"] { 58 | if rr == "net" { 59 | continue 60 | } 61 | if len(result[rr]) <= 0 { 62 | t.Error("result error", result) 63 | } 64 | } 65 | // SearchChain with main package 66 | chains = dg.SearchChain("cmd/vet") 67 | if len(chains) != 1 { 68 | t.Error("not found") 69 | } 70 | if chains[0][0] != "main" || chains[0][1] != "cmd/vet" { 71 | t.Error("should be main -> cmd/vet") 72 | } 73 | // SearchMain with main package 74 | ms := dg.SearchMain("cmd/vet") 75 | if len(ms) != 1 { 76 | t.Error("not found") 77 | } 78 | if ms[0] != "cmd/vet" { 79 | t.Error("should be cmd/vet") 80 | } 81 | 82 | if dg.CountAll() != 371 || dg.CountMain() != 20 { 83 | t.Error(dg.CountAll(), dg.CountMain()) 84 | } 85 | all := dg.SearchAll("net/url") 86 | if !sliceContains(all, "net/http") { 87 | t.Error("not found") 88 | } 89 | dg.Add(DepInfo{ 90 | ImportPath: "x", 91 | Name: "x", 92 | Deps: []string{"fmt"}, 93 | Imports: []string{"fmt"}, 94 | }) 95 | unused := dg.ListUnUsed() 96 | if !sliceContains(unused, "x") { 97 | t.Error("unused not contains x") 98 | } 99 | } 100 | 101 | func sliceContains(s []string, t string) bool { 102 | for _, v := range s { 103 | if v == t { 104 | return true 105 | } 106 | } 107 | return false 108 | } 109 | -------------------------------------------------------------------------------- /go_dep_search.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "path" 9 | "strings" 10 | 11 | "github.com/ma6174/go_dep_search/depgraph" 12 | ) 13 | 14 | const usage = `Usage: 15 | 16 | go list -json all | %s package_names 17 | 18 | Args: 19 | ` 20 | 21 | var ( 22 | onlyMain = flag.Bool("main", false, "only show main package") 23 | onlyTest = flag.Bool("test", false, "only show test package") 24 | chain = flag.Bool("chain", false, "show dep chain, eg: main->package_a->b->c->d") 25 | unused = flag.Bool("unused", false, "list unused packages") 26 | graph = flag.Bool("graph", false, "show dep graph: -graph ") 27 | graphResultFile = flag.String("o", "/tmp/dep.svg", "used with -graph, result file path, supported type: svg,png,jpg,dot") 28 | ) 29 | 30 | func main() { 31 | flag.Usage = func() { 32 | fmt.Fprintf(os.Stderr, usage, os.Args[0]) 33 | flag.PrintDefaults() 34 | } 35 | flag.Parse() 36 | if flag.NArg() == 0 && !*unused { 37 | flag.Usage() 38 | return 39 | } 40 | if *chain { 41 | *onlyMain = true 42 | } 43 | dg, err := depgraph.LoadDeps(os.Stdin) 44 | if err != nil { 45 | log.Fatalln("LoadDeps failed", err) 46 | } 47 | log.Printf("successfully load %d packages (%d main packages, %d test packages)", 48 | dg.CountAll(), dg.CountMain(), dg.CountTest()) 49 | if *unused { 50 | log.Println("unused packages:") 51 | fmt.Println(strings.Join(dg.ListUnUsed(), "\n")) 52 | return 53 | } 54 | if *graph { 55 | result := dg.SearchGraph(flag.Arg(0), flag.Arg(1)) 56 | resultToSvg(result) 57 | return 58 | } 59 | for _, dep := range flag.Args() { 60 | if *chain { 61 | chains := dg.SearchChain(dep) 62 | if len(chains) == 0 { 63 | log.Printf("%v not found", dep) 64 | } 65 | for _, chain := range chains { 66 | fmt.Println(strings.Join(chain, " -> ")) 67 | } 68 | } else if *onlyMain { 69 | packages := dg.SearchMain(dep) 70 | if len(packages) == 0 { 71 | log.Printf("%v not found", dep) 72 | } 73 | for _, p := range packages { 74 | deps := []string{"main", p} 75 | if p != dep { 76 | deps = append(deps, dep) 77 | } 78 | fmt.Println(strings.Join(deps, " -> ")) 79 | } 80 | } else if *onlyTest { 81 | packages := dg.SearchTest(dep) 82 | if len(packages) == 0 { 83 | log.Printf("%v not found", dep) 84 | } 85 | for _, p := range packages { 86 | p = strings.TrimSuffix(p, ".test") 87 | fmt.Println(strings.Join([]string{"test", p, dep}, " -> ")) 88 | } 89 | } else { 90 | if dg.Exists(dep) { 91 | fmt.Println(strings.Join([]string{"[self]", dep}, " -> ")) 92 | } 93 | packages := dg.SearchAll(dep) 94 | if len(packages) == 0 && !dg.Exists(dep) { 95 | log.Printf("%v not found", dep) 96 | } 97 | for _, p := range packages { 98 | name := path.Base(p) 99 | if dg.IsMainPackage(p) { 100 | name = "[main]" 101 | } else if dg.IsTestPackage(p) { 102 | name = "[test]" 103 | p = strings.TrimSuffix(p, ".test") 104 | } 105 | fmt.Println(strings.Join([]string{name, p, dep}, " -> ")) 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go_dep_search 2 | 3 | golang dependency search tool. 4 | 5 | ### Install 6 | 7 | ``` 8 | go get -u github.com/ma6174/go_dep_search 9 | 10 | ``` 11 | 12 | or get with graphviz embed 13 | 14 | ``` 15 | go get -u -tags graphviz github.com/ma6174/go_dep_search 16 | 17 | ``` 18 | 19 | ### Usage 20 | 21 | ``` 22 | go list -json -deps -test all | go_dep_search package_names 23 | 24 | Args: 25 | -chain 26 | show dep chained 27 | -main 28 | only show main package 29 | -unused 30 | list unused packages 31 | ``` 32 | 33 | eg: find which command(main package) use `net/http` or `encoding/json` package in go source code: 34 | 35 | ``` 36 | root@b7e158d83ff2:/go# go list -json all | go_dep_search -main net/http encoding/json 37 | main -> cmd/go -> net/http 38 | main -> cmd/pprof -> net/http 39 | main -> cmd/trace -> net/http 40 | main -> cmd/pprof -> encoding/json 41 | main -> cmd/dist -> encoding/json 42 | main -> cmd/go -> encoding/json 43 | main -> cmd/test2json -> encoding/json 44 | main -> cmd/cover -> encoding/json 45 | main -> cmd/trace -> encoding/json 46 | main -> cmd/compile -> encoding/json 47 | main -> cmd/link -> encoding/json 48 | main -> cmd/vet -> encoding/json 49 | ``` 50 | 51 | eg: show chained package deps 52 | 53 | ``` 54 | root@b7e158d83ff2:/go# go list -json all | go_dep_search -chain net/http encoding/json 55 | main -> cmd/go -> cmd/go/internal/bug -> cmd/go/internal/envcmd -> cmd/go/internal/modload -> cmd/go/internal/modfetch -> cmd/go/internal/web2 -> net/http 56 | main -> cmd/pprof -> net/http 57 | main -> cmd/trace -> net/http 58 | main -> cmd/compile -> cmd/compile/internal/amd64 -> cmd/compile/internal/gc -> encoding/json 59 | main -> cmd/test2json -> cmd/internal/test2json -> encoding/json 60 | main -> cmd/cover -> encoding/json 61 | main -> cmd/dist -> encoding/json 62 | main -> cmd/go -> cmd/go/internal/envcmd -> encoding/json 63 | main -> cmd/link -> cmd/link/internal/arm64 -> cmd/link/internal/ld -> encoding/json 64 | main -> cmd/pprof -> cmd/vendor/github.com/google/pprof/driver -> cmd/vendor/github.com/google/pprof/internal/driver -> encoding/json 65 | main -> cmd/trace -> encoding/json 66 | main -> cmd/vet -> cmd/vendor/golang.org/x/tools/go/analysis/unitchecker -> encoding/json 67 | ``` 68 | 69 | eg: show dep graph from net/http to net 70 | 71 | 72 | ``` 73 | root@b7e158d83ff2:/go# go list -json all | go_dep_search -graph -o net.jpg net/http net 74 | 75 | result saved to net.jpg 76 | ``` 77 | 78 | ![net.jpg](./depgraph/testdata/net.jpg) 79 | 80 | eg: show unsed packages 81 | 82 | ``` 83 | root@b7e158d83ff2:/go# go list -json all | go_dep_search -unused 84 | archive/tar 85 | cmd/compile/internal/test 86 | cmd/go/internal/txtar 87 | cmd/go/internal/webtest 88 | cmd/vendor/github.com/google/pprof/internal/proftest 89 | cmd/vendor/golang.org/x/sys/windows 90 | cmd/vendor/golang.org/x/sys/windows/registry 91 | cmd/vendor/golang.org/x/tools/go/analysis/passes/pkgfact 92 | compress/bzip2 93 | container/ring 94 | database/sql 95 | encoding/ascii85 96 | encoding/base32 97 | encoding/csv 98 | expvar 99 | hash/crc64 100 | hash/fnv 101 | image/gif 102 | image/jpeg 103 | image/png 104 | index/suffixarray 105 | internal/syscall/windows 106 | internal/syscall/windows/registry 107 | internal/syscall/windows/sysdll 108 | internal/testenv 109 | internal/x/net/internal/nettest 110 | internal/x/net/nettest 111 | internal/x/text/secure 112 | internal/x/text/unicode 113 | log/syslog 114 | math/cmplx 115 | net/http/cookiejar 116 | net/http/fcgi 117 | net/http/httptest 118 | net/http/httputil 119 | net/internal/socktest 120 | net/mail 121 | net/rpc/jsonrpc 122 | net/smtp 123 | os/signal/internal/pty 124 | plugin 125 | runtime/pprof/internal/profile 126 | runtime/race 127 | testing/internal/testdeps 128 | testing/iotest 129 | testing/quick 130 | ``` 131 | -------------------------------------------------------------------------------- /depgraph/depgraph.go: -------------------------------------------------------------------------------- 1 | package depgraph 2 | 3 | import ( 4 | "container/list" 5 | "encoding/json" 6 | "io" 7 | "sort" 8 | "strings" 9 | ) 10 | 11 | type DepInfo struct { 12 | ImportPath string `json:"ImportPath"` // import path of package in dir 13 | Name string `json:"Name"` // package name 14 | Deps []string `json:"Deps"` // all (recursively) imported dependencies 15 | Imports []string `json:"Imports"` // import paths used by this package 16 | } 17 | 18 | func (d *DepInfo) ImportsMap() map[string]bool { 19 | m := make(map[string]bool, len(d.Imports)) 20 | for _, v := range d.Imports { 21 | m[v] = true 22 | } 23 | return m 24 | } 25 | 26 | func (d *DepInfo) DepsMap() map[string]bool { 27 | m := make(map[string]bool, len(d.Deps)) 28 | for _, v := range d.Deps { 29 | m[v] = true 30 | } 31 | return m 32 | } 33 | 34 | type DepGraph struct { 35 | imports map[string]map[string]bool 36 | allDeps map[string]map[string]bool 37 | mainPackages map[string]bool 38 | testPackages map[string]bool 39 | } 40 | 41 | func (g *DepGraph) Add(d DepInfo) { 42 | if g.imports == nil { 43 | g.imports = make(map[string]map[string]bool) 44 | g.imports["main"] = make(map[string]bool) 45 | } 46 | if g.mainPackages == nil { 47 | g.mainPackages = make(map[string]bool) 48 | } 49 | if g.testPackages == nil { 50 | g.testPackages = make(map[string]bool) 51 | } 52 | if g.allDeps == nil { 53 | g.allDeps = make(map[string]map[string]bool) 54 | } 55 | if strings.HasSuffix(d.ImportPath, "]") { // skip test package 56 | return 57 | } 58 | isTestPackage := strings.HasSuffix(d.ImportPath, ".test") 59 | if d.Name == "main" { 60 | if isTestPackage { 61 | g.testPackages[d.ImportPath] = true 62 | } else { 63 | g.mainPackages[d.ImportPath] = true 64 | } 65 | } 66 | g.imports[d.ImportPath] = d.ImportsMap() 67 | g.allDeps[d.ImportPath] = d.DepsMap() 68 | } 69 | 70 | func (g *DepGraph) CountAll() int { 71 | return len(g.imports) 72 | } 73 | func (g *DepGraph) CountMain() int { 74 | return len(g.mainPackages) 75 | } 76 | 77 | func (g *DepGraph) CountTest() int { 78 | return len(g.testPackages) 79 | } 80 | 81 | func reverseSlice(a []string) { 82 | if len(a) <= 1 { 83 | return 84 | } 85 | for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 { 86 | a[i], a[j] = a[j], a[i] 87 | } 88 | } 89 | 90 | func (g *DepGraph) SearchMain(packageName string) (packages []string) { 91 | for v := range g.mainPackages { 92 | if g.allDeps[v][packageName] || v == packageName { 93 | packages = append(packages, v) 94 | } 95 | } 96 | return 97 | } 98 | 99 | func (g *DepGraph) SearchTest(packageName string) (packages []string) { 100 | for v := range g.testPackages { 101 | if g.allDeps[v][packageName] { 102 | packages = append(packages, v) 103 | } 104 | } 105 | return 106 | } 107 | 108 | func (g *DepGraph) Exists(packageName string) bool { 109 | _, exists := g.allDeps[packageName] 110 | return exists 111 | } 112 | 113 | func (g *DepGraph) SearchAll(packageName string) (packages []string) { 114 | for k, v := range g.allDeps { 115 | if v[packageName] { 116 | packages = append(packages, k) 117 | } 118 | } 119 | return 120 | } 121 | 122 | func (g *DepGraph) ListUnUsed() (packages []string) { 123 | defer func() { 124 | sort.Strings(packages) 125 | }() 126 | for p := range g.allDeps { 127 | if g.mainPackages[p] || g.testPackages[p] { 128 | continue 129 | } 130 | found := false 131 | for m := range g.allDeps { 132 | if g.allDeps[m][p] { 133 | found = true 134 | } 135 | } 136 | if !found { 137 | packages = append(packages, p) 138 | } 139 | } 140 | return 141 | } 142 | 143 | func (g *DepGraph) IsMainPackage(packageName string) bool { 144 | return g.mainPackages[packageName] 145 | } 146 | 147 | func (g *DepGraph) IsTestPackage(packageName string) bool { 148 | return g.testPackages[packageName] 149 | } 150 | 151 | func (g *DepGraph) SearchChain(packageName string) (chains [][]string) { 152 | for _, p := range g.SearchMain(packageName) { 153 | if p == packageName { 154 | chains = append(chains, []string{"main", p}) 155 | continue 156 | } 157 | chain := []string{} 158 | chain, found := g.search(p, packageName, chain) 159 | if !found { 160 | // dep存在,但是找不到依赖链,说明依赖关系导入不全,比如缺少标准库 161 | chain = []string{packageName, "..."} 162 | } 163 | chain = append(chain, p) 164 | chain = append(chain, "main") 165 | reverseSlice(chain) 166 | chains = append(chains, chain) 167 | } 168 | return 169 | } 170 | 171 | func (g *DepGraph) search(start, packageName string, current []string) (after []string, found bool) { 172 | if !g.allDeps[start][packageName] { 173 | return 174 | } 175 | if g.imports[start][packageName] { 176 | found = true 177 | after = append(current, packageName) 178 | return 179 | } 180 | for p := range g.imports[start] { 181 | if after, ok := g.search(p, packageName, current); ok { 182 | after = append(after, p) 183 | return after, true 184 | } 185 | } 186 | return 187 | } 188 | 189 | func (g *DepGraph) SearchGraph(start, toSearch string) (result map[string][]string) { 190 | if !g.allDeps[start][toSearch] { 191 | return 192 | } 193 | result = make(map[string][]string) 194 | checked := make(map[string]bool) 195 | l := list.New() 196 | l.PushBack(start) 197 | checked[start] = true 198 | for e := l.Front(); e != nil; e = e.Next() { 199 | fromPackage := e.Value.(string) 200 | for p := range g.imports[fromPackage] { 201 | if p == toSearch { 202 | result[fromPackage] = append(result[fromPackage], p) 203 | continue 204 | } 205 | if g.allDeps[p][toSearch] { 206 | if !checked[p] { 207 | checked[p] = true 208 | l.PushBack(p) 209 | } 210 | result[fromPackage] = append(result[fromPackage], p) 211 | } 212 | } 213 | } 214 | return 215 | } 216 | 217 | func LoadDeps(r io.Reader) (dg *DepGraph, err error) { 218 | dec := json.NewDecoder(r) 219 | dg = &DepGraph{} 220 | for { 221 | var di DepInfo 222 | err = dec.Decode(&di) 223 | if err != nil { 224 | if err == io.EOF { 225 | err = nil 226 | break 227 | } 228 | return 229 | } 230 | dg.Add(di) 231 | } 232 | return 233 | } 234 | --------------------------------------------------------------------------------