mirror of
https://github.com/Kitware/CMake.git
synced 2026-08-04 14:50:23 +00:00
cmake_parse_arguments: Add PARSE_ARGN mode
Introduce a mode similar to `PARSE_ARGV` that avoids needing to specify the number of normal function arguments. Closes: #25376
This commit is contained in:
@@ -11,6 +11,9 @@ Parse function or macro arguments.
|
||||
cmake_parse_arguments(PARSE_ARGV <N> <prefix> <options>
|
||||
<one_value_keywords> <multi_value_keywords>)
|
||||
|
||||
cmake_parse_arguments(PARSE_ARGN <prefix> <options>
|
||||
<one_value_keywords> <multi_value_keywords>)
|
||||
|
||||
.. versionadded:: 3.5
|
||||
This command is implemented natively. Previously, it has been defined in the
|
||||
module :module:`CMakeParseArguments`.
|
||||
@@ -30,6 +33,12 @@ This may be used in either a :command:`macro` or a :command:`function`.
|
||||
the ``<N>``-th argument, where ``<N>`` is an unsigned integer.
|
||||
This allows for the values to have special characters like ``;`` in them.
|
||||
|
||||
.. versionadded:: 4.4
|
||||
The ``PARSE_ARGN`` signature is only for use in a :command:`function`
|
||||
body. This starts parsing after the last named argument of the calling
|
||||
function and works exactly like ``PARSE_ARGV`` with ``<N>`` being the number
|
||||
of parameters in the function definition.
|
||||
|
||||
The ``<options>`` argument contains all options for the respective function
|
||||
or macro. These are keywords that have no value following them, like the
|
||||
``OPTIONAL`` keyword of the :command:`install` command.
|
||||
|
||||
7
Help/release/dev/parse-arguments-argn.rst
Normal file
7
Help/release/dev/parse-arguments-argn.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
parse-arguments-argn
|
||||
--------------------
|
||||
|
||||
* The :command:`cmake_parse_arguments` command gained a new ``PARSE_ARGN``
|
||||
signature that starts parsing after the last named argument of the calling
|
||||
function and works exactly like ``PARSE_ARGV`` with ``<N>`` being the number
|
||||
of parameters in the function definition.
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
namespace {
|
||||
std::string const ARGC = "ARGC";
|
||||
std::string const kFUNCTION_ARGNC = "_FUNCTION_ARGNC";
|
||||
std::string const ARGN = "ARGN";
|
||||
std::string const ARGV = "ARGV";
|
||||
std::string const CMAKE_CURRENT_FUNCTION = "CMAKE_CURRENT_FUNCTION";
|
||||
@@ -93,15 +94,19 @@ bool cmFunctionHelperCommand::operator()(
|
||||
makefile.AddDefinition(this->Args[j], expandedArgs[j - 1]);
|
||||
}
|
||||
|
||||
// define ARGV and ARGN
|
||||
// define ARGV, ARGN, and _FUNCTION_ARGNC
|
||||
auto const argvDef = cmList::to_string(expandedArgs);
|
||||
auto const expIt = expandedArgs.begin() + (this->Args.size() - 1);
|
||||
auto const argnDef =
|
||||
cmList::to_string(cmMakeRange(expIt, expandedArgs.end()));
|
||||
auto const functionArgncDef =
|
||||
std::to_string(expandedArgs.size() - (this->Args.size() - 1));
|
||||
makefile.AddDefinition(ARGV, argvDef);
|
||||
makefile.MarkVariableAsUsed(ARGV);
|
||||
makefile.AddDefinition(ARGN, argnDef);
|
||||
makefile.MarkVariableAsUsed(ARGN);
|
||||
makefile.AddDefinition(kFUNCTION_ARGNC, functionArgncDef);
|
||||
makefile.MarkVariableAsUsed(kFUNCTION_ARGNC);
|
||||
|
||||
makefile.AddDefinition(CMAKE_CURRENT_FUNCTION, this->Args.front());
|
||||
makefile.MarkVariableAsUsed(CMAKE_CURRENT_FUNCTION);
|
||||
|
||||
@@ -141,6 +141,8 @@ bool cmParseArgumentsCommand(std::vector<std::string> const& args,
|
||||
// 1 2 3 4
|
||||
// or
|
||||
// cmake_parse_arguments(PARSE_ARGV N prefix options single multi)
|
||||
// or
|
||||
// cmake_parse_arguments(PARSE_ARGN prefix options single multi)
|
||||
if (args.size() < 4) {
|
||||
status.SetError("must be called with at least 4 arguments.");
|
||||
return false;
|
||||
@@ -148,8 +150,9 @@ bool cmParseArgumentsCommand(std::vector<std::string> const& args,
|
||||
|
||||
auto argIter = args.begin();
|
||||
auto argEnd = args.end();
|
||||
bool parseFromArgV = false;
|
||||
unsigned long argvStart = 0;
|
||||
bool parseFromArg = false;
|
||||
unsigned long argStart = 0;
|
||||
unsigned long argnc = 0;
|
||||
if (*argIter == "PARSE_ARGV") {
|
||||
if (args.size() != 6) {
|
||||
status.GetMakefile().IssueMessage(
|
||||
@@ -158,9 +161,9 @@ bool cmParseArgumentsCommand(std::vector<std::string> const& args,
|
||||
cmSystemTools::SetFatalErrorOccurred();
|
||||
return true;
|
||||
}
|
||||
parseFromArgV = true;
|
||||
parseFromArg = true;
|
||||
argIter++; // move past PARSE_ARGV
|
||||
if (!cmStrToULong(*argIter, &argvStart)) {
|
||||
if (!cmStrToULong(*argIter, &argStart)) {
|
||||
status.GetMakefile().IssueMessage(
|
||||
MessageType::FATAL_ERROR,
|
||||
cmStrCat("PARSE_ARGV index '", *argIter,
|
||||
@@ -169,6 +172,26 @@ bool cmParseArgumentsCommand(std::vector<std::string> const& args,
|
||||
return true;
|
||||
}
|
||||
argIter++; // move past N
|
||||
} else if (*argIter == "PARSE_ARGN") {
|
||||
if (args.size() != 5) {
|
||||
status.GetMakefile().IssueMessage(
|
||||
MessageType::FATAL_ERROR,
|
||||
"PARSE_ARGN must be called with exactly 5 arguments.");
|
||||
cmSystemTools::SetFatalErrorOccurred();
|
||||
return true;
|
||||
}
|
||||
parseFromArg = true;
|
||||
argIter++; // move past PARSE_ARGN
|
||||
std::string argncStr =
|
||||
status.GetMakefile().GetSafeDefinition("_FUNCTION_ARGNC");
|
||||
if (!cmStrToULong(argncStr, &argnc)) {
|
||||
status.GetMakefile().IssueMessage(
|
||||
MessageType::FATAL_ERROR,
|
||||
cmStrCat("PARSE_ARGN called with _FUNCTION_ARGNC='", argncStr,
|
||||
"' that is not an unsigned integer"));
|
||||
cmSystemTools::SetFatalErrorOccurred();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// the first argument is the prefix
|
||||
std::string const prefix = (*argIter++) + "_";
|
||||
@@ -202,31 +225,38 @@ bool cmParseArgumentsCommand(std::vector<std::string> const& args,
|
||||
parser.Bind(list, multiValArgs, duplicateKey);
|
||||
|
||||
list.clear();
|
||||
if (!parseFromArgV) {
|
||||
if (!parseFromArg) {
|
||||
// Flatten ;-lists in the arguments into a single list as was done
|
||||
// by the original function(CMAKE_PARSE_ARGUMENTS).
|
||||
for (; argIter != argEnd; ++argIter) {
|
||||
list.append(*argIter);
|
||||
}
|
||||
} else {
|
||||
// in the PARSE_ARGV move read the arguments from ARGC and ARGV#
|
||||
cm::string_view mode = *args.begin();
|
||||
// in the PARSE_ARGV or PARSE_ARGN mode read the arguments from ARGC and
|
||||
// ARGV#
|
||||
std::string argc = status.GetMakefile().GetSafeDefinition("ARGC");
|
||||
unsigned long count;
|
||||
if (!cmStrToULong(argc, &count)) {
|
||||
status.GetMakefile().IssueMessage(
|
||||
MessageType::FATAL_ERROR,
|
||||
cmStrCat("PARSE_ARGV called with ARGC='", argc,
|
||||
cmStrCat(mode, " called with ARGC='", argc,
|
||||
"' that is not an unsigned integer"));
|
||||
cmSystemTools::SetFatalErrorOccurred();
|
||||
return true;
|
||||
}
|
||||
for (unsigned long i = argvStart; i < count; ++i) {
|
||||
|
||||
if (mode == "PARSE_ARGN") {
|
||||
argStart = count - argnc;
|
||||
}
|
||||
|
||||
for (unsigned long i = argStart; i < count; ++i) {
|
||||
std::string const argName{ cmStrCat("ARGV", i) };
|
||||
cmValue arg = status.GetMakefile().GetDefinition(argName);
|
||||
if (!arg) {
|
||||
status.GetMakefile().IssueMessage(
|
||||
MessageType::FATAL_ERROR,
|
||||
cmStrCat("PARSE_ARGV called with ", argName, " not set"));
|
||||
cmStrCat(mode, " called with ", argName, " not set"));
|
||||
cmSystemTools::SetFatalErrorOccurred();
|
||||
return true;
|
||||
}
|
||||
@@ -251,7 +281,7 @@ bool cmParseArgumentsCommand(std::vector<std::string> const& args,
|
||||
prefix, status.GetMakefile(), options, singleValArgs, multiValArgs,
|
||||
unparsed, options_set(keywordsSeen.begin(), keywordsSeen.end()),
|
||||
options_set(keywordsMissingValues.begin(), keywordsMissingValues.end()),
|
||||
parseFromArgV);
|
||||
parseFromArg);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
41
Tests/RunCMake/cmake_parse_arguments/ArgN.cmake
Normal file
41
Tests/RunCMake/cmake_parse_arguments/ArgN.cmake
Normal file
@@ -0,0 +1,41 @@
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/test_utils.cmake)
|
||||
|
||||
function(test1)
|
||||
cmake_parse_arguments(PARSE_ARGN
|
||||
pref "OPT1;OPT2" "SINGLE1;SINGLE2" "MULTI1;MULTI2")
|
||||
|
||||
TEST(pref_OPT1 TRUE)
|
||||
TEST(pref_OPT2 FALSE)
|
||||
TEST(pref_SINGLE1 "foo;bar")
|
||||
TEST(pref_SINGLE2 UNDEFINED)
|
||||
TEST(pref_MULTI1 bar foo bar)
|
||||
TEST(pref_MULTI2 UNDEFINED)
|
||||
TEST(pref_UNPARSED_ARGUMENTS UNDEFINED)
|
||||
endfunction()
|
||||
test1(OPT1 SINGLE1 "foo;bar" MULTI1 bar foo bar)
|
||||
|
||||
function(test2 arg1 arg2)
|
||||
cmake_parse_arguments(PARSE_ARGN
|
||||
pref "OPT1;OPT2" "SINGLE1;SINGLE2" "MULTI1;MULTI2")
|
||||
|
||||
TEST(arg1 "first named")
|
||||
TEST(arg2 "second named")
|
||||
TEST(pref_OPT1 TRUE)
|
||||
TEST(pref_OPT2 FALSE)
|
||||
TEST(pref_SINGLE1 "foo;bar")
|
||||
TEST(pref_SINGLE2 UNDEFINED)
|
||||
TEST(pref_MULTI1 bar "foo;bar")
|
||||
TEST(pref_MULTI2 UNDEFINED)
|
||||
TEST(pref_UNPARSED_ARGUMENTS UNDEFINED)
|
||||
endfunction()
|
||||
test2("first named" "second named"
|
||||
OPT1 SINGLE1 "foo;bar" MULTI1 bar "foo;bar")
|
||||
|
||||
function(test3 arg1)
|
||||
cmake_parse_arguments(PARSE_ARGN
|
||||
pref "" "" "")
|
||||
|
||||
TEST(arg1 "first named")
|
||||
TEST(pref_UNPARSED_ARGUMENTS "foo;bar" dog cat)
|
||||
endfunction()
|
||||
test3("first named" "foo;bar" dog cat)
|
||||
1
Tests/RunCMake/cmake_parse_arguments/BadArgN1-result.txt
Normal file
1
Tests/RunCMake/cmake_parse_arguments/BadArgN1-result.txt
Normal file
@@ -0,0 +1 @@
|
||||
1
|
||||
5
Tests/RunCMake/cmake_parse_arguments/BadArgN1-stderr.txt
Normal file
5
Tests/RunCMake/cmake_parse_arguments/BadArgN1-stderr.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
^CMake Error at BadArgN1\.cmake:[0-9]+ \(cmake_parse_arguments\):
|
||||
PARSE_ARGN must be called with exactly 5 arguments\.
|
||||
Call Stack \(most recent call first\):
|
||||
BadArgN1\.cmake:[0-9]+ \(test1\)
|
||||
CMakeLists\.txt:[0-9]+ \(include\)$
|
||||
4
Tests/RunCMake/cmake_parse_arguments/BadArgN1.cmake
Normal file
4
Tests/RunCMake/cmake_parse_arguments/BadArgN1.cmake
Normal file
@@ -0,0 +1,4 @@
|
||||
function(test1)
|
||||
cmake_parse_arguments(PARSE_ARGN pref "" "" "" extra)
|
||||
endfunction()
|
||||
test1()
|
||||
1
Tests/RunCMake/cmake_parse_arguments/BadArgN2-result.txt
Normal file
1
Tests/RunCMake/cmake_parse_arguments/BadArgN2-result.txt
Normal file
@@ -0,0 +1 @@
|
||||
1
|
||||
5
Tests/RunCMake/cmake_parse_arguments/BadArgN2-stderr.txt
Normal file
5
Tests/RunCMake/cmake_parse_arguments/BadArgN2-stderr.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
^CMake Error at BadArgN2\.cmake:[0-9]+ \(cmake_parse_arguments\):
|
||||
PARSE_ARGN called with _FUNCTION_ARGNC='' that is not an unsigned integer
|
||||
Call Stack \(most recent call first\):
|
||||
BadArgN2\.cmake:[0-9]+ \(test2\)
|
||||
CMakeLists\.txt:[0-9]+ \(include\)$
|
||||
5
Tests/RunCMake/cmake_parse_arguments/BadArgN2.cmake
Normal file
5
Tests/RunCMake/cmake_parse_arguments/BadArgN2.cmake
Normal file
@@ -0,0 +1,5 @@
|
||||
function(test2)
|
||||
unset(_FUNCTION_ARGNC)
|
||||
cmake_parse_arguments(PARSE_ARGN pref "" "" "")
|
||||
endfunction()
|
||||
test2()
|
||||
1
Tests/RunCMake/cmake_parse_arguments/BadArgN3-result.txt
Normal file
1
Tests/RunCMake/cmake_parse_arguments/BadArgN3-result.txt
Normal file
@@ -0,0 +1 @@
|
||||
1
|
||||
5
Tests/RunCMake/cmake_parse_arguments/BadArgN3-stderr.txt
Normal file
5
Tests/RunCMake/cmake_parse_arguments/BadArgN3-stderr.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
^CMake Error at BadArgN3\.cmake:[0-9]+ \(cmake_parse_arguments\):
|
||||
PARSE_ARGN called with ARGC='bad' that is not an unsigned integer
|
||||
Call Stack \(most recent call first\):
|
||||
BadArgN3\.cmake:[0-9]+ \(test3\)
|
||||
CMakeLists\.txt:[0-9]+ \(include\)$
|
||||
5
Tests/RunCMake/cmake_parse_arguments/BadArgN3.cmake
Normal file
5
Tests/RunCMake/cmake_parse_arguments/BadArgN3.cmake
Normal file
@@ -0,0 +1,5 @@
|
||||
function(test3)
|
||||
set(ARGC bad)
|
||||
cmake_parse_arguments(PARSE_ARGN pref "" "" "")
|
||||
endfunction()
|
||||
test3()
|
||||
1
Tests/RunCMake/cmake_parse_arguments/BadArgN4-result.txt
Normal file
1
Tests/RunCMake/cmake_parse_arguments/BadArgN4-result.txt
Normal file
@@ -0,0 +1 @@
|
||||
1
|
||||
5
Tests/RunCMake/cmake_parse_arguments/BadArgN4-stderr.txt
Normal file
5
Tests/RunCMake/cmake_parse_arguments/BadArgN4-stderr.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
^CMake Error at BadArgN4\.cmake:[0-9]+ \(cmake_parse_arguments\):
|
||||
PARSE_ARGN called with ARGV0 not set
|
||||
Call Stack \(most recent call first\):
|
||||
BadArgN4\.cmake:[0-9]+ \(test4\)
|
||||
CMakeLists\.txt:[0-9]+ \(include\)$
|
||||
5
Tests/RunCMake/cmake_parse_arguments/BadArgN4.cmake
Normal file
5
Tests/RunCMake/cmake_parse_arguments/BadArgN4.cmake
Normal file
@@ -0,0 +1,5 @@
|
||||
function(test4)
|
||||
unset(ARGV0)
|
||||
cmake_parse_arguments(PARSE_ARGN pref "" "" "")
|
||||
endfunction()
|
||||
test4(arg)
|
||||
@@ -6,9 +6,14 @@ run_cmake(Mix)
|
||||
run_cmake(CornerCases)
|
||||
run_cmake(Errors)
|
||||
run_cmake(ArgvN)
|
||||
run_cmake(ArgN)
|
||||
run_cmake(BadArgvN1)
|
||||
run_cmake(BadArgvN2)
|
||||
run_cmake(BadArgvN3)
|
||||
run_cmake(BadArgvN4)
|
||||
run_cmake(BadArgN1)
|
||||
run_cmake(BadArgN2)
|
||||
run_cmake(BadArgN3)
|
||||
run_cmake(BadArgN4)
|
||||
run_cmake(CornerCasesArgvN)
|
||||
run_cmake(KeyWordsMissingValues)
|
||||
|
||||
Reference in New Issue
Block a user