Index: include/clang/Basic/VirtualFileSystem.h =================================================================== --- include/clang/Basic/VirtualFileSystem.h +++ include/clang/Basic/VirtualFileSystem.h @@ -272,6 +272,28 @@ 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); +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 { + 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,81 @@ std::make_shared(Dir, *this, EC)); } +//===-----------------------------------------------------------------------===/ +// CaseInsensitiveFileSystem implementation +//===-----------------------------------------------------------------------===/ + +std::error_code CaseInsensitiveFileSystem::findCaseInsensitivePath( + StringRef Path, SmallVectorImpl &FoundPath) { + StringRef FileName = llvm::sys::path::filename(Path); + StringRef Dir = llvm::sys::path::parent_path(Path); + + // Open the directory for iterating. In the common case, the directory exists. + 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. + if (llvm::sys::path::parent_path(Dir).empty()) + return EC; + SmallVector NewDir; + if ((EC = findCaseInsensitivePath(Dir, NewDir))) + return EC; + llvm::sys::path::append(NewDir, FileName); + return findCaseInsensitivePath(StringRef(NewDir.data(), NewDir.size()), + FoundPath); + } + + directory_iterator E; + for (; !EC && I != E; I.increment(EC)) { + StringRef DirEntry = llvm::sys::path::filename(I->getName()); + if (DirEntry.equals_lower(FileName)) { + llvm::sys::path::append(FoundPath, Dir, DirEntry); + return std::error_code(); + } + } + + return EC ? EC : llvm::errc::no_such_file_or_directory; +} + +llvm::ErrorOr CaseInsensitiveFileSystem::status(const Twine &Path) { + auto S = Base->status(Path); + if (S.getError() != llvm::errc::no_such_file_or_directory) + return S; + + SmallVector NewPath; + if (std::error_code EC = findCaseInsensitivePath(Path.str(), NewPath)) + return EC; + + return Base->status(NewPath); +} + +llvm::ErrorOr> +CaseInsensitiveFileSystem::openFileForRead(const Twine &Path) { + auto F = Base->openFileForRead(Path); + if (F.getError() != llvm::errc::no_such_file_or_directory) + return F; + + 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) { + auto I = Base->dir_begin(Path, EC); + if (EC != llvm::errc::no_such_file_or_directory) + return I; + + 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); +}