From ae0dc8ab9fb65adbe9711a0d5bc6e2e8fa4e3ea4 Mon Sep 17 00:00:00 2001 From: Cristian Adam Date: Thu, 8 Oct 2026 17:36:42 +0200 Subject: [PATCH] get_filename_component: Cache the actual case of path components get_filename_component(ABSOLUTE|REALPATH) queried the disk for the actual case of every path component on every call. Remember the actual case of components found to exist until files may have been removed or renamed, as for source file lookups. --- Source/cmGetFilenameComponentCommand.cxx | 4 ++- Source/cmSystemTools.cxx | 37 ++++++++++++++++++------ Source/cmSystemTools.h | 11 ++++++- Source/cmake.cxx | 1 + Source/cmake.h | 8 +++++ 5 files changed, 50 insertions(+), 11 deletions(-) diff --git a/Source/cmGetFilenameComponentCommand.cxx b/Source/cmGetFilenameComponentCommand.cxx index dee5c3f009..96666140f1 100644 --- a/Source/cmGetFilenameComponentCommand.cxx +++ b/Source/cmGetFilenameComponentCommand.cxx @@ -8,6 +8,7 @@ #include "cmStringAlgorithms.h" #include "cmSystemTools.h" #include "cmValue.h" +#include "cmake.h" // cmGetFilenameComponentCommand bool cmGetFilenameComponentCommand(std::vector const& args, @@ -113,7 +114,8 @@ bool cmGetFilenameComponentCommand(std::vector const& args, // Resolve symlinks if possible result = cmSystemTools::GetRealPath(result); } - result = cmSystemTools::GetActualCaseForPath(result); + result = cmSystemTools::GetActualCaseForPath( + result, status.GetMakefile().GetCMakeInstance()->GetActualCaseCache()); } else { std::string err = "unknown component " + args[2]; status.SetError(err); diff --git a/Source/cmSystemTools.cxx b/Source/cmSystemTools.cxx index af18f6cf41..e1d9a22488 100644 --- a/Source/cmSystemTools.cxx +++ b/Source/cmSystemTools.cxx @@ -2047,7 +2047,8 @@ std::string cmSystemTools::RelativeIfUnder(std::string const& top, return out; } -std::string cmSystemTools::GetActualCaseForPath(std::string const& p) +std::string cmSystemTools::GetActualCaseForPath( + std::string const& p, std::unordered_map* cache) { #ifdef _WIN32 std::string casePath; @@ -2101,15 +2102,32 @@ std::string cmSystemTools::GetActualCaseForPath(std::string const& p) std::string test_str = casePath; test_str += path_components[idx]; - WIN32_FIND_DATAW findData; - HANDLE hFind = ::FindFirstFileW( - cmsys::Encoding::ToWide(test_str).c_str(), &findData); - if (INVALID_HANDLE_VALUE != hFind) { - auto case_file_name = cmsys::Encoding::ToNarrow(findData.cFileName); - path_components[idx] = std::move(case_file_name); - ::FindClose(hFind); + // Only components found to exist are cached, so that a path + // created later is still converted. + std::string const* cached = nullptr; + if (cache) { + auto it = cache->find(test_str); + if (it != cache->end()) { + cached = &it->second; + } + } + if (cached) { + path_components[idx] = *cached; } else { - converting = false; + WIN32_FIND_DATAW findData; + HANDLE hFind = ::FindFirstFileW( + cmsys::Encoding::ToWide(test_str).c_str(), &findData); + if (INVALID_HANDLE_VALUE != hFind) { + auto case_file_name = + cmsys::Encoding::ToNarrow(findData.cFileName); + ::FindClose(hFind); + if (cache) { + cache->emplace(std::move(test_str), case_file_name); + } + path_components[idx] = std::move(case_file_name); + } else { + converting = false; + } } } } @@ -2118,6 +2136,7 @@ std::string cmSystemTools::GetActualCaseForPath(std::string const& p) } return casePath; #else + static_cast(cache); return p; #endif } diff --git a/Source/cmSystemTools.h b/Source/cmSystemTools.h index 689becea8e..5f7eba5d6a 100644 --- a/Source/cmSystemTools.h +++ b/Source/cmSystemTools.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -433,7 +434,15 @@ public: static std::string RelativeIfUnder(std::string const& top, std::string const& in); - static std::string GetActualCaseForPath(std::string const& p); + /** + * Get the path with the actual case of all existing components on disk. + * If a cache is given, it is used to remember the actual case of + * components found to exist, to avoid querying the disk again for + * paths sharing the same directories. + */ + static std::string GetActualCaseForPath( + std::string const& p, + std::unordered_map* cache = nullptr); static cm::optional GetEnvVar(std::string const& var); static std::vector GetEnvPathNormalized(std::string const& var); diff --git a/Source/cmake.cxx b/Source/cmake.cxx index 30e079b65a..ae8fc03ab9 100644 --- a/Source/cmake.cxx +++ b/Source/cmake.cxx @@ -2577,6 +2577,7 @@ int cmake::Configure() void cmake::InvalidateFileSystemCaches() { + this->ActualCaseCache.clear(); if (this->GlobalGenerator) { this->GlobalGenerator->ClearSourceFileExistsCache(); } diff --git a/Source/cmake.h b/Source/cmake.h index 225bc97f9a..4fa7b686fc 100644 --- a/Source/cmake.h +++ b/Source/cmake.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -476,6 +477,12 @@ public: return *this->GeneratorExpressionParseCache; } + //! Cache for cmSystemTools::GetActualCaseForPath. + std::unordered_map* GetActualCaseCache() + { + return &this->ActualCaseCache; + } + //! Forget what is known about the file system, e.g. after files may //! have been removed or renamed. void InvalidateFileSystemCaches(); @@ -830,6 +837,7 @@ private: std::unique_ptr FileTimeCache; std::unique_ptr GeneratorExpressionParseCache; + std::unordered_map ActualCaseCache; std::string GraphVizFile; InstalledFilesMap InstalledFiles; #ifndef CMAKE_BOOTSTRAP