1
2
3
4
5
6 package gcimporter
7
8 import (
9 "bufio"
10 "bytes"
11 "errors"
12 "fmt"
13 "go/build"
14 "go/token"
15 "go/types"
16 "internal/pkgbits"
17 "internal/saferio"
18 "io"
19 "os"
20 "os/exec"
21 "path/filepath"
22 "strings"
23 "sync"
24 )
25
26
27 const debug = false
28
29 var exportMap sync.Map
30
31
32
33
34
35
36
37
38 func lookupGorootExport(pkgDir string) (string, error) {
39 f, ok := exportMap.Load(pkgDir)
40 if !ok {
41 var (
42 listOnce sync.Once
43 exportPath string
44 err error
45 )
46 f, _ = exportMap.LoadOrStore(pkgDir, func() (string, error) {
47 listOnce.Do(func() {
48 cmd := exec.Command(filepath.Join(build.Default.GOROOT, "bin", "go"), "list", "-export", "-f", "{{.Export}}", pkgDir)
49 cmd.Dir = build.Default.GOROOT
50 cmd.Env = append(os.Environ(), "PWD="+cmd.Dir, "GOROOT="+build.Default.GOROOT)
51 var output []byte
52 output, err = cmd.Output()
53 if err != nil {
54 if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 {
55 err = errors.New(string(ee.Stderr))
56 }
57 return
58 }
59
60 exports := strings.Split(string(bytes.TrimSpace(output)), "\n")
61 if len(exports) != 1 {
62 err = fmt.Errorf("go list reported %d exports; expected 1", len(exports))
63 return
64 }
65
66 exportPath = exports[0]
67 })
68
69 return exportPath, err
70 })
71 }
72
73 return f.(func() (string, error))()
74 }
75
76 var pkgExts = [...]string{".a", ".o"}
77
78
79
80
81
82 func FindPkg(path, srcDir string) (filename, id string, err error) {
83 if path == "" {
84 return "", "", errors.New("path is empty")
85 }
86
87 var noext string
88 switch {
89 default:
90
91
92 if abs, err := filepath.Abs(srcDir); err == nil {
93 srcDir = abs
94 }
95 var bp *build.Package
96 bp, err = build.Import(path, srcDir, build.FindOnly|build.AllowBinary)
97 if bp.PkgObj == "" {
98 if bp.Goroot && bp.Dir != "" {
99 filename, err = lookupGorootExport(bp.Dir)
100 if err == nil {
101 _, err = os.Stat(filename)
102 }
103 if err == nil {
104 return filename, bp.ImportPath, nil
105 }
106 }
107 goto notfound
108 } else {
109 noext = strings.TrimSuffix(bp.PkgObj, ".a")
110 }
111 id = bp.ImportPath
112
113 case build.IsLocalImport(path):
114
115 noext = filepath.Join(srcDir, path)
116 id = noext
117
118 case filepath.IsAbs(path):
119
120
121
122 noext = path
123 id = path
124 }
125
126 if false {
127 if path != id {
128 fmt.Printf("%s -> %s\n", path, id)
129 }
130 }
131
132
133 for _, ext := range pkgExts {
134 filename = noext + ext
135 f, statErr := os.Stat(filename)
136 if statErr == nil && !f.IsDir() {
137 return filename, id, nil
138 }
139 if err == nil {
140 err = statErr
141 }
142 }
143
144 notfound:
145 if err == nil {
146 return "", path, fmt.Errorf("can't find import: %q", path)
147 }
148 return "", path, fmt.Errorf("can't find import: %q: %w", path, err)
149 }
150
151
152
153
154 func Import(fset *token.FileSet, packages map[string]*types.Package, path, srcDir string, lookup func(path string) (io.ReadCloser, error)) (pkg *types.Package, err error) {
155 var rc io.ReadCloser
156 var id string
157 if lookup != nil {
158
159
160 if path == "unsafe" {
161 return types.Unsafe, nil
162 }
163 id = path
164
165
166 if pkg = packages[id]; pkg != nil && pkg.Complete() {
167 return
168 }
169 f, err := lookup(path)
170 if err != nil {
171 return nil, err
172 }
173 rc = f
174 } else {
175 var filename string
176 filename, id, err = FindPkg(path, srcDir)
177 if filename == "" {
178 if path == "unsafe" {
179 return types.Unsafe, nil
180 }
181 return nil, err
182 }
183
184
185 if pkg = packages[id]; pkg != nil && pkg.Complete() {
186 return
187 }
188
189
190 f, err := os.Open(filename)
191 if err != nil {
192 return nil, err
193 }
194 defer func() {
195 if err != nil {
196
197 err = fmt.Errorf("%s: %v", filename, err)
198 }
199 }()
200 rc = f
201 }
202 defer rc.Close()
203
204 buf := bufio.NewReader(rc)
205 hdr, size, err := FindExportData(buf)
206 if err != nil {
207 return
208 }
209
210 switch hdr {
211 case "$$\n":
212 err = fmt.Errorf("import %q: old textual export format no longer supported (recompile library)", path)
213
214 case "$$B\n":
215 var exportFormat byte
216 if exportFormat, err = buf.ReadByte(); err != nil {
217 return
218 }
219 size--
220
221
222
223
224
225 switch exportFormat {
226 case 'u':
227 var data []byte
228 var r io.Reader = buf
229 if size >= 0 {
230 if data, err = saferio.ReadData(r, uint64(size)); err != nil {
231 return
232 }
233 } else if data, err = io.ReadAll(r); err != nil {
234 return
235 }
236 s := string(data)
237 s = s[:strings.LastIndex(s, "\n$$\n")]
238
239 input := pkgbits.NewPkgDecoder(id, s)
240 pkg = readUnifiedPackage(fset, nil, packages, input)
241 case 'i':
242 pkg, err = iImportData(fset, packages, buf, id)
243 default:
244 err = fmt.Errorf("import %q: old binary export format no longer supported (recompile library)", path)
245 }
246
247 default:
248 err = fmt.Errorf("import %q: unknown export data header: %q", path, hdr)
249 }
250
251 return
252 }
253
View as plain text