cmake_parse_arguments: add KEYWORDS_MISSING_VALUES

Add KEYWORDS_MISSING_VALUES output variable to cmake_parse_arguments() to
allow to detect keywords that received no values.

Fixes: #18706
This commit is contained in:
Torsten Robitzki
2019-02-04 20:39:25 +01:00
committed by Kyle Edwards
parent 8e746db6e1
commit 5228432b45
5 changed files with 198 additions and 10 deletions

View File

@@ -59,6 +59,11 @@ All remaining arguments are collected in a variable
where recognized. This can be checked afterwards to see
whether your macro was called with unrecognized parameters.
``<one_value_keywords>`` and ``<multi_value_keywords>`` that where given no
values at all are collected in a variable ``<prefix>_KEYWORDS_MISSING_VALUES``
that will be undefined if all keywords received values. This can be checked
to see if there where keywords without any values given.
As an example here a ``my_install()`` macro, which takes similar arguments
as the real :command:`install` command:
@@ -77,7 +82,7 @@ Assume ``my_install()`` has been called like this:
.. code-block:: cmake
my_install(TARGETS foo bar DESTINATION bin OPTIONAL blub)
my_install(TARGETS foo bar DESTINATION bin OPTIONAL blub CONFIGURATIONS)
After the ``cmake_parse_arguments`` call the macro will have set or undefined
the following variables::
@@ -89,6 +94,8 @@ the following variables::
MY_INSTALL_TARGETS = "foo;bar"
MY_INSTALL_CONFIGURATIONS <UNDEFINED> # was not used
MY_INSTALL_UNPARSED_ARGUMENTS = "blub" # nothing expected after "OPTIONAL"
MY_INSTALL_KEYWORDS_MISSING_VALUES = "CONFIGURATIONS"
# No value for "CONFIGURATIONS" given
You can then continue and process these variables.
@@ -97,5 +104,6 @@ one_value_keyword another recognized keyword follows, this is
interpreted as the beginning of the new option. E.g.
``my_install(TARGETS foo DESTINATION OPTIONAL)`` would result in
``MY_INSTALL_DESTINATION`` set to ``"OPTIONAL"``, but as ``OPTIONAL``
is a keyword itself ``MY_INSTALL_DESTINATION`` will be empty and
``MY_INSTALL_OPTIONAL`` will therefore be set to ``TRUE``.
is a keyword itself ``MY_INSTALL_DESTINATION`` will be empty (but added
to ``MY_INSTALL_KEYWORDS_MISSING_VALUES``) and ``MY_INSTALL_OPTIONAL`` will
therefore be set to ``TRUE``.

View File

@@ -0,0 +1,6 @@
cmake_parse_arguments-keywords_missing_values
---------------------------------------------
* The :command:`cmake_parse_arguments` command gained an additional
``<prefix>_KEYWORDS_MISSING_VALUES`` output variable to report
keyword arguments that were given by the caller with no values.

View File

