macro: Add policy to preserve backslashes in arguments

Preserve backslashes in arguments passed to macros by re-escaping
them before substituting in the macro body, where they will be
parsed again.

Fixes: #19281, #22157, #23641, #25803, #27645, #20891
This commit is contained in:
Alex Reinking
2026-05-31 14:36:15 -04:00
parent d0deabd995
commit 9d2e85373a
54 changed files with 771 additions and 7 deletions
+1
View File
@@ -100,6 +100,7 @@ Policies Introduced by CMake 4.4
.. toctree::
:maxdepth: 1
CMP0219: Macro invocations preserve backslashes in arguments. </policy/CMP0219>
CMP0218: The CMAKE_WARN_DEPRECATED and CMAKE_ERROR_DEPRECATED variables are ignored. </policy/CMP0218>
CMP0217: The MACROS directory property does not exist anymore. </policy/CMP0217>
CMP0216: Swift targets have a default project name. </policy/CMP0216>
+103
View File
@@ -0,0 +1,103 @@
CMP0219
-------
.. versionadded:: 4.4
:command:`macro` invocations preserve backslashes in arguments.
In CMake 4.3 and below, macro argument references (``${ARGN}``, ``${ARGV}``,
``${ARGV<n>}``, and named macro arguments) are substituted textually and then
evaluated by the called command. Backslashes in substituted values are
re-evaluated as escape prefixes, and invalid escape sequences (such as
``\b`` in Windows paths) can cause errors. The same re-evaluation can also
occur for arguments passed to a :command:`variable_watch` callback command.
CMake 4.4 and above prefer to preserve literal backslashes in those macro
argument references.
The ``OLD`` behavior for this policy is to interpret escape sequences in macro
argument references and in arguments passed to ``variable_watch()`` callback
commands. The ``NEW`` behavior is to preserve backslashes in those values
before command invocation.
This policy applies to macro argument references and to arguments passed to
``variable_watch()`` callback commands. Other variable references keep their
existing behavior.
Mixed Policy Chains
^^^^^^^^^^^^^^^^^^^
Whether pre-escaping occurs is determined by the policy setting at each
call site. When a project and its dependencies use different ``CMP0219``
settings, each macro call in the chain follows the policy where that call
appears.
Consider a project (``CMP0219`` is ``OLD``) that calls a macro from
dependency A (upgraded to
:command:`cmake_minimum_required(VERSION 4.4) <cmake_minimum_required>`,
so ``CMP0219`` is ``NEW``), which in turn forwards arguments to a macro
from dependency B (not yet upgraded, so ``CMP0219`` is ``OLD``):
.. code-block:: cmake
# Dependency B: cmake_minimum_required(VERSION 3.24)
# CMP0219 is OLD inside dependency B.
macro(depB_store var_name)
set(${var_name} "${ARGN}")
endmacro()
# Dependency A: cmake_minimum_required(VERSION 4.4)
# CMP0219 is NEW inside dependency A.
macro(depA_forward var_name)
depB_store(${var_name} ${ARGN}) # call site is NEW -> pre-escapes
endmacro()
# Project: cmake_minimum_required(VERSION 3.24)
# CMP0219 is OLD in the project.
macro(my_wrapper var_name)
depA_forward(${var_name} ${ARGN}) # call site is OLD -> no pre-escaping
endmacro()
In a fully ``OLD`` chain, each macro boundary re-evaluates backslash
escapes, so callers must add extra escaping layers to compensate.
To pass the Windows path ``C:\build\new\temp`` through three ``OLD``
boundaries, the caller needs eight backslashes per separator:
.. code-block:: cmake
my_wrapper(result "C:\\\\\\\\build\\\\\\\\new\\\\\\\\temp")
message("${result}") # -> C:\build\new\temp
When dependency A upgrades and the middle boundary becomes ``NEW``,
that boundary preserves backslashes instead of consuming a level.
The same input now retains an extra layer:
.. code-block:: cmake
# Same 8x-escaped input through OLD -> NEW -> OLD:
my_wrapper(result "C:\\\\\\\\build\\\\\\\\new\\\\\\\\temp")
message("${result}") # -> C:\\build\\new\\temp (one extra layer)
The caller must then use half the escaping to reach the native path:
.. code-block:: cmake
# Only 4x-escaped input needed for OLD -> NEW -> OLD:
my_wrapper(result "C:\\\\build\\\\new\\\\temp")
message("${result}") # -> C:\build\new\temp
When a dependency updates its :command:`cmake_minimum_required` version
to 4.4 or above, call sites inside that dependency begin pre-escaping.
Callers that previously added extra backslash layers to compensate for
multiple levels of ``OLD`` re-evaluation may then need fewer escape
layers.
Where possible, converting paths to forward slashes with
:command:`cmake_path(CONVERT ... TO_CMAKE_PATH_LIST)` avoids the issue entirely, because
forward slashes require no escaping regardless of the policy setting.
.. |INTRODUCED_IN_CMAKE_VERSION| replace:: 4.4
.. |WARNS_OR_DOES_NOT_WARN| replace:: warns when backslashes are present
.. include:: include/STANDARD_ADVICE.rst
.. include:: include/DEPRECATED.rst
@@ -0,0 +1,7 @@
macro-argument-escape
---------------------
* :command:`macro` invocations now preserve backslashes in arguments.
See policy :policy:`CMP0219`.
* :command:`variable_watch` callback command arguments preserve backslashes.
See policy :policy:`CMP0219`.
+19 -3
View File
@@ -63,9 +63,6 @@ bool cmMacroHelperCommand::operator()(
return false;
}
cmMakefile::MacroPushPop macroScope(&makefile, this->FilePath,
this->Policies, this->Diagnostics);
// set the value of argc
std::string argcDef = std::to_string(expandedArgs.size());
@@ -73,6 +70,21 @@ bool cmMacroHelperCommand::operator()(
std::string expandedArgn =
cmList::to_string(cmMakeRange(expIt, expandedArgs.end()));
std::string expandedArgv = cmList::to_string(expandedArgs);
{
cmPolicies::PolicyStatus cmp0219 =
makefile.CheckCMP0219(this->Args[0], expandedArgs);
if (cmp0219 == cmPolicies::NEW) {
// Escape macro argument backslashes so that callees receive the same
// values the caller had.
for (std::string& expandedArg : expandedArgs) {
cmSystemTools::ReplaceString(expandedArg, "\\", "\\\\");
}
cmSystemTools::ReplaceString(expandedArgn, "\\", "\\\\");
cmSystemTools::ReplaceString(expandedArgv, "\\", "\\\\");
} else if (cmp0219 == cmPolicies::WARN) {
makefile.IssueCMP0219Warning(this->Args[0], expandedArgs);
}
}
std::vector<std::string> variables;
variables.reserve(this->Args.size() - 1);
for (unsigned int j = 1; j < this->Args.size(); ++j) {
@@ -83,6 +95,10 @@ bool cmMacroHelperCommand::operator()(
for (unsigned int j = 0; j < expandedArgs.size(); ++j) {
argVs.emplace_back(cmStrCat("${ARGV", j, '}'));
}
cmMakefile::MacroPushPop macroScope(&makefile, this->FilePath,
this->Policies, this->Diagnostics);
// Invoke all the functions that were collected in the block.
// for each function
for (cmListFileFunction const& func : this->Functions) {
+77
View File
@@ -2625,6 +2625,83 @@ cm::optional<std::string> cmMakefile::DeferGetCall(std::string const& id) const
return call;
}
namespace {
cmPolicies::PolicyStatus CheckCMP0219Impl(cmPolicies::PolicyStatus status,
std::string const& calleeName,
bool hasBackslashes,
std::set<std::string>& warned)
{
if (status == cmPolicies::WARN && hasBackslashes &&
warned.insert(calleeName).second) {
return cmPolicies::WARN;
}
// Suppress WARN when there are no backslashes or already warned.
return status == cmPolicies::NEW ? cmPolicies::NEW : cmPolicies::OLD;
}
}
cmPolicies::PolicyStatus cmMakefile::CheckCMP0219(
std::string const& calleeName, std::vector<std::string> const& args)
{
bool const hasBackslashes =
std::any_of(args.begin(), args.end(), [](std::string const& s) {
return s.find('\\') != std::string::npos;
});
return CheckCMP0219Impl(GetPolicyStatus(cmPolicies::CMP0219), calleeName,
hasBackslashes, WarnedCMP0219);
}
cmPolicies::PolicyStatus cmMakefile::CheckCMP0219(
std::string const& calleeName, std::vector<cmListFileArgument> const& args)
{
bool const hasBackslashes =
std::any_of(args.begin(), args.end(), [](cmListFileArgument const& s) {
return s.Value.find('\\') != std::string::npos;
});
return CheckCMP0219Impl(GetPolicyStatus(cmPolicies::CMP0219), calleeName,
hasBackslashes, WarnedCMP0219);
}
void cmMakefile::IssueCMP0219Warning(
std::string const& calleeName, std::vector<std::string> const& args) const
{
std::string oldArgs;
for (std::string const& arg : args) {
if (arg.find('\\') == std::string::npos) {
continue;
}
if (!oldArgs.empty()) {
oldArgs += '\n';
}
oldArgs += cmStrCat(" \"", arg, '"');
}
std::string newArgs = oldArgs;
cmSystemTools::ReplaceString(newArgs, "\\", "\\\\");
this->IssueDiagnostic(
cmDiagnostics::CMD_POLICY,
cmStrCat(
cmPolicies::GetPolicyWarning(cmPolicies::CMP0219), '\n', "Command \"",
calleeName, "\" called with arguments containing backslashes.\n",
"Since the policy is not set, backslashes in the arguments:\n", oldArgs,
"\n", "will be interpreted as escape sequences for compatibility.\n",
"Set the policy to NEW to instead pass\n", newArgs, "\n",
"so that argument parsing will preserve the original values."));
}
void cmMakefile::IssueCMP0219Warning(
std::string const& calleeName,
std::vector<cmListFileArgument> const& args) const
{
std::vector<std::string> stringArgs;
stringArgs.reserve(args.size());
for (cmListFileArgument const& arg : args) {
stringArgs.push_back(arg.Value);
}
this->IssueCMP0219Warning(calleeName, stringArgs);
}
MessageType cmMakefile::ExpandVariablesInStringImpl(
std::string& errorstr, std::string& source, bool escapeQuotes,
bool noEscapes, bool atOnly, char const* filename, long line,
+15
View File
@@ -1186,6 +1186,20 @@ public:
cm::optional<std::string> DeferGetCallIds() const;
cm::optional<std::string> DeferGetCall(std::string const& id) const;
//! Check CMP0219 policy status for the given callee and arguments.
//! Returns the effective policy status: OLD, NEW, or WARN (only on
//! first occurrence of calleeName with backslashes present). The
//! caller is responsible for issuing any warning when WARN is returned.
cmPolicies::PolicyStatus CheckCMP0219(std::string const& calleeName,
std::vector<std::string> const& args);
cmPolicies::PolicyStatus CheckCMP0219(
std::string const& calleeName,
std::vector<cmListFileArgument> const& args);
void IssueCMP0219Warning(std::string const& calleeName,
std::vector<std::string> const& args) const;
void IssueCMP0219Warning(std::string const& calleeName,
std::vector<cmListFileArgument> const& args) const;
protected:
// add link libraries and directories to the target
void AddGlobalLinkInformation(cmTarget& target);
@@ -1350,6 +1364,7 @@ private:
bool CheckCMP0000;
std::set<std::string> WarnedCMP0074;
std::set<std::string> WarnedCMP0144;
std::set<std::string> WarnedCMP0219;
bool IsSourceFileTryCompile;
ImportedTargetScope CurrentImportedTargetScope = ImportedTargetScope::Local;
};
+4 -1
View File
@@ -654,7 +654,10 @@ class cmMakefile;
SELECT(POLICY, CMP0218, \
"The CMAKE_WARN_DEPRECATED and CMAKE_ERROR_DEPRECATED variables " \
"are ignored.", \
4, 4, 0, WARN)
4, 4, 0, WARN) \
SELECT(POLICY, CMP0219, \
"Macro invocations preserve backslashes in arguments.", 4, 4, 0, \
WARN)
#define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
#define CM_FOR_EACH_POLICY_ID(POLICY) \
+12
View File
@@ -10,6 +10,7 @@
#include "cmListFileCache.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
#include "cmPolicies.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmValue.h"
@@ -56,6 +57,17 @@ void cmVariableWatchCommandVariableAccessed(
{ *currentListFile, cmListFileArgument::Quoted, fakeLineNo },
{ stack, cmListFileArgument::Quoted, fakeLineNo }
};
{
cmPolicies::PolicyStatus cmp0219 =
makefile->CheckCMP0219(data->Command, newLFFArgs);
if (cmp0219 == cmPolicies::NEW) {
for (cmListFileArgument& arg : newLFFArgs) {
cmSystemTools::ReplaceString(arg.Value, "\\", "\\\\");
}
} else if (cmp0219 == cmPolicies::WARN) {
makefile->IssueCMP0219Warning(data->Command, newLFFArgs);
}
}
cmListFileFunction newLFF{ data->Command, fakeLineNo, fakeLineNo,
std::move(newLFFArgs) };
@@ -0,0 +1,17 @@
cmake_policy(SET CMP0219 NEW)
include("${CMAKE_CURRENT_LIST_DIR}/CMP0219-helpers.cmake")
macro(cmp0219_inner)
set(cmp0219_inner_argn "${ARGN}")
endmacro()
macro(cmp0219_middle)
cmp0219_inner(${ARGN})
endmacro()
macro(cmp0219_outer)
cmp0219_middle(${ARGN})
endmacro()
cmp0219_outer(HINTS "${cmp0219_path_native}")
cmp0219_assert_equal("${cmp0219_inner_argn}" "HINTS;${cmp0219_path_native}")
+18
View File
@@ -0,0 +1,18 @@
cmake_policy(SET CMP0219 NEW)
include("${CMAKE_CURRENT_LIST_DIR}/CMP0219-helpers.cmake")
macro(cmp0219_capture package_name first_named)
set(cmp0219_named "${first_named}")
set(cmp0219_argn "${ARGN}")
set(cmp0219_argv "${ARGV}")
set(cmp0219_argv0 "${ARGV0}")
set(cmp0219_argv1 "${ARGV1}")
endmacro()
cmp0219_capture(pybind11 HINTS "${cmp0219_path_native}")
cmp0219_assert_equal("${cmp0219_named}" "HINTS")
cmp0219_assert_equal("${cmp0219_argn}" "${cmp0219_path_native}")
cmp0219_assert_equal("${cmp0219_argv}" "pybind11;HINTS;${cmp0219_path_native}")
cmp0219_assert_equal("${cmp0219_argv0}" "pybind11")
cmp0219_assert_equal("${cmp0219_argv1}" "HINTS")
@@ -0,0 +1 @@
1
@@ -0,0 +1 @@
Invalid character escape '\\b'\.
+8
View File
@@ -0,0 +1,8 @@
cmake_policy(SET CMP0219 OLD)
include("${CMAKE_CURRENT_LIST_DIR}/CMP0219-helpers.cmake")
macro(cmp0219_capture_old)
set(cmp0219_old_argn "${ARGN}")
endmacro()
cmp0219_capture_old(HINTS "${cmp0219_path_native}")
@@ -0,0 +1,20 @@
function(cmp0219_assert_equal actual expected)
if(NOT "${actual}" STREQUAL "${expected}")
message(FATAL_ERROR
"Assertion failed:\n"
" expected=[${expected}]\n"
" actual=[${actual}]")
endif()
endfunction()
function(cmp0219_assert_undefined variable_name)
if(DEFINED ${variable_name})
message(FATAL_ERROR
"Assertion failed:\n"
" ${variable_name} should be undefined\n"
" actual=[${${variable_name}}]")
endif()
endfunction()
set(cmp0219_path_fwd "C:/build_bot/new/temp/vendor")
string(REPLACE "/" "\\" cmp0219_path_native "${cmp0219_path_fwd}")
+3
View File
@@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 3.10)
project(${RunCMake_TEST} NONE)
include(${RunCMake_TEST}.cmake NO_POLICY_SCOPE)
@@ -0,0 +1,8 @@
cmake_policy(SET CMP0219 NEW)
enable_language(C)
include(CheckCSourceCompiles)
check_c_source_compiles("int main() { return '\\0'; }" RESULT)
if(NOT RESULT)
message(FATAL_ERROR "check_c_source_compiles failed for escaped NUL character source")
endif()
@@ -0,0 +1 @@
1
@@ -0,0 +1 @@
Invalid character escape '\\0'\.
@@ -0,0 +1,5 @@
cmake_policy(SET CMP0219 OLD)
enable_language(C)
include(CheckCSourceCompiles)
check_c_source_compiles("int main() { return '\\0'; }" RESULT)
@@ -0,0 +1,30 @@
include("${CMAKE_CURRENT_LIST_DIR}/CMP0219-helpers.cmake")
macro(cmp0219_defer_capture var_name)
set(${var_name} "${ARGN}")
endmacro()
# Deferred call is scheduled with OLD at the call site,
# but executed with NEW at end-of-file.
cmake_policy(SET CMP0219 OLD)
cmake_language(
DEFER CALL
cmp0219_defer_capture cmp0219_defer_old_callsite
HINTS "${cmp0219_path_native}")
# Deferred call is scheduled with NEW at the call site,
# and executed with NEW at end-of-file.
cmake_policy(SET CMP0219 NEW)
cmake_language(
DEFER CALL
cmp0219_defer_capture cmp0219_defer_new_callsite
HINTS "${cmp0219_path_native}")
cmake_language(
DEFER CALL
cmp0219_assert_equal "${cmp0219_defer_old_callsite}"
"HINTS;${cmp0219_path_native}")
cmake_language(
DEFER CALL
cmp0219_assert_equal "${cmp0219_defer_new_callsite}"
"HINTS;${cmp0219_path_native}")
@@ -0,0 +1 @@
1
@@ -0,0 +1 @@
Invalid character escape '\\b'\.
@@ -0,0 +1,15 @@
include("${CMAKE_CURRENT_LIST_DIR}/CMP0219-helpers.cmake")
macro(cmp0219_defer_capture var_name)
set(${var_name} "${ARGN}")
endmacro()
# Deferred call is scheduled with NEW at the call site,
# but executed with OLD at end-of-file.
cmake_policy(SET CMP0219 NEW)
cmake_language(
DEFER CALL
cmp0219_defer_capture cmp0219_defer_new_callsite
HINTS "${cmp0219_path_native}")
cmake_policy(SET CMP0219 OLD)
@@ -0,0 +1,7 @@
include("${CMAKE_CURRENT_LIST_DIR}/CMP0219-helpers.cmake")
find_package(pybind11 HINTS "${cmp0219_path_native}")
cmp0219_assert_equal("${cmp0219_provider_method}" "FIND_PACKAGE")
cmp0219_assert_equal("${cmp0219_provider_package}" "pybind11")
cmp0219_assert_equal("${cmp0219_provider_argn}" "HINTS;${cmp0219_path_native}")
@@ -0,0 +1 @@
1
@@ -0,0 +1 @@
Invalid character escape '\\b'\.
@@ -0,0 +1,3 @@
include("${CMAKE_CURRENT_LIST_DIR}/CMP0219-helpers.cmake")
find_package(pybind11 HINTS "${cmp0219_path_native}")
@@ -0,0 +1,11 @@
macro(cmp0219_provider method package_name)
set(cmp0219_provider_method "${method}")
set(cmp0219_provider_package "${package_name}")
set(cmp0219_provider_argn "${ARGN}")
set(${package_name}_FOUND TRUE)
endmacro()
cmake_language(
SET_DEPENDENCY_PROVIDER cmp0219_provider
SUPPORTED_METHODS FIND_PACKAGE
)
@@ -0,0 +1,9 @@
cmake_policy(SET CMP0219 NEW)
include("${CMAKE_CURRENT_LIST_DIR}/CMP0219-helpers.cmake")
macro(cmp0219_dispatch)
set(cmp0219_dispatch_argn "${ARGN}")
endmacro()
cmake_language(CALL cmp0219_dispatch HINTS "${cmp0219_path_native}")
cmp0219_assert_equal("${cmp0219_dispatch_argn}" "HINTS;${cmp0219_path_native}")
+8
View File
@@ -0,0 +1,8 @@
cmake_policy(SET CMP0219 NEW)
include("${CMAKE_CURRENT_LIST_DIR}/CMP0219-helpers.cmake")
macro(cmp0219_escape str)
cmp0219_assert_equal("${str}" "\\")
endmacro()
cmp0219_escape("\\")
@@ -0,0 +1,48 @@
include("${CMAKE_CURRENT_LIST_DIR}/CMP0219-helpers.cmake")
# Build progressively escaped variants to model callers that added extra
# backslashes to make an OLD-only macro chain work.
string(REPLACE "\\" "\\\\" cmp0219_path_2 "${cmp0219_path_native}")
string(REPLACE "\\" "\\\\" cmp0219_path_4 "${cmp0219_path_2}")
string(REPLACE "\\" "\\\\" cmp0219_path_8 "${cmp0219_path_4}")
cmake_policy(SET CMP0219 OLD)
macro(cmp0219_leaf var_name)
set(${var_name} "${ARGN}")
endmacro()
macro(cmp0219_middle_old)
cmp0219_leaf(cmp0219_old_chain_capture ${ARGN})
endmacro()
macro(cmp0219_middle_new)
# Simulate a dependency update opting this middle forwarding layer into NEW.
cmake_policy(PUSH)
cmake_policy(SET CMP0219 NEW)
cmp0219_leaf(cmp0219_mixed_chain_capture ${ARGN})
cmake_policy(POP)
endmacro()
macro(cmp0219_outer_to_old)
cmp0219_middle_old(${ARGN})
endmacro()
macro(cmp0219_outer_to_new_middle)
cmp0219_middle_new(${ARGN})
endmacro()
# OLD->OLD->OLD requires heavily escaped input and produces a native path.
cmp0219_outer_to_old(HINTS "${cmp0219_path_8}")
cmp0219_assert_equal(
"${cmp0219_old_chain_capture}" "HINTS;${cmp0219_path_native}")
# OLD->NEW->OLD with the same input preserves one extra layer.
cmp0219_outer_to_new_middle(HINTS "${cmp0219_path_8}")
cmp0219_assert_equal(
"${cmp0219_mixed_chain_capture}" "HINTS;${cmp0219_path_2}")
# OLD->NEW->OLD needs fewer escape layers to reach a native path.
cmp0219_outer_to_new_middle(HINTS "${cmp0219_path_4}")
cmp0219_assert_equal(
"${cmp0219_mixed_chain_capture}" "HINTS;${cmp0219_path_native}")
@@ -0,0 +1,12 @@
cmake_policy(PUSH)
cmake_policy(SET CMP0219 OLD)
macro(cmp0219_old_capture)
set(cmp0219_old_argn "${ARGN}")
endmacro()
cmake_policy(POP)
cmake_policy(SET CMP0219 NEW)
include("${CMAKE_CURRENT_LIST_DIR}/CMP0219-helpers.cmake")
cmp0219_old_capture(HINTS "${cmp0219_path_native}")
cmp0219_assert_equal("${cmp0219_old_argn}" "HINTS;${cmp0219_path_native}")
@@ -0,0 +1 @@
1
@@ -0,0 +1 @@
Invalid character escape '\\b'\.
@@ -0,0 +1,11 @@
cmake_policy(PUSH)
cmake_policy(SET CMP0219 NEW)
macro(cmp0219_new_capture)
set(cmp0219_new_argn "${ARGN}")
endmacro()
cmake_policy(POP)
cmake_policy(SET CMP0219 OLD)
include("${CMAKE_CURRENT_LIST_DIR}/CMP0219-helpers.cmake")
cmp0219_new_capture(HINTS "${cmp0219_path_native}")
@@ -0,0 +1,46 @@
include("${CMAKE_CURRENT_LIST_DIR}/CMP0219-helpers.cmake")
cmake_policy(SET CMP0219 NEW)
macro(cmp0219_parse_corner_case)
set(_options)
set(_one_value_args FOO)
set(_multi_value_args)
cmake_parse_arguments(cmp0219_q
"${_options}"
"${_one_value_args}"
"${_multi_value_args}"
"${ARGN}")
cmake_parse_arguments(cmp0219_u
"${_options}"
"${_one_value_args}"
"${_multi_value_args}"
${ARGN})
endmacro()
cmp0219_parse_corner_case(FOO "foo\\;bar")
cmp0219_assert_equal("${cmp0219_q_FOO}" "foo;bar")
cmp0219_assert_undefined(cmp0219_q_UNPARSED_ARGUMENTS)
cmp0219_assert_equal("${cmp0219_u_FOO}" "foo")
cmp0219_assert_equal("${cmp0219_u_UNPARSED_ARGUMENTS}" "bar")
cmp0219_parse_corner_case(FOO "foo\;bar")
cmp0219_assert_equal("${cmp0219_q_FOO}" "foo;bar")
cmp0219_assert_undefined(cmp0219_q_UNPARSED_ARGUMENTS)
cmp0219_assert_equal("${cmp0219_u_FOO}" "foo")
cmp0219_assert_equal("${cmp0219_u_UNPARSED_ARGUMENTS}" "bar")
cmp0219_parse_corner_case(FOO "foo;bar")
cmp0219_assert_equal("${cmp0219_q_FOO}" "foo")
cmp0219_assert_equal("${cmp0219_u_UNPARSED_ARGUMENTS}" "bar")
cmp0219_assert_equal("${cmp0219_u_FOO}" "foo")
cmp0219_assert_equal("${cmp0219_u_UNPARSED_ARGUMENTS}" "bar")
cmp0219_parse_corner_case(FOO foo;bar)
cmp0219_assert_equal("${cmp0219_q_FOO}" "foo")
cmp0219_assert_equal("${cmp0219_u_UNPARSED_ARGUMENTS}" "bar")
cmp0219_assert_equal("${cmp0219_u_FOO}" "foo")
cmp0219_assert_equal("${cmp0219_u_UNPARSED_ARGUMENTS}" "bar")
@@ -0,0 +1,32 @@
cmake_policy(SET CMP0219 NEW)
include("${CMAKE_CURRENT_LIST_DIR}/CMP0219-helpers.cmake")
cmake_policy(PUSH)
cmake_policy(SET CMP0219 OLD)
macro(cmp0219_old_return)
set(cmp0219_old_return_argn "${ARGN}" PARENT_SCOPE)
return()
endmacro()
macro(cmp0219_old_set_policy)
set(cmp0219_old_set_policy_argn "${ARGN}")
cmake_policy(SET CMP0210 OLD)
endmacro()
cmake_policy(POP)
function(cmp0219_test_return)
set(cmp0219_return_state "before" PARENT_SCOPE)
cmp0219_old_return(HINTS "${cmp0219_path_native}")
set(cmp0219_return_state "after" PARENT_SCOPE)
endfunction()
cmp0219_test_return()
cmp0219_assert_equal("${cmp0219_return_state}" "before")
cmp0219_assert_equal(
"${cmp0219_old_return_argn}" "HINTS;${cmp0219_path_native}")
cmp0219_old_set_policy(HINTS "${cmp0219_path_native}")
cmake_policy(GET CMP0210 cmp0219_cmp0210_status)
cmp0219_assert_equal("${cmp0219_cmp0210_status}" "OLD")
cmp0219_assert_equal(
"${cmp0219_old_set_policy_argn}" "HINTS;${cmp0219_path_native}")
+30
View File
@@ -0,0 +1,30 @@
include(RunCMake)
run_cmake_script(Basic-NEW)
run_cmake_script(Basic-OLD)
run_cmake_script(Escape2-NEW)
run_cmake_script(AllNEW-Forward)
run_cmake_script(NEW-calls-OLD)
run_cmake_script(OLD-calls-NEW)
run_cmake_script(MixedChain-OLD-NEWMiddle)
run_cmake_script(DynamicDispatch-NEW)
run_cmake_script(ParseArguments-Semicolon)
run_cmake_script(SemicolonEscape-QuotedUnquoted)
run_cmake_script(ReturnAndPolicyPropagation)
run_cmake_script(Warn-Unset-Macro)
run_cmake_script(Warn-Unset-Macro-Multi)
run_cmake_script(Warn-Unset-VariableWatch)
run_cmake_script(VariableWatch-OLD)
run_cmake_script(VariableWatch-NEW)
run_cmake(CheckCSourceCompiles-OLD)
run_cmake(CheckCSourceCompiles-NEW)
run_cmake_with_options(DependencyProviderMacro-OLD
-D "CMAKE_PROJECT_TOP_LEVEL_INCLUDES=${RunCMake_SOURCE_DIR}/DependencyProviderMacro-TopInclude.cmake"
-D "CMAKE_POLICY_DEFAULT_CMP0219=OLD"
)
run_cmake_with_options(DependencyProviderMacro-NEW
-D "CMAKE_PROJECT_TOP_LEVEL_INCLUDES=${RunCMake_SOURCE_DIR}/DependencyProviderMacro-TopInclude.cmake"
-D "CMAKE_POLICY_DEFAULT_CMP0219=NEW"
)
run_cmake(Defer-EndPolicy-NEW)
run_cmake(Defer-EndPolicy-OLD)
@@ -0,0 +1,62 @@
include("${CMAKE_CURRENT_LIST_DIR}/CMP0219-helpers.cmake")
function(cmp0219_assert_encoded actual expected)
string(REPLACE "\\" "<BS>" cmp0219_encoded "${actual}")
string(REPLACE ";" "<SC>" cmp0219_encoded "${cmp0219_encoded}")
cmp0219_assert_equal("${cmp0219_encoded}" "${expected}")
endfunction()
function(cmp0219_sink out_prefix)
math(EXPR cmp0219_sink_argc "${ARGC} - 1")
set(${out_prefix}_argc "${cmp0219_sink_argc}" PARENT_SCOPE)
if(cmp0219_sink_argc GREATER 0)
set(${out_prefix}_arg0 "${ARGV1}" PARENT_SCOPE)
endif()
if(cmp0219_sink_argc GREATER 1)
set(${out_prefix}_arg1 "${ARGV2}" PARENT_SCOPE)
endif()
endfunction()
macro(cmp0219_semicolon_probe prefix value)
set(${prefix}_named_q "${value}")
set(${prefix}_named_u ${value})
set(${prefix}_argv1_q "${ARGV1}")
set(${prefix}_argv1_u ${ARGV1})
set(${prefix}_argn_q "${ARGN}")
set(${prefix}_argn_u ${ARGN})
cmp0219_sink("${prefix}_sink_named_q" "${value}")
cmp0219_sink("${prefix}_sink_named_u" ${value})
cmp0219_sink("${prefix}_sink_argn_q" "${ARGN}")
cmp0219_sink("${prefix}_sink_argn_u" ${ARGN})
endmacro()
function(cmp0219_run_semicolon_escape_mode mode)
cmake_policy(SET CMP0219 "${mode}")
# Value passed with a quoted argument keeps '\;' in textual substitutions
# until an unquoted use decodes it.
cmp0219_semicolon_probe("${mode}_quoted" "foo\\;bar" "foo\\;bar")
cmp0219_assert_encoded("${${mode}_quoted_named_q}" "foo<BS><SC>bar")
cmp0219_assert_encoded("${${mode}_quoted_named_u}" "foo<SC>bar")
cmp0219_assert_equal("${${mode}_quoted_sink_named_q_argc}" "1")
cmp0219_assert_equal("${${mode}_quoted_sink_named_u_argc}" "1")
cmp0219_assert_equal("${${mode}_quoted_sink_argn_q_argc}" "1")
cmp0219_assert_equal("${${mode}_quoted_sink_argn_u_argc}" "1")
# Value passed with an unquoted argument uses '\;' to keep one argument at
# the call site, but unquoted forwarding splits at ';' in the macro body.
cmp0219_semicolon_probe("${mode}_unquoted" foo\;bar foo\;bar)
cmp0219_assert_encoded("${${mode}_unquoted_named_q}" "foo<SC>bar")
cmp0219_assert_equal("${${mode}_unquoted_sink_named_q_argc}" "1")
cmp0219_assert_equal("${${mode}_unquoted_sink_named_u_argc}" "2")
cmp0219_assert_encoded("${${mode}_unquoted_sink_named_u_arg0}" "foo")
cmp0219_assert_encoded("${${mode}_unquoted_sink_named_u_arg1}" "bar")
cmp0219_assert_equal("${${mode}_unquoted_sink_argn_q_argc}" "1")
cmp0219_assert_equal("${${mode}_unquoted_sink_argn_u_argc}" "2")
cmp0219_assert_encoded("${${mode}_unquoted_sink_argn_u_arg0}" "foo")
cmp0219_assert_encoded("${${mode}_unquoted_sink_argn_u_arg1}" "bar")
endfunction()
cmp0219_run_semicolon_escape_mode(OLD)
cmp0219_run_semicolon_escape_mode(NEW)
@@ -0,0 +1,11 @@
cmake_policy(SET CMP0219 NEW)
include("${CMAKE_CURRENT_LIST_DIR}/CMP0219-helpers.cmake")
function(cmp0219_watch_callback variable access value current_list_file stack)
set(cmp0219_watch_value "${value}" PARENT_SCOPE)
endfunction()
variable_watch(cmp0219_watched cmp0219_watch_callback)
set(cmp0219_watched "${cmp0219_path_native}")
cmp0219_assert_equal("${cmp0219_watch_value}" "${cmp0219_path_native}")
@@ -0,0 +1 @@
1
@@ -0,0 +1 @@
Invalid character escape '\\b'\.
@@ -0,0 +1,9 @@
cmake_policy(SET CMP0219 OLD)
include("${CMAKE_CURRENT_LIST_DIR}/CMP0219-helpers.cmake")
function(cmp0219_watch_callback variable access value current_list_file stack)
set(cmp0219_watch_value "${value}" PARENT_SCOPE)
endfunction()
variable_watch(cmp0219_watched cmp0219_watch_callback)
set(cmp0219_watched "${cmp0219_path_native}")
@@ -0,0 +1,22 @@
CMake Warning \(policy\) at .*/Warn-Unset-Macro-Multi\.cmake:[0-9]+ \(cmp0219_warn_macro\):
Policy CMP0219 is not set: Macro invocations preserve backslashes in
arguments\. Run "cmake --help-policy CMP0219" for policy details\. Use the
cmake_policy command to set the policy and suppress this warning\.
Command "cmp0219_warn_macro" called with arguments containing backslashes\.
Since the policy is not set, backslashes in the arguments:
"prefix\\tsuffix"
"left\\;right"
will be interpreted as escape sequences for compatibility\.
Set the policy to NEW to instead pass
"prefix\\\\tsuffix"
"left\\\\;right"
so that argument parsing will preserve the original values\.
This warning is for project developers\. Use -Wno-author or -Wno-policy to
suppress it\.
@@ -0,0 +1,9 @@
set(cmp0219_warn_value1 "prefix\\tsuffix")
set(cmp0219_warn_value2 "left\\;right")
macro(cmp0219_warn_macro arg1 arg2)
set(cmp0219_warn_seen1 "${arg1}")
set(cmp0219_warn_seen2 "${arg2}")
endmacro()
cmp0219_warn_macro("${cmp0219_warn_value1}" "${cmp0219_warn_value2}")
@@ -0,0 +1,20 @@
CMake Warning \(policy\) at .*/Warn-Unset-Macro\.cmake:[0-9]+ \(cmp0219_warn_macro\):
Policy CMP0219 is not set: Macro invocations preserve backslashes in
arguments\. Run "cmake --help-policy CMP0219" for policy details\. Use the
cmake_policy command to set the policy and suppress this warning\.
Command "cmp0219_warn_macro" called with arguments containing backslashes\.
Since the policy is not set, backslashes in the arguments:
"prefix\\tsuffix"
will be interpreted as escape sequences for compatibility\.
Set the policy to NEW to instead pass
"prefix\\\\tsuffix"
so that argument parsing will preserve the original values\.
This warning is for project developers\. Use -Wno-author or -Wno-policy to
suppress it\.
@@ -0,0 +1,8 @@
set(cmp0219_warn_value "prefix\\tsuffix")
macro(cmp0219_warn_macro value)
set(cmp0219_warn_seen "${value}")
endmacro()
cmp0219_warn_macro("${cmp0219_warn_value}")
cmp0219_warn_macro("${cmp0219_warn_value}")
@@ -0,0 +1,20 @@
CMake Warning \(policy\) at .*/Warn-Unset-VariableWatch\.cmake:[0-9]+ \(set\):
Policy CMP0219 is not set: Macro invocations preserve backslashes in
arguments\. Run "cmake --help-policy CMP0219" for policy details\. Use the
cmake_policy command to set the policy and suppress this warning\.
Command "cmp0219_warn_watch" called with arguments containing backslashes\.
Since the policy is not set, backslashes in the arguments:
"prefix\\tsuffix"
will be interpreted as escape sequences for compatibility\.
Set the policy to NEW to instead pass
"prefix\\\\tsuffix"
so that argument parsing will preserve the original values\.
This warning is for project developers\. Use -Wno-author or -Wno-policy to
suppress it\.
@@ -0,0 +1,6 @@
function(cmp0219_warn_watch variable access value current_list_file stack)
endfunction()
variable_watch(cmp0219_warned cmp0219_warn_watch)
set(cmp0219_warned "prefix\\tsuffix")
set(cmp0219_warned "prefix\\tsuffix")
+1
View File
@@ -185,6 +185,7 @@ if(WIN32)
add_RunCMake_test(CMP0212)
endif()
add_RunCMake_test(CMP0217)
add_RunCMake_test(CMP0219)
if(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
add_RunCMake_test(CMP0194 -DCMAKE_C_COMPILER_VERSION=${CMAKE_C_COMPILER_VERSION})
@@ -1,5 +1,9 @@
cmake_minimum_required(VERSION 3.13)
if(POLICY CMP0219)
cmake_policy(SET CMP0219 OLD)
endif()
project(${RunCMake_TEST} LANGUAGES NONE)
include(${RunCMake_TEST}.cmake)
+3 -3
View File
@@ -1,7 +1,7 @@
CMake Error at Escape2\.cmake:2 \(message\):
CMake Error at Escape2\.cmake:[0-9]+ \(message\):
Syntax error in cmake code at
.*/Tests/RunCMake/Syntax/Escape2\.cmake:2
.*/Tests/RunCMake/Syntax/Escape2\.cmake:[0-9]+
when parsing string
@@ -9,5 +9,5 @@ CMake Error at Escape2\.cmake:2 \(message\):
Invalid character escape '\\' \(at end of input\)\.
Call Stack \(most recent call first\):
Escape2\.cmake:5 \(escape\)
Escape2\.cmake:[0-9]+ \(escape\)
CMakeLists\.txt:3 \(include\)
+4
View File
@@ -2,4 +2,8 @@ macro (escape str)
message("${str}")
endmacro ()
if(POLICY CMP0219)
cmake_policy(SET CMP0219 OLD)
endif()
escape("\\")
@@ -1,4 +1,5 @@
include(${CMAKE_CURRENT_LIST_DIR}/test_utils.cmake)
cmake_policy(SET CMP0219 OLD)
# example from the documentation
# OPTIONAL is a keyword and therefore terminates the definition of