CMakeParseArguments: replace by native cmake_parse_arguments command

Implement a native `cmake_parse_arguments` command that is fully
compatible with the documented behaviour of the previous implementation.
Leave the CMakeParseArguments module empty but existing for
compatibility.
This commit is contained in:
Matthias Maennich 2015-12-05 19:01:12 +01:00 committed by Brad King
parent cbbdfc2b61
commit e8b148318f
12 changed files with 330 additions and 161 deletions

View File

@ -0,0 +1,78 @@
cmake_parse_arguments
---------------------
``cmake_parse_arguments`` is intended to be used in macros or functions for
parsing the arguments given to that macro or function. It processes the
arguments and defines a set of variables which hold the values of the
respective options.
::
cmake_parse_arguments(<prefix> <options> <one_value_keywords>
<multi_value_keywords> args...)
The ``<options>`` argument contains all options for the respective macro,
i.e. keywords which can be used when calling the macro without any value
following, like e.g. the ``OPTIONAL`` keyword of the :command:`install`
command.
The ``<one_value_keywords>`` argument contains all keywords for this macro
which are followed by one value, like e.g. ``DESTINATION`` keyword of the
:command:`install` command.
The ``<multi_value_keywords>`` argument contains all keywords for this
macro which can be followed by more than one value, like e.g. the
``TARGETS`` or ``FILES`` keywords of the :command:`install` command.
When done, ``cmake_parse_arguments`` will have defined for each of the
keywords listed in ``<options>``, ``<one_value_keywords>`` and
``<multi_value_keywords>`` a variable composed of the given ``<prefix>``
followed by ``"_"`` and the name of the respective keyword. These
variables will then hold the respective value from the argument list.
For the ``<options>`` keywords this will be ``TRUE`` or ``FALSE``.
All remaining arguments are collected in a variable
``<prefix>_UNPARSED_ARGUMENTS``, this can be checked afterwards to see
whether your macro was called with unrecognized parameters.
As an example here a ``my_install()`` macro, which takes similar arguments
as the real :command:`install` command:
.. code-block:: cmake
function(MY_INSTALL)
set(options OPTIONAL FAST)
set(oneValueArgs DESTINATION RENAME)
set(multiValueArgs TARGETS CONFIGURATIONS)
cmake_parse_arguments(MY_INSTALL "${options}" "${oneValueArgs}"
"${multiValueArgs}" ${ARGN} )
# ...
Assume ``my_install()`` has been called like this:
.. code-block:: cmake
my_install(TARGETS foo bar DESTINATION bin OPTIONAL blub)
After the ``cmake_parse_arguments`` call the macro will have set the
following variables::
MY_INSTALL_OPTIONAL = TRUE
MY_INSTALL_FAST = FALSE (was not used in call to my_install)
MY_INSTALL_DESTINATION = "bin"
MY_INSTALL_RENAME = "" (was not used)
MY_INSTALL_TARGETS = "foo;bar"
MY_INSTALL_CONFIGURATIONS = "" (was not used)
MY_INSTALL_UNPARSED_ARGUMENTS = "blub" (nothing expected after "OPTIONAL")
You can then continue and process these variables.
Keywords terminate lists of values, e.g. if directly after a
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``.

View File

@ -29,6 +29,7 @@ These commands may be used freely in CMake projects.
/command/build_command /command/build_command
/command/cmake_host_system_information /command/cmake_host_system_information
/command/cmake_minimum_required /command/cmake_minimum_required
/command/cmake_parse_arguments
/command/cmake_policy /command/cmake_policy
/command/configure_file /command/configure_file
/command/continue /command/continue

View File

@ -0,0 +1,6 @@
CMakeParseArguments-native-impl
-------------------------------
* The :command:`cmake_parse_arguments` command is now implemented natively.
The :module:`CMakeParseArguments` module remains as an empty placeholder
for compatibility.

View File

