diff --git a/manifest.go b/manifest.go index c871357..d135417 100644 --- a/manifest.go +++ b/manifest.go @@ -18,6 +18,7 @@ import ( "bufio" "fmt" "os" + "path" "path/filepath" "strings" ) @@ -60,13 +61,22 @@ func (f ManifestFile) parse() (manifest, error) { func (m manifest) path(s string) (string, error) { r, ok := m[s] - if !ok { - return "", os.ErrNotExist - } - if r == "" { + if ok && r == "" { return "", ErrEmpty } - return r, nil + if ok { + return r, nil + } + // 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. + for prefix := s; prefix != ""; prefix, _ = path.Split(prefix) { + prefix = strings.TrimSuffix(prefix, "/") + if prefixMatch, ok := m[prefix]; ok { + return prefixMatch + strings.TrimPrefix(s, prefix), nil + } + } + return "", os.ErrNotExist } const manifestFileVar = "RUNFILES_MANIFEST_FILE" diff --git a/runfiles_test.go b/runfiles_test.go index 5434654..1b99365 100644 --- a/runfiles_test.go +++ b/runfiles_test.go @@ -101,3 +101,30 @@ func TestRunfiles_empty(t *testing.T) { t.Errorf("Path for empty file: got error %q, want something that wraps %q", got, want) } } + +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)) + if err != nil { + t.Fatal(err) + } + for rlocation, want := range map[string]string{ + "foo/dir": "path/to/foo/dir", + "foo/dir/file": "path/to/foo/dir/file", + "foo/dir/deeply/nested/file": "path/to/foo/dir/deeply/nested/file", + } { + t.Run(rlocation, func(t *testing.T) { + got, err := r.Path(rlocation) + if err != nil { + t.Fatalf("Path failed: got unexpected error %q", err) + } + if got != want { + t.Errorf("Path failed: got %q, want %q", got, want) + } + }) + } +}