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:
Arha Gatram
2026-06-01 13:41:05 -07:00
committed by Brad King
parent 4d945a6491
commit 97c36916d9
18 changed files with 151 additions and 11 deletions

View File

@@ -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.

View 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.

View File

@@ -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);

View File

@@ -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;
}

View 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)

View File

@@ -0,0 +1 @@
1

View 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\)$

View File

@@ -0,0 +1,4 @@
function(test1)
cmake_parse_arguments(PARSE_ARGN pref "" "" "" extra)
endfunction()
test1()

View File

@@ -0,0 +1 @@
1

View 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\)$

View File

@@ -0,0 +1,5 @@
function(test2)
unset(_FUNCTION_ARGNC)
cmake_parse_arguments(PARSE_ARGN pref "" "" "")
endfunction()
test2()

View File

@@ -0,0 +1 @@
1

View 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\)$

View File

@@ -0,0 +1,5 @@
function(test3)
set(ARGC bad)
cmake_parse_arguments(PARSE_ARGN pref "" "" "")
endfunction()
test3()

View File

@@ -0,0 +1 @@
1

View 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\)$

View File

@@ -0,0 +1,5 @@
function(test4)
unset(ARGV0)
cmake_parse_arguments(PARSE_ARGN pref "" "" "")
endfunction()
test4(arg)

View File

@@ -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)