From 97c36916d9b3c00a0f5718bea5df25ed8fa5d9dc Mon Sep 17 00:00:00 2001 From: Arha Gatram Date: Mon, 1 Jun 2026 13:41:05 -0700 Subject: [PATCH] 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 --- Help/command/cmake_parse_arguments.rst | 9 ++++ Help/release/dev/parse-arguments-argn.rst | 7 +++ Source/cmFunctionCommand.cxx | 7 ++- Source/cmParseArgumentsCommand.cxx | 50 +++++++++++++++---- .../RunCMake/cmake_parse_arguments/ArgN.cmake | 41 +++++++++++++++ .../cmake_parse_arguments/BadArgN1-result.txt | 1 + .../cmake_parse_arguments/BadArgN1-stderr.txt | 5 ++ .../cmake_parse_arguments/BadArgN1.cmake | 4 ++ .../cmake_parse_arguments/BadArgN2-result.txt | 1 + .../cmake_parse_arguments/BadArgN2-stderr.txt | 5 ++ .../cmake_parse_arguments/BadArgN2.cmake | 5 ++ .../cmake_parse_arguments/BadArgN3-result.txt | 1 + .../cmake_parse_arguments/BadArgN3-stderr.txt | 5 ++ .../cmake_parse_arguments/BadArgN3.cmake | 5 ++ .../cmake_parse_arguments/BadArgN4-result.txt | 1 + .../cmake_parse_arguments/BadArgN4-stderr.txt | 5 ++ .../cmake_parse_arguments/BadArgN4.cmake | 5 ++ .../cmake_parse_arguments/RunCMakeTest.cmake | 5 ++ 18 files changed, 151 insertions(+), 11 deletions(-) create mode 100644 Help/release/dev/parse-arguments-argn.rst create mode 100644 Tests/RunCMake/cmake_parse_arguments/ArgN.cmake create mode 100644 Tests/RunCMake/cmake_parse_arguments/BadArgN1-result.txt create mode 100644 Tests/RunCMake/cmake_parse_arguments/BadArgN1-stderr.txt create mode 100644 Tests/RunCMake/cmake_parse_arguments/BadArgN1.cmake create mode 100644 Tests/RunCMake/cmake_parse_arguments/BadArgN2-result.txt create mode 100644 Tests/RunCMake/cmake_parse_arguments/BadArgN2-stderr.txt create mode 100644 Tests/RunCMake/cmake_parse_arguments/BadArgN2.cmake create mode 100644 Tests/RunCMake/cmake_parse_arguments/BadArgN3-result.txt create mode 100644 Tests/RunCMake/cmake_parse_arguments/BadArgN3-stderr.txt create mode 100644 Tests/RunCMake/cmake_parse_arguments/BadArgN3.cmake create mode 100644 Tests/RunCMake/cmake_parse_arguments/BadArgN4-result.txt create mode 100644 Tests/RunCMake/cmake_parse_arguments/BadArgN4-stderr.txt create mode 100644 Tests/RunCMake/cmake_parse_arguments/BadArgN4.cmake diff --git a/Help/command/cmake_parse_arguments.rst b/Help/command/cmake_parse_arguments.rst index 441cbfa470..e5505bc0ae 100644 --- a/Help/command/cmake_parse_arguments.rst +++ b/Help/command/cmake_parse_arguments.rst @@ -11,6 +11,9 @@ Parse function or macro arguments. cmake_parse_arguments(PARSE_ARGV ) + cmake_parse_arguments(PARSE_ARGN + ) + .. 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 ````-th argument, where ```` 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 ```` being the number + of parameters in the function definition. + The ```` 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. diff --git a/Help/release/dev/parse-arguments-argn.rst b/Help/release/dev/parse-arguments-argn.rst new file mode 100644 index 0000000000..2c592eca0f --- /dev/null +++ b/Help/release/dev/parse-arguments-argn.rst @@ -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 ```` being the number + of parameters in the function definition. diff --git a/Source/cmFunctionCommand.cxx b/Source/cmFunctionCommand.cxx index 456cb72984..3e62649c89 100644 --- a/Source/cmFunctionCommand.cxx +++ b/Source/cmFunctionCommand.cxx @@ -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); diff --git a/Source/cmParseArgumentsCommand.cxx b/Source/cmParseArgumentsCommand.cxx index ee0a87751a..88b62952a8 100644 --- a/Source/cmParseArgumentsCommand.cxx +++ b/Source/cmParseArgumentsCommand.cxx @@ -141,6 +141,8 @@ bool cmParseArgumentsCommand(std::vector 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 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 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 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 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 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; } diff --git a/Tests/RunCMake/cmake_parse_arguments/ArgN.cmake b/Tests/RunCMake/cmake_parse_arguments/ArgN.cmake new file mode 100644 index 0000000000..2977339059 --- /dev/null +++ b/Tests/RunCMake/cmake_parse_arguments/ArgN.cmake @@ -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) diff --git a/Tests/RunCMake/cmake_parse_arguments/BadArgN1-result.txt b/Tests/RunCMake/cmake_parse_arguments/BadArgN1-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/cmake_parse_arguments/BadArgN1-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/cmake_parse_arguments/BadArgN1-stderr.txt b/Tests/RunCMake/cmake_parse_arguments/BadArgN1-stderr.txt new file mode 100644 index 0000000000..221701a009 --- /dev/null +++ b/Tests/RunCMake/cmake_parse_arguments/BadArgN1-stderr.txt @@ -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\)$ diff --git a/Tests/RunCMake/cmake_parse_arguments/BadArgN1.cmake b/Tests/RunCMake/cmake_parse_arguments/BadArgN1.cmake new file mode 100644 index 0000000000..67cf5e0d7d --- /dev/null +++ b/Tests/RunCMake/cmake_parse_arguments/BadArgN1.cmake @@ -0,0 +1,4 @@ +function(test1) + cmake_parse_arguments(PARSE_ARGN pref "" "" "" extra) +endfunction() +test1() diff --git a/Tests/RunCMake/cmake_parse_arguments/BadArgN2-result.txt b/Tests/RunCMake/cmake_parse_arguments/BadArgN2-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/cmake_parse_arguments/BadArgN2-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/cmake_parse_arguments/BadArgN2-stderr.txt b/Tests/RunCMake/cmake_parse_arguments/BadArgN2-stderr.txt new file mode 100644 index 0000000000..806756a379 --- /dev/null +++ b/Tests/RunCMake/cmake_parse_arguments/BadArgN2-stderr.txt @@ -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\)$ diff --git a/Tests/RunCMake/cmake_parse_arguments/BadArgN2.cmake b/Tests/RunCMake/cmake_parse_arguments/BadArgN2.cmake new file mode 100644 index 0000000000..0e916791c4 --- /dev/null +++ b/Tests/RunCMake/cmake_parse_arguments/BadArgN2.cmake @@ -0,0 +1,5 @@ +function(test2) + unset(_FUNCTION_ARGNC) + cmake_parse_arguments(PARSE_ARGN pref "" "" "") +endfunction() +test2() diff --git a/Tests/RunCMake/cmake_parse_arguments/BadArgN3-result.txt b/Tests/RunCMake/cmake_parse_arguments/BadArgN3-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/cmake_parse_arguments/BadArgN3-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/cmake_parse_arguments/BadArgN3-stderr.txt b/Tests/RunCMake/cmake_parse_arguments/BadArgN3-stderr.txt new file mode 100644 index 0000000000..8bda707c44 --- /dev/null +++ b/Tests/RunCMake/cmake_parse_arguments/BadArgN3-stderr.txt @@ -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\)$ diff --git a/Tests/RunCMake/cmake_parse_arguments/BadArgN3.cmake b/Tests/RunCMake/cmake_parse_arguments/BadArgN3.cmake new file mode 100644 index 0000000000..90f2c6e19b --- /dev/null +++ b/Tests/RunCMake/cmake_parse_arguments/BadArgN3.cmake @@ -0,0 +1,5 @@ +function(test3) + set(ARGC bad) + cmake_parse_arguments(PARSE_ARGN pref "" "" "") +endfunction() +test3() diff --git a/Tests/RunCMake/cmake_parse_arguments/BadArgN4-result.txt b/Tests/RunCMake/cmake_parse_arguments/BadArgN4-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/cmake_parse_arguments/BadArgN4-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/cmake_parse_arguments/BadArgN4-stderr.txt b/Tests/RunCMake/cmake_parse_arguments/BadArgN4-stderr.txt new file mode 100644 index 0000000000..c9617a4793 --- /dev/null +++ b/Tests/RunCMake/cmake_parse_arguments/BadArgN4-stderr.txt @@ -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\)$ diff --git a/Tests/RunCMake/cmake_parse_arguments/BadArgN4.cmake b/Tests/RunCMake/cmake_parse_arguments/BadArgN4.cmake new file mode 100644 index 0000000000..627592a65c --- /dev/null +++ b/Tests/RunCMake/cmake_parse_arguments/BadArgN4.cmake @@ -0,0 +1,5 @@ +function(test4) + unset(ARGV0) + cmake_parse_arguments(PARSE_ARGN pref "" "" "") +endfunction() +test4(arg) diff --git a/Tests/RunCMake/cmake_parse_arguments/RunCMakeTest.cmake b/Tests/RunCMake/cmake_parse_arguments/RunCMakeTest.cmake index 505840d7a7..dc530a61f3 100644 --- a/Tests/RunCMake/cmake_parse_arguments/RunCMakeTest.cmake +++ b/Tests/RunCMake/cmake_parse_arguments/RunCMakeTest.cmake @@ -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)