@ -2,86 +2,10 @@
# CMakeParseArguments # CMakeParseArguments
# ------------------- # -------------------
# #
# # This module once implemented the :command:`cmake_parse_arguments` command
# # that is now implemented natively by CMake. It is now an empty placeholder
# CMAKE_PARSE_ARGUMENTS(<prefix> <options> <one_value_keywords> # for compatibility with projects that include it to get the command from
# <multi_value_keywords> args...) # CMake 3.4 and lower.
#
# CMAKE_PARSE_ARGUMENTS() is intended to be used in macros or functions
# for parsing the arguments given to that macro or function. It
# processes the arguments and defines a set of variables which hold the
# values of the respective options.
#
# The <options> argument contains all options for the respective macro,
# i.e. keywords which can be used when calling the macro without any
# value following, like e.g. the OPTIONAL keyword of the install()
# command.
#
# The <one_value_keywords> argument contains all keywords for this macro
# which are followed by one value, like e.g. DESTINATION keyword of the
# install() command.
#
# The <multi_value_keywords> argument contains all keywords for this
# macro which can be followed by more than one value, like e.g. the
# TARGETS or FILES keywords of the install() command.
#
# When done, CMAKE_PARSE_ARGUMENTS() will have defined for each of the
# keywords listed in <options>, <one_value_keywords> and
# <multi_value_keywords> a variable composed of the given <prefix>
# followed by "_" and the name of the respective keyword. These
# variables will then hold the respective value from the argument list.
# For the <options> keywords this will be TRUE or FALSE.
#
# All remaining arguments are collected in a variable
# <prefix>_UNPARSED_ARGUMENTS, this can be checked afterwards to see
# whether your macro was called with unrecognized parameters.
#
# As an example here a my_install() macro, which takes similar arguments
# as the real install() command:
#
# ::
#
# function(MY_INSTALL)
# set(options OPTIONAL FAST)
# set(oneValueArgs DESTINATION RENAME)
# set(multiValueArgs TARGETS CONFIGURATIONS)
# cmake_parse_arguments(MY_INSTALL "${options}" "${oneValueArgs}"
# "${multiValueArgs}" ${ARGN} )
# ...
#
#
#
# Assume my_install() has been called like this:
#
# ::
#
# my_install(TARGETS foo bar DESTINATION bin OPTIONAL blub)
#
#
#
# After the cmake_parse_arguments() call the macro will have set the
# following variables:
#
# ::
#
# MY_INSTALL_OPTIONAL = TRUE
# MY_INSTALL_FAST = FALSE (this option was not used when calling my_install()
# MY_INSTALL_DESTINATION = "bin"
# MY_INSTALL_RENAME = "" (was not used)
# MY_INSTALL_TARGETS = "foo;bar"
# MY_INSTALL_CONFIGURATIONS = "" (was not used)
# MY_INSTALL_UNPARSED_ARGUMENTS = "blub" (no value expected after "OPTIONAL"
#
#
#
# You can then continue and process these variables.
#
# Keywords terminate lists of values, e.g. if directly after a
# 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 MY_INSTALL_DESTINATION
# would be empty and MY_INSTALL_OPTIONAL would be set to TRUE therefor.
#============================================================================= #=============================================================================
# Copyright 2010 Alexander Neundorf <neundorf@kde.org> # Copyright 2010 Alexander Neundorf <neundorf@kde.org>
@ -95,67 +19,3 @@
#============================================================================= #=============================================================================
# (To distribute this file outside of CMake, substitute the full # (To distribute this file outside of CMake, substitute the full
# License text for the above reference.) # License text for the above reference.)
if(__CMAKE_PARSE_ARGUMENTS_INCLUDED)
return()
endif()
set(__CMAKE_PARSE_ARGUMENTS_INCLUDED TRUE)
function(CMAKE_PARSE_ARGUMENTS prefix _optionNames _singleArgNames _multiArgNames)
# first set all result variables to empty/FALSE
foreach(arg_name ${_singleArgNames} ${_multiArgNames})
set(${prefix}_${arg_name})
endforeach()
foreach(option ${_optionNames})
set(${prefix}_${option} FALSE)
endforeach()
set(${prefix}_UNPARSED_ARGUMENTS)
set(insideValues FALSE)
set(currentArgName)
# now iterate over all arguments and fill the result variables
foreach(currentArg ${ARGN})
list(FIND _optionNames "${currentArg}" optionIndex) # ... then this marks the end of the arguments belonging to this keyword
list(FIND _singleArgNames "${currentArg}" singleArgIndex) # ... then this marks the end of the arguments belonging to this keyword
list(FIND _multiArgNames "${currentArg}" multiArgIndex) # ... then this marks the end of the arguments belonging to this keyword
if(${optionIndex} EQUAL -1 AND ${singleArgIndex} EQUAL -1 AND ${multiArgIndex} EQUAL -1)
if(insideValues)
if("${insideValues}" STREQUAL "SINGLE")
set(${prefix}_${currentArgName} ${currentArg})
set(insideValues FALSE)
elseif("${insideValues}" STREQUAL "MULTI")
list(APPEND ${prefix}_${currentArgName} ${currentArg})
endif()
else()
list(APPEND ${prefix}_UNPARSED_ARGUMENTS ${currentArg})
endif()
else()
if(NOT ${optionIndex} EQUAL -1)
set(${prefix}_${currentArg} TRUE)
set(insideValues FALSE)
elseif(NOT ${singleArgIndex} EQUAL -1)
set(currentArgName ${currentArg})
set(${prefix}_${currentArgName})
set(insideValues "SINGLE")
elseif(NOT ${multiArgIndex} EQUAL -1)
set(currentArgName ${currentArg})
set(${prefix}_${currentArgName})
set(insideValues "MULTI")
endif()
endif()
endforeach()
# propagate the result variables to the caller:
foreach(arg_name ${_singleArgNames} ${_multiArgNames} ${_optionNames})
set(${prefix}_${arg_name} ${${prefix}_${arg_name}} PARENT_SCOPE)
endforeach()
set(${prefix}_UNPARSED_ARGUMENTS ${${prefix}_UNPARSED_ARGUMENTS} PARENT_SCOPE)
endfunction()

