[llvm] ecb00a7 - [VFS] Add support to RedirectingFileSystem for mapping a virtual directory to one in the external FS.

Nathan Hawes via llvm-commits llvm-commits at lists.llvm.org
Mon Feb 1 20:57:29 PST 2021


Author: Nathan Hawes
Date: 2021-02-02T14:56:17+10:00
New Revision: ecb00a77624c94ce38fccf9b4095e026ecf14aed

URL: https://github.com/llvm/llvm-project/commit/ecb00a77624c94ce38fccf9b4095e026ecf14aed
DIFF: https://github.com/llvm/llvm-project/commit/ecb00a77624c94ce38fccf9b4095e026ecf14aed.diff

LOG: [VFS] Add support to RedirectingFileSystem for mapping a virtual directory to one in the external FS.

Previously file entries in the -ivfsoverlay yaml could map to a file in the
external file system, but directories had to list their contents in the form of
other file entries or directories. Allowing directory entries to map to a
directory in the external file system makes it possible to present an external
directory's contents in a different location and (in combination with the
'fallthrough' option) overlay one directory's contents on top of another.

rdar://problem/72485443
Differential Revision: https://reviews.llvm.org/D94844

Added: 
    clang/test/VFS/Inputs/vfsoverlay-directory-relative.yaml
    clang/test/VFS/Inputs/vfsoverlay-directory.yaml
    clang/test/VFS/directory.c

Modified: 
    lldb/source/Host/common/FileSystem.cpp
    llvm/include/llvm/Support/VirtualFileSystem.h
    llvm/lib/Support/VirtualFileSystem.cpp
    llvm/unittests/Support/VirtualFileSystemTest.cpp

Removed: 
    


################################################################################
diff  --git a/clang/test/VFS/Inputs/vfsoverlay-directory-relative.yaml b/clang/test/VFS/Inputs/vfsoverlay-directory-relative.yaml
new file mode 100644
index 000000000000..635460a4a9b0
--- /dev/null
+++ b/clang/test/VFS/Inputs/vfsoverlay-directory-relative.yaml
@@ -0,0 +1,11 @@
+{
+  'version': 0,
+  'fallthrough': true,
+  'overlay-relative': true,
+  'roots': [
+    { 'name': 'OUT_DIR',
+      'type': 'directory-remap',
+      'external-contents': 'INPUT_DIR'
+    }
+  ]
+}

diff  --git a/clang/test/VFS/Inputs/vfsoverlay-directory.yaml b/clang/test/VFS/Inputs/vfsoverlay-directory.yaml
new file mode 100644
index 000000000000..d8a283ee3f19
--- /dev/null
+++ b/clang/test/VFS/Inputs/vfsoverlay-directory.yaml
@@ -0,0 +1,10 @@
+{
+  'version': 0,
+  'fallthrough': true,
+  'roots': [
+    { 'name': 'OUT_DIR',
+      'type': 'directory-remap',
+      'external-contents': 'INPUT_DIR'
+    }
+  ]
+}

diff  --git a/clang/test/VFS/directory.c b/clang/test/VFS/directory.c
new file mode 100644
index 000000000000..b850ace54c93
--- /dev/null
+++ b/clang/test/VFS/directory.c
@@ -0,0 +1,48 @@
+// RUN: rm -rf %t
+// RUN: mkdir -p %t/Underlying
+// RUN: mkdir -p %t/Overlay
+// RUN: mkdir -p %t/Middle
+// RUN: echo '// B.h in Underlying' > %t/Underlying/B.h
+// RUN: echo '#ifdef NESTED' >> %t/Underlying/B.h
+// RUN: echo '#include "C.h"' >> %t/Underlying/B.h
+// RUN: echo '#endif' >> %t/Underlying/B.h
+// RUN: echo '// C.h in Underlying' > %t/Underlying/C.h
+// RUN: echo '// C.h in Middle' > %t/Middle/C.h
+// RUN: echo '// C.h in Overlay' > %t/Overlay/C.h
+
+// 1) Underlying -> Overlay (C.h found, B.h falling back to Underlying)
+// RUN: sed -e "s at INPUT_DIR@%{/t:regex_replacement}/Overlay at g" -e "s at OUT_DIR@%{/t:regex_replacement}/Underlying at g" %S/Inputs/vfsoverlay-directory.yaml > %t/vfs.yaml
+// RUN: %clang_cc1 -Werror -I %t/Underlying -ivfsoverlay %t/vfs.yaml -fsyntax-only -E -C %s 2>&1 | FileCheck --check-prefix=DIRECT %s
+// RUN: %clang_cc1 -Werror -I %t/Underlying -ivfsoverlay %t/vfs.yaml -fsyntax-only -DNESTED -E -C %s 2>&1 | FileCheck --check-prefix=DIRECT %s
+// RUN: sed -e "s at INPUT_DIR@Overlay at g" -e "s at OUT_DIR@%{/t:regex_replacement}/Underlying at g" %S/Inputs/vfsoverlay-directory-relative.yaml > %t/vfs-relative.yaml
+// RUN: %clang_cc1 -Werror -I %t/Underlying -ivfsoverlay %t/vfs-relative.yaml -fsyntax-only -E -C %s 2>&1 | FileCheck --check-prefix=DIRECT %s
+
+// DIRECT: {{^}}// B.h in Underlying
+// DIRECT: {{^}}// C.h in Overlay
+
+// 2) Underlying -> Middle -> Overlay (C.h found, B.h falling back to Underlying)
+// RUN: sed -e "s at INPUT_DIR@%{/t:regex_replacement}/Overlay at g" -e "s at OUT_DIR@%{/t:regex_replacement}/Middle at g" %S/Inputs/vfsoverlay-directory.yaml > %t/vfs.yaml
+// RUN: sed -e "s at INPUT_DIR@%{/t:regex_replacement}/Middle at g" -e "s at OUT_DIR@%{/t:regex_replacement}/Underlying at g" %S/Inputs/vfsoverlay-directory.yaml > %t/vfs2.yaml
+// RUN: %clang_cc1 -Werror -I %t/Underlying -ivfsoverlay %t/vfs.yaml -ivfsoverlay %t/vfs2.yaml -fsyntax-only -E -C %s 2>&1 | FileCheck --check-prefix=DIRECT %s
+// RUN: %clang_cc1 -Werror -I %t/Underlying -ivfsoverlay %t/vfs.yaml -ivfsoverlay %t/vfs2.yaml -DNESTED -fsyntax-only -E -C %s 2>&1 | FileCheck --check-prefix=DIRECT %s
+
+// Same as direct above
+
+// 3) Underlying -> Middle -> Overlay (C.h falling back to Middle, B.h falling back to Underlying)
+// RUN: rm -f %t/Overlay/C.h
+// RUN: %clang_cc1 -Werror -I %t/Underlying -ivfsoverlay %t/vfs.yaml -ivfsoverlay %t/vfs2.yaml -fsyntax-only -E -C %s 2>&1 | FileCheck --check-prefix=FALLBACK %s
+
+// FALLBACK: {{^}}// B.h in Underlying
+// FALLBACK: {{^}}// C.h in Middle
+
+// 3) Underlying -> Middle -> Overlay (C.h falling back to Underlying, B.h falling back to Underlying)
+// RUN: rm -f %t/Middle/C.h
+// RUN: %clang_cc1 -Werror -I %t/Underlying -ivfsoverlay %t/vfs.yaml -ivfsoverlay %t/vfs2.yaml -fsyntax-only -E -C %s 2>&1 | FileCheck --check-prefix=FALLBACK2 %s
+
+// FALLBACK2: {{^}}// B.h in Underlying
+// FALLBACK2: {{^}}// C.h in Underlying
+
+#include "B.h"
+#ifndef NESTED
+#include "C.h"
+#endif

