Index: include/clang/Basic/VirtualFileSystem.h =================================================================== --- include/clang/Basic/VirtualFileSystem.h +++ include/clang/Basic/VirtualFileSystem.h @@ -272,6 +272,37 @@ iterator overlays_end() { return FSList.rend(); } }; +class CaseInsensitiveFileSystem : public FileSystem { + IntrusiveRefCntPtr Base; + + /// Try to find Path by means of case-insensitive lookup. Stores the result in + /// FoundPath on success, or returns an error code otherwise. + std::error_code findCaseInsensitivePath(StringRef Path, + SmallVectorImpl &FoundPath); + + /// Attempt to exclude the possibility that File exists in Dir based on + /// previous information. + bool exclude(StringRef Dir, StringRef File); + + /// Map from directory to map from lowercase to real-case filename. + llvm::StringMap> Maps; + +public: + CaseInsensitiveFileSystem(IntrusiveRefCntPtr Base) : Base(Base) {} + + llvm::ErrorOr status(const Twine &Path) override; + llvm::ErrorOr> + openFileForRead(const Twine &Path) override; + directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override; + llvm::ErrorOr getCurrentWorkingDirectory() const override { + return Base->getCurrentWorkingDirectory(); + } + std::error_code setCurrentWorkingDirectory(const Twine &Path) override { + Maps.clear(); + return Base->setCurrentWorkingDirectory(Path); + } +}; + namespace detail { class InMemoryDirectory; } // end namespace detail Index: include/clang/Driver/Options.td =================================================================== --- include/clang/Driver/Options.td +++ include/clang/Driver/Options.td @@ -525,6 +525,8 @@ def fdiagnostics_color_EQ : Joined<["-"], "fdiagnostics-color=">, Group; def fansi_escape_codes : Flag<["-"], "fansi-escape-codes">, Group, Flags<[CoreOption, CC1Option]>, HelpText<"Use ANSI escape codes for diagnostics">; +def fcase_insensitive_paths : Flag<["-"], "fcase-insensitive-paths">, Group, + Flags<[CC1Option, CoreOption]>, HelpText<"Treat file paths as case-insensitive">; def fcomment_block_commands : CommaJoined<["-"], "fcomment-block-commands=">, Group, Flags<[CC1Option]>, HelpText<"Treat each comma separated argument in as a documentation comment block command">, MetaVarName<"">; Index: include/clang/Lex/HeaderSearchOptions.h =================================================================== --- include/clang/Lex/HeaderSearchOptions.h +++ include/clang/Lex/HeaderSearchOptions.h @@ -161,6 +161,9 @@ /// Whether header search information should be output as for -v. unsigned Verbose : 1; + /// Whether header search should be case-insensitive. + unsigned CaseInsensitive : 1; + /// \brief If true, skip verifying input files used by modules if the /// module was already verified during this build session (see /// \c BuildSessionTimestamp). Index: lib/Basic/VirtualFileSystem.cpp =================================================================== --- lib/Basic/VirtualFileSystem.cpp +++ lib/Basic/VirtualFileSystem.cpp @@ -392,6 +392,110 @@ std::make_shared(Dir, *this, EC)); } +//===-----------------------------------------------------------------------===/ +// CaseInsensitiveFileSystem implementation +//===-----------------------------------------------------------------------===/ + +bool CaseInsensitiveFileSystem::exclude(StringRef Dir, StringRef File) { + if (!Maps.count(Dir)) { + // We have no map for this Dir, but see if we can exclude the file by + // excluding Dir from its parent. + StringRef Parent = llvm::sys::path::parent_path(Dir); + if (!Parent.empty() && + exclude(Parent, llvm::sys::path::filename(Dir))) { + return true; + } + + return false; + } + + return !Maps[Dir].count(File.lower()); +} + +std::error_code CaseInsensitiveFileSystem::findCaseInsensitivePath( + StringRef Path, SmallVectorImpl &FoundPath) { + StringRef FileName = llvm::sys::path::filename(Path); + StringRef Dir = llvm::sys::path::parent_path(Path); + + if (Dir.empty()) + Dir = "."; + + if (exclude(Dir, FileName)) + return llvm::errc::no_such_file_or_directory; + + if (Maps.count(Dir)) { + // If we have a map for this Dir and File wasn't excluded above, it must + // exist. + llvm::sys::path::append(FoundPath, Dir, Maps[Dir][FileName.lower()]); + return std::error_code(); + } + + std::error_code EC; + directory_iterator I = Base->dir_begin(Dir, EC); + if (EC == errc::no_such_file_or_directory) { + // If the dir doesn't exist, try to find it and try again. + SmallVector NewDir; + if (llvm::sys::path::parent_path(Dir).empty() || + (EC = findCaseInsensitivePath(Dir, NewDir))) { + // Insert a dummy map value to mark the dir as non-existent. + Maps.lookup(Dir); + return EC; + } + llvm::sys::path::append(NewDir, FileName); + return findCaseInsensitivePath(StringRef(NewDir.data(), NewDir.size()), + FoundPath); + } + + // These special entries always exist, but won't show up in the listing below. + Maps[Dir]["."] = "."; + Maps[Dir][".."] = ".."; + + directory_iterator E; + for (; !EC && I != E; I.increment(EC)) { + StringRef DirEntry = llvm::sys::path::filename(I->getName()); + Maps[Dir][DirEntry.lower()] = DirEntry; + } + if (EC) { + // If there were problems, scrap the whole map as it may not be complete. + Maps.erase(Dir); + return EC; + } + + auto MI = Maps[Dir].find(FileName.lower()); + if (MI != Maps[Dir].end()) { + llvm::sys::path::append(FoundPath, Dir, MI->second); + return std::error_code(); + } + + return llvm::errc::no_such_file_or_directory; +} + +llvm::ErrorOr CaseInsensitiveFileSystem::status(const Twine &Path) { + SmallVector NewPath; + if (std::error_code EC = findCaseInsensitivePath(Path.str(), NewPath)) + return EC; + + return Base->status(NewPath); +} + +llvm::ErrorOr> +CaseInsensitiveFileSystem::openFileForRead(const Twine &Path) { + SmallVector NewPath; + if (std::error_code EC = findCaseInsensitivePath(Path.str(), NewPath)) + return EC; + + return Base->openFileForRead(NewPath); +} + +directory_iterator CaseInsensitiveFileSystem::dir_begin(const Twine &Path, + std::error_code &EC) { + SmallVector NewPath; + if ((EC = findCaseInsensitivePath(Path.str(), NewPath))) + return directory_iterator(); + + return Base->dir_begin(NewPath, EC); +} + namespace clang { namespace vfs { namespace detail { Index: lib/Driver/Tools.cpp =================================================================== --- lib/Driver/Tools.cpp +++ lib/Driver/Tools.cpp @@ -570,6 +570,9 @@ // Add CUDA include arguments, if needed. if (types::isCuda(Inputs[0].getType())) getToolChain().AddCudaIncludeArgs(Args, CmdArgs); + + if (Args.hasArg(options::OPT_fcase_insensitive_paths)) + CmdArgs.push_back("-fcase-insensitive-paths"); } // FIXME: Move to target hook. Index: lib/Frontend/CompilerInvocation.cpp =================================================================== --- lib/Frontend/CompilerInvocation.cpp +++ lib/Frontend/CompilerInvocation.cpp @@ -1447,6 +1447,8 @@ for (const Arg *A : Args.filtered(OPT_ivfsoverlay)) Opts.AddVFSOverlayFile(A->getValue()); + + Opts.CaseInsensitive = Args.hasArg(OPT_fcase_insensitive_paths); } void CompilerInvocation::setLangDefaults(LangOptions &Opts, InputKind IK, @@ -2538,12 +2540,8 @@ GraveYard[Idx] = Ptr; } -IntrusiveRefCntPtr -createVFSFromCompilerInvocation(const CompilerInvocation &CI, - DiagnosticsEngine &Diags) { - if (CI.getHeaderSearchOpts().VFSOverlayFiles.empty()) - return vfs::getRealFileSystem(); - +static IntrusiveRefCntPtr +getOverlayFS(const CompilerInvocation &CI, DiagnosticsEngine &Diags) { IntrusiveRefCntPtr Overlay(new vfs::OverlayFileSystem(vfs::getRealFileSystem())); // earlier vfs files are on the bottom @@ -2565,4 +2563,20 @@ } return Overlay; } + +IntrusiveRefCntPtr +createVFSFromCompilerInvocation(const CompilerInvocation &CI, + DiagnosticsEngine &Diags) { + IntrusiveRefCntPtr FS; + + if (CI.getHeaderSearchOpts().VFSOverlayFiles.empty()) + FS = vfs::getRealFileSystem(); + else + FS = getOverlayFS(CI, Diags); + + if (CI.getHeaderSearchOpts().CaseInsensitive) + FS = new vfs::CaseInsensitiveFileSystem(FS); + + return FS; +} } // end namespace clang Index: test/Driver/cl-options.c =================================================================== --- test/Driver/cl-options.c +++ test/Driver/cl-options.c @@ -466,6 +466,7 @@ // RUN: -mllvm -disable-llvm-optzns \ // RUN: -Wunused-variable \ // RUN: -fmacro-backtrace-limit=0 \ +// RUN: -fcase-insensitive-paths \ // RUN: -Werror /Zs -- %s 2>&1 Index: test/Frontend/case-insensitive-paths.c =================================================================== --- /dev/null +++ test/Frontend/case-insensitive-paths.c @@ -0,0 +1,7 @@ +// RUN: %clang_cc1 -fsyntax-only -fcase-insensitive-paths -verify %s +// RUN: %clang_cc1 -fsyntax-only --show-includes -fcase-insensitive-paths %s | FileCheck %s + +#include "InpUts/CasE-InsensitivE-Paths.h" // expected-no-diagnostics + +// Make sure the real filename is used when printing header dependencies. +// CHECK: including file: {{.*}}case-insensitive-paths.h Index: unittests/Basic/VirtualFileSystemTest.cpp =================================================================== --- unittests/Basic/VirtualFileSystemTest.cpp +++ unittests/Basic/VirtualFileSystemTest.cpp @@ -107,8 +107,15 @@ vfs::directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override { - return vfs::directory_iterator( + auto I = vfs::directory_iterator( std::make_shared(FilesAndDirs, Dir)); + + // Even if there is no entry for /foo, /foo/bar may exist, so only set the + // error code if /foo returns an empty iterator. + if (I == vfs::directory_iterator()) + EC = status(Dir).getError(); + + return I; } void addEntry(StringRef Path, const vfs::Status &Status) { @@ -1164,3 +1171,52 @@ } EXPECT_EQ(I, E); } + +class CaseInsensitiveFileSystemTest : public ::testing::Test { +private: + IntrusiveRefCntPtr Base; + +protected: + IntrusiveRefCntPtr FS; + + CaseInsensitiveFileSystemTest() + : Base(new DummyFileSystem()), + FS(new clang::vfs::CaseInsensitiveFileSystem(Base)) { + Base->addRegularFile("/foo"); + Base->addDirectory("/bar"); + Base->addRegularFile("/bar/baz"); + } +}; + +TEST_F(CaseInsensitiveFileSystemTest, Basic) { + // Not just accepting anything. + auto Status = FS->status("/F00"); + ASSERT_EQ(llvm::errc::no_such_file_or_directory, Status.getError()); + + // Case-insensitive file is found. + Status = FS->status("/FoO"); + ASSERT_FALSE(Status.getError()); + + // Case-insensitive dir works too. + Status = FS->status("/bAr/baZ"); + ASSERT_FALSE(Status.getError()); + + // Test openFileForRead. + auto File = FS->openFileForRead("/F00"); + ASSERT_EQ(llvm::errc::no_such_file_or_directory, File.getError()); + File = FS->openFileForRead("/Foo"); + ASSERT_FALSE(File.getError()); + File = FS->openFileForRead("/Bar/Baz"); + ASSERT_FALSE(File.getError()); + + // Test directory listing. + std::error_code EC; + auto Dir = FS->dir_begin("/b4r", EC); + ASSERT_EQ(llvm::errc::no_such_file_or_directory, EC); + Dir = FS->dir_begin("/bAr", EC); + ASSERT_FALSE(EC); + ASSERT_EQ("/bar/baz", Dir->getName()); + Dir.increment(EC); + ASSERT_FALSE(EC); + ASSERT_EQ(vfs::directory_iterator(), Dir); +}