1 | // Copyright 2011 The Go Authors. All rights reserved. |
---|---|
2 | // Use of this source code is governed by a BSD-style |
3 | // license that can be found in the LICENSE file. |
4 | |
5 | // This file is a reduced copy of $GOROOT/src/go/internal/gcimporter/gcimporter.go. |
6 | |
7 | // Package gcimporter provides various functions for reading |
8 | // gc-generated object files that can be used to implement the |
9 | // Importer interface defined by the Go 1.5 standard library package. |
10 | package gcimporter // import "golang.org/x/tools/internal/gcimporter" |
11 | |
12 | import ( |
13 | "bufio" |
14 | "bytes" |
15 | "fmt" |
16 | "go/build" |
17 | "go/token" |
18 | "go/types" |
19 | "io" |
20 | "io/ioutil" |
21 | "os" |
22 | "os/exec" |
23 | "path/filepath" |
24 | "strings" |
25 | "sync" |
26 | ) |
27 | |
28 | const ( |
29 | // Enable debug during development: it adds some additional checks, and |
30 | // prevents errors from being recovered. |
31 | debug = false |
32 | |
33 | // If trace is set, debugging output is printed to std out. |
34 | trace = false |
35 | ) |
36 | |
37 | var exportMap sync.Map // package dir → func() (string, bool) |
38 | |
39 | // lookupGorootExport returns the location of the export data |
40 | // (normally found in the build cache, but located in GOROOT/pkg |
41 | // in prior Go releases) for the package located in pkgDir. |
42 | // |
43 | // (We use the package's directory instead of its import path |
44 | // mainly to simplify handling of the packages in src/vendor |
45 | // and cmd/vendor.) |
46 | func lookupGorootExport(pkgDir string) (string, bool) { |
47 | f, ok := exportMap.Load(pkgDir) |
48 | if !ok { |
49 | var ( |
50 | listOnce sync.Once |
51 | exportPath string |
52 | ) |
53 | f, _ = exportMap.LoadOrStore(pkgDir, func() (string, bool) { |
54 | listOnce.Do(func() { |
55 | cmd := exec.Command("go", "list", "-export", "-f", "{{.Export}}", pkgDir) |
56 | cmd.Dir = build.Default.GOROOT |
57 | var output []byte |
58 | output, err := cmd.Output() |
59 | if err != nil { |
60 | return |
61 | } |
62 | |
63 | exports := strings.Split(string(bytes.TrimSpace(output)), "\n") |
64 | if len(exports) != 1 { |
65 | return |
66 | } |
67 | |
68 | exportPath = exports[0] |
69 | }) |
70 | |
71 | return exportPath, exportPath != "" |
72 | }) |
73 | } |
74 | |
75 | return f.(func() (string, bool))() |
76 | } |
77 | |
78 | var pkgExts = [...]string{".a", ".o"} |
79 | |
80 | // FindPkg returns the filename and unique package id for an import |
81 | // path based on package information provided by build.Import (using |
82 | // the build.Default build.Context). A relative srcDir is interpreted |
83 | // relative to the current working directory. |
84 | // If no file was found, an empty filename is returned. |
85 | func FindPkg(path, srcDir string) (filename, id string) { |
86 | if path == "" { |
87 | return |
88 | } |
89 | |
90 | var noext string |
91 | switch { |
92 | default: |
93 | // "x" -> "$GOPATH/pkg/$GOOS_$GOARCH/x.ext", "x" |
94 | // Don't require the source files to be present. |
95 | if abs, err := filepath.Abs(srcDir); err == nil { // see issue 14282 |
96 | srcDir = abs |
97 | } |
98 | bp, _ := build.Import(path, srcDir, build.FindOnly|build.AllowBinary) |
99 | if bp.PkgObj == "" { |
100 | var ok bool |
101 | if bp.Goroot && bp.Dir != "" { |
102 | filename, ok = lookupGorootExport(bp.Dir) |
103 | } |
104 | if !ok { |
105 | id = path // make sure we have an id to print in error message |
106 | return |
107 | } |
108 | } else { |
109 | noext = strings.TrimSuffix(bp.PkgObj, ".a") |
110 | id = bp.ImportPath |
111 | } |
112 | |
113 | case build.IsLocalImport(path): |
114 | // "./x" -> "/this/directory/x.ext", "/this/directory/x" |
115 | noext = filepath.Join(srcDir, path) |
116 | id = noext |
117 | |
118 | case filepath.IsAbs(path): |
119 | // for completeness only - go/build.Import |
120 | // does not support absolute imports |
121 | // "/x" -> "/x.ext", "/x" |
122 | noext = path |
123 | id = path |
124 | } |
125 | |
126 | if false { // for debugging |
127 | if path != id { |
128 | fmt.Printf("%s -> %s\n", path, id) |
129 | } |
130 | } |
131 | |
132 | if filename != "" { |
133 | if f, err := os.Stat(filename); err == nil && !f.IsDir() { |
134 | return |
135 | } |
136 | } |
137 | |
138 | // try extensions |
139 | for _, ext := range pkgExts { |
140 | filename = noext + ext |
141 | if f, err := os.Stat(filename); err == nil && !f.IsDir() { |
142 | return |
143 | } |
144 | } |
145 | |
146 | filename = "" // not found |
147 | return |
148 | } |
149 | |
150 | // Import imports a gc-generated package given its import path and srcDir, adds |
151 | // the corresponding package object to the packages map, and returns the object. |
152 | // The packages map must contain all packages already imported. |
153 | func Import(packages map[string]*types.Package, path, srcDir string, lookup func(path string) (io.ReadCloser, error)) (pkg *types.Package, err error) { |
154 | var rc io.ReadCloser |
155 | var filename, id string |
156 | if lookup != nil { |
157 | // With custom lookup specified, assume that caller has |
158 | // converted path to a canonical import path for use in the map. |
159 | if path == "unsafe" { |
160 | return types.Unsafe, nil |
161 | } |
162 | id = path |
163 | |
164 | // No need to re-import if the package was imported completely before. |
165 | if pkg = packages[id]; pkg != nil && pkg.Complete() { |
166 | return |
167 | } |
168 | f, err := lookup(path) |
169 | if err != nil { |
170 | return nil, err |
171 | } |
172 | rc = f |
173 | } else { |
174 | filename, id = FindPkg(path, srcDir) |
175 | if filename == "" { |
176 | if path == "unsafe" { |
177 | return types.Unsafe, nil |
178 | } |
179 | return nil, fmt.Errorf("can't find import: %q", id) |
180 | } |
181 | |
182 | // no need to re-import if the package was imported completely before |
183 | if pkg = packages[id]; pkg != nil && pkg.Complete() { |
184 | return |
185 | } |
186 | |
187 | // open file |
188 | f, err := os.Open(filename) |
189 | if err != nil { |
190 | return nil, err |
191 | } |
192 | defer func() { |
193 | if err != nil { |
194 | // add file name to error |
195 | err = fmt.Errorf("%s: %v", filename, err) |
196 | } |
197 | }() |
198 | rc = f |
199 | } |
200 | defer rc.Close() |
201 | |
202 | var hdr string |
203 | var size int64 |
204 | buf := bufio.NewReader(rc) |
205 | if hdr, size, err = FindExportData(buf); err != nil { |
206 | return |
207 | } |
208 | |
209 | switch hdr { |
210 | case "$$B\n": |
211 | var data []byte |
212 | data, err = ioutil.ReadAll(buf) |
213 | if err != nil { |
214 | break |
215 | } |
216 | |
217 | // TODO(gri): allow clients of go/importer to provide a FileSet. |
218 | // Or, define a new standard go/types/gcexportdata package. |
219 | fset := token.NewFileSet() |
220 | |
221 | // The indexed export format starts with an 'i'; the older |
222 | // binary export format starts with a 'c', 'd', or 'v' |
223 | // (from "version"). Select appropriate importer. |
224 | if len(data) > 0 { |
225 | switch data[0] { |
226 | case 'i': |
227 | _, pkg, err := IImportData(fset, packages, data[1:], id) |
228 | return pkg, err |
229 | |
230 | case 'v', 'c', 'd': |
231 | _, pkg, err := BImportData(fset, packages, data, id) |
232 | return pkg, err |
233 | |
234 | case 'u': |
235 | _, pkg, err := UImportData(fset, packages, data[1:size], id) |
236 | return pkg, err |
237 | |
238 | default: |
239 | l := len(data) |
240 | if l > 10 { |
241 | l = 10 |
242 | } |
243 | return nil, fmt.Errorf("unexpected export data with prefix %q for path %s", string(data[:l]), id) |
244 | } |
245 | } |
246 | |
247 | default: |
248 | err = fmt.Errorf("unknown export data header: %q", hdr) |
249 | } |
250 | |
251 | return |
252 | } |
253 | |
254 | func deref(typ types.Type) types.Type { |
255 | if p, _ := typ.(*types.Pointer); p != nil { |
256 | return p.Elem() |
257 | } |
258 | return typ |
259 | } |
260 | |
261 | type byPath []*types.Package |
262 | |
263 | func (a byPath) Len() int { return len(a) } |
264 | func (a byPath) Swap(i, j int) { a[i], a[j] = a[j], a[i] } |
265 | func (a byPath) Less(i, j int) bool { return a[i].Path() < a[j].Path() } |
266 |
Members