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 ()
@@ -54,18 +56,89 @@ func runGoListWithEnv(format string, pkgpath string, additionalEnv []string, fla
5456 return strings .TrimSpace (string (out )), nil
5557}
5658
59+ // PkgInfo holds package directory and module directory (if any) for a package
60+ type PkgInfo struct {
61+ PkgDir string // the directory directly containing source code of this package
62+ ModDir string // the module directory containing this package, empty if not a module
63+ }
64+
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+ // GetPkgInfo fills the package info structure for the specified package path.
122+ // It passes the `go list` the flags specified by `flags`.
123+ func GetPkgInfo (pkgpath string , flags ... string ) PkgInfo {
124+ return PkgInfo {
125+ PkgDir : GetPkgDir (pkgpath , flags ... ),
126+ ModDir : GetModDir (pkgpath , flags ... ),
127+ }
128+ }
129+
57130// GetModDir gets the absolute directory of the module containing the package with path
58131// `pkgpath`. It passes the `go list` the flags specified by `flags`.
59132func GetModDir (pkgpath string , flags ... string ) string {
60133 // enable module mode so that we can find a module root if it exists, even if go module support is
61134 // disabled by a build
62- mod , err := runGoListWithEnv ("{{.Module}}" , pkgpath , []string {"GO111MODULE=on" }, flags ... )
135+ mod , err := runGoListWithEnv ("{{.Module}}" , [] string { pkgpath } , []string {"GO111MODULE=on" }, flags ... )
63136 if err != nil || mod == "<nil>" {
64137 // if the command errors or modules aren't being used, return the empty string
65138 return ""
66139 }
67140
68- modDir , err := runGoListWithEnv ("{{.Module.Dir}}" , pkgpath , []string {"GO111MODULE=on" }, flags ... )
141+ modDir , err := runGoListWithEnv ("{{.Module.Dir}}" , [] string { pkgpath } , []string {"GO111MODULE=on" }, flags ... )
69142 if err != nil {
70143 return ""
71144 }
@@ -81,7 +154,7 @@ func GetModDir(pkgpath string, flags ...string) string {
81154// GetPkgDir gets the absolute directory containing the package with path `pkgpath`. It passes the
82155// `go list` command the flags specified by `flags`.
83156func GetPkgDir (pkgpath string , flags ... string ) string {
84- pkgDir , err := runGoList ("{{.Dir}}" , pkgpath , flags ... )
157+ pkgDir , err := runGoList ("{{.Dir}}" , [] string { pkgpath } , flags ... )
85158 if err != nil {
86159 return ""
87160 }
@@ -97,7 +170,7 @@ func GetPkgDir(pkgpath string, flags ...string) string {
97170// DepErrors checks there are any errors resolving dependencies for `pkgpath`. It passes the `go
98171// list` command the flags specified by `flags`.
99172func DepErrors (pkgpath string , flags ... string ) bool {
100- out , err := runGoList ("{{if .DepsErrors}}{{else}}error{{end}}" , pkgpath , flags ... )
173+ out , err := runGoList ("{{if .DepsErrors}}{{else}}error{{end}}" , [] string { pkgpath } , flags ... )
101174 if err != nil {
102175 // if go list failed, assume dependencies are broken
103176 return false
0 commit comments