diff --git a/Source/cmPathResolver.cxx b/Source/cmPathResolver.cxx index 93457a574e..e55ebd057f 100644 --- a/Source/cmPathResolver.cxx +++ b/Source/cmPathResolver.cxx @@ -396,7 +396,7 @@ Control Impl::ResolveComponent(Root root, } } -#ifdef _WIN32 +#if defined(_WIN32) || defined(__APPLE__) bool exists = false; if (Policy::ActualCase == Options::ActualCase::Yes) { std::string name; @@ -418,7 +418,7 @@ Control Impl::ResolveComponent(Root root, #endif if (Policy::Existence == Options::Existence::Required -#ifdef _WIN32 +#if defined(_WIN32) || defined(__APPLE__) && !exists #endif ) { @@ -486,7 +486,7 @@ cmsys::Status Impl::Resolve(std::string in, std::string& out) namespace Policies { struct NaivePath { -#ifdef _WIN32 +#if defined(_WIN32) || defined(__APPLE__) static constexpr Options::ActualCase ActualCase = Options::ActualCase::No; #endif static constexpr Options::Symlinks Symlinks = Options::Symlinks::None; @@ -494,7 +494,7 @@ struct NaivePath }; struct RealPath { -#ifdef _WIN32 +#if defined(_WIN32) || defined(__APPLE__) static constexpr Options::ActualCase ActualCase = Options::ActualCase::Yes; #endif static constexpr Options::Symlinks Symlinks = Options::Symlinks::Eager; @@ -502,7 +502,7 @@ struct RealPath }; struct LogicalPath { -#ifdef _WIN32 +#if defined(_WIN32) || defined(__APPLE__) static constexpr Options::ActualCase ActualCase = Options::ActualCase::Yes; #endif static constexpr Options::Symlinks Symlinks = Options::Symlinks::Lazy; diff --git a/Source/cmPathResolver.h b/Source/cmPathResolver.h index 0f8baf8ca6..efe60a5916 100644 --- a/Source/cmPathResolver.h +++ b/Source/cmPathResolver.h @@ -64,7 +64,9 @@ public: /** 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; +#endif +#if defined(_WIN32) || defined(__APPLE__) /** 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; diff --git a/Source/cmSystemTools.cxx b/Source/cmSystemTools.cxx index cdb661c3fe..24ab7ae949 100644 --- a/Source/cmSystemTools.cxx +++ b/Source/cmSystemTools.cxx @@ -22,7 +22,7 @@ #include -#ifdef _WIN32 +#if defined(_WIN32) || defined(__APPLE__) # include #endif @@ -191,6 +191,61 @@ cmsys::Status ReadNameOnDisk(std::string const& path, std::string& name) CloseHandle(h); return cmsys::Status::Success(); } +#elif defined(__APPLE__) +cmsys::Status ReadNameOnDiskIterateDir(std::string const& path, + std::string& name) +{ + // Read contents of the parent directory to find the + // entry matching the given path. + std::string const bn = cmSystemTools::GetFilenameName(path); + std::string const dn = cmSystemTools::GetFilenamePath(path); + DIR* d = opendir(dn.c_str()); + while (struct dirent* dr = readdir(d)) { + if (strcasecmp(dr->d_name, bn.c_str()) == 0) { + name = dr->d_name; + closedir(d); + return cmsys::Status::Success(); + } + } + closedir(d); + return cmsys::Status::POSIX(ENOENT); +} + +cmsys::Status ReadNameOnDiskFcntlGetPath(std::string const& path, + std::string& name) +{ + // macOS (and *BSD) offer a syscall to get an on-disk path to + // a descriptor's file. + int fd = open(path.c_str(), O_SYMLINK | O_RDONLY); + if (fd == -1) { + return cmsys::Status::POSIX(errno); + } + char out[MAXPATHLEN + 1]; + if (fcntl(fd, F_GETPATH, out) == -1) { + int e = errno; + close(fd); + return cmsys::Status::POSIX(e); + } + close(fd); + name = cmSystemTools::GetFilenameName(out); + return cmsys::Status::Success(); +} + +cmsys::Status ReadNameOnDisk(std::string const& path, std::string& name) +{ + struct stat stat_path; + if (lstat(path.c_str(), &stat_path) != 0) { + return cmsys::Status::POSIX(errno); + } + // macOS (and *BSD) use namei(9) to cache file paths. Use it unless + // the inode has multiple hardlinks: if it is opened through multiple + // paths, the results may be unpredictable. + if (S_ISDIR(stat_path.st_mode) || stat_path.st_nlink < 2) { + return ReadNameOnDiskFcntlGetPath(path, name); + } + // Fall back to reading the parent directory. + return ReadNameOnDiskIterateDir(path, name); +} #endif class RealSystem : public cm::PathResolver::System @@ -215,7 +270,9 @@ public: { return GetDosDriveWorkingDirectory(letter); } +#endif +#if defined(_WIN32) || defined(__APPLE__) struct NameOnDisk { cmsys::Status Status; diff --git a/Tests/CMakeLib/testPathResolver.cxx b/Tests/CMakeLib/testPathResolver.cxx index 4c4ff873f9..7172274f06 100644 --- a/Tests/CMakeLib/testPathResolver.cxx +++ b/Tests/CMakeLib/testPathResolver.cxx @@ -8,13 +8,15 @@ #include #include +#ifdef _WIN32 +# include +#endif + #include #include "cmPathResolver.h" -#ifdef _WIN32 -# include - +#if defined(_WIN32) || defined(__APPLE__) # include "cmSystemTools.h" #endif @@ -46,7 +48,7 @@ public: static std::string AdjustCase(std::string const& path) { -#ifdef _WIN32 +#if defined(_WIN32) || defined(__APPLE__) return cmSystemTools::LowerCase(path); #else return path; @@ -95,7 +97,9 @@ public: } return result; } +#endif +#if defined(_WIN32) || defined(__APPLE__) cmsys::Status ReadName(std::string const& path, std::string& name) override { auto i = this->Paths.find(AdjustCase(path)); @@ -242,6 +246,47 @@ bool posixSymlink() return true; } +#ifdef __APPLE__ +bool macosActualCase() +{ + std::cout << "macosActualCase()\n"; + MockSystem os; + os.SetPaths({ + { "/", { {}, {} } }, + { "/mixed", { "MiXeD", {} } }, + { "/mixed/link-mixed", { "LiNk-MiXeD", "mixed" } }, + { "/mixed/mixed", { "MiXeD", {} } }, + { "/mixed/link-c-mixed", { "LiNk-C-MiXeD", "/mIxEd" } }, + { "/upper", { "UPPER", {} } }, + { "/upper/link-upper", { "LINK-UPPER", "upper" } }, + { "/upper/upper", { "UPPER", {} } }, + { "/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_ENOENT("/mIxEd/MiSsInG", "/MiXeD/MiSsInG"); + EXPECT_RESOLVE("/mIxEd/link-MiXeD", "/MiXeD/MiXeD"); + EXPECT_RESOLVE("/mIxEd/link-c-MiXeD", "/MiXeD"); + EXPECT_ENOENT("/upper/mIsSiNg", "/UPPER/mIsSiNg"); + EXPECT_RESOLVE("/upper/link-upper", "/UPPER/UPPER"); + EXPECT_RESOLVE("/upper/link-c-upper", "/UPPER"); + } + + return true; +} +#endif + #ifdef _WIN32 bool windowsRoot() { @@ -464,6 +509,9 @@ int testPathResolver(int /*unused*/, char* /*unused*/[]) posixAbsolutePath, posixWorkingDirectory, posixSymlink, +#ifdef __APPLE__ + macosActualCase, +#endif #ifdef _WIN32 windowsRoot, windowsAbsolutePath,