View File

@ -54,6 +54,7 @@
#include "cmFunctionCommand.cxx" #include "cmFunctionCommand.cxx"
#include "cmPathLabel.cxx" #include "cmPathLabel.cxx"
#include "cmSearchPath.cxx" #include "cmSearchPath.cxx"
#include "cmParseArgumentsCommand.cxx"
void GetBootstrapCommands1(std::vector<cmCommand*>& commands) void GetBootstrapCommands1(std::vector<cmCommand*>& commands)
{ {
@ -91,4 +92,5 @@ void GetBootstrapCommands1(std::vector<cmCommand*>& commands)
commands.push_back(new cmFindProgramCommand); commands.push_back(new cmFindProgramCommand);
commands.push_back(new cmForEachCommand); commands.push_back(new cmForEachCommand);
commands.push_back(new cmFunctionCommand); commands.push_back(new cmFunctionCommand);
commands.push_back(new cmParseArgumentsCommand);
} }

View File

@ -0,0 +1,176 @@
/*============================================================================
CMake - Cross Platform Makefile Generator
Copyright 2015 Matthias Maennich <matthias@maennich.net>
Copyright 2010 Alexander Neundorf <neundorf@kde.org>
Distributed under the OSI-approved BSD License (the "License");
see accompanying file Copyright.txt for details.
This software is distributed WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the License for more information.
============================================================================*/
#include "cmParseArgumentsCommand.h"
#include "cmAlgorithms.h"
//----------------------------------------------------------------------------
bool cmParseArgumentsCommand
::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &)
{
// cmake_parse_arguments(prefix options single multi <ARGN>)
// 1 2 3 4
if (args.size() < 4)
{
this->SetError("must be called with at least 4 arguments.");
return false;
}
std::vector<std::string>::const_iterator argIter = args.begin(),
argEnd = args.end();
// the first argument is the prefix
const std::string prefix = (*argIter++) + "_";
// define the result maps holding key/value pairs for
// options, single values and multi values
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;
options_map options;
single_map single;
multi_map multi;
// anything else is put into a vector of unparsed strings
std::vector<std::string> unparsed;
// the second argument is a (cmake) list of options without argument
std::vector<std::string> list;
cmSystemTools::ExpandListArgument(*argIter++, list);
for (std::vector<std::string>::const_iterator iter = list.begin(),
end = list.end();
iter != end; ++iter)
{
options[*iter]; // default initialize
}
// the third argument is a (cmake) list of single argument options
list.clear();
cmSystemTools::ExpandListArgument(*argIter++, list);
for (std::vector<std::string>::const_iterator iter = list.begin(),
end = list.end();
iter != end; ++iter)
{
single[*iter]; // default initialize
}
// the fourth argument is a (cmake) list of multi argument options
list.clear();
cmSystemTools::ExpandListArgument(*argIter++, list);
for (std::vector<std::string>::const_iterator iter = list.begin(),
end = list.end();
iter != end; ++iter)
{
multi[*iter]; // default initialize
}
enum insideValues
{
NONE,
SINGLE,
MULTI
} insideValues = NONE;
std::string currentArgName;
// now iterate over the remaining arguments
// and fill in the values where applicable
for(; argIter != argEnd; ++argIter)
{
const options_map::iterator optIter = options.find(*argIter);
if (optIter != options.end())
{
insideValues = NONE;
optIter->second = true;
continue;
}
const single_map::iterator singleIter = single.find(*argIter);
if (singleIter != single.end())
{
insideValues = SINGLE;
currentArgName = *argIter;
continue;
}
const multi_map::iterator multiIter = multi.find(*argIter);
if (multiIter != multi.end())
{
insideValues = MULTI;
currentArgName = *argIter;
continue;
}
switch(insideValues)
{
case SINGLE:
single[currentArgName] = *argIter;
insideValues = NONE;
break;
case MULTI:
multi[currentArgName].push_back(*argIter);
break;
default:
unparsed.push_back(*argIter);
break;
}
}
// now iterate over the collected values and update their definition
// within the current scope. undefine if necessary.
for (options_map::const_iterator iter = options.begin(), end = options.end();
iter != end; ++iter)
{
this->Makefile->AddDefinition(prefix + iter->first,
iter->second? "TRUE": "FALSE");
}
for (single_map::const_iterator iter = single.begin(), end = single.end();
iter != end; ++iter)
{
if (!iter->second.empty())
{
this->Makefile->AddDefinition(prefix + iter->first,
iter->second.c_str());
}
else
{
this->Makefile->RemoveDefinition(prefix + iter->first);
}
}
for (multi_map::const_iterator iter = multi.begin(), end = multi.end();
iter != end; ++iter)
{
if (!iter->second.empty())
{
this->Makefile->AddDefinition(prefix + iter->first,
cmJoin(cmMakeRange(iter->second), ";")
.c_str());
}
else
{
this->Makefile->RemoveDefinition(prefix + iter->first);
}
}
if (!unparsed.empty())
{
this->Makefile->AddDefinition(prefix + "UNPARSED_ARGUMENTS",
cmJoin(cmMakeRange(unparsed), ";").c_str());
}
else
{
this->Makefile->RemoveDefinition(prefix + "UNPARSED_ARGUMENTS");
}
return true;
}

