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
This commit is contained in:
Brad King 2025-03-07 16:16:54 -05:00
parent 75913fe430
commit 8dfc725cdb
3 changed files with 92 additions and 0 deletions

View File

@ -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<Policies::LogicalPath>;
template class Resolver<Policies::RealPath>;
template class Resolver<Policies::CasePath>;
template class Resolver<Policies::NaivePath>;
}

View File

@ -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<Policies::LogicalPath>;
extern template class Resolver<Policies::RealPath>;
extern template class Resolver<Policies::CasePath>;
extern template class Resolver<Policies::NaivePath>;
}

View File

@ -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<Policies::CasePath> 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<Policies::LogicalPath> const r(os);
EXPECT_RESOLVE("/link-a", "/link-a");
@ -263,6 +280,16 @@ bool macosActualCase()
{ "/upper/link-c-upper", { "LINK-C-UPPER", "/upper" } },
});
{
Resolver<Policies::CasePath> 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<Policies::LogicalPath> const r(os);
EXPECT_RESOLVE("/mIxEd/MiSsInG", "/MiXeD/MiSsInG");
@ -302,6 +329,16 @@ bool windowsRoot()
EXPECT_RESOLVE("C:/..", "C:/");
EXPECT_RESOLVE("c:/../", "c:/");
}
{
Resolver<Policies::CasePath> 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<Policies::CasePath> 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<Policies::LogicalPath> 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<Policies::CasePath> 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<Policies::CasePath> 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