cmake_parse_arguments: Add option to read arguments from ARGC/ARGV#

The `ARGC`/`ARGV#` variables in function scope hold the original
arguments with no ;-list flattening.  Add a way for functions to
cleanly parse arguments that may contain `;`.  This also avoids
extra copying of the arguments.

Co-Author: Brad King <brad.king@kitware.com>
This commit is contained in:
Bill Hoffman 2016-09-07 16:47:23 -04:00 committed by Brad King
parent f506489d1e
commit cb299acc27
17 changed files with 139 additions and 4 deletions

View File

@ -11,6 +11,17 @@ respective options.
cmake_parse_arguments(<prefix> <options> <one_value_keywords> cmake_parse_arguments(<prefix> <options> <one_value_keywords>
<multi_value_keywords> args...) <multi_value_keywords> args...)
cmake_parse_arguments(PARSE_ARGV N <prefix> <options> <one_value_keywords>
<multi_value_keywords>)
The first signature reads processes arguments passed in the ``args...``.
This may be used in either a :command:`macro` or a :command:`function`.
The ``PARSE_ARGV`` signature is only for use in a :command:`function`
body. In this case the arguments that are parsed come from the
``ARGV#`` variables of the calling function. The parsing starts with
the Nth argument, where ``N`` is an unsigned integer. This allows for
the values to have special characters like ``;`` in them.
The ``<options>`` argument contains all options for the respective macro, The ``<options>`` argument contains all options for the respective macro,
i.e. keywords which can be used when calling the macro without any value i.e. keywords which can be used when calling the macro without any value

View File

@ -0,0 +1,6 @@
parse_arguments_argv_n
----------------------
* The :command:`cmake_parse_arguments` command gained a new
mode to read arguments directly from ``ARGC`` and ``ARGV#``
variables inside a :command:`function` body.

View File