View File

@ -0,0 +1,54 @@
/*============================================================================
CMake - Cross Platform Makefile Generator
Copyright 2015 Matthias Maennich <matthias@maennich.net>
Distributed under the OSI-approved BSD License (the "License");
see accompanying file Copyright.txt for details.
This software is distributed WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the License for more information.
============================================================================*/
#ifndef cmParseArgumentsCommand_h
#define cmParseArgumentsCommand_h
#include "cmCommand.h"
/** \class cmParseArgumentsCommand
*
*/
class cmParseArgumentsCommand : public cmCommand
{
public:
/**
* This is a virtual constructor for the command.
*/
virtual cmCommand* Clone()
{
return new cmParseArgumentsCommand;
}
/**
* This is called when the command is first encountered in
* the CMakeLists.txt file.
*/
virtual bool InitialPass(std::vector<std::string> const& args,
cmExecutionStatus &status);
/**
* This determines if the command is invoked when in script mode.
*/
virtual bool IsScriptable() const { return true; }
/**
* The name of the command as specified in CMakeList.txt.
*/
virtual std::string GetName() const { return "cmake_parse_arguments";}
cmTypeMacro(cmParseArgumentsCommand, cmCommand);
};
#endif

View File

@ -1,5 +1,4 @@
include(${CMAKE_CURRENT_LIST_DIR}/test_utils.cmake) include(${CMAKE_CURRENT_LIST_DIR}/test_utils.cmake)
include(CMakeParseArguments)
# example from the documentation # example from the documentation
# OPTIONAL is a keyword and therefore terminates the definition of # OPTIONAL is a keyword and therefore terminates the definition of

