Skip to content

Commit 9f5031c

Browse files
committed
Find runfiles in directories that are themselves runfiles
When a target has a runfile that is contained in a directory that is itself one of its runfiles, the runfile will be shadowed by the SymlinkEntry for the directory. While this still allows to resolve the file in the runfiles symlink tree, a manifest-based lookup will fail. This PR extends the lookup logic to also find runfiles contained within directories that are themselves runfiles. It does so by searching the manifest not only for the exact provided rlocation path, but also for all path prefixes. If a prefix is looked up successfully, the corresponding suffix is resolved relative to the looked up path. See bazelbuild/bazel#14336 for more context.
1 parent 52d530a commit 9f5031c

File tree

2 files changed

+55
-0
lines changed

2 files changed

+55
-0
lines changed

manifest.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,32 @@ func (f ManifestFile) parse() (manifest, error) {
5959
}
6060

6161
func (m manifest) path(s string) (string, error) {
62+
r, err := m.pathExact(s)
63+
if err == nil || err == ErrEmpty {
64+
return r, err
65+
}
66+
// If path references a runfile that lies under a directory that itself is a
67+
// runfile, then only the directory is listed in the manifest. Look up all
68+
// prefixes of path in the manifest.
69+
prefixEnd := len(s)
70+
for {
71+
prefixEnd = strings.LastIndex(s[0:prefixEnd-1], "/")
72+
if prefixEnd == -1 {
73+
break
74+
}
75+
prefixMatch, err := m.pathExact(s[0:prefixEnd])
76+
if err == ErrEmpty {
77+
return "", ErrEmpty
78+
}
79+
if err == nil {
80+
suffixPathSegments := strings.Split(s[prefixEnd+1:], "/")
81+
return filepath.Join(append([]string{prefixMatch}, suffixPathSegments...)...), nil
82+
}
83+
}
84+
return "", os.ErrNotExist
85+
}
86+
87+
func (m manifest) pathExact(s string) (string, error) {
6288
r, ok := m[s]
6389
if !ok {
6490
return "", os.ErrNotExist

runfiles_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"fmt"
2020
"os"
2121
"os/exec"
22+
"path"
2223
"path/filepath"
2324
"testing"
2425

@@ -101,3 +102,31 @@ func TestRunfiles_empty(t *testing.T) {
101102
t.Errorf("Path for empty file: got error %q, want something that wraps %q", got, want)
102103
}
103104
}
105+
106+
var manifestWithDirTestCases = map[string]string {
107+
"foo/dir": "path/to/foo/dir",
108+
"foo/dir/file": path.Join("path/to/foo/dir", "file"),
109+
"foo/dir/deeply/nested/file": path.Join("path/to/foo/dir", "deeply", "nested", "file"),
110+
}
111+
112+
func TestRunfiles_manifestWithDir(t *testing.T) {
113+
dir := t.TempDir()
114+
manifest := filepath.Join(dir, "manifest")
115+
if err := os.WriteFile(manifest, []byte("foo/dir path/to/foo/dir\n"), 0600); err != nil {
116+
t.Fatal(err)
117+
}
118+
r, err := runfiles.New(runfiles.ManifestFile(manifest), runfiles.ProgramName("/invalid"), runfiles.Directory("/invalid"))
119+
if err != nil {
120+
t.Fatal(err)
121+
}
122+
for rlocation, want := range manifestWithDirTestCases {
123+
got, err := r.Path(rlocation)
124+
if err != nil {
125+
t.Errorf("Path for %q: got unexpected error %q", rlocation, err)
126+
continue
127+
}
128+
if got != want {
129+
t.Errorf("Path for %q: got %q, want %q", rlocation, got, want)
130+
}
131+
}
132+
}

0 commit comments

Comments
 (0)