bnbarham created this revision. Herald added subscribers: dexonsmith, hiraditya. bnbarham requested review of this revision. Herald added projects: clang, LLVM. Herald added subscribers: llvm-commits, cfe-commits.
86e2af8043c7728710a711b623f27425801a36c3 <https://reviews.llvm.org/rG86e2af8043c7728710a711b623f27425801a36c3> aimed to preserve the original relative path when falling back to the external filesystem. But when there's nested `RedirectingFileSystems` this results in the outer-most VFS overwriting the path of a lower VFS. For example, take a directory remapping overlay of `A -> B` and another of `B -> C` where both have `use-external-names` set. If `A/foo` is requested this will map `A/foo` to `B/foo` to `C/foo` but when this result returns to the `A -> B` VFS, it will then remap the `Status` and `File` to `B/foo` instead. This is only a partial fix - it fixes `openFileForRead`, but `status` and `getRealPath` would need equivalent changes. I'm putting up this partial PR to gather opinions on whether this seems reasonable first. Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D117730 Files: clang/test/VFS/directory.c llvm/include/llvm/Support/VirtualFileSystem.h llvm/lib/Support/FileCollector.cpp llvm/lib/Support/VirtualFileSystem.cpp llvm/unittests/Support/VirtualFileSystemTest.cpp
Index: llvm/unittests/Support/VirtualFileSystemTest.cpp =================================================================== --- llvm/unittests/Support/VirtualFileSystemTest.cpp +++ llvm/unittests/Support/VirtualFileSystemTest.cpp @@ -63,11 +63,13 @@ return make_error_code(llvm::errc::no_such_file_or_directory); return I->second; } - ErrorOr<std::unique_ptr<vfs::File>> - openFileForRead(const Twine &Path) override { - auto S = status(Path); - if (S) - return std::unique_ptr<vfs::File>(new DummyFile{*S}); + ErrorOr<std::unique_ptr<vfs::File>> openFileForReadImpl( + const Twine &LookupPath, const Twine &OriginalPath) override { + auto S = status(LookupPath); + if (S) { + auto FixedS = vfs::Status::copyWithNewName(*S, OriginalPath); + return std::unique_ptr<vfs::File>(new DummyFile{FixedS}); + } return S.getError(); } llvm::ErrorOr<std::string> getCurrentWorkingDirectory() const override { @@ -2708,6 +2710,80 @@ EXPECT_FALSE(FS->exists(_c.path("c"))); } +TEST_F(VFSFromYAMLTest, MultipleRemappedDirectories) { + // Test the interaction of two overlays where one maps back to the other, + // ie. `a` -> `b` and then `b` -> `a`. This should always use `a` if it + // exists and fallback to `b` otherwise. + + IntrusiveRefCntPtr<DummyFileSystem> Both(new DummyFileSystem()); + Both->addDirectory("//root/a"); + Both->addRegularFile("//root/a/f"); + Both->addDirectory("//root/b"); + Both->addRegularFile("//root/b/f"); + + IntrusiveRefCntPtr<DummyFileSystem> AOnly(new DummyFileSystem()); + AOnly->addDirectory("//root/a"); + AOnly->addRegularFile("//root/a/f"); + + IntrusiveRefCntPtr<DummyFileSystem> BOnly(new DummyFileSystem()); + BOnly->addDirectory("//root/b"); + BOnly->addRegularFile("//root/b/f"); + + auto AToBStr = "{ 'roots': [\n" + " {\n" + " 'type': 'directory-remap',\n" + " 'name': '//root/a',\n" + " 'external-contents': '//root/b'\n" + " }" + "]}"; + auto BToAStr = "{ 'roots': [\n" + " {\n" + " 'type': 'directory-remap',\n" + " 'name': '//root/b',\n" + " 'external-contents': '//root/a'\n" + " }" + "]}"; + + auto ExpectPath = [&](vfs::FileSystem &FS, StringRef Expected) { + auto AF = FS.openFileForRead("//root/a/f"); + ASSERT_FALSE(AF.getError()); + auto AFName = (*AF)->getName(); + ASSERT_FALSE(AFName.getError()); + EXPECT_EQ(Expected.str(), AFName.get()); + + auto AS = FS.status("//root/a/f"); + ASSERT_FALSE(AS.getError()); + EXPECT_EQ(Expected.str(), AS->getName()); + + auto BF = FS.openFileForRead("//root/b/f"); + ASSERT_FALSE(BF.getError()); + auto BName = (*BF)->getName(); + ASSERT_FALSE(BName.getError()); + EXPECT_EQ(Expected.str(), BName.get()); + + auto BS = FS.status("//root/b/f"); + ASSERT_FALSE(BS.getError()); + EXPECT_EQ(Expected.str(), BS->getName()); + }; + + IntrusiveRefCntPtr<vfs::FileSystem> BothFS = + getFromYAMLString(AToBStr, getFromYAMLString(BToAStr, Both)); + ASSERT_TRUE(BothFS.get() != nullptr); + ExpectPath(*BothFS, "//root/a/f"); + + IntrusiveRefCntPtr<vfs::FileSystem> OnlyAFS = + getFromYAMLString(AToBStr, getFromYAMLString(BToAStr, AOnly)); + ASSERT_TRUE(OnlyAFS.get() != nullptr); + ExpectPath(*OnlyAFS, "//root/a/f"); + + IntrusiveRefCntPtr<vfs::FileSystem> OnlyBFS = + getFromYAMLString(AToBStr, getFromYAMLString(BToAStr, BOnly)); + ASSERT_TRUE(OnlyBFS.get() != nullptr); + ExpectPath(*OnlyBFS, "//root/b/f"); + + EXPECT_EQ(0, NumDiagnostics); +} + TEST(VFSFromRemappedFilesTest, Basic) { IntrusiveRefCntPtr<vfs::InMemoryFileSystem> BaseFS = new vfs::InMemoryFileSystem; Index: llvm/lib/Support/VirtualFileSystem.cpp =================================================================== --- llvm/lib/Support/VirtualFileSystem.cpp +++ llvm/lib/Support/VirtualFileSystem.cpp @@ -267,7 +267,8 @@ } ErrorOr<Status> status(const Twine &Path) override; - ErrorOr<std::unique_ptr<File>> openFileForRead(const Twine &Path) override; + ErrorOr<std::unique_ptr<File>> openFileForReadImpl( + const Twine &LookupPath, const Twine &OriginalPath) override; directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override; llvm::ErrorOr<std::string> getCurrentWorkingDirectory() const override; @@ -307,15 +308,17 @@ return Status::copyWithNewName(RealStatus, Path); } + ErrorOr<std::unique_ptr<File>> -RealFileSystem::openFileForRead(const Twine &Name) { +RealFileSystem::openFileForReadImpl(const Twine &LookupPath, + const Twine &OriginalPath) { SmallString<256> RealName, Storage; Expected<file_t> FDOrErr = sys::fs::openNativeFileForRead( - adjustPath(Name, Storage), sys::fs::OF_None, &RealName); + adjustPath(LookupPath, Storage), sys::fs::OF_None, &RealName); if (!FDOrErr) return errorToErrorCode(FDOrErr.takeError()); return std::unique_ptr<File>( - new RealFile(*FDOrErr, Name.str(), RealName.str())); + new RealFile(*FDOrErr, OriginalPath.str(), RealName.str())); } llvm::ErrorOr<std::string> RealFileSystem::getCurrentWorkingDirectory() const { @@ -422,10 +425,11 @@ } ErrorOr<std::unique_ptr<File>> -OverlayFileSystem::openFileForRead(const llvm::Twine &Path) { +OverlayFileSystem::openFileForReadImpl(const Twine &LookupPath, + const Twine &OriginalPath) { // FIXME: handle symlinks that cross file systems for (iterator I = overlays_begin(), E = overlays_end(); I != E; ++I) { - auto Result = (*I)->openFileForRead(Path); + auto Result = (*I)->openFileForReadImpl(LookupPath, OriginalPath); if (Result || Result.getError() != llvm::errc::no_such_file_or_directory) return Result; } @@ -928,8 +932,9 @@ } llvm::ErrorOr<std::unique_ptr<File>> -InMemoryFileSystem::openFileForRead(const Twine &Path) { - auto Node = lookupInMemoryNode(*this, Root.get(), Path); +InMemoryFileSystem::openFileForReadImpl(const Twine &LookupPath, + const Twine &OriginalPath) { + auto Node = lookupInMemoryNode(*this, Root.get(), LookupPath); if (!Node) return Node.getError(); @@ -937,7 +942,7 @@ // to match the ownership semantics for File. if (auto *F = dyn_cast<detail::InMemoryFile>(*Node)) return std::unique_ptr<File>( - new detail::InMemoryFileAdaptor(*F, Path.str())); + new detail::InMemoryFileAdaptor(*F, OriginalPath.str())); // FIXME: errc::not_a_file? return make_error_code(llvm::errc::invalid_argument); @@ -2100,9 +2105,10 @@ } ErrorOr<std::unique_ptr<File>> -RedirectingFileSystem::openFileForRead(const Twine &OriginalPath) { +RedirectingFileSystem::openFileForReadImpl(const Twine &LookupPath, + const Twine &OriginalPath) { SmallString<256> CanonicalPath; - OriginalPath.toVector(CanonicalPath); + LookupPath.toVector(CanonicalPath); if (std::error_code EC = makeCanonical(CanonicalPath)) return EC; @@ -2111,8 +2117,7 @@ lookupPath(CanonicalPath); if (!Result) { if (shouldFallBackToExternalFS(Result.getError())) - return File::getWithPath(ExternalFS->openFileForRead(CanonicalPath), - OriginalPath); + return ExternalFS->openFileForReadImpl(CanonicalPath, OriginalPath); return Result.getError(); } @@ -2127,12 +2132,12 @@ auto *RE = cast<RedirectingFileSystem::RemapEntry>(Result->E); - auto ExternalFile = File::getWithPath( - ExternalFS->openFileForRead(CanonicalRemappedPath), ExtRedirect); + auto ExternalFile = ExternalFS->openFileForReadImpl( + CanonicalRemappedPath, + RE->useExternalName(UseExternalNames) ? ExtRedirect : OriginalPath); if (!ExternalFile) { if (shouldFallBackToExternalFS(ExternalFile.getError(), Result->E)) - return File::getWithPath(ExternalFS->openFileForRead(CanonicalPath), - OriginalPath); + return ExternalFS->openFileForReadImpl(CanonicalPath, OriginalPath); return ExternalFile; } @@ -2140,9 +2145,8 @@ if (!ExternalStatus) return ExternalStatus.getError(); - // FIXME: Update the status with the name and VFSMapped. - Status S = getRedirectedFileStatus( - OriginalPath, RE->useExternalName(UseExternalNames), *ExternalStatus); + Status S = *ExternalStatus; + S.IsVFSMapped = true; return std::unique_ptr<File>( std::make_unique<FileWithFixedStatus>(std::move(*ExternalFile), S)); } Index: llvm/lib/Support/FileCollector.cpp =================================================================== --- llvm/lib/Support/FileCollector.cpp +++ llvm/lib/Support/FileCollector.cpp @@ -265,11 +265,11 @@ return Result; } - llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>> - openFileForRead(const Twine &Path) override { - auto Result = FS->openFileForRead(Path); + llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>> openFileForReadImpl( + const Twine &LookupPath, const Twine &OriginalPath) override { + auto Result = FS->openFileForReadImpl(LookupPath, OriginalPath); if (Result && *Result) - Collector->addFile(Path); + Collector->addFile(LookupPath); return Result; } Index: llvm/include/llvm/Support/VirtualFileSystem.h =================================================================== --- llvm/include/llvm/Support/VirtualFileSystem.h +++ llvm/include/llvm/Support/VirtualFileSystem.h @@ -260,8 +260,17 @@ virtual llvm::ErrorOr<Status> status(const Twine &Path) = 0; /// Get a \p File object for the file at \p Path, if one exists. + llvm::ErrorOr<std::unique_ptr<File>> openFileForRead(const Twine &Path) { + return openFileForReadImpl(Path, Path); + } + + /// Get a \p File object for the file at \p LookupPath. In most cases the + /// filesystem should ensure that the returned file has a path of + /// \p OriginalPath, but this is not a guarantee. In some cases the path will + /// have to differ, eg. if it has been remapped and is intentionally being + /// leaked. virtual llvm::ErrorOr<std::unique_ptr<File>> - openFileForRead(const Twine &Path) = 0; + openFileForReadImpl(const Twine &LookupPath, const Twine &OriginalPath) = 0; /// This is a convenience method that opens a file, gets its content and then /// closes the file. @@ -344,7 +353,8 @@ llvm::ErrorOr<Status> status(const Twine &Path) override; llvm::ErrorOr<std::unique_ptr<File>> - openFileForRead(const Twine &Path) override; + ErrorOr<std::unique_ptr<File>> openFileForReadImpl( + const Twine &LookupPath, const Twine &OriginalPath) override; directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override; llvm::ErrorOr<std::string> getCurrentWorkingDirectory() const override; std::error_code setCurrentWorkingDirectory(const Twine &Path) override; @@ -385,9 +395,9 @@ llvm::ErrorOr<Status> status(const Twine &Path) override { return FS->status(Path); } - llvm::ErrorOr<std::unique_ptr<File>> - openFileForRead(const Twine &Path) override { - return FS->openFileForRead(Path); + llvm::ErrorOr<std::unique_ptr<File>> openFileForReadImpl( + const Twine &LookupPath, const Twine &OriginalPath) override { + return FS->openFileForReadImpl(LookupPath, OriginalPath); } directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override { return FS->dir_begin(Dir, EC); @@ -486,8 +496,8 @@ bool useNormalizedPaths() const { return UseNormalizedPaths; } llvm::ErrorOr<Status> status(const Twine &Path) override; - llvm::ErrorOr<std::unique_ptr<File>> - openFileForRead(const Twine &Path) override; + llvm::ErrorOr<std::unique_ptr<File>> openFileForReadImpl( + const Twine &LookupPath, const Twine &OriginalPath) override; directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override; llvm::ErrorOr<std::string> getCurrentWorkingDirectory() const override { @@ -855,7 +865,8 @@ bool UseExternalNames, FileSystem &ExternalFS); ErrorOr<Status> status(const Twine &Path) override; - ErrorOr<std::unique_ptr<File>> openFileForRead(const Twine &Path) override; + llvm::ErrorOr<std::unique_ptr<File>> openFileForReadImpl( + const Twine &LookupPath, const Twine &OriginalPath) override; std::error_code getRealPath(const Twine &Path, SmallVectorImpl<char> &Output) const override; Index: clang/test/VFS/directory.c =================================================================== --- clang/test/VFS/directory.c +++ clang/test/VFS/directory.c @@ -1,48 +1,92 @@ // 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@INPUT_DIR@%{/t:regex_replacement}/Overlay@g" -e "s@OUT_DIR@%{/t:regex_replacement}/Underlying@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@INPUT_DIR@Overlay@g" -e "s@OUT_DIR@%{/t:regex_replacement}/Underlying@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@INPUT_DIR@%{/t:regex_replacement}/Overlay@g" -e "s@OUT_DIR@%{/t:regex_replacement}/Middle@g" %S/Inputs/vfsoverlay-directory.yaml > %t/vfs.yaml -// RUN: sed -e "s@INPUT_DIR@%{/t:regex_replacement}/Middle@g" -e "s@OUT_DIR@%{/t:regex_replacement}/Underlying@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: split-file %s %t + +// Underlying -> Overlay +// RUN: sed -e "s@EXTERNAL_DIR@%{/t:regex_replacement}/Overlay@g" -e "s@NAME_DIR@%{/t:regex_replacement}/Underlying@g" %t/vfs/base.yaml > %t/vfs/underlying-to-overlay.yaml +// RUN: sed -e "s@EXTERNAL_DIR@../Overlay@g" -e "s@NAME_DIR@%{/t:regex_replacement}/Underlying@g" %t/vfs/base-relative.yaml > %t/vfs/underlying-to-relative-overlay.yaml + +// Middle -> Overlay +// RUN: sed -e "s@EXTERNAL_DIR@%{/t:regex_replacement}/Overlay@g" -e "s@NAME_DIR@%{/t:regex_replacement}/Middle@g" %t/vfs/base.yaml > %t/vfs/middle-to-overlay.yaml + +// Underlying -> Middle +// RUN: sed -e "s@EXTERNAL_DIR@%{/t:regex_replacement}/Middle@g" -e "s@NAME_DIR@%{/t:regex_replacement}/Underlying@g" %t/vfs/base.yaml > %t/vfs/underlying-to-middle.yaml + +// 1) Underlying -> Overlay (C.h found in Overlay, B.h falls back to Underlying) +// RUN: %clang_cc1 -Werror -I %t/Underlying -ivfsoverlay %t/vfs/underlying-to-overlay.yaml -fsyntax-only -E -C %t/main.c 2>&1 | FileCheck --check-prefix=U_TO_O %s +// RUN: %clang_cc1 -Werror -I %t/Underlying -ivfsoverlay %t/vfs/underlying-to-overlay.yaml -fsyntax-only -DNESTED -E -C %t/main.c 2>&1 | FileCheck --check-prefix=U_TO_O %s +// RUN: %clang_cc1 -Werror -I %t/Underlying -ivfsoverlay %t/vfs/underlying-to-relative-overlay.yaml -fsyntax-only -E -C %t/main.c 2>&1 | FileCheck --check-prefix=U_TO_O %s + +// U_TO_O: # 1 "{{.*(/|\\\\)Underlying(/|\\\\)}}B.h" +// U_TO_O-NEXT: // B.h in Underlying +// U_TO_O: # 1 "{{.*(/|\\\\)Overlay(/|\\\\)}}C.h" +// U_TO_O-NEXT: // C.h in Overlay + +// 2) Underlying -> Middle -> Overlay (C.h found in Overlay, B.h falls back to Underlying) +// RUN: %clang_cc1 -Werror -I %t/Underlying -ivfsoverlay %t/vfs/middle-to-overlay.yaml -ivfsoverlay %t/vfs/underlying-to-middle.yaml -fsyntax-only -E -C %t/main.c 2>&1 | FileCheck --check-prefix=U_TO_O %s +// RUN: %clang_cc1 -Werror -I %t/Underlying -ivfsoverlay %t/vfs/middle-to-overlay.yaml -ivfsoverlay %t/vfs/underlying-to-middle.yaml -DNESTED -fsyntax-only -E -C %t/main.c 2>&1 | FileCheck --check-prefix=U_TO_O %s + +// Ends up same as U_TO_O above since Middle is mapped to Overlay + +// 3) Underlying -> Middle -> Overlay but with the Overlay/C.h removed (C.h falls back to Middle, B.h falls back to Underlying) +// RUN: rm %t/Overlay/C.h +// RUN: %clang_cc1 -Werror -I %t/Underlying -ivfsoverlay %t/vfs/middle-to-overlay.yaml -ivfsoverlay %t/vfs/underlying-to-middle.yaml -fsyntax-only -E -C %t/main.c 2>&1 | FileCheck --check-prefix=U_TO_M %s + +// U_TO_M: # 1 "{{.*(/|\\\\)Underlying(/|\\\\)}}B.h" +// U_TO_M-NEXT: // B.h in Underlying +// U_TO_M: # 1 "{{.*(/|\\\\)Middle(/|\\\\)}}C.h" +// U_TO_M-NEXT: // C.h in Middle + +// 4) Underlying -> Middle -> Overlay but with Middle/C.h also removed (C.h and B.h fall 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 +// RUN: %clang_cc1 -Werror -I %t/Underlying -ivfsoverlay %t/vfs/middle-to-overlay.yaml -ivfsoverlay %t/vfs/underlying-to-middle.yaml -fsyntax-only -E -C %t/main.c 2>&1 | FileCheck --check-prefix=U_ONLY %s -// FALLBACK2: {{^}}// B.h in Underlying -// FALLBACK2: {{^}}// C.h in Underlying +// U_ONLY: # 1 "{{.*(/|\\\\)Underlying(/|\\\\)}}B.h" +// U_ONLY-NEXT: // B.h in Underlying +// U_ONLY: # 1 "{{.*(/|\\\\)Underlying(/|\\\\)}}C.h" +// U_ONLY-NEXT: // C.h in Underlying +//--- main.c #include "B.h" #ifndef NESTED #include "C.h" #endif + +//--- Underlying/B.h +// B.h in Underlying +#ifdef NESTED +#include "C.h" +#endif + +//--- Underlying/C.h +// C.h in Underlying + +//--- Middle/C.h +// C.h in Middle + +//--- Overlay/C.h +// C.h in Overlay + +//--- vfs/base.yaml +{ + 'version': 0, + 'fallthrough': true, + 'roots': [ + { 'name': 'NAME_DIR', + 'type': 'directory-remap', + 'external-contents': 'EXTERNAL_DIR' + } + ] +} + +//--- vfs/base-relative.yaml +{ + 'version': 0, + 'fallthrough': true, + 'overlay-relative': true, + 'roots': [ + { 'name': 'NAME_DIR', + 'type': 'directory-remap', + 'external-contents': 'EXTERNAL_DIR' + } + ] +}
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits