Merge topic 'path-resolver'

1a6015e5fc PathResolver: Add helper to compute normalized paths
2a6f86ec5e Tests/CMakeLib: Remove stray output from test loop
646f37b473 clang-tidy: disable modernize-concat-nested-namespaces

Acked-by: Kitware Robot <kwrobot@kitware.com>
Acked-by: buildbot <buildbot@kitware.com>
Merge-request: !9965
This commit is contained in:
Brad King
2024-11-03 13:18:57 +00:00
committed by Kitware Robot
8 changed files with 1122 additions and 3 deletions

View File

@@ -29,6 +29,7 @@ misc-*,\
-misc-use-internal-linkage,\
modernize-*,\
-modernize-avoid-c-arrays,\
-modernize-concat-nested-namespaces,\
-modernize-macro-to-enum,\
-modernize-return-braced-init-list,\
-modernize-type-traits,\

View File

@@ -412,6 +412,8 @@ add_library(
cmNewLineStyle.cxx
cmOrderDirectories.cxx
cmOrderDirectories.h
cmPathResolver.cxx
cmPathResolver.h
cmPlistParser.cxx
cmPlistParser.h
cmPolicies.h

541
Source/cmPathResolver.cxx Normal file
View File

@@ -0,0 +1,541 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmPathResolver.h"
#include <algorithm>
#include <cerrno>
#include <cstddef>
#include <string>
#include <utility>
#include <cm/optional>
#include <cm/string_view>
#include <cmext/string_view>
#ifdef _WIN32
# include <cctype>
# include <windows.h>
#endif
#define MAX_SYMBOLIC_LINKS 32
namespace cm {
namespace PathResolver {
namespace {
namespace Options {
enum class ActualCase
{
No,
Yes,
};
enum class Symlinks
{
None,
Lazy,
Eager,
};
enum class Existence
{
Agnostic,
Required,
};
}
enum class Root
{
None,
POSIX,
#ifdef _WIN32
Drive,
Network,
#endif
};
struct Control
{
enum class Tag
{
Continue,
Restart,
Error,
};
Tag tag;
union
{
std::string::size_type slash; // data for Continue
cmsys::Status error; // data for Error
};
static Control Continue(std::string::size_type s)
{
Control c{ Tag::Continue };
c.slash = s;
return c;
}
static Control Restart() { return Control{ Tag::Restart }; }
static Control Error(cmsys::Status e)
{
Control c{ Tag::Error };
c.error = e;
return c;
}
private:
Control(Tag t)
: tag(t)
{
}
};
Root ClassifyRoot(cm::string_view p)
{
#ifdef _WIN32
if (p.size() >= 2 && std::isalpha(p[0]) && p[1] == ':') {
return Root::Drive;
}
if (p.size() >= 3 && p[0] == '/' && p[1] == '/' && p[2] != '/') {
return Root::Network;
}
#endif
if (!p.empty() && p[0] == '/') {
return Root::POSIX;
}
return Root::None;
}
class ImplBase
{
protected:
ImplBase(System& os)
: OS(os)
{
}
System& OS;
std::string P;
std::size_t SymlinkDepth = 0;
#ifdef _WIN32
std::string GetWorkingDirectoryOnDrive(char letter);
Control ResolveRootRelative();
#endif
cm::optional<std::string> ReadSymlink(std::string const& path,
cmsys::Status& status);
Control ResolveSymlink(Root root, std::string::size_type slash,
std::string::size_type next_slash,
std::string symlink_target);
};
template <class Policy>
class Impl : public ImplBase
{
Control ResolveRelativePath();
Control ResolveRoot(Root root);
Control ResolveComponent(Root root, std::string::size_type root_slash,
std::string::size_type slash);
Control ResolvePath();
public:
Impl(System& os)
: ImplBase(os)
{
}
cmsys::Status Resolve(std::string in, std::string& out);
};
template <class Policy>
Control Impl<Policy>::ResolveRelativePath()
{
// This is a relative path. Convert it to absolute and restart.
std::string p = this->OS.GetWorkingDirectory();
std::replace(p.begin(), p.end(), '\\', '/');
if (ClassifyRoot(p) == Root::None) {
p.insert(0, 1, '/');
}
if (p.back() != '/') {
p.push_back('/');
}
P.insert(0, p);
return Control::Restart();
}
#ifdef _WIN32
std::string ImplBase::GetWorkingDirectoryOnDrive(char letter)
{
// Use the drive's working directory, if any.
std::string d = this->OS.GetWorkingDirectoryOnDrive(letter);
std::replace(d.begin(), d.end(), '\\', '/');
if (d.size() >= 3 && std::toupper(d[0]) == std::toupper(letter) &&
d[1] == ':' && d[2] == '/') {
d[0] = letter;
d.push_back('/');
return d;
}
// Use the current working directory if the drive matches.
d = this->OS.GetWorkingDirectory();
if (d.size() >= 3 && std::toupper(d[0]) == std::toupper(letter) &&
d[1] == ':' && d[2] == '/') {
d[0] = letter;
d.push_back('/');
return d;
}
// Fall back to the root directory on the drive.
d = "_:/";
d[0] = letter;
return d;
}
Control ImplBase::ResolveRootRelative()
{
// This is a root-relative path. Resolve the root drive and restart.
P.replace(0, 2, this->GetWorkingDirectoryOnDrive(P[0]));
return Control::Restart();
}
#endif
cm::optional<std::string> ImplBase::ReadSymlink(std::string const& path,
cmsys::Status& status)
{
cm::optional<std::string> result;
std::string target;
status = this->OS.ReadSymlink(path, target);
if (status && ++this->SymlinkDepth >= MAX_SYMBOLIC_LINKS) {
status = cmsys::Status::POSIX(ELOOP);
}
if (status) {
if (!target.empty()) {
result = std::move(target);
}
} else if (status.GetPOSIX() == EINVAL
#ifdef _WIN32
|| status.GetWindows() == ERROR_NOT_A_REPARSE_POINT
#endif
) {
// The path was not a symlink.
status = cmsys::Status::Success();
}
return result;
}
Control ImplBase::ResolveSymlink(Root root, std::string::size_type slash,
std::string::size_type next_slash,
std::string symlink_target)
{
std::replace(symlink_target.begin(), symlink_target.end(), '\\', '/');
Root const symlink_target_root = ClassifyRoot(symlink_target);
if (symlink_target_root == Root::None) {
// This is a symlink to a relative path.
// Resolve the symlink, while preserving the leading and
// trailing (if any) slash:
// "*/link/" => "*/dest/"
// ^slash ^slash
P.replace(slash + 1, next_slash - slash - 1, symlink_target);
return Control::Continue(slash);
}
#ifdef _WIN32
if (root == Root::Drive && symlink_target_root == Root::POSIX) {
// This is a symlink to a POSIX absolute path,
// but the current path is on a drive letter. Resolve the
// symlink while preserving the drive letter, and start over:
// "C:/*/link/" => "C:/dest/"
// ^slash (restart)
P.replace(2, next_slash - 2, symlink_target);
return Control::Restart();
}
#else
static_cast<void>(root);
#endif
// This is a symlink to an absolute path.
// Resolve it and start over:
// "*/link/" => "/dest/"
// ^slash (restart)
P.replace(0, next_slash, symlink_target);
return Control::Restart();
}
template <class Policy>
Control Impl<Policy>::ResolveRoot(Root root)
{
if (root == Root::None) {
return this->ResolveRelativePath();
}
// POSIX absolute paths always start with a '/'.
std::string::size_type root_slash = 0;
#ifdef _WIN32
if (root == Root::Drive) {
if (P.size() == 2 || P[2] != '/') {
return this->ResolveRootRelative();
}
if (Policy::ActualCase == Options::ActualCase::Yes) {
// Normalize the drive letter to upper-case.
P[0] = static_cast<char>(std::toupper(P[0]));
}
// The root is a drive letter. The root '/' immediately follows.
root_slash = 2;
} else if (root == Root::Network) {
// The root is a network name. Find the root '/' after it.
root_slash = P.find('/', 2);
if (root_slash == std::string::npos) {
root_slash = P.size();
P.push_back('/');
}
}
#endif
if (Policy::Existence == Options::Existence::Required
#ifdef _WIN32
&& root != Root::Network
#endif
) {
std::string path = P.substr(0, root_slash + 1);
if (!this->OS.PathExists(path)) {
P = std::move(path);
return Control::Error(cmsys::Status::POSIX(ENOENT));
}
}
return Control::Continue(root_slash);
}
template <class Policy>
Control Impl<Policy>::ResolveComponent(Root root,
std::string::size_type root_slash,
std::string::size_type slash)
{
// Look for the '/' or end-of-input that ends this component.
// The sample paths in comments below show the trailing slash
// even if it is actually beyond the end of the path.
std::string::size_type next_slash = P.find('/', slash + 1);
if (next_slash == std::string::npos) {
next_slash = P.size();
}
cm::string_view c =
cm::string_view(P).substr(slash + 1, next_slash - (slash + 1));
if (slash == root_slash) {
if (c.empty() || c == "."_s || c == ".."_s) {
// This is an empty, '.', or '..' component at the root.
// Drop the component and its trailing slash, if any,
// while preserving the root slash:
// "//" => "/"
// "/./" => "/"
// "/../" => "/"
// ^slash ^slash
P.erase(slash + 1, next_slash - slash);
return Control::Continue(slash);
}
} else {
if (c.empty() || c == "."_s) {
// This is an empty or '.' component not at the root.
// Drop the component and its leading slash:
// "*//" => "*/"
// "*/./" => "*/"
// ^slash ^slash
P.erase(slash, next_slash - slash);
return Control::Continue(slash);
}
if (c == ".."_s) {
// This is a '..' component not at the root.
// Rewind to the previous component:
// "*/prev/../" => "*/prev/../"
// ^slash ^slash
next_slash = slash;
slash = P.rfind('/', slash - 1);
if (Policy::Symlinks == Options::Symlinks::Lazy) {
cmsys::Status status;
std::string path = P.substr(0, next_slash);
if (cm::optional<std::string> maybe_symlink_target =
this->ReadSymlink(path, status)) {
return this->ResolveSymlink(root, slash, next_slash,
std::move(*maybe_symlink_target));
}
if (!status && Policy::Existence == Options::Existence::Required) {
P = std::move(path);
return Control::Error(status);
}
}
// This is not a symlink.
// Drop the component, the following '..', and its trailing slash,
// if any, while preserving the (possibly root) leading slash:
// "*/dir/../" => "*/"
// ^slash ^slash
P.erase(slash + 1, next_slash + 3 - slash);
return Control::Continue(slash);
}
}
// This is a named component.
if (Policy::Symlinks == Options::Symlinks::Eager) {
cmsys::Status status;
std::string path = P.substr(0, next_slash);
if (cm::optional<std::string> maybe_symlink_target =
this->ReadSymlink(path, status)) {
return this->ResolveSymlink(root, slash, next_slash,
std::move(*maybe_symlink_target));
}
if (!status && Policy::Existence == Options::Existence::Required) {
P = std::move(path);
return Control::Error(status);
}
}
#ifdef _WIN32
bool exists = false;
if (Policy::ActualCase == Options::ActualCase::Yes) {
std::string name;
std::string path = P.substr(0, next_slash);
if (cmsys::Status status = this->OS.ReadName(path, name)) {
exists = true;
if (!name.empty()) {
// Rename this component:
// "*/name/" => "*/Name/"
// ^slash ^slash
P.replace(slash + 1, next_slash - slash - 1, name);
next_slash = slash + 1 + name.length();
}
} else if (Policy::Existence == Options::Existence::Required) {
P = std::move(path);
return Control::Error(status);
}
}
#endif
if (Policy::Existence == Options::Existence::Required
#ifdef _WIN32
&& !exists
#endif
) {
std::string path = P.substr(0, next_slash);
if (!this->OS.PathExists(path)) {
P = std::move(path);
return Control::Error(cmsys::Status::POSIX(ENOENT));
}
}
// Keep this component:
// "*/name/" => "*/name/"
// ^slash ^slash
return Control::Continue(next_slash);
}
template <class Policy>
Control Impl<Policy>::ResolvePath()
{
Root const root = ClassifyRoot(P);
// Resolve the root component. It always ends in a slash.
Control control = this->ResolveRoot(root);
if (control.tag != Control::Tag::Continue) {
return control;
}
std::string::size_type const root_slash = control.slash;
// Resolve later components. Every iteration that finishes
// the loop body makes progress either by removing a component
// or advancing the slash past it.
for (std::string::size_type slash = root_slash;
P.size() > root_slash + 1 && slash < P.size();) {
control = this->ResolveComponent(root, root_slash, slash);
if (control.tag != Control::Tag::Continue) {
return control;
}
slash = control.slash;
}
return Control::Continue(P.size());
}
template <class Policy>
cmsys::Status Impl<Policy>::Resolve(std::string in, std::string& out)
{
P = std::move(in);
std::replace(P.begin(), P.end(), '\\', '/');
for (;;) {
Control control = this->ResolvePath();
switch (control.tag) {
case Control::Tag::Continue:
out = std::move(P);
return cmsys::Status::Success();
case Control::Tag::Restart:
continue;
case Control::Tag::Error:
out = std::move(P);
return control.error;
};
}
}
}
namespace Policies {
struct NaivePath
{
#ifdef _WIN32
static constexpr Options::ActualCase ActualCase = Options::ActualCase::No;
#endif
static constexpr Options::Symlinks Symlinks = Options::Symlinks::None;
static constexpr Options::Existence Existence = Options::Existence::Agnostic;
};
struct RealPath
{
#ifdef _WIN32
static constexpr Options::ActualCase ActualCase = Options::ActualCase::Yes;
#endif
static constexpr Options::Symlinks Symlinks = Options::Symlinks::Eager;
static constexpr Options::Existence Existence = Options::Existence::Required;
};
struct LogicalPath
{
#ifdef _WIN32
static constexpr Options::ActualCase ActualCase = Options::ActualCase::Yes;
#endif
static constexpr Options::Symlinks Symlinks = Options::Symlinks::Lazy;
static constexpr Options::Existence Existence = Options::Existence::Agnostic;
};
#if defined(__SUNPRO_CC)
constexpr Options::Symlinks NaivePath::Symlinks;
constexpr Options::Existence NaivePath::Existence;
constexpr Options::Symlinks RealPath::Symlinks;
constexpr Options::Existence RealPath::Existence;
constexpr Options::Symlinks LogicalPath::Symlinks;
constexpr Options::Existence LogicalPath::Existence;
#endif
}
template <class Policy>
Resolver<Policy>::Resolver(System& os)
: OS(os)
{
}
template <class Policy>
cmsys::Status Resolver<Policy>::Resolve(std::string in, std::string& out) const
{
return Impl<Policy>(OS).Resolve(std::move(in), out);
}
System::System() = default;
System::~System() = default;
template class Resolver<Policies::LogicalPath>;
template class Resolver<Policies::RealPath>;
template class Resolver<Policies::NaivePath>;
}
}

98
Source/cmPathResolver.h Normal file
View File

@@ -0,0 +1,98 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#pragma once
#include "cmConfigure.h" // IWYU pragma: keep
#include <string>
#include "cmsys/Status.hxx"
namespace cm {
namespace PathResolver {
class System;
/** Normalize filesystem paths according to a Policy.
*
* Resolved paths are always absolute, have no '..', '.', or empty
* components, and have a trailing '/' if and only if the entire
* path is a root component.
*
* The Policy determines behavior w.r.t. symbolic links, existence,
* and matching the on-disk case (upper/lower) of existing paths.
*/
template <class Policy>
class Resolver
{
System& OS;
public:
/** Construct with a concrete filesystem access implementation. */
Resolver(System& os);
/** Resolve the input path according to the Policy, if possible.
On success, the resolved path is stored in 'out'.
On failure, the non-existent path is stored in 'out'. */
cmsys::Status Resolve(std::string in, std::string& out) const;
};
/** Access the filesystem via runtime dispatch.
This allows unit tests to work without accessing a real filesystem,
which is particularly important on Windows where symbolic links
may not be something we can create without administrator privileges.
*/
class System
{
public:
System();
virtual ~System() = 0;
/** If the given path is a symbolic link, read its target.
If the path exists but is not a symbolic link, fail
with EINVAL or ERROR_NOT_A_REPARSE_POINT. */
virtual cmsys::Status ReadSymlink(std::string const& path,
std::string& symlink_target) = 0;
/** Return whether the given path exists on disk. */
virtual bool PathExists(std::string const& path) = 0;
/** Get the process's working directory. */
virtual std::string GetWorkingDirectory() = 0;
#ifdef _WIN32
/** Get the process's working directory on a Windows drive letter.
This is a legacy DOS concept supported by 'cmd' shells. */
virtual std::string GetWorkingDirectoryOnDrive(char drive_letter) = 0;
/** Read the on-disk spelling of the last component of a file path. */
virtual cmsys::Status ReadName(std::string const& path,
std::string& name) = 0;
#endif
};
namespace Policies {
// IWYU pragma: begin_exports
/** Normalizes paths while resolving symlinks only when followed
by '..' components. Does not require paths to exist, but
reads on-disk case of paths that do exist (on Windows). */
struct LogicalPath;
/** Normalizes paths while resolving all symlinks.
Requires paths to exist, and reads their on-disk case (on Windows). */
struct RealPath;
/** Normalizes paths in memory without disk access.
Assumes components followed by '..' components are not symlinks. */
struct NaivePath;
// IWYU pragma: end_exports
}
extern template class Resolver<Policies::LogicalPath>;
extern template class Resolver<Policies::RealPath>;
extern template class Resolver<Policies::NaivePath>;
}
}

View File

@@ -23,6 +23,7 @@ set(CMakeLib_TESTS
testRST.cxx
testRange.cxx
testOptional.cxx
testPathResolver.cxx
testString.cxx
testStringAlgorithms.cxx
testSystemTools.cxx

View File

@@ -40,10 +40,9 @@ inline int runTests(std::initializer_list<std::function<bool()>> const& tests,
break;
}
}
std::cout << '.';
}
if (!result) {
std::cout << " Passed\n";
if (result == 0) {
std::cout << "Passed\n";
}
return result;
}

