From fe16cac9172a0de9b7d3469bfe6b675cdfd818cd Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 26 Nov 2021 13:30:23 +0100 Subject: [PATCH] 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 https://github.com/bazelbuild/bazel/issues/14336 for more context. --- manifest.go | 26 ++++++++++++++++++++++++++ runfiles_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/manifest.go b/manifest.go index c871357..509ed9f 100644 --- a/manifest.go +++ b/manifest.go @@ -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 diff --git a/runfiles_test.go b/runfiles_test.go index 1aba1e7..4ccc9cd 100644 --- a/runfiles_test.go +++ b/runfiles_test.go @@ -19,6 +19,7 @@ import ( "fmt" "os" "os/exec" + "path" "path/filepath" "testing" @@ -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) + } + } +}