From 8dfc725cdb6a318fc21f9c612855cf92075d6cb8 Mon Sep 17 00:00:00 2001 From: Brad King Date: Fri, 7 Mar 2025 16:16:54 -0500 Subject: [PATCH] PathResolver: Add mode to collapse paths naively and look up on-disk case In CMake 3.31 and below, `CollapseFullPath` did this on Windows. KWSys has since stopped looking up the on-disk case in `CollapseFullPath` to avoid disk access when most callers only need an in-memory operation. We currently call `GetActualCaseForPath` explicitly when needed. Add a mode to `cm::PathResolver` to combine these operations and cache disk access behind the `System::ReadName` callback. We will use this to restore the way CMake 3.31 and below normalized input paths on Windows. Issue: #26750 Issue: #20214 --- Source/cmPathResolver.cxx | 11 +++++ Source/cmPathResolver.h | 6 +++ Tests/CMakeLib/testPathResolver.cxx | 75 +++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+) diff --git a/Source/cmPathResolver.cxx b/Source/cmPathResolver.cxx index 71ce7b4451..32f3ef7406 100644 --- a/Source/cmPathResolver.cxx +++ b/Source/cmPathResolver.cxx @@ -492,6 +492,14 @@ struct NaivePath static constexpr Options::Symlinks Symlinks = Options::Symlinks::None; static constexpr Options::Existence Existence = Options::Existence::Agnostic; }; +struct CasePath +{ +#if defined(_WIN32) || defined(__APPLE__) + static constexpr Options::ActualCase ActualCase = Options::ActualCase::Yes; +#endif + static constexpr Options::Symlinks Symlinks = Options::Symlinks::None; + static constexpr Options::Existence Existence = Options::Existence::Agnostic; +}; struct RealPath { #if defined(_WIN32) || defined(__APPLE__) @@ -512,6 +520,8 @@ struct LogicalPath #if defined(__SUNPRO_CC) constexpr Options::Symlinks NaivePath::Symlinks; constexpr Options::Existence NaivePath::Existence; +constexpr Options::Symlinks CasePath::Symlinks; +constexpr Options::Existence CasePath::Existence; constexpr Options::Symlinks RealPath::Symlinks; constexpr Options::Existence RealPath::Existence; constexpr Options::Symlinks LogicalPath::Symlinks; @@ -535,6 +545,7 @@ System::~System() = default; template class Resolver; template class Resolver; +template class Resolver; template class Resolver; } diff --git a/Source/cmPathResolver.h b/Source/cmPathResolver.h index 25c3961b1d..e2399baba5 100644 --- a/Source/cmPathResolver.h +++ b/Source/cmPathResolver.h @@ -85,6 +85,11 @@ struct LogicalPath; and reads their on-disk case (on Windows and macOS). */ struct RealPath; +/** Normalizes paths while assuming components followed by '..' + components are not symlinks. Does not require paths to exist, but + reads on-disk case of paths that do exist (on Windows and macOS). */ +struct CasePath; + /** Normalizes paths in memory without disk access. Assumes components followed by '..' components are not symlinks. */ struct NaivePath; @@ -94,6 +99,7 @@ struct NaivePath; extern template class Resolver; extern template class Resolver; +extern template class Resolver; extern template class Resolver; } diff --git a/Tests/CMakeLib/testPathResolver.cxx b/Tests/CMakeLib/testPathResolver.cxx index d79afd9e90..d5cee74c45 100644 --- a/Tests/CMakeLib/testPathResolver.cxx +++ b/Tests/CMakeLib/testPathResolver.cxx @@ -24,6 +24,7 @@ // IWYU pragma: no_forward_declare cm::PathResolver::Policies::LogicalPath // IWYU pragma: no_forward_declare cm::PathResolver::Policies::NaivePath +// IWYU pragma: no_forward_declare cm::PathResolver::Policies::CasePath // IWYU pragma: no_forward_declare cm::PathResolver::Policies::RealPath namespace { @@ -212,6 +213,22 @@ bool posixSymlink() { "/1/2/3", { {}, {} } }, }); + { + Resolver const r(os); + EXPECT_RESOLVE("/link-a", "/link-a"); + EXPECT_RESOLVE("/link-a-excess", "/link-a-excess"); + EXPECT_RESOLVE("/link-a-excess/b", "/link-a-excess/b"); + EXPECT_RESOLVE("/link-broken", "/link-broken"); + EXPECT_RESOLVE("/link-a/../missing", "/missing"); + EXPECT_RESOLVE("/a/b/link-c", "/a/b/link-c"); + EXPECT_RESOLVE("/a/link-b/c", "/a/link-b/c"); + EXPECT_RESOLVE("/a/link-b/link-c/..", "/a/link-b"); + EXPECT_RESOLVE("/a/b/c/link-..|..", "/a/b/c/link-..|.."); + EXPECT_RESOLVE("/a/b/c/link-..|../link-b", "/a/b/c/link-..|../link-b"); + EXPECT_RESOLVE("/a/link-|1|2/3", "/a/link-|1|2/3"); + EXPECT_RESOLVE("/a/link-|1|2/../2/3", "/a/2/3"); + } + { Resolver const r(os); EXPECT_RESOLVE("/link-a", "/link-a"); @@ -263,6 +280,16 @@ bool macosActualCase() { "/upper/link-c-upper", { "LINK-C-UPPER", "/upper" } }, }); + { + Resolver const r(os); + EXPECT_RESOLVE("/mIxEd/MiSsInG", "/MiXeD/MiSsInG"); + EXPECT_RESOLVE("/mIxEd/link-MiXeD", "/MiXeD/LiNk-MiXeD"); + EXPECT_RESOLVE("/mIxEd/link-c-MiXeD", "/MiXeD/LiNk-C-MiXeD"); + EXPECT_RESOLVE("/upper/mIsSiNg", "/UPPER/mIsSiNg"); + EXPECT_RESOLVE("/upper/link-upper", "/UPPER/LINK-UPPER"); + EXPECT_RESOLVE("/upper/link-c-upper", "/UPPER/LINK-C-UPPER"); + } + { Resolver const r(os); EXPECT_RESOLVE("/mIxEd/MiSsInG", "/MiXeD/MiSsInG"); @@ -302,6 +329,16 @@ bool windowsRoot() EXPECT_RESOLVE("C:/..", "C:/"); EXPECT_RESOLVE("c:/../", "c:/"); } + { + Resolver const r(os); + EXPECT_RESOLVE("c:/", "C:/"); + EXPECT_RESOLVE("C:/", "C:/"); + EXPECT_RESOLVE("c://", "C:/"); + EXPECT_RESOLVE("C:/.", "C:/"); + EXPECT_RESOLVE("c:/./", "C:/"); + EXPECT_RESOLVE("C:/..", "C:/"); + EXPECT_RESOLVE("c:/../", "C:/"); + } os.SetPaths({ { "c:/", { {}, {} } }, { "//host/", { {}, {} } }, @@ -360,6 +397,16 @@ bool windowsActualCase() { "c:/upper/link-c-upper", { "LINK-C-UPPER", "c:/upper" } }, }); + { + Resolver const r(os); + EXPECT_RESOLVE("c:/mIxEd/MiSsInG", "C:/MiXeD/MiSsInG"); + EXPECT_RESOLVE("c:/mIxEd/link-MiXeD", "C:/MiXeD/LiNk-MiXeD"); + EXPECT_RESOLVE("c:/mIxEd/link-c-MiXeD", "C:/MiXeD/LiNk-C-MiXeD"); + EXPECT_RESOLVE("c:/upper/mIsSiNg", "C:/UPPER/mIsSiNg"); + EXPECT_RESOLVE("c:/upper/link-upper", "C:/UPPER/LINK-UPPER"); + EXPECT_RESOLVE("c:/upper/link-c-upper", "C:/UPPER/LINK-C-UPPER"); + } + { Resolver const r(os); EXPECT_RESOLVE("c:/mIxEd/MiSsInG", "C:/MiXeD/MiSsInG"); @@ -441,6 +488,27 @@ bool windowsWorkingDirectoryOnDrive() EXPECT_RESOLVE("E:.", "E:/"); EXPECT_RESOLVE("E:..", "E:/"); } + { + Resolver const r(os); + EXPECT_RESOLVE("c:", "C:/cwd"); + EXPECT_RESOLVE("c:.", "C:/cwd"); + EXPECT_RESOLVE("c:..", "C:/"); + EXPECT_RESOLVE("C:", "C:/cwd"); + EXPECT_RESOLVE("C:.", "C:/cwd"); + EXPECT_RESOLVE("C:..", "C:/"); + EXPECT_RESOLVE("d:", "D:/cwd-d"); + EXPECT_RESOLVE("d:.", "D:/cwd-d"); + EXPECT_RESOLVE("d:..", "D:/"); + EXPECT_RESOLVE("D:", "D:/cwd-d"); + EXPECT_RESOLVE("D:.", "D:/cwd-d"); + EXPECT_RESOLVE("D:..", "D:/"); + EXPECT_RESOLVE("e:", "E:/"); + EXPECT_RESOLVE("e:.", "E:/"); + EXPECT_RESOLVE("e:..", "E:/"); + EXPECT_RESOLVE("E:", "E:/"); + EXPECT_RESOLVE("E:.", "E:/"); + EXPECT_RESOLVE("E:..", "E:/"); + } os.SetPaths({ { "c:/", { {}, {} } }, { "c:/cwd", { {}, {} } }, @@ -496,6 +564,13 @@ bool windowsNetworkShare() EXPECT_RESOLVE("link-to-host-share/..", "//host/"); EXPECT_RESOLVE("link-to-host-share/../missing", "//host/missing"); } + + { + Resolver const r(os); + EXPECT_RESOLVE("link-to-host-share", "C:/cwd/link-to-host-share"); + EXPECT_RESOLVE("link-to-host-share/..", "C:/cwd"); + EXPECT_RESOLVE("link-to-host-share/../missing", "C:/cwd/missing"); + } return true; } #endif