xref: /aosp_15_r20/external/bazelbuild-rules_go/go/tools/bazel/runfiles.go (revision 9bb1b549b6a84214c53be0924760be030e66b93a)
1*9bb1b549SSpandan Das// Copyright 2018 The Bazel Authors.
2*9bb1b549SSpandan Das//
3*9bb1b549SSpandan Das// Licensed under the Apache License, Version 2.0 (the "License");
4*9bb1b549SSpandan Das// you may not use this file except in compliance with the License.
5*9bb1b549SSpandan Das// You may obtain a copy of the License at
6*9bb1b549SSpandan Das//
7*9bb1b549SSpandan Das//      http://www.apache.org/licenses/LICENSE-2.0
8*9bb1b549SSpandan Das//
9*9bb1b549SSpandan Das// Unless required by applicable law or agreed to in writing, software
10*9bb1b549SSpandan Das// distributed under the License is distributed on an "AS IS" BASIS,
11*9bb1b549SSpandan Das// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*9bb1b549SSpandan Das// See the License for the specific language governing permissions and
13*9bb1b549SSpandan Das// limitations under the License.
14*9bb1b549SSpandan Das
15*9bb1b549SSpandan Daspackage bazel
16*9bb1b549SSpandan Das
17*9bb1b549SSpandan Dasimport (
18*9bb1b549SSpandan Das	"bytes"
19*9bb1b549SSpandan Das	"errors"
20*9bb1b549SSpandan Das	"fmt"
21*9bb1b549SSpandan Das	"io/ioutil"
22*9bb1b549SSpandan Das	"os"
23*9bb1b549SSpandan Das	"path"
24*9bb1b549SSpandan Das	"path/filepath"
25*9bb1b549SSpandan Das	"runtime"
26*9bb1b549SSpandan Das	"sort"
27*9bb1b549SSpandan Das	"strings"
28*9bb1b549SSpandan Das	"sync"
29*9bb1b549SSpandan Das)
30*9bb1b549SSpandan Das
31*9bb1b549SSpandan Dasconst (
32*9bb1b549SSpandan Das	RUNFILES_MANIFEST_FILE = "RUNFILES_MANIFEST_FILE"
33*9bb1b549SSpandan Das	RUNFILES_DIR           = "RUNFILES_DIR"
34*9bb1b549SSpandan Das)
35*9bb1b549SSpandan Das
36*9bb1b549SSpandan Das// Runfile returns an absolute path to the file named by "path", which
37*9bb1b549SSpandan Das// should be a relative path from the workspace root to the file within
38*9bb1b549SSpandan Das// the bazel workspace.
39*9bb1b549SSpandan Das//
40*9bb1b549SSpandan Das// Runfile may be called from tests invoked with 'bazel test' and
41*9bb1b549SSpandan Das// binaries invoked with 'bazel run'. On Windows,
42*9bb1b549SSpandan Das// only tests invoked with 'bazel test' are supported.
43*9bb1b549SSpandan Das//
44*9bb1b549SSpandan Das// Deprecated: Use github.com/bazelbuild/rules_go/go/runfiles instead for
45*9bb1b549SSpandan Das// cross-platform support matching the behavior of the Bazel-provided runfiles
46*9bb1b549SSpandan Das// libraries.
47*9bb1b549SSpandan Dasfunc Runfile(path string) (string, error) {
48*9bb1b549SSpandan Das	// Search in working directory
49*9bb1b549SSpandan Das	if _, err := os.Stat(path); err == nil {
50*9bb1b549SSpandan Das		return filepath.Abs(path)
51*9bb1b549SSpandan Das	}
52*9bb1b549SSpandan Das
53*9bb1b549SSpandan Das	if err := ensureRunfiles(); err != nil {
54*9bb1b549SSpandan Das		return "", err
55*9bb1b549SSpandan Das	}
56*9bb1b549SSpandan Das
57*9bb1b549SSpandan Das	// Search manifest if we have one.
58*9bb1b549SSpandan Das	if entry, ok := runfiles.index.GetIgnoringWorkspace(path); ok {
59*9bb1b549SSpandan Das		return entry.Path, nil
60*9bb1b549SSpandan Das	}
61*9bb1b549SSpandan Das
62*9bb1b549SSpandan Das	if strings.HasPrefix(path, "../") || strings.HasPrefix(path, "external/") {
63*9bb1b549SSpandan Das		pathParts := strings.Split(path, "/")
64*9bb1b549SSpandan Das		if len(pathParts) >= 3 {
65*9bb1b549SSpandan Das			workspace := pathParts[1]
66*9bb1b549SSpandan Das			pathInsideWorkspace := strings.Join(pathParts[2:], "/")
67*9bb1b549SSpandan Das			if path := runfiles.index.Get(workspace, pathInsideWorkspace); path != "" {
68*9bb1b549SSpandan Das				return path, nil
69*9bb1b549SSpandan Das			}
70*9bb1b549SSpandan Das		}
71*9bb1b549SSpandan Das	}
72*9bb1b549SSpandan Das
73*9bb1b549SSpandan Das	// Search the main workspace.
74*9bb1b549SSpandan Das	if runfiles.workspace != "" {
75*9bb1b549SSpandan Das		mainPath := filepath.Join(runfiles.dir, runfiles.workspace, path)
76*9bb1b549SSpandan Das		if _, err := os.Stat(mainPath); err == nil {
77*9bb1b549SSpandan Das			return mainPath, nil
78*9bb1b549SSpandan Das		}
79*9bb1b549SSpandan Das	}
80*9bb1b549SSpandan Das
81*9bb1b549SSpandan Das	// Search other workspaces.
82*9bb1b549SSpandan Das	for _, w := range runfiles.workspaces {
83*9bb1b549SSpandan Das		workPath := filepath.Join(runfiles.dir, w, path)
84*9bb1b549SSpandan Das		if _, err := os.Stat(workPath); err == nil {
85*9bb1b549SSpandan Das			return workPath, nil
86*9bb1b549SSpandan Das		}
87*9bb1b549SSpandan Das	}
88*9bb1b549SSpandan Das
89*9bb1b549SSpandan Das	return "", fmt.Errorf("Runfile %s: could not locate file", path)
90*9bb1b549SSpandan Das}
91*9bb1b549SSpandan Das
92*9bb1b549SSpandan Das// FindBinary returns an absolute path to the binary built from a go_binary
93*9bb1b549SSpandan Das// rule in the given package with the given name. FindBinary is similar to
94*9bb1b549SSpandan Das// Runfile, but it accounts for varying configurations and file extensions,
95*9bb1b549SSpandan Das// which may cause the binary to have different paths on different platforms.
96*9bb1b549SSpandan Das//
97*9bb1b549SSpandan Das// FindBinary may be called from tests invoked with 'bazel test' and
98*9bb1b549SSpandan Das// binaries invoked with 'bazel run'. On Windows,
99*9bb1b549SSpandan Das// only tests invoked with 'bazel test' are supported.
100*9bb1b549SSpandan Dasfunc FindBinary(pkg, name string) (string, bool) {
101*9bb1b549SSpandan Das	if err := ensureRunfiles(); err != nil {
102*9bb1b549SSpandan Das		return "", false
103*9bb1b549SSpandan Das	}
104*9bb1b549SSpandan Das
105*9bb1b549SSpandan Das	// If we've gathered a list of runfiles, either by calling ListRunfiles or
106*9bb1b549SSpandan Das	// parsing the manifest on Windows, just use that instead of searching
107*9bb1b549SSpandan Das	// directories. Return the first match. The manifest on Windows may contain
108*9bb1b549SSpandan Das	// multiple entries for the same file.
109*9bb1b549SSpandan Das	if runfiles.list != nil {
110*9bb1b549SSpandan Das		if runtime.GOOS == "windows" {
111*9bb1b549SSpandan Das			name += ".exe"
112*9bb1b549SSpandan Das		}
113*9bb1b549SSpandan Das		for _, entry := range runfiles.list {
114*9bb1b549SSpandan Das			if path.Base(entry.ShortPath) != name {
115*9bb1b549SSpandan Das				continue
116*9bb1b549SSpandan Das			}
117*9bb1b549SSpandan Das			pkgDir := path.Dir(path.Dir(entry.ShortPath))
118*9bb1b549SSpandan Das			if pkgDir == "." {
119*9bb1b549SSpandan Das				pkgDir = ""
120*9bb1b549SSpandan Das			}
121*9bb1b549SSpandan Das			if pkgDir != pkg {
122*9bb1b549SSpandan Das				continue
123*9bb1b549SSpandan Das			}
124*9bb1b549SSpandan Das			return entry.Path, true
125*9bb1b549SSpandan Das		}
126*9bb1b549SSpandan Das		return "", false
127*9bb1b549SSpandan Das	}
128*9bb1b549SSpandan Das
129*9bb1b549SSpandan Das	dir, err := Runfile(pkg)
130*9bb1b549SSpandan Das	if err != nil {
131*9bb1b549SSpandan Das		return "", false
132*9bb1b549SSpandan Das	}
133*9bb1b549SSpandan Das	var found string
134*9bb1b549SSpandan Das	stopErr := errors.New("stop")
135*9bb1b549SSpandan Das	err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
136*9bb1b549SSpandan Das		if err != nil {
137*9bb1b549SSpandan Das			return err
138*9bb1b549SSpandan Das		}
139*9bb1b549SSpandan Das		if info.IsDir() {
140*9bb1b549SSpandan Das			return nil
141*9bb1b549SSpandan Das		}
142*9bb1b549SSpandan Das		base := filepath.Base(path)
143*9bb1b549SSpandan Das		stem := strings.TrimSuffix(base, ".exe")
144*9bb1b549SSpandan Das		if stem != name {
145*9bb1b549SSpandan Das			return nil
146*9bb1b549SSpandan Das		}
147*9bb1b549SSpandan Das		if runtime.GOOS != "windows" {
148*9bb1b549SSpandan Das			if st, err := os.Stat(path); err != nil {
149*9bb1b549SSpandan Das				return err
150*9bb1b549SSpandan Das			} else if st.Mode()&0111 == 0 {
151*9bb1b549SSpandan Das				return nil
152*9bb1b549SSpandan Das			}
153*9bb1b549SSpandan Das		}
154*9bb1b549SSpandan Das		if stem == name {
155*9bb1b549SSpandan Das			found = path
156*9bb1b549SSpandan Das			return stopErr
157*9bb1b549SSpandan Das		}
158*9bb1b549SSpandan Das		return nil
159*9bb1b549SSpandan Das	})
160*9bb1b549SSpandan Das	if err == stopErr {
161*9bb1b549SSpandan Das		return found, true
162*9bb1b549SSpandan Das	} else {
163*9bb1b549SSpandan Das		return "", false
164*9bb1b549SSpandan Das	}
165*9bb1b549SSpandan Das}
166*9bb1b549SSpandan Das
167*9bb1b549SSpandan Das// A RunfileEntry describes a runfile.
168*9bb1b549SSpandan Dastype RunfileEntry struct {
169*9bb1b549SSpandan Das	// Workspace is the bazel workspace the file came from. For example,
170*9bb1b549SSpandan Das	// this would be "io_bazel_rules_go" for a file in rules_go.
171*9bb1b549SSpandan Das	Workspace string
172*9bb1b549SSpandan Das
173*9bb1b549SSpandan Das	// ShortPath is a relative, slash-separated path from the workspace root
174*9bb1b549SSpandan Das	// to the file. For non-binary files, this may be passed to Runfile
175*9bb1b549SSpandan Das	// to locate a file.
176*9bb1b549SSpandan Das	ShortPath string
177*9bb1b549SSpandan Das
178*9bb1b549SSpandan Das	// Path is an absolute path to the file.
179*9bb1b549SSpandan Das	Path string
180*9bb1b549SSpandan Das}
181*9bb1b549SSpandan Das
182*9bb1b549SSpandan Das// ListRunfiles returns a list of available runfiles.
183*9bb1b549SSpandan Dasfunc ListRunfiles() ([]RunfileEntry, error) {
184*9bb1b549SSpandan Das	if err := ensureRunfiles(); err != nil {
185*9bb1b549SSpandan Das		return nil, err
186*9bb1b549SSpandan Das	}
187*9bb1b549SSpandan Das
188*9bb1b549SSpandan Das	if runfiles.list == nil && runfiles.dir != "" {
189*9bb1b549SSpandan Das		runfiles.listOnce.Do(func() {
190*9bb1b549SSpandan Das			var list []RunfileEntry
191*9bb1b549SSpandan Das			haveWorkspaces := strings.HasSuffix(runfiles.dir, ".runfiles") && runfiles.workspace != ""
192*9bb1b549SSpandan Das
193*9bb1b549SSpandan Das			err := filepath.Walk(runfiles.dir, func(path string, info os.FileInfo, err error) error {
194*9bb1b549SSpandan Das				if err != nil {
195*9bb1b549SSpandan Das					return err
196*9bb1b549SSpandan Das				}
197*9bb1b549SSpandan Das				rel, _ := filepath.Rel(runfiles.dir, path)
198*9bb1b549SSpandan Das				rel = filepath.ToSlash(rel)
199*9bb1b549SSpandan Das				if rel == "." {
200*9bb1b549SSpandan Das					return nil
201*9bb1b549SSpandan Das				}
202*9bb1b549SSpandan Das
203*9bb1b549SSpandan Das				var workspace, shortPath string
204*9bb1b549SSpandan Das				if haveWorkspaces {
205*9bb1b549SSpandan Das					if i := strings.IndexByte(rel, '/'); i < 0 {
206*9bb1b549SSpandan Das						return nil
207*9bb1b549SSpandan Das					} else {
208*9bb1b549SSpandan Das						workspace, shortPath = rel[:i], rel[i+1:]
209*9bb1b549SSpandan Das					}
210*9bb1b549SSpandan Das				} else {
211*9bb1b549SSpandan Das					workspace, shortPath = "", rel
212*9bb1b549SSpandan Das				}
213*9bb1b549SSpandan Das
214*9bb1b549SSpandan Das				list = append(list, RunfileEntry{Workspace: workspace, ShortPath: shortPath, Path: path})
215*9bb1b549SSpandan Das				return nil
216*9bb1b549SSpandan Das			})
217*9bb1b549SSpandan Das			if err != nil {
218*9bb1b549SSpandan Das				runfiles.err = err
219*9bb1b549SSpandan Das				return
220*9bb1b549SSpandan Das			}
221*9bb1b549SSpandan Das			runfiles.list = list
222*9bb1b549SSpandan Das		})
223*9bb1b549SSpandan Das	}
224*9bb1b549SSpandan Das	return runfiles.list, runfiles.err
225*9bb1b549SSpandan Das}
226*9bb1b549SSpandan Das
227*9bb1b549SSpandan Das// TestWorkspace returns the name of the Bazel workspace for this test.
228*9bb1b549SSpandan Das// TestWorkspace returns an error if the TEST_WORKSPACE environment variable
229*9bb1b549SSpandan Das// was not set or SetDefaultTestWorkspace was not called.
230*9bb1b549SSpandan Dasfunc TestWorkspace() (string, error) {
231*9bb1b549SSpandan Das	if err := ensureRunfiles(); err != nil {
232*9bb1b549SSpandan Das		return "", err
233*9bb1b549SSpandan Das	}
234*9bb1b549SSpandan Das	if runfiles.workspace != "" {
235*9bb1b549SSpandan Das		return runfiles.workspace, nil
236*9bb1b549SSpandan Das	}
237*9bb1b549SSpandan Das	return "", errors.New("TEST_WORKSPACE not set and SetDefaultTestWorkspace not called")
238*9bb1b549SSpandan Das}
239*9bb1b549SSpandan Das
240*9bb1b549SSpandan Das// SetDefaultTestWorkspace allows you to set a fake value for the
241*9bb1b549SSpandan Das// environment variable TEST_WORKSPACE if it is not defined. This is useful
242*9bb1b549SSpandan Das// when running tests on the command line and not through Bazel.
243*9bb1b549SSpandan Dasfunc SetDefaultTestWorkspace(w string) {
244*9bb1b549SSpandan Das	ensureRunfiles()
245*9bb1b549SSpandan Das	runfiles.workspace = w
246*9bb1b549SSpandan Das}
247*9bb1b549SSpandan Das
248*9bb1b549SSpandan Das// RunfilesPath return the path to the runfiles tree.
249*9bb1b549SSpandan Das// It will return an error if there is no runfiles tree, for example because
250*9bb1b549SSpandan Das// the executable is run on Windows or was not invoked with 'bazel test'
251*9bb1b549SSpandan Das// or 'bazel run'.
252*9bb1b549SSpandan Dasfunc RunfilesPath() (string, error) {
253*9bb1b549SSpandan Das	if err := ensureRunfiles(); err != nil {
254*9bb1b549SSpandan Das		return "", err
255*9bb1b549SSpandan Das	}
256*9bb1b549SSpandan Das	if runfiles.dir == "" {
257*9bb1b549SSpandan Das		if runtime.GOOS == "windows" {
258*9bb1b549SSpandan Das			return "", errors.New("RunfilesPath: no runfiles directory on windows")
259*9bb1b549SSpandan Das		} else {
260*9bb1b549SSpandan Das			return "", errors.New("could not locate runfiles directory")
261*9bb1b549SSpandan Das		}
262*9bb1b549SSpandan Das	}
263*9bb1b549SSpandan Das	if runfiles.workspace == "" {
264*9bb1b549SSpandan Das		return "", errors.New("could not locate runfiles workspace")
265*9bb1b549SSpandan Das	}
266*9bb1b549SSpandan Das	return filepath.Join(runfiles.dir, runfiles.workspace), nil
267*9bb1b549SSpandan Das}
268*9bb1b549SSpandan Das
269*9bb1b549SSpandan Dasvar runfiles = struct {
270*9bb1b549SSpandan Das	once, listOnce sync.Once
271*9bb1b549SSpandan Das
272*9bb1b549SSpandan Das	// list is a list of known runfiles, either loaded from the manifest
273*9bb1b549SSpandan Das	// or discovered by walking the runfile directory.
274*9bb1b549SSpandan Das	list []RunfileEntry
275*9bb1b549SSpandan Das
276*9bb1b549SSpandan Das	// index maps runfile short paths to absolute paths.
277*9bb1b549SSpandan Das	index index
278*9bb1b549SSpandan Das
279*9bb1b549SSpandan Das	// dir is a path to the runfile directory. Typically this is a directory
280*9bb1b549SSpandan Das	// named <target>.runfiles, with a subdirectory for each workspace.
281*9bb1b549SSpandan Das	dir string
282*9bb1b549SSpandan Das
283*9bb1b549SSpandan Das	// workspace is workspace where the binary or test was built.
284*9bb1b549SSpandan Das	workspace string
285*9bb1b549SSpandan Das
286*9bb1b549SSpandan Das	// workspaces is a list of other workspace names.
287*9bb1b549SSpandan Das	workspaces []string
288*9bb1b549SSpandan Das
289*9bb1b549SSpandan Das	// err is set when there is an error loading runfiles, for example,
290*9bb1b549SSpandan Das	// parsing the manifest.
291*9bb1b549SSpandan Das	err error
292*9bb1b549SSpandan Das}{}
293*9bb1b549SSpandan Das
294*9bb1b549SSpandan Dastype index struct {
295*9bb1b549SSpandan Das	indexWithWorkspace     map[indexKey]*RunfileEntry
296*9bb1b549SSpandan Das	indexIgnoringWorksapce map[string]*RunfileEntry
297*9bb1b549SSpandan Das}
298*9bb1b549SSpandan Das
299*9bb1b549SSpandan Dasfunc newIndex() index {
300*9bb1b549SSpandan Das	return index{
301*9bb1b549SSpandan Das		indexWithWorkspace:     make(map[indexKey]*RunfileEntry),
302*9bb1b549SSpandan Das		indexIgnoringWorksapce: make(map[string]*RunfileEntry),
303*9bb1b549SSpandan Das	}
304*9bb1b549SSpandan Das}
305*9bb1b549SSpandan Das
306*9bb1b549SSpandan Dasfunc (i *index) Put(entry *RunfileEntry) {
307*9bb1b549SSpandan Das	i.indexWithWorkspace[indexKey{
308*9bb1b549SSpandan Das		workspace: entry.Workspace,
309*9bb1b549SSpandan Das		shortPath: entry.ShortPath,
310*9bb1b549SSpandan Das	}] = entry
311*9bb1b549SSpandan Das	i.indexIgnoringWorksapce[entry.ShortPath] = entry
312*9bb1b549SSpandan Das}
313*9bb1b549SSpandan Das
314*9bb1b549SSpandan Dasfunc (i *index) Get(workspace string, shortPath string) string {
315*9bb1b549SSpandan Das	entry := i.indexWithWorkspace[indexKey{
316*9bb1b549SSpandan Das		workspace: workspace,
317*9bb1b549SSpandan Das		shortPath: shortPath,
318*9bb1b549SSpandan Das	}]
319*9bb1b549SSpandan Das	if entry == nil {
320*9bb1b549SSpandan Das		return ""
321*9bb1b549SSpandan Das	}
322*9bb1b549SSpandan Das	return entry.Path
323*9bb1b549SSpandan Das}
324*9bb1b549SSpandan Das
325*9bb1b549SSpandan Dasfunc (i *index) GetIgnoringWorkspace(shortPath string) (*RunfileEntry, bool) {
326*9bb1b549SSpandan Das	entry, ok := i.indexIgnoringWorksapce[shortPath]
327*9bb1b549SSpandan Das	return entry, ok
328*9bb1b549SSpandan Das}
329*9bb1b549SSpandan Das
330*9bb1b549SSpandan Dastype indexKey struct {
331*9bb1b549SSpandan Das	workspace string
332*9bb1b549SSpandan Das	shortPath string
333*9bb1b549SSpandan Das}
334*9bb1b549SSpandan Das
335*9bb1b549SSpandan Dasfunc ensureRunfiles() error {
336*9bb1b549SSpandan Das	runfiles.once.Do(initRunfiles)
337*9bb1b549SSpandan Das	return runfiles.err
338*9bb1b549SSpandan Das}
339*9bb1b549SSpandan Das
340*9bb1b549SSpandan Dasfunc initRunfiles() {
341*9bb1b549SSpandan Das	manifest := os.Getenv("RUNFILES_MANIFEST_FILE")
342*9bb1b549SSpandan Das	if manifest != "" {
343*9bb1b549SSpandan Das		// On Windows, Bazel doesn't create a symlink tree of runfiles because
344*9bb1b549SSpandan Das		// Windows doesn't support symbolic links by default. Instead, runfile
345*9bb1b549SSpandan Das		// locations are written to a manifest file.
346*9bb1b549SSpandan Das		runfiles.index = newIndex()
347*9bb1b549SSpandan Das		data, err := ioutil.ReadFile(manifest)
348*9bb1b549SSpandan Das		if err != nil {
349*9bb1b549SSpandan Das			runfiles.err = err
350*9bb1b549SSpandan Das			return
351*9bb1b549SSpandan Das		}
352*9bb1b549SSpandan Das		lineno := 0
353*9bb1b549SSpandan Das		for len(data) > 0 {
354*9bb1b549SSpandan Das			i := bytes.IndexByte(data, '\n')
355*9bb1b549SSpandan Das			var line []byte
356*9bb1b549SSpandan Das			if i < 0 {
357*9bb1b549SSpandan Das				line = data
358*9bb1b549SSpandan Das				data = nil
359*9bb1b549SSpandan Das			} else {
360*9bb1b549SSpandan Das				line = data[:i]
361*9bb1b549SSpandan Das				data = data[i+1:]
362*9bb1b549SSpandan Das			}
363*9bb1b549SSpandan Das			lineno++
364*9bb1b549SSpandan Das
365*9bb1b549SSpandan Das			// Only TrimRight newlines. Do not TrimRight() completely, because that would remove spaces too.
366*9bb1b549SSpandan Das			// This is necessary in order to have at least one space in every manifest line.
367*9bb1b549SSpandan Das			// Some manifest entries don't have any path after this space, namely the "__init__.py" entries.
368*9bb1b549SSpandan Das			// original comment sourced from: https://github.com/bazelbuild/bazel/blob/09c621e4cf5b968f4c6cdf905ab142d5961f9ddc/src/test/py/bazel/runfiles_test.py#L225
369*9bb1b549SSpandan Das			line = bytes.TrimRight(line, "\r\n")
370*9bb1b549SSpandan Das			if len(line) == 0 {
371*9bb1b549SSpandan Das				continue
372*9bb1b549SSpandan Das			}
373*9bb1b549SSpandan Das
374*9bb1b549SSpandan Das			spaceIndex := bytes.IndexByte(line, ' ')
375*9bb1b549SSpandan Das			if spaceIndex < 0 {
376*9bb1b549SSpandan Das				runfiles.err = fmt.Errorf(
377*9bb1b549SSpandan Das					"error parsing runfiles manifest: %s:%d: no space: '%s'", manifest, lineno, line)
378*9bb1b549SSpandan Das				return
379*9bb1b549SSpandan Das			}
380*9bb1b549SSpandan Das			shortPath := string(line[0:spaceIndex])
381*9bb1b549SSpandan Das			abspath := ""
382*9bb1b549SSpandan Das			if len(line) > spaceIndex+1 {
383*9bb1b549SSpandan Das				abspath = string(line[spaceIndex+1:])
384*9bb1b549SSpandan Das			}
385*9bb1b549SSpandan Das
386*9bb1b549SSpandan Das			entry := RunfileEntry{ShortPath: shortPath, Path: abspath}
387*9bb1b549SSpandan Das			if i := strings.IndexByte(entry.ShortPath, '/'); i >= 0 {
388*9bb1b549SSpandan Das				entry.Workspace = entry.ShortPath[:i]
389*9bb1b549SSpandan Das				entry.ShortPath = entry.ShortPath[i+1:]
390*9bb1b549SSpandan Das			}
391*9bb1b549SSpandan Das			if strings.HasPrefix(entry.ShortPath, "external/") {
392*9bb1b549SSpandan Das				entry.ShortPath = entry.ShortPath[len("external/"):]
393*9bb1b549SSpandan Das				if i := strings.IndexByte(entry.ShortPath, '/'); i >= 0 {
394*9bb1b549SSpandan Das					entry.Workspace = entry.ShortPath[:i]
395*9bb1b549SSpandan Das					entry.ShortPath = entry.ShortPath[i+1:]
396*9bb1b549SSpandan Das				}
397*9bb1b549SSpandan Das			}
398*9bb1b549SSpandan Das
399*9bb1b549SSpandan Das			runfiles.list = append(runfiles.list, entry)
400*9bb1b549SSpandan Das			runfiles.index.Put(&entry)
401*9bb1b549SSpandan Das		}
402*9bb1b549SSpandan Das	}
403*9bb1b549SSpandan Das
404*9bb1b549SSpandan Das	runfiles.workspace = os.Getenv("TEST_WORKSPACE")
405*9bb1b549SSpandan Das
406*9bb1b549SSpandan Das	if dir := os.Getenv("RUNFILES_DIR"); dir != "" {
407*9bb1b549SSpandan Das		runfiles.dir = dir
408*9bb1b549SSpandan Das	} else if dir = os.Getenv("TEST_SRCDIR"); dir != "" {
409*9bb1b549SSpandan Das		runfiles.dir = dir
410*9bb1b549SSpandan Das	} else if runtime.GOOS != "windows" {
411*9bb1b549SSpandan Das		dir, err := os.Getwd()
412*9bb1b549SSpandan Das		if err != nil {
413*9bb1b549SSpandan Das			runfiles.err = fmt.Errorf("error locating runfiles dir: %v", err)
414*9bb1b549SSpandan Das			return
415*9bb1b549SSpandan Das		}
416*9bb1b549SSpandan Das
417*9bb1b549SSpandan Das		parent := filepath.Dir(dir)
418*9bb1b549SSpandan Das		if strings.HasSuffix(parent, ".runfiles") {
419*9bb1b549SSpandan Das			runfiles.dir = parent
420*9bb1b549SSpandan Das			if runfiles.workspace == "" {
421*9bb1b549SSpandan Das				runfiles.workspace = filepath.Base(dir)
422*9bb1b549SSpandan Das			}
423*9bb1b549SSpandan Das		} else {
424*9bb1b549SSpandan Das			runfiles.err = errors.New("could not locate runfiles directory")
425*9bb1b549SSpandan Das			return
426*9bb1b549SSpandan Das		}
427*9bb1b549SSpandan Das	}
428*9bb1b549SSpandan Das
429*9bb1b549SSpandan Das	if runfiles.dir != "" {
430*9bb1b549SSpandan Das		fis, err := ioutil.ReadDir(runfiles.dir)
431*9bb1b549SSpandan Das		if err != nil {
432*9bb1b549SSpandan Das			runfiles.err = fmt.Errorf("could not open runfiles directory: %v", err)
433*9bb1b549SSpandan Das			return
434*9bb1b549SSpandan Das		}
435*9bb1b549SSpandan Das		for _, fi := range fis {
436*9bb1b549SSpandan Das			if fi.IsDir() {
437*9bb1b549SSpandan Das				runfiles.workspaces = append(runfiles.workspaces, fi.Name())
438*9bb1b549SSpandan Das			}
439*9bb1b549SSpandan Das		}
440*9bb1b549SSpandan Das		sort.Strings(runfiles.workspaces)
441*9bb1b549SSpandan Das	}
442*9bb1b549SSpandan Das}
443