diff --git a/Source/cmSystemTools.cxx b/Source/cmSystemTools.cxx index dfbaa32e76..0755148ca6 100644 --- a/Source/cmSystemTools.cxx +++ b/Source/cmSystemTools.cxx @@ -1305,23 +1305,77 @@ bool FileModeGuard::HasErrors() const return filepath_.empty(); } +std::string cmSystemTools::GetRealPathResolvingWindowsSubst( + std::string const& path, std::string* errorMessage) +{ +#ifdef _WIN32 + // uv_fs_realpath uses Windows Vista API so fallback to kwsys if not found + std::string resolved_path; + uv_fs_t req; + int err = uv_fs_realpath(nullptr, &req, path.c_str(), nullptr); + if (!err) { + resolved_path = std::string((char*)req.ptr); + cmSystemTools::ConvertToUnixSlashes(resolved_path); + } else if (err == UV_ENOSYS) { + resolved_path = cmsys::SystemTools::GetRealPath(path, errorMessage); + } else if (errorMessage) { + cmsys::Status status = + cmsys::Status::Windows(uv_fs_get_system_error(&req)); + *errorMessage = status.GetString(); + resolved_path.clear(); + } else { + resolved_path = path; + } + // Normalize to upper-case drive letter as cm::PathResolver does. + if (resolved_path.size() > 1 && resolved_path[1] == ':') { + resolved_path[0] = toupper(resolved_path[0]); + } + return resolved_path; +#else + return cmsys::SystemTools::GetRealPath(path, errorMessage); +#endif +} + std::string cmSystemTools::GetRealPath(std::string const& path, std::string* errorMessage) { #ifdef _WIN32 - std::string resolved_path; - using namespace cm::PathResolver; - // IWYU pragma: no_forward_declare cm::PathResolver::Policies::RealPath - static Resolver const resolver(RealOS); - cmsys::Status status = resolver.Resolve(path, resolved_path); - if (!status) { - if (errorMessage) { - *errorMessage = status.GetString(); - resolved_path.clear(); - } else { - resolved_path = path; + std::string resolved_path = + cmSystemTools::GetRealPathResolvingWindowsSubst(path, errorMessage); + + // If the original path used a subst drive and the real path starts + // with the substitution, restore the subst drive prefix. This may + // incorrectly restore a subst drive if the underlying drive was + // encountered via an absolute symlink, but this is an acceptable + // limitation to otherwise preserve susbt drives. + if (resolved_path.size() >= 2 && resolved_path[1] == ':' && + path.size() >= 2 && path[1] == ':' && + toupper(resolved_path[0]) != toupper(path[0])) { + // FIXME: Add thread_local or mutex if we use threads. + static std::map substMap; + char const drive = static_cast(toupper(path[0])); + std::string maybe_subst = cmStrCat(drive, ":/"); + auto smi = substMap.find(drive); + if (smi == substMap.end()) { + smi = substMap + .emplace( + drive, + cmSystemTools::GetRealPathResolvingWindowsSubst(maybe_subst)) + .first; + } + std::string const& resolved_subst = smi->second; + std::string::size_type const ns = resolved_subst.size(); + if (ns > 0) { + std::string::size_type const np = resolved_path.size(); + if (ns == np && resolved_path == resolved_subst) { + resolved_path = maybe_subst; + } else if (ns > 0 && ns < np && resolved_path[ns] == '/' && + resolved_path.compare(0, ns, resolved_subst) == 0) { + resolved_path.replace(0, ns + 1, maybe_subst); + } } } + return resolved_path; #else return cmsys::SystemTools::GetRealPath(path, errorMessage); diff --git a/Source/cmSystemTools.h b/Source/cmSystemTools.h index d6967b65e3..16c5caa321 100644 --- a/Source/cmSystemTools.h +++ b/Source/cmSystemTools.h @@ -651,6 +651,12 @@ public: static std::string GetComspec(); #endif + /** Get the real path for a given path, removing all symlinks. + This variant of GetRealPath also works on Windows but will + resolve subst drives too. */ + static std::string GetRealPathResolvingWindowsSubst( + std::string const& path, std::string* errorMessage = nullptr); + /** Get the real path for a given path, removing all symlinks. */ static std::string GetRealPath(std::string const& path, std::string* errorMessage = nullptr); diff --git a/Source/cmTimestamp.cxx b/Source/cmTimestamp.cxx index d921a89361..35913c729f 100644 --- a/Source/cmTimestamp.cxx +++ b/Source/cmTimestamp.cxx @@ -63,7 +63,8 @@ std::string cmTimestamp::FileModificationTime(char const* path, std::string const& formatString, bool utcFlag) const { - std::string real_path = cmSystemTools::GetRealPath(path); + std::string real_path = + cmSystemTools::GetRealPathResolvingWindowsSubst(path); if (!cmsys::SystemTools::FileExists(real_path)) { return std::string();