/
which.go
212 lines (194 loc) · 5.58 KB
/
which.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
package which
import (
"debug/gosym"
"errors"
"os"
"path/filepath"
"runtime"
"strings"
)
// TODO(rjeczalik): support all platform types
func init() {
// Add $GOROOT and $GOROOT_FINAL to the filtered paths.
goroot := runtime.GOROOT()
filtered[goroot] = struct{}{}
if err := os.Setenv("GOROOT", ""); err == nil {
filtered[runtime.GOROOT()] = struct{}{} // $GOROOT_FINAL
os.Setenv("GOROOT", goroot)
}
// Make the order of file factory methods platform-specific.
switch runtime.GOOS {
case "darwin":
alltbl = append(alltbl, newmacho, newelf, newpe)
case "windows":
alltbl = append(alltbl, newpe, newmacho, newelf)
default:
alltbl = append(alltbl, newelf, newmacho, newpe)
}
}
type tabler interface {
Close() error
Pcln() ([]byte, error)
Sym() ([]byte, error)
Text() (uint64, error)
Type() *PlatformType
}
// All supported symbol table builders.
var alltbl []func(string) (tabler, error)
// A path is discarded if it contains any of the filtered strings.
// TODO(rjeczalik): add $HOME/.gvm/gos?
var filtered = map[string]struct{}{
filepath.FromSlash("/tmp/makerelease"): {},
filepath.FromSlash("<autogenerated>"): {},
filepath.FromSlash("c:/go/src"): {},
filepath.FromSlash("/usr/local/go/src"): {},
}
var (
// ErrNotGoExec is an error.
ErrNotGoExec = errors.New("which: not a Go executable")
// ErrGuessFail is an error.
ErrGuessFail = errors.New("which: unable to guess an import path of the main package")
)
// PlatformType represents the target platform of the executable.
type PlatformType struct {
GOOS string // target operating system
GOARCH string // target architecture
}
// String gives Go platform string.
func (typ PlatformType) String() string {
return typ.GOOS + "_" + typ.GOARCH
}
var (
// PlatformDarwin386 represents the darwin_386 target arch.
PlatformDarwin386 = &PlatformType{"darwin", "386"}
// PlatformDarwinAMD64 represents the darwin_amd64 target arch.
PlatformDarwinAMD64 = &PlatformType{"darwin", "amd64"}
// PlatformFreeBSD386 represents the freebsd_386 target arch.
PlatformFreeBSD386 = &PlatformType{"freebsd", "386"}
// PlatformFreeBSDAMD64 represents the freebsd_amd64 target arch.
PlatformFreeBSDAMD64 = &PlatformType{"freebsd", "amd64"}
// PlatformLinux386 represents the linux_386 target arch.
PlatformLinux386 = &PlatformType{"linux", "386"}
// PlatformLinuxAMD64 represents the linux_amd64 target arch.
PlatformLinuxAMD64 = &PlatformType{"linux", "amd64"}
// PlatformWindows386 represents the windows_386 target arch.
PlatformWindows386 = &PlatformType{"windows", "386"}
// PlatformWindowsAMD64 represents the windows_amd64 target arch.
PlatformWindowsAMD64 = &PlatformType{"windows", "amd64"}
)
// Exec represents a single Go executable file.
type Exec struct {
Path string // Path to the executable.
Type *PlatformType // Fileutable file format.
table *gosym.Table
}
// NewExec tries to detect executable type for the given path and returns
// a new executable. It fails if file does not exist, is not a Go executable or
// it's unable to parse the file format.
func NewExec(path string) (*Exec, error) {
typ, symtab, pclntab, text, err := newtbl(path)
if err != nil {
return nil, err
}
lntab := gosym.NewLineTable(pclntab, text)
if lntab == nil {
return nil, ErrNotGoExec
}
tab, err := gosym.NewTable(symtab, lntab)
if err != nil {
return nil, ErrNotGoExec
}
return &Exec{Path: path, Type: typ, table: tab}, nil
}
// Import gives the import path of main package of given executable. It returns
// non-nil error when it fails to guess the exact path.
func (ex *Exec) Import() (string, error) {
var dirs = make(map[string]struct{})
for file, obj := range ex.table.Files {
for i := range obj.Funcs {
// main.main symbol is referenced by every file of each package
// imported by the main package of the executable.
if obj.Funcs[i].Sym.Name == "main.main" && !isfiltered(file) {
dirs[filepath.Dir(file)] = struct{}{}
}
}
}
name := filepath.Base(ex.Path)
if ex.Type == PlatformWindows386 || ex.Type == PlatformWindowsAMD64 {
name = strings.TrimRight(name, ".exe")
}
if pkg, unique := guesspkg(name, dirs); unique && pkg != "" {
return pkg, nil
}
return "", ErrGuessFail
}
// Import reads the import path of main package of the Go executable given by
// the path.
func Import(path string) (string, error) {
ex, err := NewExec(path)
if err != nil {
return "", err
}
return ex.Import()
}
func newtbl(path string) (typ *PlatformType, symtab, pclntab []byte, text uint64, err error) {
var tbl tabler
fail := func() {
err = errors.New("which: unable to read Go symbol table: " + err.Error())
tbl.Close()
}
for _, newt := range alltbl {
if tbl, err = newt(path); err != nil {
err = ErrNotGoExec
continue
}
if symtab, err = tbl.Sym(); err != nil {
fail()
continue
}
if pclntab, err = tbl.Pcln(); err != nil {
fail()
continue
}
if text, err = tbl.Text(); err != nil {
fail()
continue
}
typ = tbl.Type()
tbl.Close()
break
}
return
}
func isfiltered(file string) bool {
for f := range filtered {
if strings.Contains(file, f) {
return true
}
}
return false
}
var src = filepath.FromSlash("/src/")
func guesspkg(name string, dirs map[string]struct{}) (pkg string, unique bool) {
defer func() {
pkg = filepath.ToSlash(pkg)
}()
for _, s := range []string{"cmd" + string(os.PathSeparator) + name, name} {
for dir := range dirs {
if strings.Contains(dir, s) {
if i := strings.LastIndex(dir, src); i != -1 {
pkg = dir[i+len(src):]
if unique {
unique = false
return
}
unique = true
}
}
}
if unique {
return
}
}
return
}