View File

@@ -0,0 +1,476 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include <cmConfigure.h> // IWYU pragma: keep
#include <cerrno>
#include <map>
#include <string>
#include <utility>
#include <cmsys/Status.hxx>
#include "cmPathResolver.h"
#ifdef _WIN32
# include <cctype>
# include "cmSystemTools.h"
#endif
#include "testCommon.h"
// 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::RealPath
namespace {
class MockSystem : public cm::PathResolver::System
{
public:
~MockSystem() override = default;
struct Path
{
std::string Name;
std::string Link;
};
std::map<std::string, Path> Paths;
void SetPaths(std::map<std::string, Path> paths)
{
this->Paths = std::move(paths);
}
static std::string AdjustCase(std::string const& path)
{
#ifdef _WIN32
return cmSystemTools::LowerCase(path);
#else
return path;
#endif
}
cmsys::Status ReadSymlink(std::string const& path,
std::string& link) override
{
auto i = this->Paths.find(AdjustCase(path));
if (i == this->Paths.end()) {
return cmsys::Status::POSIX(ENOENT);
}
if (i->second.Link.empty()) {
return cmsys::Status::POSIX(EINVAL);
}
link = i->second.Link;
return cmsys::Status::Success();
}
bool PathExists(std::string const& path) override
{
return this->Paths.find(AdjustCase(path)) != this->Paths.end();
}
std::string WorkDir;
void SetWorkDir(std::string wd) { this->WorkDir = std::move(wd); }
std::string GetWorkingDirectory() override { return this->WorkDir; }
#ifdef _WIN32
std::map<char, std::string> WorkDirOnDrive;
void SetWorkDirOnDrive(std::map<char, std::string> wd)
{
this->WorkDirOnDrive = std::move(wd);
}
std::string GetWorkingDirectoryOnDrive(char letter) override
{
std::string result;
auto i = this->WorkDirOnDrive.find(std::tolower(letter));
if (i != this->WorkDirOnDrive.end()) {
result = i->second;
}
return result;
}
cmsys::Status ReadName(std::string const& path, std::string& name) override
{
auto i = this->Paths.find(AdjustCase(path));
if (i == this->Paths.end()) {
return cmsys::Status::POSIX(ENOENT);
}
name = i->second.Name;
return cmsys::Status::Success();
}
#endif
};
#define EXPECT_RESOLVE(_in, _expect) \
do { \
std::string out; \
ASSERT_TRUE(r.Resolve(_in, out)); \
ASSERT_EQUAL(out, _expect); \
} while (false)
#define EXPECT_ENOENT(_in, _expect) \
do { \
std::string out; \
ASSERT_EQUAL(r.Resolve(_in, out).GetPOSIX(), ENOENT); \
ASSERT_EQUAL(out, _expect); \
} while (false)
using namespace cm::PathResolver;
bool posixRoot()
{
std::cout << "posixRoot()\n";
MockSystem os;
os.SetPaths({
{ "/", { {}, {} } },
});
Resolver<Policies::RealPath> const r(os);
EXPECT_RESOLVE("/", "/");
EXPECT_RESOLVE("//", "/");
EXPECT_RESOLVE("/.", "/");
EXPECT_RESOLVE("/./", "/");
EXPECT_RESOLVE("/..", "/");
EXPECT_RESOLVE("/../", "/");
return true;
}
bool posixAbsolutePath()
{
std::cout << "posixAbsolutePath()\n";
MockSystem os;
os.SetPaths({
{ "/", { {}, {} } },
{ "/a", { {}, {} } },
});
Resolver<Policies::RealPath> const r(os);
EXPECT_RESOLVE("/a", "/a");
EXPECT_RESOLVE("/a/", "/a");
EXPECT_RESOLVE("/a//", "/a");
EXPECT_RESOLVE("/a/.", "/a");
EXPECT_RESOLVE("/a/./", "/a");
EXPECT_RESOLVE("/a/..", "/");
EXPECT_RESOLVE("/a/../", "/");
EXPECT_RESOLVE("/a/../..", "/");
#ifndef _WIN32
EXPECT_RESOLVE("//a", "/a");
#endif
return true;
}
bool posixWorkingDirectory()
{
std::cout << "posixWorkingDirectory()\n";
MockSystem os;
os.SetPaths({
{ "/", { {}, {} } },
{ "/a", { {}, {} } },
{ "/cwd", { {}, {} } },
{ "/cwd/a", { {}, {} } },
});
Resolver<Policies::RealPath> const r(os);
EXPECT_RESOLVE("", "/");
EXPECT_RESOLVE(".", "/");
EXPECT_RESOLVE("..", "/");
EXPECT_RESOLVE("a", "/a");
os.SetWorkDir("/cwd");
EXPECT_RESOLVE("", "/cwd");
EXPECT_RESOLVE(".", "/cwd");
EXPECT_RESOLVE("..", "/");
EXPECT_RESOLVE("a", "/cwd/a");
return true;
}
bool posixSymlink()
{
std::cout << "posixSymlink()\n";
MockSystem os;
os.SetPaths({
{ "/", { {}, {} } },
{ "/link-a", { {}, "a" } },
{ "/link-a-excess", { {}, "a//." } },
{ "/link-broken", { {}, "link-broken-dest" } },
{ "/a", { {}, {} } },
{ "/a/b", { {}, {} } },
{ "/a/link-b", { {}, "b" } },
{ "/a/b/link-c", { {}, "c" } },
{ "/a/b/c", { {}, {} } },
{ "/a/b/c/link-..|..", { {}, "../.." } },
{ "/a/link-|1|2", { {}, "/1/2" } },
{ "/1", { {}, {} } },
{ "/1/2", { {}, {} } },
{ "/1/2/3", { {}, {} } },
});
{
Resolver<Policies::LogicalPath> 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", "/1/2/3");
}
{
Resolver<Policies::RealPath> const r(os);
EXPECT_RESOLVE("/link-a", "/a");
EXPECT_RESOLVE("/link-a-excess", "/a");
EXPECT_RESOLVE("/link-a-excess/b", "/a/b");
EXPECT_ENOENT("/link-broken", "/link-broken-dest");
EXPECT_ENOENT("/link-a/../missing", "/missing");
EXPECT_RESOLVE("/a/b/link-c", "/a/b/c");
EXPECT_RESOLVE("/a/link-b/c", "/a/b/c");
EXPECT_RESOLVE("/a/link-b/link-c/..", "/a/b");
EXPECT_RESOLVE("/a/b/c/link-..|..", "/a");
EXPECT_RESOLVE("/a/b/c/link-..|../link-b", "/a/b");
EXPECT_RESOLVE("/a/link-|1|2/3", "/1/2/3");
}
return true;
}
#ifdef _WIN32
bool windowsRoot()
{
std::cout << "windowsRoot()\n";
MockSystem os;
{
Resolver<Policies::NaivePath> 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/", { {}, {} } },
});
{
Resolver<Policies::RealPath> 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:/");
EXPECT_RESOLVE("//host", "//host/");
EXPECT_RESOLVE("//host/.", "//host/");
EXPECT_RESOLVE("//host/./", "//host/");
EXPECT_RESOLVE("//host/..", "//host/");
EXPECT_RESOLVE("//host/../", "//host/");
}
return true;
}
bool windowsAbsolutePath()
{
std::cout << "windowsAbsolutePath()\n";
MockSystem os;
os.SetPaths({
{ "c:/", { {}, {} } },
{ "c:/a", { {}, {} } },
});
Resolver<Policies::RealPath> const r(os);
EXPECT_RESOLVE("c:/a", "C:/a");
EXPECT_RESOLVE("c:/a/", "C:/a");
EXPECT_RESOLVE("c:/a//", "C:/a");
EXPECT_RESOLVE("c:/a/.", "C:/a");
EXPECT_RESOLVE("c:/a/./", "C:/a");
EXPECT_RESOLVE("c:/a/..", "C:/");
EXPECT_RESOLVE("c:/a/../", "C:/");
EXPECT_RESOLVE("c:/a/../..", "C:/");
return true;
}
bool windowsActualCase()
{
std::cout << "windowsActualCase()\n";
MockSystem os;
os.SetPaths({
{ "c:/", { {}, {} } },
{ "c:/mixed", { "MiXeD", {} } },
{ "c:/mixed/link-mixed", { "LiNk-MiXeD", "mixed" } },
{ "c:/mixed/mixed", { "MiXeD", {} } },
{ "c:/mixed/link-c-mixed", { "LiNk-C-MiXeD", "C:/mIxEd" } },
{ "c:/upper", { "UPPER", {} } },
{ "c:/upper/link-upper", { "LINK-UPPER", "upper" } },
{ "c:/upper/upper", { "UPPER", {} } },
{ "c:/upper/link-c-upper", { "LINK-C-UPPER", "c:/upper" } },
});
{
Resolver<Policies::LogicalPath> 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::RealPath> const r(os);
EXPECT_ENOENT("c:/mIxEd/MiSsInG", "C:/MiXeD/MiSsInG");
EXPECT_RESOLVE("c:/mIxEd/link-MiXeD", "C:/MiXeD/MiXeD");
EXPECT_RESOLVE("c:/mIxEd/link-c-MiXeD", "C:/MiXeD");
EXPECT_ENOENT("c:/upper/mIsSiNg", "C:/UPPER/mIsSiNg");
EXPECT_RESOLVE("c:/upper/link-upper", "C:/UPPER/UPPER");
EXPECT_RESOLVE("c:/upper/link-c-upper", "C:/UPPER");
}
return true;
}
bool windowsWorkingDirectory()
{
std::cout << "windowsWorkingDirectory()\n";
MockSystem os;
os.SetPaths({
{ "c:/", { {}, {} } },
{ "c:/a", { {}, {} } },
{ "c:/cwd", { {}, {} } },
{ "c:/cwd/a", { {}, {} } },
});
{
Resolver<Policies::LogicalPath> const r(os);
EXPECT_RESOLVE("", "/");
EXPECT_RESOLVE(".", "/");
EXPECT_RESOLVE("..", "/");
EXPECT_RESOLVE("a", "/a");
}
{
Resolver<Policies::RealPath> const r(os);
os.SetWorkDir("c:/cwd");
EXPECT_RESOLVE("", "C:/cwd");
EXPECT_RESOLVE(".", "C:/cwd");
EXPECT_RESOLVE("..", "C:/");
EXPECT_RESOLVE("a", "C:/cwd/a");
EXPECT_ENOENT("missing", "C:/cwd/missing");
}
return true;
}
bool windowsWorkingDirectoryOnDrive()
{
std::cout << "windowsWorkingDirectoryOnDrive()\n";
MockSystem os;
os.SetWorkDir("c:/cwd");
os.SetWorkDirOnDrive({
{ 'd', "d:/cwd-d" },
});
{
Resolver<Policies::NaivePath> 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", { {}, {} } },
{ "c:/cwd/existing", { {}, {} } },
{ "d:/", { {}, {} } },
{ "d:/cwd-d", { {}, {} } },
{ "d:/cwd-d/existing", { {}, {} } },
{ "e:/", { {}, {} } },
});
{
Resolver<Policies::RealPath> const r(os);
EXPECT_RESOLVE("c:existing", "C:/cwd/existing");
EXPECT_ENOENT("c:missing", "C:/cwd/missing");
EXPECT_RESOLVE("C:existing", "C:/cwd/existing");
EXPECT_ENOENT("C:missing", "C:/cwd/missing");
EXPECT_RESOLVE("d:existing", "D:/cwd-d/existing");
EXPECT_ENOENT("d:missing", "D:/cwd-d/missing");
EXPECT_ENOENT("e:missing", "E:/missing");
EXPECT_ENOENT("f:", "F:/");
}
return true;
}
bool windowsNetworkShare()
{
std::cout << "windowsNetworkShare()\n";
MockSystem os;
os.SetPaths({
{ "c:/", { {}, {} } },
{ "c:/cwd", { {}, {} } },
{ "c:/cwd/link-to-host-share", { {}, "//host/share" } },
{ "//host/", { {}, {} } },
{ "//host/share", { {}, {} } },
});
os.SetWorkDir("c:/cwd");
{
Resolver<Policies::RealPath> const r(os);
EXPECT_RESOLVE("//host/share", "//host/share");
EXPECT_RESOLVE("//host/share/", "//host/share");
EXPECT_RESOLVE("//host/share/.", "//host/share");
EXPECT_RESOLVE("//host/share/./", "//host/share");
EXPECT_RESOLVE("//host/share/..", "//host/");
EXPECT_RESOLVE("//host/share/../", "//host/");
EXPECT_RESOLVE("//host/share/../..", "//host/");
EXPECT_RESOLVE("link-to-host-share", "//host/share");
EXPECT_RESOLVE("link-to-host-share/..", "//host/");
EXPECT_ENOENT("link-to-host-share/../missing", "//host/missing");
}
{
Resolver<Policies::LogicalPath> const r(os);
EXPECT_RESOLVE("link-to-host-share", "C:/cwd/link-to-host-share");
EXPECT_RESOLVE("link-to-host-share/..", "//host/");
EXPECT_RESOLVE("link-to-host-share/../missing", "//host/missing");
}
return true;
}
#endif
}
int testPathResolver(int /*unused*/, char* /*unused*/[])
{
return runTests({
posixRoot,
posixAbsolutePath,
posixWorkingDirectory,
posixSymlink,
#ifdef _WIN32
windowsRoot,
windowsAbsolutePath,
windowsActualCase,
windowsWorkingDirectory,
windowsWorkingDirectoryOnDrive,
windowsNetworkShare,
#endif
});
}

View File

@@ -459,6 +459,7 @@ CMAKE_CXX_SOURCES="\
cmOutputConverter \
cmParseArgumentsCommand \
cmPathLabel \
cmPathResolver \
cmPolicies \
cmProcessOutput \
cmProjectCommand \