mirror of
https://gitlab.kitware.com/cmake/cmake.git
synced 2026-09-25 04:09:36 +03:00
cmake_path: An empty path is not a prefix of any path
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
This commit is contained in:
committed by
Brad King
parent
34bbc91e3b
commit
7192a2b798
@@ -456,6 +456,12 @@ meaning of each path component.
|
||||
When the ``NORMALIZE`` option is specified, ``<path-var>`` and ``<input>``
|
||||
are :ref:`normalized <Normalization>` before the check.
|
||||
|
||||
An empty path is not a prefix of any path.
|
||||
|
||||
.. versionchanged:: 4.5
|
||||
An empty path was previously a prefix of every path. See policy
|
||||
:policy:`CMP0223`.
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
set(path "/a/b/c")
|
||||
|
||||
@@ -459,6 +459,9 @@ Path Comparisons
|
||||
Normalize with :command:`cmake_path(NORMAL_PATH)` first if that must be
|
||||
rejected.
|
||||
|
||||
An empty prefix is not a prefix of any path. See policy
|
||||
:policy:`CMP0223`.
|
||||
|
||||
Equivalent to :command:`cmake_path(IS_PREFIX)` and
|
||||
``$<PATH:IS_PREFIX>`` without their ``NORMALIZE`` option. See
|
||||
:command:`cmake_path(IS_PREFIX)` for more details.
|
||||
|
||||
@@ -1236,6 +1236,12 @@ All paths are expected to be in cmake-style format.
|
||||
When the ``NORMALIZE`` option is specified, ``path`` and ``input`` are
|
||||
:ref:`normalized <Normalization>` before the check.
|
||||
|
||||
An empty ``path`` is not a prefix of any ``input``.
|
||||
|
||||
.. versionchanged:: 4.5
|
||||
An empty ``path`` was previously a prefix of every ``input``. See
|
||||
policy :policy:`CMP0223`.
|
||||
|
||||
.. _GenEx Path Decomposition:
|
||||
|
||||
Path Decomposition
|
||||
|
||||
@@ -100,6 +100,7 @@ Policies Introduced by CMake 4.5
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
CMP0223: An empty path is not a prefix of any path. </policy/CMP0223>
|
||||
CMP0222: The if() command supports path prefix tests using PATH_IS_PREFIX operator. </policy/CMP0222>
|
||||
CMP0221: cmake_host_system_information() DISTRIB_* queries read the host os-release. </policy/CMP0221>
|
||||
CMP0220: Languages enabled in subdirectories propagate to the top-level directory. </policy/CMP0220>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
CMP0223
|
||||
-------
|
||||
|
||||
.. versionadded:: 4.5
|
||||
|
||||
An empty path is not a prefix of any path.
|
||||
|
||||
:command:`cmake_path(IS_PREFIX)` and the ``$<PATH:IS_PREFIX>`` generator
|
||||
expression treat an empty path as a prefix of every path, including
|
||||
another empty 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 satisfies a check that was meant to reject it.
|
||||
|
||||
The :command:`if` command's ``PATH_IS_PREFIX`` operator is new in the same
|
||||
release and has no previous behavior of its own, but it follows this
|
||||
policy so that it agrees with :command:`cmake_path(IS_PREFIX)` in every
|
||||
policy state.
|
||||
|
||||
The ``OLD`` behavior for this policy is to treat an empty path as a prefix
|
||||
of every path. The ``NEW`` behavior is to treat an empty path as a prefix
|
||||
of no path.
|
||||
|
||||
.. |INTRODUCED_IN_CMAKE_VERSION| replace:: 4.5
|
||||
.. |WARNS_OR_DOES_NOT_WARN| replace:: warns
|
||||
.. include:: include/STANDARD_ADVICE.rst
|
||||
|
||||
.. include:: include/DEPRECATED.rst
|
||||
@@ -0,0 +1,8 @@
|
||||
cmake_path-IS_PREFIX-empty
|
||||
--------------------------
|
||||
|
||||
* The :command:`cmake_path(IS_PREFIX)` command and the
|
||||
:genex:`$<PATH:IS_PREFIX>` generator expression no longer treat
|
||||
an empty path as a prefix of every path. The :command:`if` command's
|
||||
``PATH_IS_PREFIX`` operator follows the same rule.
|
||||
See policy :policy:`CMP0223`.
|
||||
@@ -83,6 +83,11 @@ cmCMakePath cmCMakePath::Absolute(cm::filesystem::path const& base) const
|
||||
|
||||
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();
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "cmExecutionStatus.h"
|
||||
#include "cmList.h"
|
||||
#include "cmMakefile.h"
|
||||
#include "cmPolicies.h"
|
||||
#include "cmRange.h"
|
||||
#include "cmStringAlgorithms.h"
|
||||
#include "cmSubcommandTable.h"
|
||||
@@ -839,6 +840,24 @@ bool HandleIsRelativeCommand(std::vector<std::string> const& args,
|
||||
return true;
|
||||
}
|
||||
|
||||
// CMP0223: an empty path used to be a prefix of every path.
|
||||
bool IsPrefixCMP0223(cmCMakePath const& prefix, cmMakefile& mf)
|
||||
{
|
||||
if (!prefix.IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
switch (mf.GetPolicyStatus(cmPolicies::CMP0223)) {
|
||||
case cmPolicies::WARN:
|
||||
mf.IssuePolicyWarning(cmPolicies::CMP0223);
|
||||
CM_FALLTHROUGH;
|
||||
case cmPolicies::OLD:
|
||||
return true;
|
||||
case cmPolicies::NEW:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HandleIsPrefixCommand(std::vector<std::string> const& args,
|
||||
cmExecutionStatus& status)
|
||||
{
|
||||
@@ -869,14 +888,16 @@ bool HandleIsPrefixCommand(std::vector<std::string> const& args,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isPrefix;
|
||||
cmCMakePath prefix{ inputPath };
|
||||
cmCMakePath value{ input };
|
||||
if (arguments.Normalize) {
|
||||
isPrefix =
|
||||
cmCMakePath(inputPath).Normal().IsPrefix(cmCMakePath(input).Normal());
|
||||
} else {
|
||||
isPrefix = cmCMakePath(inputPath).IsPrefix(input);
|
||||
prefix = prefix.Normal();
|
||||
value = value.Normal();
|
||||
}
|
||||
|
||||
bool const isPrefix =
|
||||
prefix.IsPrefix(value) || IsPrefixCMP0223(prefix, status.GetMakefile());
|
||||
|
||||
status.GetMakefile().AddDefinitionBool(output, isPrefix);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -117,6 +117,24 @@ bool looksLikeSpecialVariable(std::string const& var,
|
||||
return ((prefix.size() + 3) <= varNameLen) &&
|
||||
cmHasPrefix(var, cmStrCat(prefix, '{')) && var[varNameLen - 1] == '}';
|
||||
}
|
||||
|
||||
// CMP0223: an empty path used to be a prefix of every path.
|
||||
bool IsPrefixCMP0223(cmCMakePath const& prefix, cmMakefile& mf)
|
||||
{
|
||||
if (!prefix.IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
switch (mf.GetPolicyStatus(cmPolicies::CMP0223)) {
|
||||
case cmPolicies::WARN:
|
||||
mf.IssuePolicyWarning(cmPolicies::CMP0223);
|
||||
CM_FALLTHROUGH;
|
||||
case cmPolicies::OLD:
|
||||
return true;
|
||||
case cmPolicies::NEW:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
#if defined(__SUNPRO_CC)
|
||||
@@ -689,7 +707,9 @@ bool cmConditionEvaluator::HandleLevel2(cmArgumentList& newArgs,
|
||||
|
||||
cmValue lhs = this->GetVariableOrString(*args.current);
|
||||
cmValue rhs = this->GetVariableOrString(*args.nextnext);
|
||||
auto const result = cmCMakePath{ *lhs }.IsPrefix(cmCMakePath{ *rhs });
|
||||
cmCMakePath const prefix{ *lhs };
|
||||
auto const result = prefix.IsPrefix(cmCMakePath{ *rhs }) ||
|
||||
IsPrefixCMP0223(prefix, this->Makefile);
|
||||
newArgs.ReduceTwoArgs(result, args);
|
||||
}
|
||||
|
||||
|
||||
@@ -964,6 +964,25 @@ bool GetNumericArguments(
|
||||
return true;
|
||||
}
|
||||
|
||||
// CMP0223: an empty path used to be a prefix of every path.
|
||||
bool IsPrefixCMP0223(cmCMakePath const& prefix, cm::GenEx::Evaluation* eval)
|
||||
{
|
||||
if (!prefix.IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
cmLocalGenerator const* const lg = eval->Context.LG;
|
||||
switch (lg->GetPolicyStatus(cmPolicies::CMP0223)) {
|
||||
case cmPolicies::WARN:
|
||||
lg->IssuePolicyWarning(cmPolicies::CMP0223, {}, {}, eval->Backtrace);
|
||||
CM_FALLTHROUGH;
|
||||
case cmPolicies::OLD:
|
||||
return true;
|
||||
case cmPolicies::NEW:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CheckPathParametersEx(cm::GenEx::Evaluation* eval,
|
||||
GeneratorExpressionContent const* cnt,
|
||||
cm::string_view option, std::size_t count,
|
||||
@@ -1208,12 +1227,14 @@ static const struct PathNode : public cmGeneratorExpressionNode
|
||||
if (CheckPathParametersEx(
|
||||
ev, cnt, normalize ? "IS_PREFIX,NORMALIZE"_s : "IS_PREFIX"_s,
|
||||
args.size(), 2)) {
|
||||
cmCMakePath prefix{ args[0] };
|
||||
cmCMakePath value{ args[1] };
|
||||
if (normalize) {
|
||||
return ToString(cmCMakePath{ args[0] }.Normal().IsPrefix(
|
||||
cmCMakePath{ args[1] }.Normal()));
|
||||
prefix = prefix.Normal();
|
||||
value = value.Normal();
|
||||
}
|
||||
return ToString(
|
||||
cmCMakePath{ args[0] }.IsPrefix(cmCMakePath{ args[1] }));
|
||||
return ToString(prefix.IsPrefix(value) ||
|
||||
IsPrefixCMP0223(prefix, ev));
|
||||
}
|
||||
return std::string{};
|
||||
} },
|
||||
|
||||
+3
-1
@@ -670,7 +670,9 @@ class cmMakefile;
|
||||
SELECT(POLICY, CMP0222, \
|
||||
"The if() command supports path prefix tests using " \
|
||||
"PATH_IS_PREFIX operator.", \
|
||||
4, 5, 0, WARN)
|
||||
4, 5, 0, WARN) \
|
||||
SELECT(POLICY, CMP0223, "An empty path is not a prefix of any path.", 4, 5, \
|
||||
0, WARN)
|
||||
|
||||
#define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
|
||||
#define CM_FOR_EACH_POLICY_ID(POLICY) \
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
cmake_policy(SET CMP0223 NEW)
|
||||
|
||||
set(prefix "")
|
||||
cmake_path(IS_PREFIX prefix "/a/b" output)
|
||||
if(output)
|
||||
message(SEND_ERROR "empty prefix is a prefix of '/a/b' under NEW")
|
||||
endif()
|
||||
|
||||
cmake_path(IS_PREFIX prefix "" output)
|
||||
if(output)
|
||||
message(SEND_ERROR "empty prefix is a prefix of the empty path under NEW")
|
||||
endif()
|
||||
|
||||
# NORMALIZE takes the same path, because normalizing an empty path leaves
|
||||
# it empty.
|
||||
cmake_path(IS_PREFIX prefix "/a/b" NORMALIZE output)
|
||||
if(output)
|
||||
message(SEND_ERROR "empty prefix is a prefix of '/a/b' under NEW, NORMALIZE")
|
||||
endif()
|
||||
|
||||
# A non-empty prefix is unaffected.
|
||||
set(prefix "/a")
|
||||
cmake_path(IS_PREFIX prefix "/a/b" output)
|
||||
if(NOT output)
|
||||
message(SEND_ERROR "'/a' is not a prefix of '/a/b' under NEW")
|
||||
endif()
|
||||
@@ -0,0 +1,12 @@
|
||||
cmake_policy(SET CMP0223 OLD)
|
||||
|
||||
set(prefix "")
|
||||
cmake_path(IS_PREFIX prefix "/a/b" output)
|
||||
if(NOT output)
|
||||
message(SEND_ERROR "empty prefix is not a prefix of '/a/b' under OLD")
|
||||
endif()
|
||||
|
||||
cmake_path(IS_PREFIX prefix "" output)
|
||||
if(NOT output)
|
||||
message(SEND_ERROR "empty prefix is not a prefix of the empty path under OLD")
|
||||
endif()
|
||||
@@ -0,0 +1,8 @@
|
||||
CMake Warning \(policy\) at CMP0223-WARN\.cmake:[0-9]+ \(cmake_path\):
|
||||
Policy CMP0223 is not set: An empty path is not a prefix of any path\. Run
|
||||
"cmake --help-policy CMP0223" for policy details\. Use the cmake_policy
|
||||
command to set the policy and suppress this warning\.
|
||||
Call Stack \(most recent call first\):
|
||||
CMakeLists\.txt:[0-9]+ \(include\)
|
||||
This warning is for project developers\. Use -Wno-author or -Wno-policy to
|
||||
suppress it\.
|
||||
@@ -0,0 +1,6 @@
|
||||
# Policy deliberately not set, so the warning fires and OLD behavior applies.
|
||||
set(prefix "")
|
||||
cmake_path(IS_PREFIX prefix "/a/b" output)
|
||||
if(NOT output)
|
||||
message(SEND_ERROR "empty prefix is not a prefix of '/a/b' under WARN")
|
||||
endif()
|
||||
@@ -0,0 +1,5 @@
|
||||
file(READ "${RunCMake_TEST_BINARY_DIR}/result.txt" generated)
|
||||
set(expected "empty=0 normalize=0 nonempty=1")
|
||||
if(NOT generated STREQUAL expected)
|
||||
set(RunCMake_TEST_FAILED "generated: ${generated}\nexpected: ${expected}")
|
||||
endif()
|
||||
@@ -0,0 +1 @@
|
||||
include(CMP0223-genex-common.cmake)
|
||||
@@ -0,0 +1,5 @@
|
||||
file(READ "${RunCMake_TEST_BINARY_DIR}/result.txt" generated)
|
||||
set(expected "empty=1 normalize=1 nonempty=1")
|
||||
if(NOT generated STREQUAL expected)
|
||||
set(RunCMake_TEST_FAILED "generated: ${generated}\nexpected: ${expected}")
|
||||
endif()
|
||||
@@ -0,0 +1 @@
|
||||
include(CMP0223-genex-common.cmake)
|
||||
@@ -0,0 +1,5 @@
|
||||
file(READ "${RunCMake_TEST_BINARY_DIR}/result.txt" generated)
|
||||
set(expected "empty=1 normalize=1 nonempty=1")
|
||||
if(NOT generated STREQUAL expected)
|
||||
set(RunCMake_TEST_FAILED "generated: ${generated}\nexpected: ${expected}")
|
||||
endif()
|
||||
@@ -0,0 +1,4 @@
|
||||
CMake Warning \(policy\) at CMP0223-genex-common\.cmake:[0-9]+ \(file\):
|
||||
Policy CMP0223 is not set: An empty path is not a prefix of any path\. Run
|
||||
"cmake --help-policy CMP0223" for policy details\. Use the cmake_policy
|
||||
command to set the policy and suppress this warning\.
|
||||
@@ -0,0 +1 @@
|
||||
include(CMP0223-genex-common.cmake)
|
||||
@@ -0,0 +1,2 @@
|
||||
file(GENERATE OUTPUT "result.txt" CONTENT
|
||||
"empty=$<PATH:IS_PREFIX,,/a/b> normalize=$<PATH:IS_PREFIX,NORMALIZE,,/a/b> nonempty=$<PATH:IS_PREFIX,/a,/a/b>")
|
||||
@@ -0,0 +1,8 @@
|
||||
CMake Warning \(policy\) at CMP0223-if-WARN\.cmake:[0-9]+ \(if\):
|
||||
Policy CMP0223 is not set: An empty path is not a prefix of any path\. Run
|
||||
"cmake --help-policy CMP0223" for policy details\. Use the cmake_policy
|
||||
command to set the policy and suppress this warning\.
|
||||
Call Stack \(most recent call first\):
|
||||
CMakeLists\.txt:[0-9]+ \(include\)
|
||||
This warning is for project developers\. Use -Wno-author or -Wno-policy to
|
||||
suppress it\.
|
||||
@@ -0,0 +1,6 @@
|
||||
cmake_policy(SET CMP0222 NEW)
|
||||
|
||||
# CMP0223 deliberately not set, so the warning fires and OLD behavior applies.
|
||||
if(NOT "" PATH_IS_PREFIX "/a/b")
|
||||
message(SEND_ERROR "empty prefix is not a prefix of '/a/b' under WARN")
|
||||
endif()
|
||||
@@ -0,0 +1,3 @@
|
||||
cmake_minimum_required(VERSION 3.23)
|
||||
project(${RunCMake_TEST} NONE)
|
||||
include(${RunCMake_TEST}.cmake)
|
||||
@@ -0,0 +1,11 @@
|
||||
include(RunCMake)
|
||||
|
||||
run_cmake(CMP0223-OLD)
|
||||
run_cmake(CMP0223-WARN)
|
||||
run_cmake(CMP0223-NEW)
|
||||
|
||||
run_cmake_with_options(CMP0223-genex-OLD -DCMAKE_POLICY_DEFAULT_CMP0223=OLD)
|
||||
run_cmake_with_options(CMP0223-genex-NEW -DCMAKE_POLICY_DEFAULT_CMP0223=NEW)
|
||||
run_cmake(CMP0223-genex-WARN)
|
||||
|
||||
run_cmake(CMP0223-if-WARN)
|
||||
@@ -187,6 +187,7 @@ endif()
|
||||
add_RunCMake_test(CMP0217)
|
||||
add_RunCMake_test(CMP0219)
|
||||
add_RunCMake_test(CMP0222)
|
||||
add_RunCMake_test(CMP0223)
|
||||
|
||||
if(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
|
||||
add_RunCMake_test(CMP0194 -DCMAKE_C_COMPILER_VERSION=${CMAKE_C_COMPILER_VERSION})
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
cmake_policy(SET CMP0223 NEW)
|
||||
|
||||
include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
|
||||
unset (errors)
|
||||
@@ -99,15 +100,15 @@ if (NOT output)
|
||||
list (APPEND errors "'${prefix}' is not prefix of './a/b'")
|
||||
endif()
|
||||
|
||||
# The empty path is a prefix of every path, including itself.
|
||||
# The empty path is not a prefix of any path, including itself.
|
||||
set (prefix "")
|
||||
cmake_path(IS_PREFIX prefix "/a/b" output)
|
||||
if (NOT output)
|
||||
list (APPEND errors "the empty path is not prefix of '/a/b'")
|
||||
if (output)
|
||||
list (APPEND errors "the empty path is a prefix of '/a/b'")
|
||||
endif()
|
||||
cmake_path(IS_PREFIX prefix "" output)
|
||||
if (NOT output)
|
||||
list (APPEND errors "the empty path is not prefix of itself")
|
||||
if (output)
|
||||
list (APPEND errors "the empty path is a prefix of itself")
|
||||
endif()
|
||||
set (prefix "/a")
|
||||
cmake_path(IS_PREFIX prefix "" output)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
cmake_policy(SET CMP0222 NEW)
|
||||
cmake_policy(SET CMP0223 NEW)
|
||||
|
||||
# The operator is an if() spelling of cmake_path(IS_PREFIX), so assert that
|
||||
# the two agree rather than repeating a table of expected values here. What
|
||||
|
||||
Reference in New Issue
Block a user