Skip to content

Commit 3f1c7ba

Browse files
committed
Fix estimate when the repo is not the root of the module
Instead of running "go mod tidy" (which need the root of the repo to be the root of the Go module) in the root of the repo, we now generate a dummy module (called "dummymod") to be able to run "go get pkg/..." again. This has multiple advantages: 1. It does not clone the full repo, which can be costly on very big repos. 2. It uses released/stable verisons of the module when available, which is more is more similar to what will be packaged in Debian. 3. It can handle repos with multiple modules or with a module in a subdirectory.
1 parent c76d153 commit 3f1c7ba

File tree

2 files changed

+24
-48
lines changed

2 files changed

+24
-48
lines changed

estimate.go

Lines changed: 24 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -17,30 +17,19 @@ import (
1717
// majorVersionRegexp checks if an import path contains a major version suffix.
1818
var majorVersionRegexp = regexp.MustCompile(`([/.])v([0-9]+)$`)
1919

20-
func clone(srcdir, repo string) error {
21-
done := make(chan struct{})
22-
defer close(done)
23-
go progressSize("vcs clone", srcdir, done)
24-
25-
// Get the sources of the module in a temporary dir to be able to run
26-
// go get in module mode, as the gopath mode has been removed in latest
27-
// version of Go.
28-
rr, err := vcs.RepoRootForImportPath(repo, false)
29-
if err != nil {
30-
return fmt.Errorf("get repo root: %w", err)
31-
}
32-
// Run "git clone {repo} {dir}" (or the equivalent command for hg, svn, bzr)
33-
return rr.VCS.Create(srcdir, rr.Repo)
34-
}
35-
3620
func get(gopath, repodir, repo string) error {
3721
done := make(chan struct{})
3822
defer close(done)
3923
go progressSize("go get", repodir, done)
4024

41-
// Run go mod tidy directly in the module directory to sync go.(mod|sum) and
42-
// download all its dependencies.
43-
cmd := exec.Command("go", "mod", "tidy")
25+
// As per https://groups.google.com/forum/#!topic/golang-nuts/N5apfenE4m4,
26+
// the arguments to “go get” are packages, not repositories. Hence, we
27+
// specify “gopkg/...” in order to cover all packages.
28+
// As a concrete example, github.com/jacobsa/util is a repository we want
29+
// to package into a single Debian package, and using “go get -t
30+
// github.com/jacobsa/util” fails because there are no buildable go files
31+
// in the top level of that repository.
32+
cmd := exec.Command("go", "get", "-t", repo+"/...")
4433
cmd.Dir = repodir
4534
cmd.Stderr = os.Stderr
4635
cmd.Env = append([]string{
@@ -107,17 +96,10 @@ func estimate(importpath string) error {
10796
}
10897
defer removeTemp(repodir)
10998

110-
// clone the repo inside the src directory of the GOPATH
111-
// and init a Go module if it is not yet one.
112-
if err := clone(repodir, importpath); err != nil {
113-
return fmt.Errorf("vcs clone: %w", err)
114-
}
115-
if !isFile(filepath.Join(repodir, "go.mod")) {
116-
cmd := exec.Command("go", "mod", "init", importpath)
117-
cmd.Dir = repodir
118-
if err := cmd.Run(); err != nil {
119-
return fmt.Errorf("go mod init: %w", err)
120-
}
99+
// Create a dummy go module in repodir to be able to use go get.
100+
err = os.WriteFile(filepath.Join(repodir, "go.mod"), []byte("module dummymod\n"), 0644)
101+
if err != nil {
102+
return fmt.Errorf("create dummymod: %w", err)
121103
}
122104

123105
if err := get(gopath, repodir, importpath); err != nil {
@@ -168,23 +150,25 @@ func estimate(importpath string) error {
168150
// imported it, separated by a single space. The module names
169151
// can have a version information delimited by the @ character
170152
src, dep, _ := strings.Cut(line, " ")
171-
depNode := &Node{name: dep}
172-
// Sometimes, the given import path is not the one outputed by
173-
// go mod graph, for instance when there are multiple major
174-
// versions.
175153
// The root module is the only one that does not have a version
176-
// indication with @ in the output of go mod graph, so if there
177-
// is no @ we always use the given importpath instead.
178-
if !strings.Contains(src, "@") {
154+
// indication with @ in the output of go mod graph. We use this
155+
// to filter out the depencencies of the "dummymod" module.
156+
if mod, _, found := strings.Cut(src, "@"); !found {
157+
continue
158+
} else if mod == importpath || strings.HasPrefix(mod, importpath+"/") {
179159
src = importpath
180160
}
161+
depNode, ok := nodes[dep]
162+
if !ok {
163+
depNode = &Node{name: dep}
164+
nodes[dep] = depNode
165+
}
181166
srcNode, ok := nodes[src]
182167
if !ok {
183-
log.Printf("source not found in graph: %s", src)
184-
continue
168+
srcNode = &Node{name: src}
169+
nodes[src] = srcNode
185170
}
186171
srcNode.children = append(srcNode.children, depNode)
187-
nodes[dep] = depNode
188172
}
189173

190174
// Analyse the dependency graph

fs_utils.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,6 @@ import (
66
"path/filepath"
77
)
88

9-
// isFile checks if a path exists and is a file (not a directory).
10-
func isFile(path string) bool {
11-
if info, err := os.Stat(path); err == nil {
12-
return !info.IsDir()
13-
}
14-
return false
15-
}
16-
179
// forceRemoveAll is a more robust alternative to [os.RemoveAll] that tries
1810
// harder to remove all the files and directories.
1911
func forceRemoveAll(path string) error {

0 commit comments

Comments
 (0)