@@ -14,7 +14,7 @@
class cmExecutionStatus;
static std::string escape_arg(const std::string& arg)
static std::string EscapeArg(const std::string& arg)
{
// replace ";" with "\;" so output argument lists will split correctly
std::string escapedArg;
@@ -28,7 +28,7 @@ static std::string escape_arg(const std::string& arg)
}
namespace {
enum InsideValues
enum insideValues
{
NONE,
SINGLE,
@@ -38,6 +38,22 @@ enum InsideValues
typedef std::map<std::string, bool> options_map;
typedef std::map<std::string, std::string> single_map;
typedef std::map<std::string, std::vector<std::string>> multi_map;
typedef std::set<std::string> options_set;
}
// function to be called every time, a new key word was parsed or all
// parameters where parsed.
static void DetectKeywordsMissingValues(insideValues currentState,
const std::string& currentArgName,
int& argumentsFound,
options_set& keywordsMissingValues)
{
if (currentState == SINGLE ||
(currentState == MULTI && argumentsFound == 0)) {
keywordsMissingValues.insert(currentArgName);
}
argumentsFound = 0;
}
static void PassParsedArguments(const std::string& prefix,
@@ -45,7 +61,8 @@ static void PassParsedArguments(const std::string& prefix,
const options_map& options,
const single_map& singleValArgs,
const multi_map& multiValArgs,
const std::vector<std::string>& unparsed)
const std::vector<std::string>& unparsed,
const options_set& keywordsMissingValues)
{
for (auto const& iter : options) {
makefile.AddDefinition(prefix + iter.first,
@@ -75,6 +92,14 @@ static void PassParsedArguments(const std::string& prefix,
} else {
makefile.RemoveDefinition(prefix + "UNPARSED_ARGUMENTS");
}
if (!keywordsMissingValues.empty()) {
makefile.AddDefinition(
prefix + "KEYWORDS_MISSING_VALUES",
cmJoin(cmMakeRange(keywordsMissingValues), ";").c_str());
} else {
makefile.RemoveDefinition(prefix + "KEYWORDS_MISSING_VALUES");
}
}
bool cmParseArgumentsCommand::InitialPass(std::vector<std::string> const& args,
@@ -161,7 +186,7 @@ bool cmParseArgumentsCommand::InitialPass(std::vector<std::string> const& args,
multiValArgs[iter]; // default initialize
}
InsideValues insideValues = NONE;
insideValues insideValues = NONE;
std::string currentArgName;
list.clear();
@@ -197,10 +222,15 @@ bool cmParseArgumentsCommand::InitialPass(std::vector<std::string> const& args,
}
}
options_set keywordsMissingValues;
int multiArgumentsFound = 0;
// iterate over the arguments list and fill in the values where applicable
for (std::string const& arg : list) {
const options_map::iterator optIter = options.find(arg);
if (optIter != options.end()) {
DetectKeywordsMissingValues(insideValues, currentArgName,
multiArgumentsFound, keywordsMissingValues);
insideValues = NONE;
optIter->second = true;
continue;
@@ -208,6 +238,8 @@ bool cmParseArgumentsCommand::InitialPass(std::vector<std::string> const& args,
const single_map::iterator singleIter = singleValArgs.find(arg);
if (singleIter != singleValArgs.end()) {
DetectKeywordsMissingValues(insideValues, currentArgName,
multiArgumentsFound, keywordsMissingValues);
insideValues = SINGLE;
currentArgName = arg;
continue;
@@ -215,6 +247,8 @@ bool cmParseArgumentsCommand::InitialPass(std::vector<std::string> const& args,
const multi_map::iterator multiIter = multiValArgs.find(arg);
if (multiIter != multiValArgs.end()) {
DetectKeywordsMissingValues(insideValues, currentArgName,
multiArgumentsFound, keywordsMissingValues);
insideValues = MULTI;
currentArgName = arg;
continue;
@@ -226,15 +260,18 @@ bool cmParseArgumentsCommand::InitialPass(std::vector<std::string> const& args,
insideValues = NONE;
break;
case MULTI:
++multiArgumentsFound;
if (parseFromArgV) {
multiValArgs[currentArgName].push_back(escape_arg(arg));
multiValArgs[currentArgName].push_back(EscapeArg(arg));
} else {
multiValArgs[currentArgName].push_back(arg);
}
break;
default:
multiArgumentsFound = 0;
if (parseFromArgV) {
unparsed.push_back(escape_arg(arg));
unparsed.push_back(EscapeArg(arg));
} else {
unparsed.push_back(arg);
}
@@ -242,8 +279,11 @@ bool cmParseArgumentsCommand::InitialPass(std::vector<std::string> const& args,
}
}
DetectKeywordsMissingValues(insideValues, currentArgName,
multiArgumentsFound, keywordsMissingValues);
PassParsedArguments(prefix, *this->Makefile, options, singleValArgs,
multiValArgs, unparsed);
multiValArgs, unparsed, keywordsMissingValues);
return true;
}

View File

@@ -0,0 +1,133 @@
include(${CMAKE_CURRENT_LIST_DIR}/test_utils.cmake)
# No keywords that miss any values, _KEYWORDS_MISSING_VALUES should not be defined
cmake_parse_arguments(PREF "" "P1" "P2" P1 p1 P2 p2_a p2_b)
TEST(PREF_KEYWORDS_MISSING_VALUES "UNDEFINED")
# Keyword should even be deleted from the actual scope
set(PREF_KEYWORDS_MISSING_VALUES "What ever")
cmake_parse_arguments(PREF "" "" "")
TEST(PREF_KEYWORDS_MISSING_VALUES "UNDEFINED")
# Given missing keywords as only option
cmake_parse_arguments(PREF "" "P1" "P2" P1)
TEST(PREF_KEYWORDS_MISSING_VALUES "P1")
TEST(PREF_P1 "UNDEFINED")
TEST(PREF_UNPARSED_ARGUMENTS "UNDEFINED")
# Mixed with unparsed arguments
cmake_parse_arguments(UPREF "" "P1" "P2" A B P2 C P1)
TEST(UPREF_KEYWORDS_MISSING_VALUES "P1")
TEST(UPREF_UNPARSED_ARGUMENTS A B)
# one_value_keyword followed by option
cmake_parse_arguments(REF "OP" "P1" "" P1 OP)
TEST(REF_KEYWORDS_MISSING_VALUES "P1")
TEST(REF_UNPARSED_ARGUMENTS "UNDEFINED")
TEST(REF_OP "TRUE")
# Counter Test
cmake_parse_arguments(REF "OP" "P1" "" P1 p1 OP)
TEST(REF_KEYWORDS_MISSING_VALUES "UNDEFINED")
TEST(REF_P1 "p1")
TEST(REF_UNPARSED_ARGUMENTS "UNDEFINED")
TEST(REF_OP "TRUE")
# one_value_keyword followed by a one_value_keyword
cmake_parse_arguments(REF "" "P1;P2" "" P1 P2 p2)
TEST(REF_KEYWORDS_MISSING_VALUES "P1")
TEST(REF_P1 "UNDEFINED")
TEST(REF_UNPARSED_ARGUMENTS "UNDEFINED")
TEST(REF_P2 "p2")
# Counter Test
cmake_parse_arguments(REF "" "P1;P2" "" P1 p1 P2 p2)
TEST(REF_KEYWORDS_MISSING_VALUES "UNDEFINED")
TEST(REF_P1 "p1")
TEST(REF_UNPARSED_ARGUMENTS "UNDEFINED")
TEST(REF_P2 "p2")
# one_value_keyword followed by a multi_value_keywords
cmake_parse_arguments(REF "" "P1" "P2" P1 P2 p1 p2)
TEST(REF_KEYWORDS_MISSING_VALUES "P1")
TEST(REF_P1 "UNDEFINED")
TEST(REF_UNPARSED_ARGUMENTS "UNDEFINED")
TEST(REF_P2 p1 p2)
# Counter Examples
cmake_parse_arguments(REF "" "P1" "P2" P1 p1 P2 p1 p2)
TEST(REF_KEYWORDS_MISSING_VALUES "UNDEFINED")
TEST(REF_P1 "p1")
TEST(REF_UNPARSED_ARGUMENTS "UNDEFINED")
TEST(REF_P2 p1 p2)
# multi_value_keywords as only option
cmake_parse_arguments(REF "" "P1" "P2" P2)
TEST(REF_KEYWORDS_MISSING_VALUES "P2")
TEST(REF_P1 "UNDEFINED")
TEST(REF_UNPARSED_ARGUMENTS "UNDEFINED")
TEST(REF_P2 "UNDEFINED")
# multi_value_keywords followed by option
cmake_parse_arguments(REF "O1" "" "P1" P1 O1)
TEST(REF_KEYWORDS_MISSING_VALUES "P1")
TEST(REF_P1 "UNDEFINED")
TEST(REF_UNPARSED_ARGUMENTS "UNDEFINED")
TEST(REF_O1 "TRUE")
# counter test
cmake_parse_arguments(REF "O1" "" "P1" P1 p1 p2 O1)
TEST(REF_KEYWORDS_MISSING_VALUES "UNDEFINED")
TEST(REF_P1 "p1;p2")
TEST(REF_UNPARSED_ARGUMENTS "UNDEFINED")
TEST(REF_O1 "TRUE")
# multi_value_keywords followed by one_value_keyword
cmake_parse_arguments(REF "" "P1" "P2" P2 P1 p1)
TEST(REF_KEYWORDS_MISSING_VALUES "P2")
TEST(REF_P1 "p1")
TEST(REF_UNPARSED_ARGUMENTS "UNDEFINED")
TEST(REF_P2 "UNDEFINED")
# counter test
cmake_parse_arguments(REF "" "P1" "P2" P2 p2 P1 p1)
TEST(REF_KEYWORDS_MISSING_VALUES "UNDEFINED")
TEST(REF_P1 "p1")
TEST(REF_UNPARSED_ARGUMENTS "UNDEFINED")
TEST(REF_P2 "p2")
# one_value_keyword as last argument
cmake_parse_arguments(REF "" "P1" "P2" A P2 p2 P1)
TEST(REF_KEYWORDS_MISSING_VALUES "P1")
TEST(REF_P1 "UNDEFINED")
TEST(REF_UNPARSED_ARGUMENTS "A")
TEST(REF_P2 "p2")
# multi_value_keywords as last argument
cmake_parse_arguments(REF "" "P1" "P2" P1 p1 P2)
TEST(REF_KEYWORDS_MISSING_VALUES "P2")
TEST(REF_P1 "p1")
TEST(REF_P2 "UNDEFINED")
# Multiple one_value_keyword and multi_value_keywords at different places
cmake_parse_arguments(REF "O1;O2" "P1" "P2" P1 O1 P2 O2)
TEST(REF_KEYWORDS_MISSING_VALUES P1 P2)
TEST(REF_P1 "UNDEFINED")
TEST(REF_P2 "UNDEFINED")
# Duplicated missing keywords
cmake_parse_arguments(REF "O1;O2" "P1" "P2" P1 O1 P2 O2 P1 P2)
TEST(REF_KEYWORDS_MISSING_VALUES P1 P2)
TEST(REF_P1 "UNDEFINED")
TEST(REF_P2 "UNDEFINED")
# make sure keywords that are never used, don't get added to KEYWORDS_MISSING_VALUES
cmake_parse_arguments(REF "O1;O2" "P1" "P2")
TEST(REF_KEYWORDS_MISSING_VALUES "UNDEFINED")
TEST(REF_O1 FALSE)
TEST(REF_O2 FALSE)
TEST(REF_P1 UNDEFINED)
TEST(REF_P2 UNDEFINED)

View File

@@ -11,3 +11,4 @@ run_cmake(BadArgvN2)
run_cmake(BadArgvN3)
run_cmake(BadArgvN4)
run_cmake(CornerCasesArgvN)
run_cmake(KeyWordsMissingValues)