/
gopkgs.go
221 lines (181 loc) · 4.37 KB
/
gopkgs.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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
package gopkgs
import (
"bufio"
"errors"
"go/build"
"io"
"os"
"path/filepath"
"strings"
"github.com/karrick/godirwalk"
pkgerrors "github.com/pkg/errors"
)
// Pkg hold the information of the package.
type Pkg struct {
Dir string // directory containing package sources
ImportPath string // import path of package in dir
Name string // package name
}
// Options for retrieve packages.
type Options struct {
WorkDir string // Will return importable package under WorkDir. Any vendor dependencies outside the WorkDir will be ignored.
NoVendor bool // Will not retrieve vendor dependencies, except inside WorkDir (if specified)
}
type goFile struct {
path string
dir string
srcDir string
}
func mustClose(c io.Closer) {
if err := c.Close(); err != nil {
panic(err)
}
}
func readPackageName(filename string) (string, error) {
f, err := os.Open(filename)
if err != nil {
return "", err
}
s := bufio.NewScanner(f)
var inComment bool
for s.Scan() {
line := strings.TrimSpace(s.Text())
if line == "" {
continue
}
if !inComment {
if strings.HasPrefix(line, "/*") {
inComment = true
continue
}
if strings.HasPrefix(line, "//") {
// skip inline comment
continue
}
if strings.HasPrefix(line, "package") {
ls := strings.Split(line, " ")
if len(ls) < 2 {
mustClose(f)
return "", errors.New("expect pattern 'package <name>':" + line)
}
mustClose(f)
return ls[1], nil
}
// package should be found first
mustClose(f)
return "", errors.New("invalid go file, expect package declaration")
}
// inComment = true
if strings.HasSuffix(line, "*/") {
inComment = false
}
}
mustClose(f)
return "", errors.New("cannot find package information")
}
// Packages available to import.
func Packages(opts Options) (map[string]Pkg, error) {
pkgs := make(map[string]Pkg)
filec, errc := listFiles(opts)
for f := range filec {
pkgDir := f.dir
if _, found := pkgs[pkgDir]; found {
// already have this package, skip
continue
}
pkgName, err := readPackageName(f.path)
if err != nil {
// skip unparseable file
continue
}
if pkgName == "main" {
// skip main package
continue
}
pkgs[pkgDir] = Pkg{
Name: pkgName,
ImportPath: filepath.ToSlash(pkgDir[len(f.srcDir)+len("/"):]),
Dir: pkgDir,
}
}
if err := <-errc; err != nil {
return nil, err
}
return pkgs, nil
}
func listFiles(opts Options) (<-chan goFile, <-chan error) {
filec := make(chan goFile, 10000)
errc := make(chan error, 1)
go func() {
defer func() {
close(filec)
close(errc)
}()
workDir := opts.WorkDir
if workDir != "" && !filepath.IsAbs(workDir) {
wd, err := filepath.Abs(workDir)
if err != nil {
errc <- err
return
}
workDir = wd
}
for _, srcDir := range build.Default.SrcDirs() {
err := godirwalk.Walk(srcDir, &godirwalk.Options{
FollowSymbolicLinks: true,
Callback: func(osPathname string, de *godirwalk.Dirent) error {
name := de.Name()
pathDir := filepath.Dir(osPathname)
// Symlink not supported by go
if de.IsSymlink() {
return filepath.SkipDir
}
// Ignore files begin with "_", "." "_test.go" and directory named "testdata"
// see: https://golang.org/cmd/go/#hdr-Description_of_package_lists
if de.IsDir() {
if name[0] == '.' || name[0] == '_' || name == "testdata" || name == "node_modules" {
return filepath.SkipDir
}
if name == "vendor" {
if workDir != "" {
if !visibleVendor(workDir, pathDir) {
return filepath.SkipDir
}
return nil
}
if opts.NoVendor {
return filepath.SkipDir
}
}
return nil
}
if name[0] == '.' || !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") {
return nil
}
if pathDir == srcDir {
// Cannot put files on $GOPATH/src or $GOROOT/src.
return nil
}
filec <- goFile{
path: osPathname,
dir: pathDir,
srcDir: srcDir,
}
return nil
},
ErrorCallback: func(s string, err error) godirwalk.ErrorAction {
err = pkgerrors.Cause(err)
if v, ok := err.(*os.PathError); ok && os.IsNotExist(v.Err) {
return godirwalk.SkipNode
}
return godirwalk.Halt
},
})
if err != nil {
errc <- err
return
}
}
}()
return filec, errc
}