11package util
22
33import (
4+ "encoding/json"
45 "errors"
6+ "io"
57 "log"
68 "os"
79 "os/exec"
@@ -12,9 +14,9 @@ import (
1214
1315var extractorPath string
1416
15- // Getenv retrieves the value of the environment variable named by the key.
16- // If that variable is not present, it iterates over the given aliases until
17- // it finds one that is. If none are present, the empty string is returned.
17+ // Getenv retrieves the value of the environment variable named by the key. If that variable is not
18+ // present, it iterates over the given aliases until it finds one that is. If none are present, the
19+ // empty string is returned.
1820func Getenv (key string , aliases ... string ) string {
1921 val := os .Getenv (key )
2022 if val != "" {
@@ -30,16 +32,12 @@ func Getenv(key string, aliases ...string) string {
3032}
3133
3234// runGoList is a helper function for running go list with format `format` and flags `flags` on
33- // package `pkgpath`.
34- func runGoList (format string , pkgpath string , flags ... string ) (string , error ) {
35- return runGoListWithEnv (format , pkgpath , nil , flags ... )
36- }
37-
38- func runGoListWithEnv (format string , pkgpath string , additionalEnv []string , flags ... string ) (string , error ) {
35+ // the packages defined by `patterns`.
36+ func runGoList (format string , patterns []string , flags ... string ) (string , error ) {
3937 args := append ([]string {"list" , "-e" , "-f" , format }, flags ... )
40- args = append (args , pkgpath )
38+ args = append (args , patterns ... )
4139 cmd := exec .Command ("go" , args ... )
42- cmd . Env = append ( os . Environ (), additionalEnv ... )
40+ log . Printf ( "Running cmd: %s" , cmd . String () )
4341 out , err := cmd .Output ()
4442
4543 if err != nil {
@@ -54,50 +52,82 @@ func runGoListWithEnv(format string, pkgpath string, additionalEnv []string, fla
5452 return strings .TrimSpace (string (out )), nil
5553}
5654
57- // GetModDir gets the absolute directory of the module containing the package with path
58- // `pkgpath`. It passes the `go list` the flags specified by `flags`.
59- func GetModDir (pkgpath string , flags ... string ) string {
55+ // PkgInfo holds package directory and module directory (if any) for a package
56+ type PkgInfo struct {
57+ PkgDir string // the directory directly containing source code of this package
58+ ModDir string // the module directory containing this package, empty if not a module
59+ }
60+
61+ // GetPkgsInfo gets the absolute module and package root directories for the packages matched by the
62+ // patterns `patterns`. It passes to `go list` the flags specified by `flags`. If `includingDeps`
63+ // is true, all dependencies will also be included.
64+ func GetPkgsInfo (patterns []string , includingDeps bool , flags ... string ) (map [string ]* PkgInfo , error ) {
6065 // enable module mode so that we can find a module root if it exists, even if go module support is
6166 // disabled by a build
62- mod , err := runGoListWithEnv ("{{.Module}}" , pkgpath , []string {"GO111MODULE=on" }, flags ... )
63- if err != nil || mod == "<nil>" {
64- // if the command errors or modules aren't being used, return the empty string
65- return ""
67+ if includingDeps {
68+ // the flag `-deps` causes all dependencies to be retrieved
69+ flags = append (flags , "-deps" )
6670 }
6771
68- modDir , err := runGoListWithEnv ("{{.Module.Dir}}" , pkgpath , []string {"GO111MODULE=on" }, flags ... )
72+ // using -json overrides -f format
73+ output , err := runGoList ("" , patterns , append (flags , "-json" )... )
6974 if err != nil {
70- return ""
75+ return nil , err
7176 }
7277
73- abs , err := filepath .Abs (modDir )
74- if err != nil {
75- log .Printf ("Warning: unable to make %s absolute: %s" , modDir , err .Error ())
76- return ""
78+ // the output of `go list -json` is a stream of json object
79+ type goListPkgInfo struct {
80+ ImportPath string
81+ Dir string
82+ Module * struct {
83+ Dir string
84+ }
7785 }
78- return abs
79- }
80-
81- // GetPkgDir gets the absolute directory containing the package with path `pkgpath`. It passes the
82- // `go list` command the flags specified by `flags`.
83- func GetPkgDir (pkgpath string , flags ... string ) string {
84- pkgDir , err := runGoList ("{{.Dir}}" , pkgpath , flags ... )
85- if err != nil {
86- return ""
86+ pkgInfoMapping := make (map [string ]* PkgInfo )
87+ streamDecoder := json .NewDecoder (strings .NewReader (output ))
88+ for {
89+ var pkgInfo goListPkgInfo
90+ decErr := streamDecoder .Decode (& pkgInfo )
91+ if decErr == io .EOF {
92+ break
93+ }
94+ if decErr != nil {
95+ log .Printf ("Error decoding output of go list -json: %s" , err .Error ())
96+ return nil , decErr
97+ }
98+ pkgAbsDir , err := filepath .Abs (pkgInfo .Dir )
99+ if err != nil {
100+ log .Printf ("Unable to make package dir %s absolute: %s" , pkgInfo .Dir , err .Error ())
101+ }
102+ var modAbsDir string
103+ if pkgInfo .Module != nil {
104+ modAbsDir , err = filepath .Abs (pkgInfo .Module .Dir )
105+ if err != nil {
106+ log .Printf ("Unable to make module dir %s absolute: %s" , pkgInfo .Module .Dir , err .Error ())
107+ }
108+ }
109+ pkgInfoMapping [pkgInfo .ImportPath ] = & PkgInfo {
110+ PkgDir : pkgAbsDir ,
111+ ModDir : modAbsDir ,
112+ }
87113 }
114+ return pkgInfoMapping , nil
115+ }
88116
89- abs , err := filepath .Abs (pkgDir )
117+ // GetPkgsInfo gets the absolute module and package root directories for the package `pkg`, passing
118+ // the internal `go list` command the flags specified by `flags`.
119+ func GetPkgInfo (pkg string , flags ... string ) (* PkgInfo , error ) {
120+ pkgInfos , err := GetPkgsInfo ([]string {pkg }, false , flags ... )
90121 if err != nil {
91- log .Printf ("Warning: unable to make %s absolute: %s" , pkgDir , err .Error ())
92- return ""
122+ return nil , err
93123 }
94- return abs
124+ return pkgInfos [ pkg ], nil
95125}
96126
97127// DepErrors checks there are any errors resolving dependencies for `pkgpath`. It passes the `go
98128// list` command the flags specified by `flags`.
99129func DepErrors (pkgpath string , flags ... string ) bool {
100- out , err := runGoList ("{{if .DepsErrors}}{{else}}error{{end}}" , pkgpath , flags ... )
130+ out , err := runGoList ("{{if .DepsErrors}}{{else}}error{{end}}" , [] string { pkgpath } , flags ... )
101131 if err != nil {
102132 // if go list failed, assume dependencies are broken
103133 return false
0 commit comments