From 212e953d352c2ca20cf6280492633d21fbacdbc9 Mon Sep 17 00:00:00 2001 From: Marc Chevrier Date: Thu, 23 Jul 2020 16:52:39 +0200 Subject: cmCMakePath: Class for path handling --- Source/CMakeLists.txt | 2 + Source/cmCMakePath.cxx | 146 ++++++++++ Source/cmCMakePath.h | 571 ++++++++++++++++++++++++++++++++++++++ Utilities/std/cm/bits/fs_path.cxx | 2 +- Utilities/std/cm/filesystem | 4 +- bootstrap | 1 + 6 files changed, 723 insertions(+), 3 deletions(-) create mode 100644 Source/cmCMakePath.cxx create mode 100644 Source/cmCMakePath.h diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index ee8767f..c1610d4 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -182,6 +182,8 @@ set(SRCS cmCheckCustomOutputs.cxx cmCLocaleEnvironmentScope.h cmCLocaleEnvironmentScope.cxx + cmCMakePath.h + cmCMakePath.cxx cmCommandArgumentParserHelper.cxx cmCommonTargetGenerator.cxx cmCommonTargetGenerator.h diff --git a/Source/cmCMakePath.cxx b/Source/cmCMakePath.cxx new file mode 100644 index 0000000..b8215df --- /dev/null +++ b/Source/cmCMakePath.cxx @@ -0,0 +1,146 @@ +/* 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 "cmCMakePath.h" + +#include + +#if defined(_WIN32) +# include +#endif + +#include +#include + +#if defined(_WIN32) +# include "cmStringAlgorithms.h" +#endif + +cmCMakePath& cmCMakePath::ReplaceWideExtension(cm::string_view extension) +{ + auto file = this->Path.filename().string(); + if (!file.empty() && file != "." && file != "..") { + auto pos = file.find('.', file[0] == '.' ? 1 : 0); + if (pos != std::string::npos) { + file.erase(pos); + } + } + if (!extension.empty()) { + if (extension[0] != '.') { + file += '.'; + } + file.append(std::string(extension)); + } + this->Path.replace_filename(file); + return *this; +} + +cmCMakePath cmCMakePath::GetWideExtension() const +{ + auto file = this->Path.filename().string(); + if (file.empty() || file == "." || file == "..") { + return cmCMakePath{}; + } + + auto pos = file.find('.', file[0] == '.' ? 1 : 0); + if (pos != std::string::npos) { + return cm::string_view(file.data() + pos, file.length() - pos); + } + + return cmCMakePath{}; +} + +cmCMakePath cmCMakePath::GetNarrowStem() const +{ + auto stem = this->Path.stem().string(); + if (!stem.empty()) { + auto pos = stem.find('.', stem[0] == '.' ? 1 : 0); + if (pos != std::string::npos) { + return stem.substr(0, pos); + } + } + return stem; +} + +cmCMakePath cmCMakePath::Absolute(const cm::filesystem::path& base) const +{ + if (this->Path.is_relative()) { + auto path = base; + path /= this->Path; + // filesystem::path::operator/= use preferred_separator ('\' on Windows) + // so converts back to '/' + return path.generic_string(); + } + return *this; +} + +bool cmCMakePath::IsPrefix(const cmCMakePath& path) const +{ + auto prefix_it = this->Path.begin(); + auto prefix_end = this->Path.end(); + auto path_it = path.Path.begin(); + auto path_end = path.Path.end(); + + while (prefix_it != prefix_end && path_it != path_end && + *prefix_it == *path_it) { + ++prefix_it; + ++path_it; + } + return prefix_it == prefix_end; +} + +std::string cmCMakePath::FormatPath(std::string path, format fmt) +{ +#if defined(_WIN32) + if (fmt == auto_format || fmt == native_format) { + auto prefix = path.substr(0, 4); + for (auto& c : prefix) { + if (c == '\\') { + c = '/'; + } + } + // remove Windows long filename marker + if (prefix == "//?/"_s) { + path.erase(0, 4); + } + if (cmHasPrefix(path, "UNC/"_s) || cmHasPrefix(path, "UNC\\"_s)) { + path.erase(0, 2); + path[0] = '/'; + } + } +#else + static_cast(fmt); +#endif + return path; +} + +void cmCMakePath::GetNativePath(std::string& path) const +{ + cm::filesystem::path tmp(this->Path); + tmp.make_preferred(); + + path = tmp.string(); +} +void cmCMakePath::GetNativePath(std::wstring& path) const +{ + cm::filesystem::path tmp(this->Path); + tmp.make_preferred(); + + path = tmp.wstring(); + +#if defined(_WIN32) + // Windows long filename + static std::wstring UNC(L"\\\\?\\UNC"); + static std::wstring PREFIX(L"\\\\?\\"); + + if (this->IsAbsolute() && path.length() > _MAX_PATH - 12) { + if (this->HasRootName() && path[0] == L'\\') { + path = UNC + path.substr(1); + } else { + path = PREFIX + path; + } + } +#endif +} diff --git a/Source/cmCMakePath.h b/Source/cmCMakePath.h new file mode 100644 index 0000000..15aa30c --- /dev/null +++ b/Source/cmCMakePath.h @@ -0,0 +1,571 @@ +/* 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 +#include +#include + +#include +#include +#include +#include + +namespace detail { +#if defined(__SUNPRO_CC) && defined(__sparc) +// Oracle DeveloperStudio C++ compiler on Solaris/Sparc fails to compile +// the full 'is_pathable' and 'is_move_pathable' checks. We use it only to +// improve error messages via 'enable_if' when calling methods with incorrect +// types. Just pretend all types are allowed so we can at least compile valid +// code. +template +struct is_pathable : std::true_type +{ +}; + +template +struct is_move_pathable : std::true_type +{ +}; + +#else +template +struct is_pathable : std::false_type +{ +}; + +template <> +struct is_pathable : std::true_type +{ +}; +template <> +struct is_pathable : std::true_type +{ +}; +template <> +struct is_pathable : std::true_type +{ +}; +template <> +struct is_pathable : std::true_type +{ +}; +template +struct is_pathable< + T, + cm::enable_if_t::type>::value, + void>> + : cm::bool_constant::type>::value> +{ +}; + +template +struct is_move_pathable : std::false_type +{ +}; + +template <> +struct is_move_pathable : std::true_type +{ +}; +template <> +struct is_move_pathable : std::true_type +{ +}; +#endif +} + +class cmCMakePath +{ +private: + template + using enable_if_move_pathable = + cm::enable_if_t::value, cmCMakePath&>; + + template + using enable_if_pathable = + cm::enable_if_t::value, cmCMakePath&>; + +public: + using value_type = cm::filesystem::path::value_type; + using string_type = cm::filesystem::path::string_type; + + enum format : unsigned char + { + auto_format = + static_cast(cm::filesystem::path::format::auto_format), + native_format = + static_cast(cm::filesystem::path::format::native_format), + generic_format = + static_cast(cm::filesystem::path::format::generic_format) + }; + + class iterator; + using const_iterator = iterator; + + cmCMakePath() noexcept = default; + + cmCMakePath(const cmCMakePath&) = default; + + cmCMakePath(cmCMakePath&& path) noexcept + : Path(std::forward(path.Path)) + { + } + + cmCMakePath(cm::filesystem::path path) noexcept + : Path(std::move(path)) + { + } + cmCMakePath(cm::string_view source, format fmt = generic_format) noexcept + : Path(FormatPath(source, fmt)) + { + } + template > + cmCMakePath(Source source, format fmt = generic_format) + : Path(FormatPath(std::move(source), fmt)) + { + } + + template > + cmCMakePath& Assign(Source&& source) + { + this->Path = std::forward(source); + return *this; + } + template > + cmCMakePath& Assign(const Source& source) + { + this->Path = source; + return *this; + } + + cmCMakePath& operator=(const cmCMakePath& path) + { + if (this != &path) { + this->Path = path.Path; + } + return *this; + } + cmCMakePath& operator=(cmCMakePath&& path) noexcept + { + if (this != &path) { + this->Path = std::move(path.Path); + } + return *this; + } + template > + cmCMakePath& operator=(Source&& source) + { + this->Assign(std::forward(source)); + return *this; + } + template > + cmCMakePath& operator=(const Source& source) + { + this->Assign(source); + return *this; + } + + // Concatenation + cmCMakePath& Append(const cmCMakePath& path) + { + return this->Append(path.Path); + } + cmCMakePath& Append(const cm::filesystem::path& path) + { + this->Path /= path; + // filesystem::path::append use preferred_separator ('\' on Windows) + // so convert back to '/' + this->Path = this->Path.generic_string(); + return *this; + } + + template > + cmCMakePath& Append(const Source& source) + { + return this->Append(cm::filesystem::path(source)); + } + + cmCMakePath& operator/=(const cmCMakePath& path) + { + return this->Append(path); + } + template > + cmCMakePath& operator/=(const Source& source) + { + return this->Append(source); + } + + cmCMakePath& Concat(const cmCMakePath& path) + { + this->Path += path.Path; + return *this; + } + cmCMakePath& Concat(cm::static_string_view source) + { + this->Path.concat(std::string(source)); + return *this; + } + template > + cmCMakePath& Concat(const Source& source) + { + this->Path.concat(source); + return *this; + } + + cmCMakePath& operator+=(const cmCMakePath& path) + { + return this->Concat(path); + } + template > + cmCMakePath& operator+=(const Source& source) + { + return this->Concat(source); + } + + // Manipulation + void Clear() noexcept { this->Path.clear(); } + + cmCMakePath& RemoveFileName() + { + this->Path.remove_filename(); + return *this; + } + + cmCMakePath& ReplaceFileName(const cmCMakePath& filename) + { + if (this->Path.has_filename()) { + this->Path.replace_filename(filename.Path); + } + return *this; + } + template > + cmCMakePath& ReplaceFileName(const Source& filename) + { + if (this->Path.has_filename()) { + this->Path.replace_filename(filename); + } + return *this; + } + + cmCMakePath& ReplaceExtension(const cmCMakePath& extension = cmCMakePath()) + { + this->Path.replace_extension(extension.Path); + return *this; + } + template > + cmCMakePath& ReplaceExtension(const Source& extension) + { + this->Path.replace_extension(extension); + return *this; + } + + cmCMakePath& ReplaceWideExtension( + const cmCMakePath& extension = cmCMakePath()) + { + return this->ReplaceWideExtension( + static_cast(extension.Path.string())); + } + template > + cmCMakePath& ReplaceWideExtension(const Source& extension) + { + return this->ReplaceWideExtension(cm::string_view(extension)); + } + cmCMakePath& ReplaceWideExtension(cm::string_view extension); + + cmCMakePath& RemoveExtension() + { + if (this->Path.has_extension()) { + this->ReplaceExtension(cm::string_view("")); + } + return *this; + } + + cmCMakePath& RemoveWideExtension() + { + if (this->Path.has_extension()) { + this->ReplaceWideExtension(cm::string_view("")); + } + return *this; + } + + void swap(cmCMakePath& other) noexcept { this->Path.swap(other.Path); } + + // Observers + std::string String() const { return this->Path.string(); } + std::wstring WString() const { return this->Path.wstring(); } + + string_type Native() const + { + string_type path; + this->GetNativePath(path); + + return path; + } + std::string NativeString() const + { + std::string path; + this->GetNativePath(path); + + return path; + } + std::wstring NativeWString() const + { + std::wstring path; + this->GetNativePath(path); + + return path; + } + std::string GenericString() const { return this->Path.generic_string(); } + std::wstring GenericWString() const { return this->Path.generic_wstring(); } + + // Decomposition + cmCMakePath GetRootName() const { return this->Path.root_name(); } + cmCMakePath GetRootDirectory() const { return this->Path.root_directory(); } + cmCMakePath GetRootPath() const { return this->Path.root_path(); } + cmCMakePath GetFileName() const { return this->Path.filename(); } + cmCMakePath GetExtension() const { return this->Path.extension(); } + cmCMakePath GetWideExtension() const; + cmCMakePath GetStem() const { return this->Path.stem(); } + cmCMakePath GetNarrowStem() const; + + cmCMakePath GetRelativePath() const { return this->Path.relative_path(); } + cmCMakePath GetParentPath() const { return this->Path.parent_path(); } + + // Generation + cmCMakePath Normal() const + { + auto path = this->Path.lexically_normal(); + // filesystem::path:lexically_normal use preferred_separator ('\') on + // Windows) so convert back to '/' + return path.generic_string(); + } + + cmCMakePath Relative(const cmCMakePath& base) const + { + return this->Relative(base.Path); + } + cmCMakePath Relative(const cm::filesystem::path& base) const + { + auto path = this->Path.lexically_relative(base); + // filesystem::path:lexically_relative use preferred_separator ('\') on + // Windows) so convert back to '/' + return path.generic_string(); + } + template > + cmCMakePath Relative(const Source& base) const + { + return this->Relative(cm::filesystem::path(base)); + } + + cmCMakePath Proximate(const cmCMakePath& base) const + { + return this->Proximate(base.Path); + } + cmCMakePath Proximate(const cm::filesystem::path& base) const + { + auto path = this->Path.lexically_proximate(base); + // filesystem::path::lexically_proximate use preferred_separator ('\') on + // Windows) so convert back to '/' + return path.generic_string(); + } + template > + cmCMakePath Proximate(const Source& base) const + { + return this->Proximate(cm::filesystem::path(base)); + } + + cmCMakePath Absolute(const cmCMakePath& base) const + { + return this->Absolute(base.Path); + } + template > + cmCMakePath Absolute(const Source& base) const + { + return this->Absolute(cm::filesystem::path(base)); + } + cmCMakePath Absolute(const cm::filesystem::path& base) const; + + // Comparison + int Compare(const cmCMakePath& path) const noexcept + { + return this->Path.compare(path.Path); + } + + // Query + bool IsEmpty() const noexcept { return this->Path.empty(); } + + bool HasRootPath() const { return this->Path.has_root_path(); } + bool HasRootName() const { return this->Path.has_root_name(); } + bool HasRootDirectory() const { return this->Path.has_root_directory(); } + bool HasRelativePath() const { return this->Path.has_relative_path(); } + bool HasParentPath() const { return this->Path.has_parent_path(); } + bool HasFileName() const { return this->Path.has_filename(); } + bool HasStem() const { return this->Path.has_stem(); } + bool HasExtension() const { return this->Path.has_extension(); } + + bool IsAbsolute() const { return this->Path.is_absolute(); } + bool IsRelative() const { return this->Path.is_relative(); } + bool IsPrefix(const cmCMakePath& path) const; + + // Iterators + // ========= + inline iterator begin() const; + inline iterator end() const; + + // Non-members + // =========== + friend inline bool operator==(const cmCMakePath& lhs, + const cmCMakePath& rhs) noexcept + { + return lhs.Compare(rhs) == 0; + } + friend inline bool operator!=(const cmCMakePath& lhs, + const cmCMakePath& rhs) noexcept + { + return lhs.Compare(rhs) != 0; + } + + friend inline cmCMakePath operator/(const cmCMakePath& lhs, + const cmCMakePath& rhs) + { + cmCMakePath result(lhs); + result /= rhs; + + return result; + } + +private: + friend std::size_t hash_value(const cmCMakePath& path) noexcept; + + static std::string FormatPath(std::string path, format fmt = generic_format); + static std::string FormatPath(cm::string_view path, + format fmt = generic_format) + { + return FormatPath(std::string(path), fmt); + } + + void GetNativePath(std::string& path) const; + void GetNativePath(std::wstring& path) const; + + cm::filesystem::path Path; +}; + +class cmCMakePath::iterator +{ +public: + using iterator_category = cm::filesystem::path::iterator::iterator_category; + + using value_type = cmCMakePath; + using difference_type = cm::filesystem::path::iterator::difference_type; + using pointer = const cmCMakePath*; + using reference = const cmCMakePath&; + + iterator() = default; + + iterator(const iterator& other) + : Iterator(other.Iterator) + , Path(other.Path) + , PathElement(*this->Iterator) + { + } + + ~iterator() = default; + + iterator& operator=(const iterator& other) + { + if (this != &other) { + this->Iterator = other.Iterator; + this->Path = other.Path; + this->PathElement = *this->Iterator; + } + + return *this; + } + + reference operator*() const { return this->PathElement; } + + pointer operator->() const { return &this->PathElement; } + + iterator& operator++() + { + ++this->Iterator; + this->PathElement = *this->Iterator; + + return *this; + } + + iterator operator++(int) + { + iterator it(*this); + this->operator++(); + return it; + } + + iterator& operator--() + { + --this->Iterator; + this->PathElement = *this->Iterator; + + return *this; + } + + iterator operator--(int) + { + iterator it(*this); + this->operator--(); + return it; + } + +private: + friend class cmCMakePath; + friend bool operator==(const iterator&, const iterator&); + + iterator(const cmCMakePath* path, const cm::filesystem::path::iterator& it) + : Iterator(it) + , Path(path) + , PathElement(*this->Iterator) + { + } + + cm::filesystem::path::iterator Iterator; + const cmCMakePath* Path = nullptr; + cmCMakePath PathElement; +}; + +inline cmCMakePath::iterator cmCMakePath::begin() const +{ + return iterator(this, this->Path.begin()); +} +inline cmCMakePath::iterator cmCMakePath::end() const +{ + return iterator(this, this->Path.end()); +} + +// Non-member functions +// ==================== +inline bool operator==(const cmCMakePath::iterator& lhs, + const cmCMakePath::iterator& rhs) +{ + return lhs.Path == rhs.Path && lhs.Path != nullptr && + lhs.Iterator == rhs.Iterator; +} + +inline bool operator!=(const cmCMakePath::iterator& lhs, + const cmCMakePath::iterator& rhs) +{ + return !(lhs == rhs); +} + +inline void swap(cmCMakePath& lhs, cmCMakePath& rhs) noexcept +{ + lhs.swap(rhs); +} + +inline std::size_t hash_value(const cmCMakePath& path) noexcept +{ + return cm::filesystem::hash_value(path.Path); +} diff --git a/Utilities/std/cm/bits/fs_path.cxx b/Utilities/std/cm/bits/fs_path.cxx index 8089998..1f52878 100644 --- a/Utilities/std/cm/bits/fs_path.cxx +++ b/Utilities/std/cm/bits/fs_path.cxx @@ -847,7 +847,7 @@ cm::string_view path::get_filename_fragment(filename_fragment fragment) const { auto file = this->get_filename(); - if (file == "." || file == ".." || file.empty()) { + if (file.empty() || file == "." || file == "..") { return fragment == filename_fragment::stem ? file : cm::string_view{}; } diff --git a/Utilities/std/cm/filesystem b/Utilities/std/cm/filesystem index cb05b22..6cbdea9 100644 --- a/Utilities/std/cm/filesystem +++ b/Utilities/std/cm/filesystem @@ -1150,6 +1150,8 @@ inline path::iterator path::end() const return iterator(this, true); } +// Non-member functions +// ==================== bool operator==(const path::iterator& lhs, const path::iterator& rhs); inline bool operator!=(const path::iterator& lhs, const path::iterator& rhs) @@ -1157,8 +1159,6 @@ inline bool operator!=(const path::iterator& lhs, const path::iterator& rhs) return !(lhs == rhs); } -// Non-member functions -// ==================== inline void swap(path& lhs, path& rhs) noexcept { lhs.swap(rhs); diff --git a/bootstrap b/bootstrap index faade05..8654189 100755 --- a/bootstrap +++ b/bootstrap @@ -288,6 +288,7 @@ CMAKE_CXX_SOURCES="\ cmBreakCommand \ cmBuildCommand \ cmCMakeMinimumRequired \ + cmCMakePath \ cmCMakePolicyCommand \ cmCPackPropertiesGenerator \ cmCacheManager \ -- cgit v0.12