mirror of
https://github.com/Kitware/CMake.git
synced 2026-01-05 21:31:08 -06:00
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:
committed by
Kyle Edwards
parent
8e746db6e1
commit
5228432b45
@@ -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``.
|
||||
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
133
Tests/RunCMake/cmake_parse_arguments/KeyWordsMissingValues.cmake
Normal file
133
Tests/RunCMake/cmake_parse_arguments/KeyWordsMissingValues.cmake
Normal 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)
|
||||
@@ -11,3 +11,4 @@ run_cmake(BadArgvN2)
|
||||
run_cmake(BadArgvN3)
|
||||
run_cmake(BadArgvN4)
|
||||
run_cmake(CornerCasesArgvN)
|
||||
run_cmake(KeyWordsMissingValues)
|
||||
|
||||
Reference in New Issue
Block a user