1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package bazel
16
17 import (
18 "bytes"
19 "errors"
20 "fmt"
21 "io/ioutil"
22 "os"
23 "path"
24 "path/filepath"
25 "runtime"
26 "sort"
27 "strings"
28 "sync"
29 )
30
31 const (
32 RUNFILES_MANIFEST_FILE = "RUNFILES_MANIFEST_FILE"
33 RUNFILES_DIR = "RUNFILES_DIR"
34 )
35
36
37
38
39
40
41
42
43
44
45
46
47 func Runfile(path string) (string, error) {
48
49 if _, err := os.Stat(path); err == nil {
50 return filepath.Abs(path)
51 }
52
53 if err := ensureRunfiles(); err != nil {
54 return "", err
55 }
56
57
58 if entry, ok := runfiles.index.GetIgnoringWorkspace(path); ok {
59 return entry.Path, nil
60 }
61
62 if strings.HasPrefix(path, "../") || strings.HasPrefix(path, "external/") {
63 pathParts := strings.Split(path, "/")
64 if len(pathParts) >= 3 {
65 workspace := pathParts[1]
66 pathInsideWorkspace := strings.Join(pathParts[2:], "/")
67 if path := runfiles.index.Get(workspace, pathInsideWorkspace); path != "" {
68 return path, nil
69 }
70 }
71 }
72
73
74 if runfiles.workspace != "" {
75 mainPath := filepath.Join(runfiles.dir, runfiles.workspace, path)
76 if _, err := os.Stat(mainPath); err == nil {
77 return mainPath, nil
78 }
79 }
80
81
82 for _, w := range runfiles.workspaces {
83 workPath := filepath.Join(runfiles.dir, w, path)
84 if _, err := os.Stat(workPath); err == nil {
85 return workPath, nil
86 }
87 }
88
89 return "", fmt.Errorf("Runfile %s: could not locate file", path)
90 }
91
92
93
94
95
96
97
98
99
100 func FindBinary(pkg, name string) (string, bool) {
101 if err := ensureRunfiles(); err != nil {
102 return "", false
103 }
104
105
106
107
108
109 if runfiles.list != nil {
110 if runtime.GOOS == "windows" {
111 name += ".exe"
112 }
113 for _, entry := range runfiles.list {
114 if path.Base(entry.ShortPath) != name {
115 continue
116 }
117 pkgDir := path.Dir(path.Dir(entry.ShortPath))
118 if pkgDir == "." {
119 pkgDir = ""
120 }
121 if pkgDir != pkg {
122 continue
123 }
124 return entry.Path, true
125 }
126 return "", false
127 }
128
129 dir, err := Runfile(pkg)
130 if err != nil {
131 return "", false
132 }
133 var found string
134 stopErr := errors.New("stop")
135 err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
136 if err != nil {
137 return err
138 }
139 if info.IsDir() {
140 return nil
141 }
142 base := filepath.Base(path)
143 stem := strings.TrimSuffix(base, ".exe")
144 if stem != name {
145 return nil
146 }
147 if runtime.GOOS != "windows" {
148 if st, err := os.Stat(path); err != nil {
149 return err
150 } else if st.Mode()&0111 == 0 {
151 return nil
152 }
153 }
154 if stem == name {
155 found = path
156 return stopErr
157 }
158 return nil
159 })
160 if err == stopErr {
161 return found, true
162 } else {
163 return "", false
164 }
165 }
166
167
168 type RunfileEntry struct {
169
170
171 Workspace string
172
173
174
175
176 ShortPath string
177
178
179 Path string
180 }
181
182
183 func ListRunfiles() ([]RunfileEntry, error) {
184 if err := ensureRunfiles(); err != nil {
185 return nil, err
186 }
187
188 if runfiles.list == nil && runfiles.dir != "" {
189 runfiles.listOnce.Do(func() {
190 var list []RunfileEntry
191 haveWorkspaces := strings.HasSuffix(runfiles.dir, ".runfiles") && runfiles.workspace != ""
192
193 err := filepath.Walk(runfiles.dir, func(path string, info os.FileInfo, err error) error {
194 if err != nil {
195 return err
196 }
197 rel, _ := filepath.Rel(runfiles.dir, path)
198 rel = filepath.ToSlash(rel)
199 if rel == "." {
200 return nil
201 }
202
203 var workspace, shortPath string
204 if haveWorkspaces {
205 if i := strings.IndexByte(rel, '/'); i < 0 {
206 return nil
207 } else {
208 workspace, shortPath = rel[:i], rel[i+1:]
209 }
210 } else {
211 workspace, shortPath = "", rel
212 }
213
214 list = append(list, RunfileEntry{Workspace: workspace, ShortPath: shortPath, Path: path})
215 return nil
216 })
217 if err != nil {
218 runfiles.err = err
219 return
220 }
221 runfiles.list = list
222 })
223 }
224 return runfiles.list, runfiles.err
225 }
226
227
228
229
230 func TestWorkspace() (string, error) {
231 if err := ensureRunfiles(); err != nil {
232 return "", err
233 }
234 if runfiles.workspace != "" {
235 return runfiles.workspace, nil
236 }
237 return "", errors.New("TEST_WORKSPACE not set and SetDefaultTestWorkspace not called")
238 }
239
240
241
242
243 func SetDefaultTestWorkspace(w string) {
244 ensureRunfiles()
245 runfiles.workspace = w
246 }
247
248
249
250
251
252 func RunfilesPath() (string, error) {
253 if err := ensureRunfiles(); err != nil {
254 return "", err
255 }
256 if runfiles.dir == "" {
257 if runtime.GOOS == "windows" {
258 return "", errors.New("RunfilesPath: no runfiles directory on windows")
259 } else {
260 return "", errors.New("could not locate runfiles directory")
261 }
262 }
263 if runfiles.workspace == "" {
264 return "", errors.New("could not locate runfiles workspace")
265 }
266 return filepath.Join(runfiles.dir, runfiles.workspace), nil
267 }
268
269 var runfiles = struct {
270 once, listOnce sync.Once
271
272
273
274 list []RunfileEntry
275
276
277 index index
278
279
280
281 dir string
282
283
284 workspace string
285
286
287 workspaces []string
288
289
290
291 err error
292 }{}
293
294 type index struct {
295 indexWithWorkspace map[indexKey]*RunfileEntry
296 indexIgnoringWorksapce map[string]*RunfileEntry
297 }
298
299 func newIndex() index {
300 return index{
301 indexWithWorkspace: make(map[indexKey]*RunfileEntry),
302 indexIgnoringWorksapce: make(map[string]*RunfileEntry),
303 }
304 }
305
306 func (i *index) Put(entry *RunfileEntry) {
307 i.indexWithWorkspace[indexKey{
308 workspace: entry.Workspace,
309 shortPath: entry.ShortPath,
310 }] = entry
311 i.indexIgnoringWorksapce[entry.ShortPath] = entry
312 }
313
314 func (i *index) Get(workspace string, shortPath string) string {
315 entry := i.indexWithWorkspace[indexKey{
316 workspace: workspace,
317 shortPath: shortPath,
318 }]
319 if entry == nil {
320 return ""
321 }
322 return entry.Path
323 }
324
325 func (i *index) GetIgnoringWorkspace(shortPath string) (*RunfileEntry, bool) {
326 entry, ok := i.indexIgnoringWorksapce[shortPath]
327 return entry, ok
328 }
329
330 type indexKey struct {
331 workspace string
332 shortPath string
333 }
334
335 func ensureRunfiles() error {
336 runfiles.once.Do(initRunfiles)
337 return runfiles.err
338 }
339
340 func initRunfiles() {
341 manifest := os.Getenv("RUNFILES_MANIFEST_FILE")
342 if manifest != "" {
343
344
345
346 runfiles.index = newIndex()
347 data, err := ioutil.ReadFile(manifest)
348 if err != nil {
349 runfiles.err = err
350 return
351 }
352 lineno := 0
353 for len(data) > 0 {
354 i := bytes.IndexByte(data, '\n')
355 var line []byte
356 if i < 0 {
357 line = data
358 data = nil
359 } else {
360 line = data[:i]
361 data = data[i+1:]
362 }
363 lineno++
364
365
366
367
368
369 line = bytes.TrimRight(line, "\r\n")
370 if len(line) == 0 {
371 continue
372 }
373
374 spaceIndex := bytes.IndexByte(line, ' ')
375 if spaceIndex < 0 {
376 runfiles.err = fmt.Errorf(
377 "error parsing runfiles manifest: %s:%d: no space: '%s'", manifest, lineno, line)
378 return
379 }
380 shortPath := string(line[0:spaceIndex])
381 abspath := ""
382 if len(line) > spaceIndex+1 {
383 abspath = string(line[spaceIndex+1:])
384 }
385
386 entry := RunfileEntry{ShortPath: shortPath, Path: abspath}
387 if i := strings.IndexByte(entry.ShortPath, '/'); i >= 0 {
388 entry.Workspace = entry.ShortPath[:i]
389 entry.ShortPath = entry.ShortPath[i+1:]
390 }
391 if strings.HasPrefix(entry.ShortPath, "external/") {
392 entry.ShortPath = entry.ShortPath[len("external/"):]
393 if i := strings.IndexByte(entry.ShortPath, '/'); i >= 0 {
394 entry.Workspace = entry.ShortPath[:i]
395 entry.ShortPath = entry.ShortPath[i+1:]
396 }
397 }
398 if strings.HasPrefix(entry.ShortPath, "../") {
399 entry.ShortPath = entry.ShortPath[len("../"):]
400 if i := strings.IndexByte(entry.ShortPath, '/'); i >= 0 {
401 entry.Workspace = entry.ShortPath[:i]
402 entry.ShortPath = entry.ShortPath[i+1:]
403 }
404 }
405
406 runfiles.list = append(runfiles.list, entry)
407 runfiles.index.Put(&entry)
408 }
409 }
410
411 runfiles.workspace = os.Getenv("TEST_WORKSPACE")
412
413 if dir := os.Getenv("RUNFILES_DIR"); dir != "" {
414 runfiles.dir = dir
415 } else if dir = os.Getenv("TEST_SRCDIR"); dir != "" {
416 runfiles.dir = dir
417 } else if runtime.GOOS != "windows" {
418 dir, err := os.Getwd()
419 if err != nil {
420 runfiles.err = fmt.Errorf("error locating runfiles dir: %v", err)
421 return
422 }
423
424 parent := filepath.Dir(dir)
425 if strings.HasSuffix(parent, ".runfiles") {
426 runfiles.dir = parent
427 if runfiles.workspace == "" {
428 runfiles.workspace = filepath.Base(dir)
429 }
430 } else {
431 runfiles.err = errors.New("could not locate runfiles directory")
432 return
433 }
434 }
435
436 if runfiles.dir != "" {
437 fis, err := ioutil.ReadDir(runfiles.dir)
438 if err != nil {
439 runfiles.err = fmt.Errorf("could not open runfiles directory: %v", err)
440 return
441 }
442 for _, fi := range fis {
443 if fi.IsDir() {
444 runfiles.workspaces = append(runfiles.workspaces, fi.Name())
445 }
446 }
447 sort.Strings(runfiles.workspaces)
448 }
449 }
450
View as plain text