Index: include/clang/Basic/VirtualFileSystem.h =================================================================== --- include/clang/Basic/VirtualFileSystem.h +++ include/clang/Basic/VirtualFileSystem.h @@ -448,6 +448,12 @@ void write(llvm::raw_ostream &OS); }; +/// Wrap an existing filesystem to create one that tolerates +/// case-incorrect file names and backslash path separators, even if +/// the underlying filesystem is not Windows-like. +IntrusiveRefCntPtr +createWindowsFileSystemWrapper(IntrusiveRefCntPtr BaseFS); + } // namespace vfs } // namespace clang Index: include/clang/Driver/Options.td =================================================================== --- include/clang/Driver/Options.td +++ include/clang/Driver/Options.td @@ -1690,6 +1690,9 @@ def fwhole_program_vtables : Flag<["-"], "fwhole-program-vtables">, Group, Flags<[CoreOption, CC1Option]>, HelpText<"Enables whole-program vtable optimization. Requires -flto">; +def fwindows_filesystem : Flag<["-"], "fwindows-filesystem">, Group, + Flags<[CoreOption, CC1Option]>, + HelpText<"Permits case-insensitive filenames and \\ path separators">; def fno_whole_program_vtables : Flag<["-"], "fno-whole-program-vtables">, Group, Flags<[CoreOption]>; def fforce_emit_vtables : Flag<["-"], "fforce-emit-vtables">, Group, Index: include/clang/Lex/HeaderSearchOptions.h =================================================================== --- include/clang/Lex/HeaderSearchOptions.h +++ include/clang/Lex/HeaderSearchOptions.h @@ -203,6 +203,9 @@ unsigned ModulesHashContent : 1; + /// Whether we are emulating Windows-style pathnames (-fwindows-filesystem) + unsigned WindowsPathnameEmulation : 1; + HeaderSearchOptions(StringRef _Sysroot = "/") : Sysroot(_Sysroot), ModuleFormat("raw"), DisableModuleHash(false), ImplicitModuleMaps(false), ModuleMapFileHomeIsCwd(false), Index: lib/Basic/VirtualFileSystem.cpp =================================================================== --- lib/Basic/VirtualFileSystem.cpp +++ lib/Basic/VirtualFileSystem.cpp @@ -2024,3 +2024,170 @@ return *this; } + +//===-----------------------------------------------------------------------===/ +// WindowsFileSystemWrapper implementation +//===-----------------------------------------------------------------------===/ + +class WindowsFileSystemWrapper : public vfs::FileSystem { + using String = SmallString<256>; + + /// Path name style, used to identify path separators. + sys::path::Style PathStyle = sys::path::Style::windows; + + /// The file system we are wrapping. + IntrusiveRefCntPtr BaseFS; + + /// Data about the contents of a single directory. + struct DirectoryCaseMap { + /// Map whose keys are the lowercase versions of filenames in that + /// directory, and values are the version of the same name that + /// actually exists. + std::map Filenames; + + /// Flag indicating whether we've done a complete trawl of the + /// directory yet, or whether we've only filled in a subset of + /// filenames. + bool Complete { false }; + + /// If we couldn't even scan this directory due to a filesystem + /// error, retain that error so we can return it again on the next + /// attempt. + std::error_code EC; + }; + + static String Lowercase(StringRef &Str); + + /// Directories we've explored. Two sets of them, one relative to + /// the cwd and one to the filesystem root. + using CaseMapCollection = std::map; + CaseMapCollection AbsCaseMaps, RelCaseMaps; + + /// Translates a path into one acceptable to the base file system. + ErrorOr TranslatePath(const Twine &Path); + + ErrorOr status(const Twine &Path) override { + auto TranslatedPath = TranslatePath(Path); + if (!TranslatedPath) + return TranslatedPath.getError(); + return BaseFS->status(TranslatedPath.get()); + } + + ErrorOr> openFileForRead(const Twine &Path) override { + auto TranslatedPath = TranslatePath(Path); + if (!TranslatedPath) + return TranslatedPath.getError(); + return BaseFS->openFileForRead(TranslatedPath.get()); + } + + std::error_code setCurrentWorkingDirectory(const Twine &Path) override { + auto TranslatedPath = TranslatePath(Path); + if (!TranslatedPath) + return TranslatedPath.getError(); + auto ToReturn = BaseFS->setCurrentWorkingDirectory(TranslatedPath.get()); + if (!ToReturn) + RelCaseMaps.clear(); // cwd changed, invalidating the cwd-relative cache + return ToReturn; + } + + ErrorOr getCurrentWorkingDirectory() const override { + return BaseFS->getCurrentWorkingDirectory(); + } + + directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override { + auto TranslatedPath = TranslatePath(Dir); + if (!TranslatedPath) { + EC = TranslatedPath.getError(); + return directory_iterator(); + } + return BaseFS->dir_begin(TranslatedPath.get(), EC); + } + +public: + WindowsFileSystemWrapper(IntrusiveRefCntPtr Base) + : BaseFS(std::move(Base)) {} +}; + +auto WindowsFileSystemWrapper::TranslatePath(const Twine &InPathTw) + -> ErrorOr +{ + String InPath; + InPathTw.toVector(InPath); + + if (sys::path::root_name(InPath, PathStyle) != "") + return llvm::errc::no_such_file_or_directory; + String OutPath = sys::path::root_path(InPath, PathStyle); + + CaseMapCollection *CMCP; + if (OutPath != "") { + InPath = relative_path(InPath, PathStyle); + CMCP = &AbsCaseMaps; + } else { + OutPath = "."; + CMCP = &RelCaseMaps; + } + CaseMapCollection &CMC = *CMCP; + + for (sys::path::const_iterator InIter = sys::path::begin(InPath, PathStyle), + InEnd = sys::path::end(InPath); InIter != InEnd; ++InIter) { + StringRef Component = *InIter; + String TranslatedComponent; + + auto &ThisCaseMap = CMC[OutPath]; + if (ThisCaseMap.EC) + return ThisCaseMap.EC; + + String Key = StringRef(StringRef(Component).lower()); + auto Found = ThisCaseMap.Filenames.find(Key); + + if (Found != ThisCaseMap.Filenames.end()) { + TranslatedComponent = Found->second; + } else { + String TempPath = OutPath; + sys::path::append(TempPath, Component); + auto Status = BaseFS->status(TempPath); + if (Status && Status.get().getType() != file_type::file_not_found) { + // This component in its original case refers to a directory + // entry that exists, so we don't need to look further. + ThisCaseMap.Filenames[Key] = TranslatedComponent = Component; + } else { + if (!ThisCaseMap.Complete) { + // We have to scan the whole containing directory to see if + // anything is a case-transformed version of this name. + + ThisCaseMap.Complete = true; // even if we fail, we'll never retry + + auto dir_iter = BaseFS->dir_begin(OutPath, ThisCaseMap.EC); + if (ThisCaseMap.EC) + return ThisCaseMap.EC; + + std::error_code IncrementEC; + for (; dir_iter != directory_iterator(); + dir_iter.increment(IncrementEC)) { + String Name = sys::path::filename(dir_iter->getName()); + String Key = StringRef(StringRef(Name).lower()); + ThisCaseMap.Filenames[Key] = Name; + } + + // Retry the lookup, now we've fully populated the map. + Found = ThisCaseMap.Filenames.find(Key); + } + + if (Found == ThisCaseMap.Filenames.end()) { + return llvm::errc::no_such_file_or_directory; + } + + TranslatedComponent = Found->second; + } + } + + sys::path::append(OutPath, TranslatedComponent); + } + return OutPath; +} + +IntrusiveRefCntPtr +vfs::createWindowsFileSystemWrapper(IntrusiveRefCntPtr BaseFS) +{ + return new WindowsFileSystemWrapper(std::move(BaseFS)); +} Index: lib/Frontend/CompilerInvocation.cpp =================================================================== --- lib/Frontend/CompilerInvocation.cpp +++ lib/Frontend/CompilerInvocation.cpp @@ -1846,6 +1846,9 @@ for (const auto *A : Args.filtered(OPT_ivfsoverlay)) Opts.AddVFSOverlayFile(A->getValue()); + + if (Args.hasArg(OPT_fwindows_filesystem)) + Opts.WindowsPathnameEmulation = true; } void CompilerInvocation::setLangDefaults(LangOptions &Opts, InputKind IK, @@ -3212,28 +3215,36 @@ createVFSFromCompilerInvocation(const CompilerInvocation &CI, DiagnosticsEngine &Diags, IntrusiveRefCntPtr BaseFS) { - if (CI.getHeaderSearchOpts().VFSOverlayFiles.empty()) - return BaseFS; - - IntrusiveRefCntPtr Overlay( - new vfs::OverlayFileSystem(BaseFS)); - // earlier vfs files are on the bottom - for (const auto &File : CI.getHeaderSearchOpts().VFSOverlayFiles) { - llvm::ErrorOr> Buffer = + IntrusiveRefCntPtr FS = BaseFS; + + if (!CI.getHeaderSearchOpts().VFSOverlayFiles.empty()) { + IntrusiveRefCntPtr Overlay( + new vfs::OverlayFileSystem(FS)); + // earlier vfs files are on the bottom + for (const auto &File : CI.getHeaderSearchOpts().VFSOverlayFiles) { + llvm::ErrorOr> Buffer = BaseFS->getBufferForFile(File); - if (!Buffer) { - Diags.Report(diag::err_missing_vfs_overlay_file) << File; - continue; - } + if (!Buffer) { + Diags.Report(diag::err_missing_vfs_overlay_file) << File; + continue; + } - IntrusiveRefCntPtr FS = vfs::getVFSFromYAML( + IntrusiveRefCntPtr FS = vfs::getVFSFromYAML( std::move(Buffer.get()), /*DiagHandler*/ nullptr, File); - if (FS) - Overlay->pushOverlay(FS); - else - Diags.Report(diag::err_invalid_vfs_overlay) << File; + if (FS) + Overlay->pushOverlay(FS); + else + Diags.Report(diag::err_invalid_vfs_overlay) << File; + } + + FS = std::move(Overlay); } - return Overlay; + + if (CI.getHeaderSearchOpts().WindowsPathnameEmulation) { + FS = createWindowsFileSystemWrapper(std::move(FS)); + } + + return FS; } } // namespace clang