mirror of
https://gitlab.kitware.com/cmake/cmake.git
synced 2026-09-25 04:09:36 +03:00
cmake_path(IS_PREFIX) and $<PATH:IS_PREFIX> treated an empty path as a prefix of every path, including another empty path, following std::filesystem::path. A prefix that is empty because a variable was set to an empty value, or because a generator expression argument expanded to nothing, therefore satisfied a check meant to reject it. Return false for an empty prefix, in cmCMakePath::IsPrefix so that every caller shares one implementation and both the plain and NORMALIZE forms are covered. Normalizing an empty path leaves it empty, so no separate handling of NORMALIZE is needed. Unlike the component comparison, which follows std::filesystem::path deliberately, IsPrefix has no counterpart in the standard: it borrows path iteration but the predicate itself is defined by CMake, so an empty prefix is a gap to fill rather than a standard answer to override. Add policy CMP0223 and restore the old result behind it at the two released surfaces. The other callers of IsPrefix, source_group() and the Makefile generator's source classification, take the new behavior ungated: source_group() rejects an empty TREE argument before reaching it, and the generator passes the source and binary directories. The if(PATH_IS_PREFIX) operator, new in this same release, follows the policy too rather than simply taking the new behavior, so that it agrees with cmake_path(IS_PREFIX) in every policy state and the parity assertions in its test hold unconditionally. Fixes: #28077
158 lines
3.6 KiB
C++
158 lines
3.6 KiB
C++
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
file LICENSE.rst or https://cmake.org/licensing for details. */
|
|
|
|
#include "cmConfigure.h" // IWYU pragma: keep
|
|
|
|
#include "cmCMakePath.h"
|
|
|
|
#include <string>
|
|
|
|
#include "cmStringAlgorithms.h"
|
|
|
|
#if defined(_WIN32)
|
|
# include <cstdlib>
|
|
#endif
|
|
|
|
#include <cm/filesystem>
|
|
#include <cm/string_view>
|
|
|
|
#if defined(_WIN32)
|
|
# include <cmext/string_view>
|
|
|
|
#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 = cmStrCat(std::move(file), 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() || stem == "." || stem == "..") {
|
|
return stem;
|
|
}
|
|
|
|
auto pos = stem.find('.', stem[0] == '.' ? 1 : 0);
|
|
if (pos != std::string::npos) {
|
|
return stem.substr(0, pos);
|
|
}
|
|
return stem;
|
|
}
|
|
|
|
cmCMakePath cmCMakePath::Absolute(cm::filesystem::path const& 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(cmCMakePath const& path) const
|
|
{
|
|
// An empty path is not a prefix of any path, including another empty path.
|
|
if (this->Path.empty()) {
|
|
return false;
|
|
}
|
|
|
|
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) ||
|
|
(prefix_it->empty() && path_it != path_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<void>(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
|
|
}
|