diff  --git a/lldb/source/Host/common/FileSystem.cpp b/lldb/source/Host/common/FileSystem.cpp
index a89020100ca7..64ecf27858ab 100644
--- a/lldb/source/Host/common/FileSystem.cpp
+++ b/lldb/source/Host/common/FileSystem.cpp
@@ -478,20 +478,18 @@ ErrorOr<std::string> FileSystem::GetExternalPath(const llvm::Twine &path) {
     return path.str();
 
   // If VFS mapped we know the underlying FS is a RedirectingFileSystem.
-  ErrorOr<vfs::RedirectingFileSystem::Entry *> E =
+  ErrorOr<vfs::RedirectingFileSystem::LookupResult> Result =
       static_cast<vfs::RedirectingFileSystem &>(*m_fs).lookupPath(path.str());
-  if (!E) {
-    if (E.getError() == llvm::errc::no_such_file_or_directory) {
+  if (!Result) {
+    if (Result.getError() == llvm::errc::no_such_file_or_directory) {
       return path.str();
     }
-    return E.getError();
+    return Result.getError();
   }
 
-  auto *F = dyn_cast<vfs::RedirectingFileSystem::FileEntry>(*E);
-  if (!F)
-    return make_error_code(llvm::errc::not_supported);
-
-  return F->getExternalContentsPath().str();
+  if (Optional<StringRef> ExtRedirect = Result->getExternalRedirect())
+    return std::string(*ExtRedirect);
+  return make_error_code(llvm::errc::not_supported);
 }
 
 ErrorOr<std::string> FileSystem::GetExternalPath(const FileSpec &file_spec) {

diff  --git a/llvm/include/llvm/Support/VirtualFileSystem.h b/llvm/include/llvm/Support/VirtualFileSystem.h
index 5025fee9e38e..b718dbaf3189 100644
--- a/llvm/include/llvm/Support/VirtualFileSystem.h
+++ b/llvm/include/llvm/Support/VirtualFileSystem.h
@@ -521,8 +521,10 @@ class RedirectingFileSystemParser;
 
 /// A virtual file system parsed from a YAML file.
 ///
-/// Currently, this class allows creating virtual directories and mapping
-/// virtual file paths to existing external files, available in \c ExternalFS.
+/// Currently, this class allows creating virtual files and directories. Virtual
+/// files map to existing external files in \c ExternalFS, and virtual
+/// directories may either map to existing directories in \c ExternalFS or list
+/// their contents in the form of other virtual directories and/or files.
 ///
 /// The basic structure of the parsed file is:
 /// \verbatim
@@ -541,7 +543,7 @@ class RedirectingFileSystemParser;
 ///   'overlay-relative': <boolean, default=false>
 ///   'fallthrough': <boolean, default=true>
 ///
-/// Virtual directories are represented as
+/// Virtual directories that list their contents are represented as
 /// \verbatim
 /// {
 ///   'type': 'directory',
@@ -550,7 +552,7 @@ class RedirectingFileSystemParser;
 /// }
 /// \endverbatim
 ///
-/// The default attributes for virtual directories are:
+/// The default attributes for such virtual directories are:
 /// \verbatim
 /// MTime = now() when created
 /// Perms = 0777
@@ -559,24 +561,45 @@ class RedirectingFileSystemParser;
 /// UniqueID = unspecified unique value
 /// \endverbatim
 ///
+/// When a path prefix matches such a directory, the next component in the path
+/// is matched against the entries in the 'contents' array.
+///
+/// Re-mapped directories, on the other hand, are represented as
+/// /// \verbatim
+/// {
+///   'type': 'directory-remap',
+///   'name': <string>,
+///   'use-external-name': <boolean>, # Optional
+///   'external-contents': <path to external directory>
+/// }
+/// \endverbatim
+///
+/// and inherit their attributes from the external directory. When a path
+/// prefix matches such an entry, the unmatched components are appended to the
+/// 'external-contents' path, and the resulting path is looked up in the
+/// external file system instead.
+///
 /// Re-mapped files are represented as
 /// \verbatim
 /// {
 ///   'type': 'file',
 ///   'name': <string>,
-///   'use-external-name': <boolean> # Optional
+///   'use-external-name': <boolean>, # Optional
 ///   'external-contents': <path to external file>
 /// }
 /// \endverbatim
 ///
-/// and inherit their attributes from the external contents.
+/// Their attributes and file contents are determined by looking up the file at
+/// their 'external-contents' path in the external file system.
 ///
-/// In both cases, the 'name' field may contain multiple path components (e.g.
-/// /path/to/file). However, any directory that contains more than one child
-/// must be uniquely represented by a directory entry.
+/// For 'file', 'directory' and 'directory-remap' entries the 'name' field may
+/// contain multiple path components (e.g. /path/to/file). However, any
+/// directory in such a path that contains more than one child must be uniquely
+/// represented by a 'directory' entry.
 class RedirectingFileSystem : public vfs::FileSystem {
 public:
-  enum EntryKind { EK_Directory, EK_File };
+  enum EntryKind { EK_Directory, EK_DirectoryRemap, EK_File };
+  enum NameKind { NK_NotSet, NK_External, NK_Virtual };
 
   /// A single file or directory in the VFS.
   class Entry {
@@ -591,6 +614,7 @@ class RedirectingFileSystem : public vfs::FileSystem {
     EntryKind getKind() const { return Kind; }
   };
 
+  /// A directory in the vfs with explicitly specified contents.
   class DirectoryEntry : public Entry {
     std::vector<std::unique_ptr<Entry>> Contents;
     Status S;
@@ -622,22 +646,22 @@ class RedirectingFileSystem : public vfs::FileSystem {
     static bool classof(const Entry *E) { return E->getKind() == EK_Directory; }
   };
 
-  class FileEntry : public Entry {
-  public:
-    enum NameKind { NK_NotSet, NK_External, NK_Virtual };
-
-  private:
+  /// A file or directory in the vfs that is mapped to a file or directory in
+  /// the external filesystem.
+  class RemapEntry : public Entry {
     std::string ExternalContentsPath;
     NameKind UseName;
 
-  public:
-    FileEntry(StringRef Name, StringRef ExternalContentsPath, NameKind UseName)
-        : Entry(EK_File, Name), ExternalContentsPath(ExternalContentsPath),
+  protected:
+    RemapEntry(EntryKind K, StringRef Name, StringRef ExternalContentsPath,
+               NameKind UseName)
+        : Entry(K, Name), ExternalContentsPath(ExternalContentsPath),
           UseName(UseName) {}
 
+  public:
     StringRef getExternalContentsPath() const { return ExternalContentsPath; }
 
-    /// whether to use the external path as the name for this file.
+    /// Whether to use the external path as the name for this file or directory.
     bool useExternalName(bool GlobalUseExternalName) const {
       return UseName == NK_NotSet ? GlobalUseExternalName
                                   : (UseName == NK_External);
@@ -645,9 +669,67 @@ class RedirectingFileSystem : public vfs::FileSystem {
 
     NameKind getUseName() const { return UseName; }
 
+    static bool classof(const Entry *E) {
+      switch (E->getKind()) {
+      case EK_DirectoryRemap:
+        LLVM_FALLTHROUGH;
+      case EK_File:
+        return true;
+      case EK_Directory:
+        return false;
+      }
+    }
+  };
+
+  /// A directory in the vfs that maps to a directory in the external file
+  /// system.
+  class DirectoryRemapEntry : public RemapEntry {
+  public:
+    DirectoryRemapEntry(StringRef Name, StringRef ExternalContentsPath,
+                        NameKind UseName)
+        : RemapEntry(EK_DirectoryRemap, Name, ExternalContentsPath, UseName) {}
+
+    static bool classof(const Entry *E) {
+      return E->getKind() == EK_DirectoryRemap;
+    }
+  };
+
+  /// A file in the vfs that maps to a file in the external file system.
+  class FileEntry : public RemapEntry {
+  public:
+    FileEntry(StringRef Name, StringRef ExternalContentsPath, NameKind UseName)
+        : RemapEntry(EK_File, Name, ExternalContentsPath, UseName) {}
+
     static bool classof(const Entry *E) { return E->getKind() == EK_File; }
   };
 
+  /// Represents the result of a path lookup into the RedirectingFileSystem.
+  struct LookupResult {
+    /// The entry the looked-up path corresponds to.
+    Entry *E;
+
+  private:
+    /// When the found Entry is a DirectoryRemapEntry, stores the path in the
+    /// external file system that the looked-up path in the virtual file system
+    //  corresponds to.
+    Optional<std::string> ExternalRedirect;
+
+  public:
+    LookupResult(Entry *E, sys::path::const_iterator Start,
+                 sys::path::const_iterator End);
+
+    /// If the found Entry maps the the input path to a path in the external
+    /// file system (i.e. it is a FileEntry or DirectoryRemapEntry), returns
+    /// that path.
+    Optional<StringRef> getExternalRedirect() const {
+      if (isa<DirectoryRemapEntry>(E))
+        return StringRef(*ExternalRedirect);
+      if (auto *FE = dyn_cast<FileEntry>(E))
+        return FE->getExternalContentsPath();
+      return None;
+    }
+  };
+
 private:
   friend class RedirectingFSDirIterImpl;
   friend class RedirectingFileSystemParser;
@@ -660,8 +742,8 @@ class RedirectingFileSystem : public vfs::FileSystem {
   std::error_code makeCanonical(SmallVectorImpl<char> &Path) const;
 
   /// Whether to fall back to the external file system when an operation fails
-  /// with the given error code.
-  bool shouldFallBackToExternalFS(std::error_code EC) const;
+  /// with the given error code on a path associated with the provided Entry.
+  bool shouldFallBackToExternalFS(std::error_code EC, Entry *E = nullptr) const;
 
   // In a RedirectingFileSystem, keys can be specified in Posix or Windows
   // style (or even a mixture of both), so this comparison helper allows
@@ -716,18 +798,22 @@ class RedirectingFileSystem : public vfs::FileSystem {
 
   RedirectingFileSystem(IntrusiveRefCntPtr<FileSystem> ExternalFS);
 
-  /// Looks up the path <tt>[Start, End)</tt> in \p From, possibly
-  /// recursing into the contents of \p From if it is a directory.
-  ErrorOr<Entry *> lookupPath(llvm::sys::path::const_iterator Start,
-                              llvm::sys::path::const_iterator End,
-                              Entry *From) const;
+  /// Looks up the path <tt>[Start, End)</tt> in \p From, possibly recursing
+  /// into the contents of \p From if it is a directory. Returns a LookupResult
+  /// giving the matched entry and, if that entry is a FileEntry or
+  /// DirectoryRemapEntry, the path it redirects to in the external file system.
+  ErrorOr<LookupResult> lookupPathImpl(llvm::sys::path::const_iterator Start,
+                                       llvm::sys::path::const_iterator End,
+                                       Entry *From) const;
 
-  /// Get the status of a given an \c Entry.
-  ErrorOr<Status> status(const Twine &Path, Entry *E);
+  /// Get the status for a path with the provided \c LookupResult.
+  ErrorOr<Status> status(const Twine &Path, const LookupResult &Result);
 
 public:
-  /// Looks up \p Path in \c Roots.
-  ErrorOr<Entry *> lookupPath(StringRef Path) const;
+  /// Looks up \p Path in \c Roots and returns a LookupResult giving the
+  /// matched entry and, if the entry was a FileEntry or DirectoryRemapEntry,
+  /// the path it redirects to in the external file system.
+  ErrorOr<LookupResult> lookupPath(StringRef Path) const;
 
   /// Parses \p Buffer, which is expected to be in YAML format and
   /// returns a virtual file system representing its contents.

diff  --git a/llvm/lib/Support/VirtualFileSystem.cpp b/llvm/lib/Support/VirtualFileSystem.cpp
index d231385dc473..deb0f37bd159 100644
--- a/llvm/lib/Support/VirtualFileSystem.cpp
+++ b/llvm/lib/Support/VirtualFileSystem.cpp
@@ -1011,14 +1011,20 @@ std::error_code InMemoryFileSystem::isLocal(const Twine &Path, bool &Result) {
 
 namespace {
 
-/// Removes leading "./" as well as path components like ".." and ".".
-static llvm::SmallString<256> canonicalize(llvm::StringRef Path) {
-  // First detect the path style in use by checking the first separator.
+static llvm::sys::path::Style getExistingStyle(llvm::StringRef Path) {
+  // Detect the path style in use by checking the first separator.
   llvm::sys::path::Style style = llvm::sys::path::Style::native;
   const size_t n = Path.find_first_of("/\\");
   if (n != static_cast<size_t>(-1))
     style = (Path[n] == '/') ? llvm::sys::path::Style::posix
                              : llvm::sys::path::Style::windows;
+  return style;
+}
+
+/// Removes leading "./" as well as path components like ".." and ".".
+static llvm::SmallString<256> canonicalize(llvm::StringRef Path) {
+  // First detect the path style in use by checking the first separator.
+  llvm::sys::path::Style style = getExistingStyle(Path);
 
   // Now remove the dots.  Explicitly specifying the path style prevents the
   // direction of the slashes from changing.
@@ -1057,6 +1063,8 @@ class llvm::vfs::RedirectingFSDirIterImpl
       sys::fs::file_type Type = sys::fs::file_type::type_unknown;
       switch ((*Current)->getKind()) {
       case RedirectingFileSystem::EK_Directory:
+        LLVM_FALLTHROUGH;
+      case RedirectingFileSystem::EK_DirectoryRemap:
         Type = sys::fs::file_type::directory_file;
         break;
       case RedirectingFileSystem::EK_File:
@@ -1083,6 +1091,45 @@ class llvm::vfs::RedirectingFSDirIterImpl
   }
 };
 
+/// Directory iterator implementation for \c RedirectingFileSystem's
+/// directory remap entries that maps the paths reported by the external
+/// file system's directory iterator back to the virtual directory's path.
+class RedirectingFSDirRemapIterImpl : public llvm::vfs::detail::DirIterImpl {
+  std::string Dir;
+  llvm::sys::path::Style DirStyle;
+  llvm::vfs::directory_iterator ExternalIter;
+
+public:
+  RedirectingFSDirRemapIterImpl(std::string DirPath,
+                                llvm::vfs::directory_iterator ExtIter)
+      : Dir(std::move(DirPath)), DirStyle(getExistingStyle(Dir)),
+        ExternalIter(ExtIter) {
+    if (ExternalIter != llvm::vfs::directory_iterator())
+      setCurrentEntry();
+  }
+
+  void setCurrentEntry() {
+    StringRef ExternalPath = ExternalIter->path();
+    llvm::sys::path::Style ExternalStyle = getExistingStyle(ExternalPath);
+    StringRef File = llvm::sys::path::filename(ExternalPath, ExternalStyle);
+
+    SmallString<128> NewPath(Dir);
+    llvm::sys::path::append(NewPath, DirStyle, File);
+
+    CurrentEntry = directory_entry(std::string(NewPath), ExternalIter->type());
+  }
+
+  std::error_code increment() override {
+    std::error_code EC;
+    ExternalIter.increment(EC);
+    if (!EC && ExternalIter != llvm::vfs::directory_iterator())
+      setCurrentEntry();
+    else
+      CurrentEntry = directory_entry();
+    return EC;
+  }
+};
+
 llvm::ErrorOr<std::string>
 RedirectingFileSystem::getCurrentWorkingDirectory() const {
   return WorkingDirectory;
@@ -1151,15 +1198,19 @@ directory_iterator RedirectingFileSystem::dir_begin(const Twine &Dir,
   if (EC)
     return {};
 
-  ErrorOr<RedirectingFileSystem::Entry *> E = lookupPath(Path);
-  if (!E) {
-    EC = E.getError();
+  ErrorOr<RedirectingFileSystem::LookupResult> Result = lookupPath(Path);
+  if (!Result) {
+    EC = Result.getError();
     if (shouldFallBackToExternalFS(EC))
       return ExternalFS->dir_begin(Path, EC);
     return {};
   }
-  ErrorOr<Status> S = status(Path, *E);
+
+  // Use status to make sure the path exists and refers to a directory.
+  ErrorOr<Status> S = status(Path, *Result);
   if (!S) {
+    if (shouldFallBackToExternalFS(S.getError(), Result->E))
+      return ExternalFS->dir_begin(Dir, EC);
     EC = S.getError();
     return {};
   }
@@ -1169,9 +1220,24 @@ directory_iterator RedirectingFileSystem::dir_begin(const Twine &Dir,
     return {};
   }
 
-  auto *D = cast<RedirectingFileSystem::DirectoryEntry>(*E);
-  auto DirIter = directory_iterator(std::make_shared<RedirectingFSDirIterImpl>(
-      Path, D->contents_begin(), D->contents_end(), EC));
+  // Create the appropriate directory iterator based on whether we found a
+  // DirectoryRemapEntry or DirectoryEntry.
+  directory_iterator DirIter;
+  if (auto ExtRedirect = Result->getExternalRedirect()) {
+    auto RE = cast<RedirectingFileSystem::RemapEntry>(Result->E);
+    DirIter = ExternalFS->dir_begin(*ExtRedirect, EC);
+
+    if (!RE->useExternalName(UseExternalNames)) {
+      // Update the paths in the results to use the virtual directory's path.
+      DirIter =
+          directory_iterator(std::make_shared<RedirectingFSDirRemapIterImpl>(
+              std::string(Path), DirIter));
+    }
+  } else {
+    auto DE = cast<DirectoryEntry>(Result->E);
+    DirIter = directory_iterator(std::make_shared<RedirectingFSDirIterImpl>(
+        Path, DE->contents_begin(), DE->contents_end(), EC));
+  }
 
   if (!shouldUseExternalFS())
     return DirIter;
@@ -1360,6 +1426,15 @@ class llvm::vfs::RedirectingFileSystemParser {
         uniqueOverlayTree(FS, SubEntry.get(), NewParentE);
       break;
     }
+    case RedirectingFileSystem::EK_DirectoryRemap: {
+      assert(NewParentE && "Parent entry must exist");
+      auto *DR = cast<RedirectingFileSystem::DirectoryRemapEntry>(SrcE);
+      auto *DE = cast<RedirectingFileSystem::DirectoryEntry>(NewParentE);
+      DE->addContent(
+          std::make_unique<RedirectingFileSystem::DirectoryRemapEntry>(
+              Name, DR->getExternalContentsPath(), DR->getUseName()));
+      break;
+    }
     case RedirectingFileSystem::EK_File: {
       assert(NewParentE && "Parent entry must exist");
       auto *FE = cast<RedirectingFileSystem::FileEntry>(SrcE);
@@ -1389,13 +1464,13 @@ class llvm::vfs::RedirectingFileSystemParser {
 
     DenseMap<StringRef, KeyStatus> Keys(std::begin(Fields), std::end(Fields));
 
-    bool HasContents = false; // external or otherwise
+    enum { CF_NotSet, CF_List, CF_External } ContentsField = CF_NotSet;
     std::vector<std::unique_ptr<RedirectingFileSystem::Entry>>
         EntryArrayContents;
     SmallString<256> ExternalContentsPath;
     SmallString<256> Name;
     yaml::Node *NameValueNode = nullptr;
-    auto UseExternalName = RedirectingFileSystem::FileEntry::NK_NotSet;
+    auto UseExternalName = RedirectingFileSystem::NK_NotSet;
     RedirectingFileSystem::EntryKind Kind;
 
     for (auto &I : *M) {
@@ -1425,17 +1500,19 @@ class llvm::vfs::RedirectingFileSystemParser {
           Kind = RedirectingFileSystem::EK_File;
         else if (Value == "directory")
           Kind = RedirectingFileSystem::EK_Directory;
+        else if (Value == "directory-remap")
+          Kind = RedirectingFileSystem::EK_DirectoryRemap;
         else {
           error(I.getValue(), "unknown value for 'type'");
           return nullptr;
         }
       } else if (Key == "contents") {
-        if (HasContents) {
+        if (ContentsField != CF_NotSet) {
           error(I.getKey(),
                 "entry already has 'contents' or 'external-contents'");
           return nullptr;
         }
-        HasContents = true;
+        ContentsField = CF_List;
         auto *Contents = dyn_cast<yaml::SequenceNode>(I.getValue());
         if (!Contents) {
           // FIXME: this is only for directories, what about files?
@@ -1451,12 +1528,12 @@ class llvm::vfs::RedirectingFileSystemParser {
             return nullptr;
         }
       } else if (Key == "external-contents") {
-        if (HasContents) {
+        if (ContentsField != CF_NotSet) {
           error(I.getKey(),
                 "entry already has 'contents' or 'external-contents'");
           return nullptr;
         }
-        HasContents = true;
+        ContentsField = CF_External;
         if (!parseScalarString(I.getValue(), Value, Buffer))
           return nullptr;
 
@@ -1478,8 +1555,8 @@ class llvm::vfs::RedirectingFileSystemParser {
         bool Val;
         if (!parseScalarBool(I.getValue(), Val))
           return nullptr;
-        UseExternalName = Val ? RedirectingFileSystem::FileEntry::NK_External
-                              : RedirectingFileSystem::FileEntry::NK_Virtual;
+        UseExternalName = Val ? RedirectingFileSystem::NK_External
+                              : RedirectingFileSystem::NK_Virtual;
       } else {
         llvm_unreachable("key missing from Keys");
       }
@@ -1489,7 +1566,7 @@ class llvm::vfs::RedirectingFileSystemParser {
       return nullptr;
 
     // check for missing keys
-    if (!HasContents) {
+    if (ContentsField == CF_NotSet) {
       error(N, "missing key 'contents' or 'external-contents'");
       return nullptr;
     }
@@ -1498,8 +1575,14 @@ class llvm::vfs::RedirectingFileSystemParser {
 
     // check invalid configuration
     if (Kind == RedirectingFileSystem::EK_Directory &&
-        UseExternalName != RedirectingFileSystem::FileEntry::NK_NotSet) {
-      error(N, "'use-external-name' is not supported for directories");
+        UseExternalName != RedirectingFileSystem::NK_NotSet) {
+      error(N, "'use-external-name' is not supported for 'directory' entries");
+      return nullptr;
+    }
+
+    if (Kind == RedirectingFileSystem::EK_DirectoryRemap &&
+        ContentsField == CF_List) {
+      error(N, "'contents' is not supported for 'directory-remap' entries");
       return nullptr;
     }
 
@@ -1535,6 +1618,10 @@ class llvm::vfs::RedirectingFileSystemParser {
       Result = std::make_unique<RedirectingFileSystem::FileEntry>(
           LastComponent, std::move(ExternalContentsPath), UseExternalName);
       break;
+    case RedirectingFileSystem::EK_DirectoryRemap:
+      Result = std::make_unique<RedirectingFileSystem::DirectoryRemapEntry>(
+          LastComponent, std::move(ExternalContentsPath), UseExternalName);
+      break;
     case RedirectingFileSystem::EK_Directory:
       Result = std::make_unique<RedirectingFileSystem::DirectoryEntry>(
           LastComponent, std::move(EntryArrayContents),
@@ -1745,8 +1832,8 @@ std::unique_ptr<RedirectingFileSystem> RedirectingFileSystem::create(
     // Add the file.
     auto NewFile = std::make_unique<RedirectingFileSystem::FileEntry>(
         llvm::sys::path::filename(From), To,
-        UseExternalNames ? RedirectingFileSystem::FileEntry::NK_External
-                         : RedirectingFileSystem::FileEntry::NK_Virtual);
+        UseExternalNames ? RedirectingFileSystem::NK_External
+                         : RedirectingFileSystem::NK_Virtual);
     ToEntry = NewFile.get();
     cast<RedirectingFileSystem::DirectoryEntry>(Parent)->addContent(
         std::move(NewFile));
@@ -1755,8 +1842,25 @@ std::unique_ptr<RedirectingFileSystem> RedirectingFileSystem::create(
   return FS;
 }
 
+RedirectingFileSystem::LookupResult::LookupResult(
+    Entry *E, sys::path::const_iterator Start, sys::path::const_iterator End)
+    : E(E) {
+  assert(E != nullptr);
+  // If the matched entry is a DirectoryRemapEntry, set ExternalRedirect to the
+  // path of the directory it maps to in the external file system plus any
+  // remaining path components in the provided iterator.
+  if (auto *DRE = dyn_cast<RedirectingFileSystem::DirectoryRemapEntry>(E)) {
+    SmallString<256> Redirect(DRE->getExternalContentsPath());
+    sys::path::append(Redirect, Start, End,
+                      getExistingStyle(DRE->getExternalContentsPath()));
+    ExternalRedirect = std::string(Redirect);
+  }
+}
+
 bool RedirectingFileSystem::shouldFallBackToExternalFS(
-    std::error_code EC) const {
+    std::error_code EC, RedirectingFileSystem::Entry *E) const {
+  if (E && !isa<RedirectingFileSystem::DirectoryRemapEntry>(E))
+    return false;
   return shouldUseExternalFS() && EC == llvm::errc::no_such_file_or_directory;
 }
 
@@ -1774,23 +1878,23 @@ RedirectingFileSystem::makeCanonical(SmallVectorImpl<char> &Path) const {
   return {};
 }
 
-ErrorOr<RedirectingFileSystem::Entry *>
+ErrorOr<RedirectingFileSystem::LookupResult>
 RedirectingFileSystem::lookupPath(StringRef Path) const {
   sys::path::const_iterator Start = sys::path::begin(Path);
   sys::path::const_iterator End = sys::path::end(Path);
   for (const auto &Root : Roots) {
-    ErrorOr<RedirectingFileSystem::Entry *> Result =
-        lookupPath(Start, End, Root.get());
+    ErrorOr<RedirectingFileSystem::LookupResult> Result =
+        lookupPathImpl(Start, End, Root.get());
     if (Result || Result.getError() != llvm::errc::no_such_file_or_directory)
       return Result;
   }
   return make_error_code(llvm::errc::no_such_file_or_directory);
 }
 
-ErrorOr<RedirectingFileSystem::Entry *>
-RedirectingFileSystem::lookupPath(sys::path::const_iterator Start,
-                                  sys::path::const_iterator End,
-                                  RedirectingFileSystem::Entry *From) const {
+ErrorOr<RedirectingFileSystem::LookupResult>
+RedirectingFileSystem::lookupPathImpl(
+    sys::path::const_iterator Start, sys::path::const_iterator End,
+    RedirectingFileSystem::Entry *From) const {
   assert(!isTraversalComponent(*Start) &&
          !isTraversalComponent(From->getName()) &&
          "Paths should not contain traversal components");
@@ -1806,18 +1910,21 @@ RedirectingFileSystem::lookupPath(sys::path::const_iterator Start,
 
     if (Start == End) {
       // Match!
-      return From;
+      return LookupResult(From, Start, End);
     }
   }
 
-  auto *DE = dyn_cast<RedirectingFileSystem::DirectoryEntry>(From);
-  if (!DE)
+  if (isa<RedirectingFileSystem::FileEntry>(From))
     return make_error_code(llvm::errc::not_a_directory);
 
+  if (isa<RedirectingFileSystem::DirectoryRemapEntry>(From))
+    return LookupResult(From, Start, End);
+
+  auto *DE = cast<RedirectingFileSystem::DirectoryEntry>(From);
   for (const std::unique_ptr<RedirectingFileSystem::Entry> &DirEntry :
        llvm::make_range(DE->contents_begin(), DE->contents_end())) {
-    ErrorOr<RedirectingFileSystem::Entry *> Result =
-        lookupPath(Start, End, DirEntry.get());
+    ErrorOr<RedirectingFileSystem::LookupResult> Result =
+        lookupPathImpl(Start, End, DirEntry.get());
     if (Result || Result.getError() != llvm::errc::no_such_file_or_directory)
       return Result;
   }
@@ -1834,20 +1941,19 @@ static Status getRedirectedFileStatus(const Twine &Path, bool UseExternalNames,
   return S;
 }
 
-ErrorOr<Status> RedirectingFileSystem::status(const Twine &Path,
-                                              RedirectingFileSystem::Entry *E) {
-  assert(E != nullptr);
-  if (auto *F = dyn_cast<RedirectingFileSystem::FileEntry>(E)) {
-    ErrorOr<Status> S = ExternalFS->status(F->getExternalContentsPath());
-    assert(!S || S->getName() == F->getExternalContentsPath());
-    if (S)
-      return getRedirectedFileStatus(Path, F->useExternalName(UseExternalNames),
-                                     *S);
-    return S;
-  } else { // directory
-    auto *DE = cast<RedirectingFileSystem::DirectoryEntry>(E);
-    return Status::copyWithNewName(DE->getStatus(), Path);
+ErrorOr<Status> RedirectingFileSystem::status(
+    const Twine &Path, const RedirectingFileSystem::LookupResult &Result) {
+  if (Optional<StringRef> ExtRedirect = Result.getExternalRedirect()) {
+    ErrorOr<Status> S = ExternalFS->status(*ExtRedirect);
+    if (!S)
+      return S;
+    auto *RE = cast<RedirectingFileSystem::RemapEntry>(Result.E);
+    return getRedirectedFileStatus(Path, RE->useExternalName(UseExternalNames),
+                                   *S);
   }
+
+  auto *DE = cast<RedirectingFileSystem::DirectoryEntry>(Result.E);
+  return Status::copyWithNewName(DE->getStatus(), Path);
 }
 
 ErrorOr<Status> RedirectingFileSystem::status(const Twine &Path_) {
@@ -1857,13 +1963,17 @@ ErrorOr<Status> RedirectingFileSystem::status(const Twine &Path_) {
   if (std::error_code EC = makeCanonical(Path))
     return EC;
 
-  ErrorOr<RedirectingFileSystem::Entry *> Result = lookupPath(Path);
+  ErrorOr<RedirectingFileSystem::LookupResult> Result = lookupPath(Path);
   if (!Result) {
     if (shouldFallBackToExternalFS(Result.getError()))
       return ExternalFS->status(Path);
     return Result.getError();
   }
-  return status(Path, *Result);
+
+  ErrorOr<Status> S = status(Path, *Result);
+  if (!S && shouldFallBackToExternalFS(S.getError(), Result->E))
+    S = ExternalFS->status(Path);
+  return S;
 }
 
 namespace {
@@ -1899,30 +2009,35 @@ RedirectingFileSystem::openFileForRead(const Twine &Path_) {
   if (std::error_code EC = makeCanonical(Path))
     return EC;
 
-  ErrorOr<RedirectingFileSystem::Entry *> E = lookupPath(Path);
-  if (!E) {
-    if (shouldFallBackToExternalFS(E.getError()))
+  ErrorOr<RedirectingFileSystem::LookupResult> Result = lookupPath(Path);
+  if (!Result) {
+    if (shouldFallBackToExternalFS(Result.getError()))
       return ExternalFS->openFileForRead(Path);
-    return E.getError();
+    return Result.getError();
   }
 
-  auto *F = dyn_cast<RedirectingFileSystem::FileEntry>(*E);
-  if (!F) // FIXME: errc::not_a_file?
+  if (!Result->getExternalRedirect()) // FIXME: errc::not_a_file?
     return make_error_code(llvm::errc::invalid_argument);
 
-  auto Result = ExternalFS->openFileForRead(F->getExternalContentsPath());
-  if (!Result)
-    return Result;
+  StringRef ExtRedirect = *Result->getExternalRedirect();
+  auto *RE = cast<RedirectingFileSystem::RemapEntry>(Result->E);
 
-  auto ExternalStatus = (*Result)->status();
+  auto ExternalFile = ExternalFS->openFileForRead(ExtRedirect);
+  if (!ExternalFile) {
+    if (shouldFallBackToExternalFS(ExternalFile.getError(), Result->E))
+      return ExternalFS->openFileForRead(Path);
+    return ExternalFile;
+  }
+
+  auto ExternalStatus = (*ExternalFile)->status();
   if (!ExternalStatus)
     return ExternalStatus.getError();
 
   // FIXME: Update the status with the name and VFSMapped.
-  Status S = getRedirectedFileStatus(Path, F->useExternalName(UseExternalNames),
-                                     *ExternalStatus);
+  Status S = getRedirectedFileStatus(
+      Path, RE->useExternalName(UseExternalNames), *ExternalStatus);
   return std::unique_ptr<File>(
-      std::make_unique<FileWithFixedStatus>(std::move(*Result), S));
+      std::make_unique<FileWithFixedStatus>(std::move(*ExternalFile), S));
 }
 
 std::error_code
@@ -1934,17 +2049,24 @@ RedirectingFileSystem::getRealPath(const Twine &Path_,
   if (std::error_code EC = makeCanonical(Path))
     return EC;
 
-  ErrorOr<RedirectingFileSystem::Entry *> Result = lookupPath(Path);
+  ErrorOr<RedirectingFileSystem::LookupResult> Result = lookupPath(Path);
   if (!Result) {
     if (shouldFallBackToExternalFS(Result.getError()))
       return ExternalFS->getRealPath(Path, Output);
     return Result.getError();
   }
 
-  if (auto *F = dyn_cast<RedirectingFileSystem::FileEntry>(*Result)) {
-    return ExternalFS->getRealPath(F->getExternalContentsPath(), Output);
+  // If we found FileEntry or DirectoryRemapEntry, look up the mapped
+  // path in the external file system.
+  if (auto ExtRedirect = Result->getExternalRedirect()) {
+    auto P = ExternalFS->getRealPath(*ExtRedirect, Output);
+    if (!P && shouldFallBackToExternalFS(P, Result->E)) {
+      return ExternalFS->getRealPath(Path, Output);
+    }
+    return P;
   }
-  // Even if there is a directory entry, fall back to ExternalFS if allowed,
+
+  // If we found a DirectoryEntry, still fall back to ExternalFS if allowed,
   // because directories don't have a single external contents path.
   return shouldUseExternalFS() ? ExternalFS->getRealPath(Path, Output)
                                : llvm::errc::invalid_argument;
@@ -1976,6 +2098,17 @@ static void getVFSEntries(RedirectingFileSystem::Entry *SrcE,
     return;
   }
 
+  if (Kind == RedirectingFileSystem::EK_DirectoryRemap) {
+    auto *DR = dyn_cast<RedirectingFileSystem::DirectoryRemapEntry>(SrcE);
+    assert(DR && "Must be a directory remap");
+    SmallString<128> VPath;
+    for (auto &Comp : Path)
+      llvm::sys::path::append(VPath, Comp);
+    Entries.push_back(
+        YAMLVFSEntry(VPath.c_str(), DR->getExternalContentsPath()));
+    return;
+  }
+
   assert(Kind == RedirectingFileSystem::EK_File && "Must be a EK_File");
   auto *FE = dyn_cast<RedirectingFileSystem::FileEntry>(SrcE);
   assert(FE && "Must be a file");
@@ -1994,12 +2127,13 @@ void vfs::collectVFSFromYAML(std::unique_ptr<MemoryBuffer> Buffer,
   std::unique_ptr<RedirectingFileSystem> VFS = RedirectingFileSystem::create(
       std::move(Buffer), DiagHandler, YAMLFilePath, DiagContext,
       std::move(ExternalFS));
-  ErrorOr<RedirectingFileSystem::Entry *> RootE = VFS->lookupPath("/");
-  if (!RootE)
+  ErrorOr<RedirectingFileSystem::LookupResult> RootResult =
+      VFS->lookupPath("/");
+  if (!RootResult)
     return;
   SmallVector<StringRef, 8> Components;
   Components.push_back("/");
-  getVFSEntries(*RootE, Components, CollectedEntries);
+  getVFSEntries(RootResult->E, Components, CollectedEntries);
 }
 
 UniqueID vfs::getNextVirtualUniqueID() {

diff  --git a/llvm/unittests/Support/VirtualFileSystemTest.cpp b/llvm/unittests/Support/VirtualFileSystemTest.cpp
index 86b747466b5b..6aa218937485 100644
--- a/llvm/unittests/Support/VirtualFileSystemTest.cpp
+++ b/llvm/unittests/Support/VirtualFileSystemTest.cpp
@@ -1328,6 +1328,7 @@ TEST_F(VFSFromYAMLTest, BasicVFSFromYAML) {
 
 TEST_F(VFSFromYAMLTest, MappedFiles) {
   IntrusiveRefCntPtr<DummyFileSystem> Lower(new DummyFileSystem());
+  Lower->addDirectory("//root/foo/bar");
   Lower->addRegularFile("//root/foo/bar/a");
   IntrusiveRefCntPtr<vfs::FileSystem> FS = getFromYAMLString(
       "{ 'roots': [\n"
@@ -1343,6 +1344,17 @@ TEST_F(VFSFromYAMLTest, MappedFiles) {
       "                  'type': 'file',\n"
       "                  'name': 'file2',\n"
       "                  'external-contents': '//root/foo/b'\n"
+      "                },\n"
+      "                {\n"
+      "                  'type': 'directory-remap',\n"
+      "                  'name': 'mappeddir',\n"
+      "                  'external-contents': '//root/foo/bar'\n"
+      "                },\n"
+      "                {\n"
+      "                  'type': 'directory-remap',\n"
+      "                  'name': 'mappeddir2',\n"
+      "                  'use-external-name': false,\n"
+      "                  'external-contents': '//root/foo/bar'\n"
       "                }\n"
       "              ]\n"
       "}\n"
@@ -1380,12 +1392,221 @@ TEST_F(VFSFromYAMLTest, MappedFiles) {
   EXPECT_TRUE(S->isDirectory());
   EXPECT_TRUE(S->equivalent(*O->status("//root/"))); // non-volatile UniqueID
 
+  // remapped directory
+  S = O->status("//root/mappeddir");
+  ASSERT_FALSE(S.getError());
+  EXPECT_TRUE(S->isDirectory());
+  EXPECT_TRUE(S->IsVFSMapped);
+  EXPECT_TRUE(S->equivalent(*O->status("//root/foo/bar")));
+
+  SLower = O->status("//root/foo/bar");
+  EXPECT_EQ("//root/foo/bar", SLower->getName());
+  EXPECT_TRUE(S->equivalent(*SLower));
+  EXPECT_FALSE(SLower->IsVFSMapped);
+
+  // file in remapped directory
+  S = O->status("//root/mappeddir/a");
+  ASSERT_FALSE(S.getError());
+  ASSERT_FALSE(S->isDirectory());
+  ASSERT_TRUE(S->IsVFSMapped);
+  ASSERT_EQ("//root/foo/bar/a", S->getName());
+
+  // file in remapped directory, with use-external-name=false
+  S = O->status("//root/mappeddir2/a");
+  ASSERT_FALSE(S.getError());
+  ASSERT_FALSE(S->isDirectory());
+  ASSERT_TRUE(S->IsVFSMapped);
+  ASSERT_EQ("//root/mappeddir2/a", S->getName());
+
+  // file contents in remapped directory
+  OpenedF = O->openFileForRead("//root/mappeddir/a");
+  ASSERT_FALSE(OpenedF.getError());
+  OpenedS = (*OpenedF)->status();
+  ASSERT_FALSE(OpenedS.getError());
+  EXPECT_EQ("//root/foo/bar/a", OpenedS->getName());
+  EXPECT_TRUE(OpenedS->IsVFSMapped);
+
+  // file contents in remapped directory, with use-external-name=false
+  OpenedF = O->openFileForRead("//root/mappeddir2/a");
+  ASSERT_FALSE(OpenedF.getError());
+  OpenedS = (*OpenedF)->status();
+  ASSERT_FALSE(OpenedS.getError());
+  EXPECT_EQ("//root/mappeddir2/a", OpenedS->getName());
+  EXPECT_TRUE(OpenedS->IsVFSMapped);
+
   // broken mapping
   EXPECT_EQ(O->status("//root/file2").getError(),
             llvm::errc::no_such_file_or_directory);
   EXPECT_EQ(0, NumDiagnostics);
 }
 
+TEST_F(VFSFromYAMLTest, MappedRoot) {
+  IntrusiveRefCntPtr<DummyFileSystem> Lower(new DummyFileSystem());
+  Lower->addDirectory("//root/foo/bar");
+  Lower->addRegularFile("//root/foo/bar/a");
+  IntrusiveRefCntPtr<vfs::FileSystem> FS =
+      getFromYAMLString("{ 'roots': [\n"
+                        "{\n"
+                        "  'type': 'directory-remap',\n"
+                        "  'name': '//mappedroot/',\n"
+                        "  'external-contents': '//root/foo/bar'\n"
+                        "}\n"
+                        "]\n"
+                        "}",
+                        Lower);
+  ASSERT_TRUE(FS.get() != nullptr);
+
+  IntrusiveRefCntPtr<vfs::OverlayFileSystem> O(
+      new vfs::OverlayFileSystem(Lower));
+  O->pushOverlay(FS);
+
+  // file
+  ErrorOr<vfs::Status> S = O->status("//mappedroot/a");
+  ASSERT_FALSE(S.getError());
+  EXPECT_EQ("//root/foo/bar/a", S->getName());
+  EXPECT_TRUE(S->IsVFSMapped);
+
+  ErrorOr<vfs::Status> SLower = O->status("//root/foo/bar/a");
+  EXPECT_EQ("//root/foo/bar/a", SLower->getName());
+  EXPECT_TRUE(S->equivalent(*SLower));
+  EXPECT_FALSE(SLower->IsVFSMapped);
+
+  // file after opening
+  auto OpenedF = O->openFileForRead("//mappedroot/a");
+  ASSERT_FALSE(OpenedF.getError());
+  auto OpenedS = (*OpenedF)->status();
+  ASSERT_FALSE(OpenedS.getError());
+  EXPECT_EQ("//root/foo/bar/a", OpenedS->getName());
+  EXPECT_TRUE(OpenedS->IsVFSMapped);
+
+  EXPECT_EQ(0, NumDiagnostics);
+}
+
+TEST_F(VFSFromYAMLTest, RemappedDirectoryOverlay) {
+  IntrusiveRefCntPtr<DummyFileSystem> Lower(new DummyFileSystem());
+  Lower->addDirectory("//root/foo");
+  Lower->addRegularFile("//root/foo/a");
+  Lower->addDirectory("//root/bar");
+  Lower->addRegularFile("//root/bar/b");
+  Lower->addRegularFile("//root/bar/c");
+  IntrusiveRefCntPtr<vfs::FileSystem> FS =
+      getFromYAMLString("{ 'roots': [\n"
+                        "{\n"
+                        "  'type': 'directory',\n"
+                        "  'name': '//root/',\n"
+                        "  'contents': [ {\n"
+                        "                  'type': 'directory-remap',\n"
+                        "                  'name': 'bar',\n"
+                        "                  'external-contents': '//root/foo'\n"
+                        "                }\n"
+                        "              ]\n"
+                        "}]}",
+                        Lower);
+  ASSERT_TRUE(FS.get() != nullptr);
+
+  IntrusiveRefCntPtr<vfs::OverlayFileSystem> O(
+      new vfs::OverlayFileSystem(Lower));
+  O->pushOverlay(FS);
+
+  ErrorOr<vfs::Status> S = O->status("//root/foo");
+  ASSERT_FALSE(S.getError());
+
+  ErrorOr<vfs::Status> SS = O->status("//root/bar");
+  ASSERT_FALSE(SS.getError());
+  EXPECT_TRUE(S->equivalent(*SS));
+
+  std::error_code EC;
+  checkContents(O->dir_begin("//root/bar", EC),
+                {"//root/foo/a", "//root/bar/b", "//root/bar/c"});
+
+  Lower->addRegularFile("//root/foo/b");
+  checkContents(O->dir_begin("//root/bar", EC),
+                {"//root/foo/a", "//root/foo/b", "//root/bar/c"});
+
+  EXPECT_EQ(0, NumDiagnostics);
+}
+
+TEST_F(VFSFromYAMLTest, RemappedDirectoryOverlayNoExternalNames) {
+  IntrusiveRefCntPtr<DummyFileSystem> Lower(new DummyFileSystem());
+  Lower->addDirectory("//root/foo");
+  Lower->addRegularFile("//root/foo/a");
+  Lower->addDirectory("//root/bar");
+  Lower->addRegularFile("//root/bar/b");
+  Lower->addRegularFile("//root/bar/c");
+  IntrusiveRefCntPtr<vfs::FileSystem> FS =
+      getFromYAMLString("{ 'use-external-names': false,\n"
+                        "  'roots': [\n"
+                        "{\n"
+                        "  'type': 'directory',\n"
+                        "  'name': '//root/',\n"
+                        "  'contents': [ {\n"
+                        "                  'type': 'directory-remap',\n"
+                        "                  'name': 'bar',\n"
+                        "                  'external-contents': '//root/foo'\n"
+                        "                }\n"
+                        "              ]\n"
+                        "}]}",
+                        Lower);
+  ASSERT_TRUE(FS.get() != nullptr);
+
+  ErrorOr<vfs::Status> S = FS->status("//root/foo");
+  ASSERT_FALSE(S.getError());
+
+  ErrorOr<vfs::Status> SS = FS->status("//root/bar");
+  ASSERT_FALSE(SS.getError());
+  EXPECT_TRUE(S->equivalent(*SS));
+
+  std::error_code EC;
+  checkContents(FS->dir_begin("//root/bar", EC),
+                {"//root/bar/a", "//root/bar/b", "//root/bar/c"});
+
+  Lower->addRegularFile("//root/foo/b");
+  checkContents(FS->dir_begin("//root/bar", EC),
+                {"//root/bar/a", "//root/bar/b", "//root/bar/c"});
+
+  EXPECT_EQ(0, NumDiagnostics);
+}
+
+TEST_F(VFSFromYAMLTest, RemappedDirectoryOverlayNoFallthrough) {
+  IntrusiveRefCntPtr<DummyFileSystem> Lower(new DummyFileSystem());
+  Lower->addDirectory("//root/foo");
+  Lower->addRegularFile("//root/foo/a");
+  Lower->addDirectory("//root/bar");
+  Lower->addRegularFile("//root/bar/b");
+  Lower->addRegularFile("//root/bar/c");
+  IntrusiveRefCntPtr<vfs::FileSystem> FS =
+      getFromYAMLString("{ 'fallthrough': false,\n"
+                        "  'roots': [\n"
+                        "{\n"
+                        "  'type': 'directory',\n"
+                        "  'name': '//root/',\n"
+                        "  'contents': [ {\n"
+                        "                  'type': 'directory-remap',\n"
+                        "                  'name': 'bar',\n"
+                        "                  'external-contents': '//root/foo'\n"
+                        "                }\n"
+                        "              ]\n"
+                        "}]}",
+                        Lower);
+  ASSERT_TRUE(FS.get() != nullptr);
+
+  ErrorOr<vfs::Status> S = Lower->status("//root/foo");
+  ASSERT_FALSE(S.getError());
+
+  ErrorOr<vfs::Status> SS = FS->status("//root/bar");
+  ASSERT_FALSE(SS.getError());
+  EXPECT_TRUE(S->equivalent(*SS));
+
+  std::error_code EC;
+  checkContents(FS->dir_begin("//root/bar", EC), {"//root/foo/a"});
+
+  Lower->addRegularFile("//root/foo/b");
+  checkContents(FS->dir_begin("//root/bar", EC),
+                {"//root/foo/a", "//root/foo/b"});
+
+  EXPECT_EQ(0, NumDiagnostics);
+}
+
 TEST_F(VFSFromYAMLTest, CaseInsensitive) {
   IntrusiveRefCntPtr<DummyFileSystem> Lower(new DummyFileSystem());
   Lower->addRegularFile("//root/foo/bar/a");
@@ -1542,7 +1763,24 @@ TEST_F(VFSFromYAMLTest, IllegalVFSFile) {
   EXPECT_EQ(nullptr, FS.get());
   FS = getFromYAMLRawString("{ 'version':100000, 'roots':[] }", Lower);
   EXPECT_EQ(nullptr, FS.get());
-  EXPECT_EQ(24, NumDiagnostics);
+
+  // both 'external-contents' and 'contents' specified
+  Lower->addDirectory("//root/external/dir");
+  FS = getFromYAMLString(
+      "{ 'roots':[ \n"
+      "{ 'type': 'directory', 'name': '//root/A', 'contents': [],\n"
+      "  'external-contents': '//root/external/dir'}]}",
+      Lower);
+  EXPECT_EQ(nullptr, FS.get());
+
+  // 'directory-remap' with 'contents'
+  FS = getFromYAMLString(
+      "{ 'roots':[ \n"
+      "{ 'type': 'directory-remap', 'name': '//root/A', 'contents': [] }]}",
+      Lower);
+  EXPECT_EQ(nullptr, FS.get());
+
+  EXPECT_EQ(26, NumDiagnostics);
 }
 
 TEST_F(VFSFromYAMLTest, UseExternalName) {


        


More information about the llvm-commits mailing list