Index: llvm/include/llvm/Support/VirtualFileSystem.h =================================================================== --- llvm/include/llvm/Support/VirtualFileSystem.h +++ llvm/include/llvm/Support/VirtualFileSystem.h @@ -650,6 +650,9 @@ /// The root(s) of the virtual file system. std::vector> Roots; + /// The working directories of this virtual file system. + std::vector WorkingDirs; + /// The file system to use for external references. IntrusiveRefCntPtr ExternalFS; @@ -692,6 +695,8 @@ RedirectingFileSystem(IntrusiveRefCntPtr ExternalFS) : ExternalFS(std::move(ExternalFS)) {} + ErrorOr lookupPathImpl(const Twine &Path, bool MakeAbsolute) const; + /// Looks up the path [Start, End) in \p From, possibly /// recursing into the contents of \p From if it is a directory. ErrorOr lookupPath(llvm::sys::path::const_iterator Start, @@ -722,6 +727,8 @@ std::error_code setCurrentWorkingDirectory(const Twine &Path) override; + void addWorkingDirectory(const Twine &Path); + std::error_code isLocal(const Twine &Path, bool &Result) override; directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override; Index: llvm/lib/Support/VirtualFileSystem.cpp =================================================================== --- llvm/lib/Support/VirtualFileSystem.cpp +++ llvm/lib/Support/VirtualFileSystem.cpp @@ -1043,6 +1043,10 @@ return ExternalFS->setCurrentWorkingDirectory(Path); } +void RedirectingFileSystem::addWorkingDirectory(const Twine &Path) { + WorkingDirs.push_back(Path.str()); +} + std::error_code RedirectingFileSystem::isLocal(const Twine &Path, bool &Result) { return ExternalFS->isLocal(Path, Result); @@ -1473,6 +1477,7 @@ KeyStatusPair("use-external-names", false), KeyStatusPair("overlay-relative", false), KeyStatusPair("fallthrough", false), + KeyStatusPair("working-directories", false), KeyStatusPair("roots", true), }; @@ -1533,11 +1538,23 @@ } else if (Key == "fallthrough") { if (!parseScalarBool(I.getValue(), FS->IsFallthrough)) return false; + } else if (Key == "working-directories") { + auto *Dirs = dyn_cast(I.getValue()); + if (!Dirs) { + error(I.getValue(), "expected array"); + return false; + } + SmallString<256> Storage; + StringRef WorkingDir; + for (auto &I : *Dirs) { + if (!parseScalarString(&I, WorkingDir, Storage)) + return false; + FS->addWorkingDirectory(WorkingDir); + } } else { llvm_unreachable("key missing from Keys"); } } - if (Stream.failed()) return false; @@ -1599,12 +1616,42 @@ ErrorOr RedirectingFileSystem::lookupPath(const Twine &Path_) const { + // First try the current working directory. + ErrorOr Entry = lookupPathImpl(Path_, true); + if (Entry) + return Entry; + + // Try the working directories in order. + SmallString<256> Storage; + for (const std::string &WD : WorkingDirs) { + // Initialize the buffer to the given path and resolve it against the + // working directory. + Storage.clear(); + Path_.toVector(Storage); + sys::fs::make_absolute(WD, Storage); + + // We know that the given path is absolute, so no need to resolve it again + // in lookupPathImpl. + ErrorOr RemappedEntry = + lookupPathImpl(Storage, false); + if (RemappedEntry) + return RemappedEntry; + } + + // If nothing was found, return the original error. + return Entry; +} + +ErrorOr +RedirectingFileSystem::lookupPathImpl(const Twine &Path_, + bool MakeAbsolute) const { SmallString<256> Path; Path_.toVector(Path); // Handle relative paths - if (std::error_code EC = makeAbsolute(Path)) - return EC; + if (MakeAbsolute) + if (std::error_code EC = makeAbsolute(Path)) + return EC; // Canonicalize path by removing ".", "..", "./", etc components. This is // a VFS request, do bot bother about symlinks in the path components Index: llvm/unittests/Support/VirtualFileSystemTest.cpp =================================================================== --- llvm/unittests/Support/VirtualFileSystemTest.cpp +++ llvm/unittests/Support/VirtualFileSystemTest.cpp @@ -1994,3 +1994,108 @@ EXPECT_EQ(FS->getRealPath("/non_existing", RealPath), errc::no_such_file_or_directory); } + +TEST_F(VFSFromYAMLTest, WorkingDirectoriesOneMatch) { + IntrusiveRefCntPtr Lower(new DummyFileSystem()); + Lower->addDirectory("//root/"); + Lower->addDirectory("//root/foo"); + Lower->addRegularFile("//root/foo/a"); + Lower->addRegularFile("//root/foo/b"); + IntrusiveRefCntPtr FS = getFromYAMLString( + "{ 'use-external-names': false,\n" + " 'working-directories': ['//root/quux/', '//root/bar']\n" + " 'roots': [\n" + "{\n" + " 'type': 'directory',\n" + " 'name': '//root/',\n" + " 'contents': [ {\n" + " 'type': 'file',\n" + " 'name': 'bar/a',\n" + " 'external-contents': '//root/foo/a'\n" + " }\n" + " ]\n" + "}\n" + "]\n" + "}", + Lower); + ASSERT_TRUE(FS.get() != nullptr); + + llvm::ErrorOr Status = FS->status("./a"); + ASSERT_FALSE(Status.getError()); + EXPECT_TRUE(Status->isStatusKnown()); + EXPECT_FALSE(Status->isDirectory()); + EXPECT_TRUE(Status->isRegularFile()); + EXPECT_FALSE(Status->isSymlink()); + EXPECT_FALSE(Status->isOther()); + EXPECT_TRUE(Status->exists()); +} + +TEST_F(VFSFromYAMLTest, WorkingDirectoriesNoMatch) { + IntrusiveRefCntPtr Lower(new DummyFileSystem()); + Lower->addDirectory("//root/"); + Lower->addDirectory("//root/foo"); + Lower->addRegularFile("//root/foo/a"); + Lower->addRegularFile("//root/foo/b"); + IntrusiveRefCntPtr FS = getFromYAMLString( + "{ 'use-external-names': false,\n" + " 'working-directories': ['//root/foo/', '//root/quux/']\n" + " 'roots': [\n" + "{\n" + " 'type': 'directory',\n" + " 'name': '//root/',\n" + " 'contents': [ {\n" + " 'type': 'file',\n" + " 'name': 'bar/a',\n" + " 'external-contents': '//root/foo/a'\n" + " }\n" + " ]\n" + "}\n" + "]\n" + "}", + Lower); + ASSERT_TRUE(FS.get() != nullptr); + + llvm::ErrorOr Status = FS->status("./a"); + ASSERT_TRUE(Status.getError()); +} + +TEST_F(VFSFromYAMLTest, WorkingDirectoriesMultipleMatches) { + IntrusiveRefCntPtr Lower(new DummyFileSystem()); + Lower->addDirectory("//root/"); + Lower->addDirectory("//root/foo"); + Lower->addRegularFile("//root/foo/a", sys::fs::owner_read); + Lower->addRegularFile("//root/foo/b", sys::fs::owner_write); + IntrusiveRefCntPtr FS = getFromYAMLString( + "{ 'use-external-names': false,\n" + " 'working-directories': ['//root/bar/', '//root/baz/']\n" + " 'roots': [\n" + "{\n" + " 'type': 'directory',\n" + " 'name': '//root/',\n" + " 'contents': [ {\n" + " 'type': 'file',\n" + " 'name': 'bar/a',\n" + " 'external-contents': '//root/foo/a'\n" + " },\n" + " {\n" + " 'type': 'file',\n" + " 'name': 'bar/a',\n" + " 'external-contents': '//root/foo/b'\n" + " }\n" + " ]\n" + "}\n" + "]\n" + "}", + Lower); + ASSERT_TRUE(FS.get() != nullptr); + + llvm::ErrorOr Status = FS->status("./a"); + ASSERT_FALSE(Status.getError()); + EXPECT_TRUE(Status->isStatusKnown()); + EXPECT_FALSE(Status->isDirectory()); + EXPECT_TRUE(Status->isRegularFile()); + EXPECT_FALSE(Status->isSymlink()); + EXPECT_FALSE(Status->isOther()); + EXPECT_TRUE(Status->exists()); + EXPECT_EQ(Status->getPermissions(), sys::fs::owner_read); +}