Skip to content

Commit

Permalink
Find runfiles in directories that are themselves runfiles
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
fmeum committed Dec 30, 2021
1 parent 52d530a commit 9f5031c
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 0 deletions.
26 changes: 26 additions & 0 deletions manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,32 @@ func (f ManifestFile) parse() (manifest, error) {
}

func (m manifest) path(s string) (string, error) {
r, err := m.pathExact(s)
if err == nil || err == ErrEmpty {
return r, err
}
// If path references a runfile that lies under a directory that itself is a
// runfile, then only the directory is listed in the manifest. Look up all
// prefixes of path in the manifest.
prefixEnd := len(s)
for {
prefixEnd = strings.LastIndex(s[0:prefixEnd-1], "/")
if prefixEnd == -1 {
break
}
prefixMatch, err := m.pathExact(s[0:prefixEnd])
if err == ErrEmpty {
return "", ErrEmpty
}
if err == nil {
suffixPathSegments := strings.Split(s[prefixEnd+1:], "/")
return filepath.Join(append([]string{prefixMatch}, suffixPathSegments...)...), nil
}
}
return "", os.ErrNotExist
}

func (m manifest) pathExact(s string) (string, error) {
r, ok := m[s]
if !ok {
return "", os.ErrNotExist
Expand Down
29 changes: 29 additions & 0 deletions runfiles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"testing"

Expand Down Expand Up @@ -101,3 +102,31 @@ func TestRunfiles_empty(t *testing.T) {
t.Errorf("Path for empty file: got error %q, want something that wraps %q", got, want)
}
}

var manifestWithDirTestCases = map[string]string {
"foo/dir": "path/to/foo/dir",
"foo/dir/file": path.Join("path/to/foo/dir", "file"),
"foo/dir/deeply/nested/file": path.Join("path/to/foo/dir", "deeply", "nested", "file"),
}

func TestRunfiles_manifestWithDir(t *testing.T) {
dir := t.TempDir()
manifest := filepath.Join(dir, "manifest")
if err := os.WriteFile(manifest, []byte("foo/dir path/to/foo/dir\n"), 0600); err != nil {
t.Fatal(err)
}
r, err := runfiles.New(runfiles.ManifestFile(manifest), runfiles.ProgramName("/invalid"), runfiles.Directory("/invalid"))
if err != nil {
t.Fatal(err)
}
for rlocation, want := range manifestWithDirTestCases {
got, err := r.Path(rlocation)
if err != nil {
t.Errorf("Path for %q: got unexpected error %q", rlocation, err)
continue
}
if got != want {
t.Errorf("Path for %q: got %q, want %q", rlocation, got, want)
}
}
}

0 comments on commit 9f5031c

Please sign in to comment.