@ -20,6 +20,8 @@ bool cmParseArgumentsCommand::InitialPass(std::vector<std::string> const& args,
{ {
// cmake_parse_arguments(prefix options single multi <ARGN>) // cmake_parse_arguments(prefix options single multi <ARGN>)
// 1 2 3 4 // 1 2 3 4
// or
// cmake_parse_arguments(PARSE_ARGV N prefix options single multi)
if (args.size() < 4) { if (args.size() < 4) {
this->SetError("must be called with at least 4 arguments."); this->SetError("must be called with at least 4 arguments.");
return false; return false;
@ -27,6 +29,27 @@ bool cmParseArgumentsCommand::InitialPass(std::vector<std::string> const& args,
std::vector<std::string>::const_iterator argIter = args.begin(), std::vector<std::string>::const_iterator argIter = args.begin(),
argEnd = args.end(); argEnd = args.end();
bool parseFromArgV = false;
unsigned long argvStart = 0;
if (*argIter == "PARSE_ARGV") {
if (args.size() != 6) {
this->Makefile->IssueMessage(
cmake::FATAL_ERROR,
"PARSE_ARGV must be called with exactly 6 arguments.");
cmSystemTools::SetFatalErrorOccured();
return true;
}
parseFromArgV = true;
argIter++; // move past PARSE_ARGV
if (!cmSystemTools::StringToULong(argIter->c_str(), &argvStart)) {
this->Makefile->IssueMessage(cmake::FATAL_ERROR, "PARSE_ARGV index '" +
*argIter +
"' is not an unsigned integer");
cmSystemTools::SetFatalErrorOccured();
return true;
}
argIter++; // move past N
}
// the first argument is the prefix // the first argument is the prefix
const std::string prefix = (*argIter++) + "_"; const std::string prefix = (*argIter++) + "_";
@ -90,11 +113,37 @@ bool cmParseArgumentsCommand::InitialPass(std::vector<std::string> const& args,
} insideValues = NONE; } insideValues = NONE;
std::string currentArgName; std::string currentArgName;
// Flatten ;-lists in the arguments into a single list as was done
// by the original function(CMAKE_PARSE_ARGUMENTS).
list.clear(); list.clear();
for (; argIter != argEnd; ++argIter) { if (!parseFromArgV) {
cmSystemTools::ExpandListArgument(*argIter, list); // Flatten ;-lists in the arguments into a single list as was done
// by the original function(CMAKE_PARSE_ARGUMENTS).
for (; argIter != argEnd; ++argIter) {
cmSystemTools::ExpandListArgument(*argIter, list);
}
} else {
// in the PARSE_ARGV move read the arguments from ARGC and ARGV#
std::string argc = this->Makefile->GetSafeDefinition("ARGC");
unsigned long count;
if (!cmSystemTools::StringToULong(argc.c_str(), &count)) {
this->Makefile->IssueMessage(cmake::FATAL_ERROR,
"PARSE_ARGV called with ARGC='" + argc +
"' that is not an unsigned integer");
cmSystemTools::SetFatalErrorOccured();
return true;
}
for (unsigned long i = argvStart; i < count; ++i) {
std::ostringstream argName;
argName << "ARGV" << i;
const char* arg = this->Makefile->GetDefinition(argName.str());
if (!arg) {
this->Makefile->IssueMessage(cmake::FATAL_ERROR,
"PARSE_ARGV called with " +
argName.str() + " not set");
cmSystemTools::SetFatalErrorOccured();
return true;
}
list.push_back(arg);
}
} }
// iterate over the arguments list and fill in the values where applicable // iterate over the arguments list and fill in the values where applicable

View File

@ -0,0 +1,30 @@
include(${CMAKE_CURRENT_LIST_DIR}/test_utils.cmake)
function(test1)
cmake_parse_arguments(PARSE_ARGV 0
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)
cmake_parse_arguments(PARSE_ARGV 1
pref "OPT1;OPT2" "SINGLE1;SINGLE2" "MULTI1;MULTI2")
TEST(arg1 "first 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" OPT1 SINGLE1 "foo;bar" MULTI1 bar foo bar)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,5 @@
^CMake Error at BadArgvN1.cmake:[0-9]+ \(cmake_parse_arguments\):
PARSE_ARGV must be called with exactly 6 arguments.
Call Stack \(most recent call first\):
BadArgvN1.cmake:[0-9]+ \(test1\)
CMakeLists.txt:[0-9]+ \(include\)$

View File

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

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,5 @@
^CMake Error at BadArgvN2.cmake:[0-9]+ \(cmake_parse_arguments\):
PARSE_ARGV index 'pref' is not an unsigned integer
Call Stack \(most recent call first\):
BadArgvN2.cmake:[0-9]+ \(test2\)
CMakeLists.txt:[0-9]+ \(include\)$

View File

@ -0,0 +1,4 @@
function(test2)
cmake_parse_arguments(PARSE_ARGV pref "" "" "" extra)
endfunction()
test2()

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,4 @@
^CMake Error at BadArgvN3.cmake:[0-9]+ \(cmake_parse_arguments\):
PARSE_ARGV called with ARGC='' that is not an unsigned integer
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)$

View File

@ -0,0 +1 @@
cmake_parse_arguments(PARSE_ARGV 0 pref "" "" "")

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,4 @@
^CMake Error at BadArgvN4.cmake:[0-9]+ \(cmake_parse_arguments\):
PARSE_ARGV called with ARGV0 not set
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)$

View File

@ -0,0 +1,3 @@
set(ARGC 1)
cmake_parse_arguments(PARSE_ARGV 0 pref "" "" "")
unset(ARGC)

View File

@ -5,3 +5,8 @@ run_cmake(Initialization)
run_cmake(Mix) run_cmake(Mix)
run_cmake(CornerCases) run_cmake(CornerCases)
run_cmake(Errors) run_cmake(Errors)
run_cmake(ArgvN)
run_cmake(BadArgvN1)
run_cmake(BadArgvN2)
run_cmake(BadArgvN3)
run_cmake(BadArgvN4)