Skip to content

Commit 55a1fa2

Browse files
committed
estimate: print indented tree using the importgraph package
This removes the runtime dependency on digraph(1). related to #89
1 parent 2912285 commit 55a1fa2

File tree

1 file changed

+63
-38
lines changed

1 file changed

+63
-38
lines changed

estimate.go

Lines changed: 63 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@ package main
33
import (
44
"flag"
55
"fmt"
6+
"go/build"
67
"io/ioutil"
78
"log"
89
"os"
910
"os/exec"
1011
"path/filepath"
12+
"sort"
1113
"strings"
1214

1315
"golang.org/x/tools/go/vcs"
16+
"golang.org/x/tools/refactor/importgraph"
1417
)
1518

1619
func get(gopath, repo string) error {
@@ -77,72 +80,94 @@ func estimate(importpath string) error {
7780
}
7881
}
7982

80-
// Use digraph(1) to obtain the forward transitive closure of the repo in
81-
// question.
82-
cmd := exec.Command("/bin/sh", "-c", "go list -f '{{.ImportPath}}{{.Imports}}{{.TestImports}}{{.XTestImports}}' ... | tr '[]' ' ' | digraph forward $(go list "+importpath+"/...)")
83+
// Remove standard lib packages
84+
cmd := exec.Command("go", "list", "std")
8385
cmd.Stderr = os.Stderr
8486
cmd.Env = append([]string{
8587
fmt.Sprintf("GOPATH=%s", gopath),
8688
}, passthroughEnv()...)
89+
8790
out, err := cmd.Output()
8891
if err != nil {
8992
return fmt.Errorf("%v: %v", cmd.Args, err)
9093
}
91-
92-
closure := make(map[string]bool)
94+
stdlib := make(map[string]bool)
9395
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
94-
closure[line] = true
96+
stdlib[line] = true
9597
}
9698

97-
// Remove standard lib packages
98-
cmd = exec.Command("go", "list", "std")
99-
cmd.Stderr = os.Stderr
100-
cmd.Env = append([]string{
101-
fmt.Sprintf("GOPATH=%s", gopath),
102-
}, passthroughEnv()...)
99+
stdlib["C"] = true // would fail resolving anyway
103100

104-
out, err = cmd.Output()
101+
// Filter out all already-packaged ones:
102+
golangBinaries, err := getGolangBinaries()
105103
if err != nil {
106-
return fmt.Errorf("%v: %v", cmd.Args, err)
107-
}
108-
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
109-
delete(closure, line)
104+
return nil
110105
}
111106

112-
delete(closure, "C") // would fail resolving anyway
107+
build.Default.GOPATH = gopath
108+
forward, _, errors := importgraph.Build(&build.Default)
109+
if len(errors) > 0 {
110+
lines := make([]string, 0, len(errors))
111+
for importPath, err := range errors {
112+
lines = append(lines, fmt.Sprintf("%s: %v", importPath, err))
113+
}
114+
return fmt.Errorf("could not load packages: %v", strings.Join(lines, "\n"))
115+
}
113116

114-
// Resolve all packages to the root of their repository.
115-
roots := make(map[string]bool)
116-
for dep := range closure {
117-
rr, err := vcs.RepoRootForImportPath(dep, false)
117+
var lines []string
118+
seen := make(map[string]bool)
119+
rrseen := make(map[string]bool)
120+
node := func(importPath string, indent int) {
121+
rr, err := vcs.RepoRootForImportPath(importPath, false)
118122
if err != nil {
119-
log.Printf("Could not determine repo path for import path %q: %v\n", dep, err)
120-
continue
123+
log.Printf("Could not determine repo path for import path %q: %v\n", importPath, err)
124+
return
121125
}
122-
123-
roots[rr.Root] = true
126+
if rrseen[rr.Root] {
127+
return
128+
}
129+
rrseen[rr.Root] = true
130+
if _, ok := golangBinaries[rr.Root]; ok {
131+
return // already packaged in Debian
132+
}
133+
lines = append(lines, fmt.Sprintf("%s%s", strings.Repeat(" ", indent), rr.Root))
124134
}
125-
126-
// Filter out all already-packaged ones:
127-
golangBinaries, err := getGolangBinaries()
128-
if err != nil {
129-
return nil
135+
var visit func(x string, indent int)
136+
visit = func(x string, indent int) {
137+
if seen[x] {
138+
return
139+
}
140+
seen[x] = true
141+
if !stdlib[x] {
142+
node(x, indent)
143+
}
144+
for y := range forward[x] {
145+
visit(y, indent+1)
146+
}
130147
}
131148

132-
for importpath, binary := range golangBinaries {
133-
if roots[importpath] {
134-
log.Printf("found %s in Debian package %s", importpath, binary)
135-
delete(roots, importpath)
149+
keys := make([]string, 0, len(forward))
150+
for key := range forward {
151+
keys = append(keys, key)
152+
}
153+
sort.Strings(keys)
154+
for _, key := range keys {
155+
if !strings.HasPrefix(key, importpath) {
156+
continue
157+
}
158+
if seen[key] {
159+
continue // already covered in a previous visit call
136160
}
161+
visit(key, 0)
137162
}
138163

139-
if len(roots) == 0 {
164+
if len(lines) == 0 {
140165
log.Printf("%s is already fully packaged in Debian", importpath)
141166
return nil
142167
}
143168
log.Printf("Bringing %s to Debian requires packaging the following Go packages:", importpath)
144-
for importpath := range roots {
145-
fmt.Println(importpath)
169+
for _, line := range lines {
170+
fmt.Println(line)
146171
}
147172

148173
return nil

0 commit comments

Comments
 (0)