forked from mcoops/deplist
-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathgolang.go
165 lines (142 loc) · 3.99 KB
/
golang.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package scan
import (
"context"
"encoding/json"
"errors"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
log "github.com/sirupsen/logrus"
"golang.org/x/mod/semver"
)
// GoListDeps holds the import path, version and gofiles for a given go
// dependency
type GoListDeps struct {
ImportPath string `json:"ImportPath"`
Module struct {
Version string `json:"Version"`
Replace struct {
Version string `json:"Version"`
} `json:"Replace"`
} `json:"Module"`
GoFiles []string `json:"GoFiles"`
}
// GoPkg holds the version and go paths/files for a given dep
type GoPkg struct {
Version string
Gofiles []string
}
func getVersion(deps GoListDeps) string {
/* if replace is specified, then use that version
* not seen when version and replace.version are different
* but just in case
*/
if deps.Module.Replace.Version != "" {
// due to the way we're loading the json this time, this just works
return deps.Module.Replace.Version
}
return deps.Module.Version
}
func runCmd(path string, extraFlag string) ([]byte, error) {
// go list -f '{{if not .Standard}}{{.Module}}{{end}}' -json -deps ./...
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
// go list -f '{{if not .Standard}}{{.Module}}{{end}}' -json -deps ./...
var cmd *exec.Cmd
if extraFlag == "" {
cmd = exec.CommandContext(ctx, "go", "list", "-json", "-deps", "./...")
} else {
cmd = exec.CommandContext(ctx, "go", "list", extraFlag, "-json", "-deps", "./...")
}
cmd.Dir = filepath.Dir(path) // // force directory
out, err := cmd.Output()
if ctx.Err() == context.DeadlineExceeded {
return nil, ctx.Err()
}
// mod=vendor sometimes still returns results but returns an error. In
// that case ignore the error and return what we can
if err != nil {
log.Debug(string(err.(*exec.ExitError).Stderr))
if extraFlag == "" {
// assume some retrival error, we have to redo the cmd with mod=vendor
return nil, err
}
if len(out) == 0 {
return nil, err
}
}
return out, nil
}
/*
* Need to support re-running the go list with and without -mod=vendor
* First run defaults to without, if any kind of error we'll just retry the run
*/
func runGoList(path string) ([]byte, error) {
out, err := runCmd(path, "")
if err != nil {
// rerun with -mod=vendor
vendorDir := filepath.Join(filepath.Dir(path), "vendor")
_, err := os.Stat(vendorDir)
if err == nil {
if !os.IsNotExist(err) {
log.Debug("Retrying `go list` with `-mod=vendor` flag")
out, err = runCmd(path, "-mod=vendor")
}
}
if err != nil {
log.Debug("Retrying `go list` with `-mod=readonly` flag")
out, err = runCmd(path, "-mod=readonly")
}
if err != nil {
log.Debug("Retrying `go list` with `-e` flag")
out, err = runCmd(path, "-e")
if err != nil {
return nil, errors.New("All `go list` attempts failed")
}
}
}
return out, nil
}
// GetGolangDeps uses `go list` gather a list of dependencies located at `path`
// returning an array of `GoPkg` structs
func GetGolangDeps(path string) (map[string]GoPkg, error) {
log.Debugf("GetGolangDeps %s", path)
// need to use a map as we'll get lots of duplicate entries
gathered := make(map[string]GoPkg)
out, err := runGoList(path)
if err != nil {
return nil, err
}
/* we cann't just marshall the json as go list returns multiple json
* documents not an array of json - which is annoying
*/
decoder := json.NewDecoder(strings.NewReader(string(out)))
for {
var goListDeps GoListDeps
err := decoder.Decode(&goListDeps)
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
importPath := goListDeps.ImportPath
if _, ok := gathered[importPath]; ok {
if gathered[importPath].Version != semver.Max(gathered[importPath].Version, getVersion(goListDeps)) {
gathered[importPath] = GoPkg{
Version: getVersion(goListDeps),
Gofiles: goListDeps.GoFiles,
}
}
} else {
gathered[importPath] = GoPkg{
Version: getVersion(goListDeps),
Gofiles: goListDeps.GoFiles,
}
}
}
return gathered, nil
}