View File

@ -1,17 +1,14 @@
CMake Error at Errors.cmake:3 \(cmake_parse_arguments\): CMake Error at Errors\.cmake:1 \(cmake_parse_arguments\):
CMAKE_PARSE_ARGUMENTS Function invoked with incorrect arguments for cmake_parse_arguments must be called with at least 4 arguments\.
function named: CMAKE_PARSE_ARGUMENTS
Call Stack \(most recent call first\): Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\) CMakeLists\.txt:3 \(include\)
+ +
CMake Error at Errors.cmake:4 \(cmake_parse_arguments\): CMake Error at Errors\.cmake:2 \(cmake_parse_arguments\):
CMAKE_PARSE_ARGUMENTS Function invoked with incorrect arguments for cmake_parse_arguments must be called with at least 4 arguments\.
function named: CMAKE_PARSE_ARGUMENTS
Call Stack \(most recent call first\): Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\) CMakeLists\.txt:3 \(include\)
+ +
CMake Error at Errors.cmake:5 \(cmake_parse_arguments\): CMake Error at Errors\.cmake:3 \(cmake_parse_arguments\):
CMAKE_PARSE_ARGUMENTS Function invoked with incorrect arguments for cmake_parse_arguments must be called with at least 4 arguments\.
function named: CMAKE_PARSE_ARGUMENTS
Call Stack \(most recent call first\): Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\) CMakeLists\.txt:3 \(include\)

View File

@ -1,5 +1,3 @@
include(CMakeParseArguments)
cmake_parse_arguments() cmake_parse_arguments()
cmake_parse_arguments(prefix OPT) cmake_parse_arguments(prefix OPT)
cmake_parse_arguments(prefix OPT SINGLE) cmake_parse_arguments(prefix OPT SINGLE)

View File

@ -1,5 +1,4 @@
include(${CMAKE_CURRENT_LIST_DIR}/test_utils.cmake) include(${CMAKE_CURRENT_LIST_DIR}/test_utils.cmake)
include(CMakeParseArguments)
# unparsed arguments # unparsed arguments
cmake_parse_arguments(pref "" "" "") cmake_parse_arguments(pref "" "" "")

View File

@ -1,5 +1,4 @@
include(${CMAKE_CURRENT_LIST_DIR}/test_utils.cmake) include(${CMAKE_CURRENT_LIST_DIR}/test_utils.cmake)
include(CMakeParseArguments)
# specify two keywords for each category and set the first keyword of each # specify two keywords for each category and set the first keyword of each
# within ARGN # within ARGN