11package util
22
33import (
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`.
65124func GetPkgInfo (pkgpath string , flags ... string ) PkgInfo {
@@ -74,13 +133,13 @@ func GetPkgInfo(pkgpath string, flags ...string) PkgInfo {
74133func 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`.
98157func 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`.
114173func 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