summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTorsten Robitzki <Torsten@Robitzki.de>2019-02-04 20:39:25 +0100
committerKyle Edwards <kyle.edwards@kitware.com>2019-02-07 12:59:10 -0500
commit5228432b45b7ce740b93f2c80c8c7ce6987d4bf1 (patch)
tree3b2ea40e240af8f48008ac36448d275f27b3dbee
parent8e746db6e11f9e017cd04552efaf40235d50363d (diff)
downloadcmake-5228432b45b7ce740b93f2c80c8c7ce6987d4bf1.tar.gz
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
-rw-r--r--Help/command/cmake_parse_arguments.rst14
-rw-r--r--Help/release/dev/cmake_parse_arguments-keywords_missing_values.rst6
-rw-r--r--Source/cmParseArgumentsCommand.cxx54
-rw-r--r--Tests/RunCMake/cmake_parse_arguments/KeyWordsMissingValues.cmake133
-rw-r--r--Tests/RunCMake/cmake_parse_arguments/RunCMakeTest.cmake1
5 files changed, 198 insertions, 10 deletions
diff --git a/Help/command/cmake_parse_arguments.rst b/Help/command/cmake_parse_arguments.rst
index c8327e2863..196d90f83b 100644
--- a/Help/command/cmake_parse_arguments.rst
+++ b/Help/command/cmake_parse_arguments.rst
@@ -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``.
diff --git a/Help/release/dev/cmake_parse_arguments-keywords_missing_values.rst b/Help/release/dev/cmake_parse_arguments-keywords_missing_values.rst
new file mode 100644
index 0000000000..c7fe96b080
--- /dev/null
+++ b/Help/release/dev/cmake_parse_arguments-keywords_missing_values.rst
@@ -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.
diff --git a/Source/cmParseArgumentsCommand.cxx b/Source/cmParseArgumentsCommand.cxx
index cef070403d..6231aab03e 100644
--- a/Source/cmParseArgumentsCommand.cxx
+++ b/Source/cmParseArgumentsCommand.cxx
@@ -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;
}
diff --git a/Tests/RunCMake/cmake_parse_arguments/KeyWordsMissingValues.cmake b/Tests/RunCMake/cmake_parse_arguments/KeyWordsMissingValues.cmake
new file mode 100644
index 0000000000..561f9c03b1
--- /dev/null
+++ b/Tests/RunCMake/cmake_parse_arguments/KeyWordsMissingValues.cmake
@@ -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)
diff --git a/Tests/RunCMake/cmake_parse_arguments/RunCMakeTest.cmake b/Tests/RunCMake/cmake_parse_arguments/RunCMakeTest.cmake
index 1e15b3b2cc..505840d7a7 100644
--- a/Tests/RunCMake/cmake_parse_arguments/RunCMakeTest.cmake
+++ b/Tests/RunCMake/cmake_parse_arguments/RunCMakeTest.cmake
@@ -11,3 +11,4 @@ run_cmake(BadArgvN2)
run_cmake(BadArgvN3)
run_cmake(BadArgvN4)
run_cmake(CornerCasesArgvN)
+run_cmake(KeyWordsMissingValues)