Skip to content

Commit a293239

Browse files
committed
Accelerating go-extractor by using 'go list -deps'
Resurrect github/codeql-go#554, but behind an environment variable as to avoid the broken builds noted in github#9304, but still allowing some people to opt in to the much faster approach.
1 parent 279ba60 commit a293239

File tree

2 files changed

+77
-8
lines changed

2 files changed

+77
-8
lines changed

go/extractor/extractor.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,16 @@ func ExtractWithFlags(buildFlags []string, patterns []string) error {
108108
// root directories of packages that we want to extract
109109
wantedRoots := make(map[string]bool)
110110

111+
if os.Getenv("CODEQL_EXTRACTOR_GO_FAST_PACKAGE_INFO") != "" {
112+
log.Printf("Running go list to resolve package and module directories.")
113+
// get all packages information
114+
pkgInfos, err = util.GetPkgsInfo(patterns, true, modFlags...)
115+
if err != nil {
116+
log.Fatalf("Error getting dependency package or module directories: %v.", err)
117+
}
118+
log.Printf("Done running go list deps: resolved %d packages.", len(pkgInfos))
119+
}
120+
111121
// Do a post-order traversal and extract the package scope of each package
112122
packages.Visit(pkgs, func(pkg *packages.Package) bool {
113123
return true

go/extractor/util/util.go

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package util
22

33
import (
4+
"encoding/json"
45
"errors"
6+
"io"
57
"log"
68
"os"
79
"os/exec"
@@ -31,13 +33,13 @@ func Getenv(key string, aliases ...string) string {
3133

3234
// runGoList is a helper function for running go list with format `format` and flags `flags` on
3335
// package `pkgpath`.
34-
func runGoList(format string, pkgpath string, flags ...string) (string, error) {
35-
return runGoListWithEnv(format, pkgpath, nil, flags...)
36+
func runGoList(format string, patterns []string, flags ...string) (string, error) {
37+
return runGoListWithEnv(format, patterns, nil, flags...)
3638
}
3739

38-
func runGoListWithEnv(format string, pkgpath string, additionalEnv []string, flags ...string) (string, error) {
40+
func runGoListWithEnv(format string, patterns []string, additionalEnv []string, flags ...string) (string, error) {
3941
args := append([]string{"list", "-e", "-f", format}, flags...)
40-
args = append(args, pkgpath)
42+
args = append(args, patterns...)
4143
cmd := exec.Command("go", args...)
4244
cmd.Env = append(os.Environ(), additionalEnv...)
4345
out, err := cmd.Output()
@@ -60,6 +62,63 @@ type PkgInfo struct {
6062
ModDir string // the module directory containing this package, empty if not a module
6163
}
6264

65+
// GetPkgsInfo gets the absolute module and package root directories for the packages matched by the
66+
// patterns `patterns`. It passes to `go list` the flags specified by `flags`. If `includingDeps`
67+
// is true, all dependencies will also be included.
68+
func GetPkgsInfo(patterns []string, includingDeps bool, flags ...string) (map[string]PkgInfo, error) {
69+
// enable module mode so that we can find a module root if it exists, even if go module support is
70+
// disabled by a build
71+
if includingDeps {
72+
// the flag `-deps` causes all dependencies to be retrieved
73+
flags = append(flags, "-deps")
74+
}
75+
76+
// using -json overrides -f format
77+
output, err := runGoList("", patterns, append(flags, "-json")...)
78+
if err != nil {
79+
return nil, err
80+
}
81+
82+
// the output of `go list -json` is a stream of json object
83+
type goListPkgInfo struct {
84+
ImportPath string
85+
Dir string
86+
Module *struct {
87+
Dir string
88+
}
89+
}
90+
pkgInfoMapping := make(map[string]PkgInfo)
91+
streamDecoder := json.NewDecoder(strings.NewReader(output))
92+
for {
93+
var pkgInfo goListPkgInfo
94+
decErr := streamDecoder.Decode(&pkgInfo)
95+
if decErr == io.EOF {
96+
break
97+
}
98+
if decErr != nil {
99+
log.Printf("Error decoding output of go list -json: %s", err.Error())
100+
return nil, decErr
101+
}
102+
pkgAbsDir, err := filepath.Abs(pkgInfo.Dir)
103+
if err != nil {
104+
log.Printf("Unable to make package dir %s absolute: %s", pkgInfo.Dir, err.Error())
105+
}
106+
var modAbsDir string
107+
if pkgInfo.Module != nil {
108+
modAbsDir, err = filepath.Abs(pkgInfo.Module.Dir)
109+
if err != nil {
110+
log.Printf("Unable to make module dir %s absolute: %s", pkgInfo.Module.Dir, err.Error())
111+
}
112+
}
113+
pkgInfoMapping[pkgInfo.ImportPath] = PkgInfo{
114+
PkgDir: pkgAbsDir,
115+
ModDir: modAbsDir,
116+
}
117+
}
118+
return pkgInfoMapping, nil
119+
}
120+
121+
63122
// GetPkgInfo fills the package info structure for the specified package path.
64123
// It passes the `go list` the flags specified by `flags`.
65124
func GetPkgInfo(pkgpath string, flags ...string) PkgInfo {
@@ -74,13 +133,13 @@ func GetPkgInfo(pkgpath string, flags ...string) PkgInfo {
74133
func GetModDir(pkgpath string, flags ...string) string {
75134
// enable module mode so that we can find a module root if it exists, even if go module support is
76135
// disabled by a build
77-
mod, err := runGoListWithEnv("{{.Module}}", pkgpath, []string{"GO111MODULE=on"}, flags...)
136+
mod, err := runGoListWithEnv("{{.Module}}", []string{pkgpath}, []string{"GO111MODULE=on"}, flags...)
78137
if err != nil || mod == "<nil>" {
79138
// if the command errors or modules aren't being used, return the empty string
80139
return ""
81140
}
82141

83-
modDir, err := runGoListWithEnv("{{.Module.Dir}}", pkgpath, []string{"GO111MODULE=on"}, flags...)
142+
modDir, err := runGoListWithEnv("{{.Module.Dir}}", []string{pkgpath}, []string{"GO111MODULE=on"}, flags...)
84143
if err != nil {
85144
return ""
86145
}
@@ -96,7 +155,7 @@ func GetModDir(pkgpath string, flags ...string) string {
96155
// GetPkgDir gets the absolute directory containing the package with path `pkgpath`. It passes the
97156
// `go list` command the flags specified by `flags`.
98157
func GetPkgDir(pkgpath string, flags ...string) string {
99-
pkgDir, err := runGoList("{{.Dir}}", pkgpath, flags...)
158+
pkgDir, err := runGoList("{{.Dir}}", []string{pkgpath}, flags...)
100159
if err != nil {
101160
return ""
102161
}
@@ -112,7 +171,7 @@ func GetPkgDir(pkgpath string, flags ...string) string {
112171
// DepErrors checks there are any errors resolving dependencies for `pkgpath`. It passes the `go
113172
// list` command the flags specified by `flags`.
114173
func DepErrors(pkgpath string, flags ...string) bool {
115-
out, err := runGoList("{{if .DepsErrors}}{{else}}error{{end}}", pkgpath, flags...)
174+
out, err := runGoList("{{if .DepsErrors}}{{else}}error{{end}}", []string{pkgpath}, flags...)
116175
if err != nil {
117176
// if go list failed, assume dependencies are broken
118177
return false

0 commit comments

Comments
 (0)