diff --git a/Help/command/cmake_parse_arguments.rst b/Help/command/cmake_parse_arguments.rst new file mode 100644 index 000000000..7638b6102 --- /dev/null +++ b/Help/command/cmake_parse_arguments.rst @@ -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( + args...) + + +The ```` 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 ```` argument contains all keywords for this macro +which are followed by one value, like e.g. ``DESTINATION`` keyword of the +:command:`install` command. + +The ```` 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 ````, ```` and +```` a variable composed of the given ```` +followed by ``"_"`` and the name of the respective keyword. These +variables will then hold the respective value from the argument list. +For the ```` keywords this will be ``TRUE`` or ``FALSE``. + +All remaining arguments are collected in a variable +``_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``. diff --git a/Help/manual/cmake-commands.7.rst b/Help/manual/cmake-commands.7.rst index 5b92b5156..d0c298659 100644 --- a/Help/manual/cmake-commands.7.rst +++ b/Help/manual/cmake-commands.7.rst @@ -29,6 +29,7 @@ These commands may be used freely in CMake projects. /command/build_command /command/cmake_host_system_information /command/cmake_minimum_required + /command/cmake_parse_arguments /command/cmake_policy /command/configure_file /command/continue diff --git a/Help/release/dev/CMakeParseArguments-native-impl.rst b/Help/release/dev/CMakeParseArguments-native-impl.rst new file mode 100644 index 000000000..114a09931 --- /dev/null +++ b/Help/release/dev/CMakeParseArguments-native-impl.rst @@ -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. diff --git a/Modules/CMakeParseArguments.cmake b/Modules/CMakeParseArguments.cmake index 8553f38f5..fc64ab9a2 100644 --- a/Modules/CMakeParseArguments.cmake +++ b/Modules/CMakeParseArguments.cmake @@ -2,86 +2,10 @@ # CMakeParseArguments # ------------------- # -# -# -# CMAKE_PARSE_ARGUMENTS( -# args...) -# -# 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 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 argument contains all keywords for this macro -# which are followed by one value, like e.g. DESTINATION keyword of the -# install() command. -# -# The 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 , and -# a variable composed of the given -# followed by "_" and the name of the respective keyword. These -# variables will then hold the respective value from the argument list. -# For the keywords this will be TRUE or FALSE. -# -# All remaining arguments are collected in a variable -# _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. +# This module once implemented the :command:`cmake_parse_arguments` command +# that is now implemented natively by CMake. It is now an empty placeholder +# for compatibility with projects that include it to get the command from +# CMake 3.4 and lower. #============================================================================= # Copyright 2010 Alexander Neundorf @@ -95,67 +19,3 @@ #============================================================================= # (To distribute this file outside of CMake, substitute the full # 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() diff --git a/Source/cmBootstrapCommands1.cxx b/Source/cmBootstrapCommands1.cxx index 118451432..0782b3ba7 100644 --- a/Source/cmBootstrapCommands1.cxx +++ b/Source/cmBootstrapCommands1.cxx @@ -54,6 +54,7 @@ #include "cmFunctionCommand.cxx" #include "cmPathLabel.cxx" #include "cmSearchPath.cxx" +#include "cmParseArgumentsCommand.cxx" void GetBootstrapCommands1(std::vector& commands) { @@ -91,4 +92,5 @@ void GetBootstrapCommands1(std::vector& commands) commands.push_back(new cmFindProgramCommand); commands.push_back(new cmForEachCommand); commands.push_back(new cmFunctionCommand); + commands.push_back(new cmParseArgumentsCommand); } diff --git a/Source/cmParseArgumentsCommand.cxx b/Source/cmParseArgumentsCommand.cxx new file mode 100644 index 000000000..ce7a7b3cd --- /dev/null +++ b/Source/cmParseArgumentsCommand.cxx @@ -0,0 +1,176 @@ +/*============================================================================ + CMake - Cross Platform Makefile Generator + Copyright 2015 Matthias Maennich + Copyright 2010 Alexander Neundorf + + 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 const& args, cmExecutionStatus &) +{ + // cmake_parse_arguments(prefix options single multi ) + // 1 2 3 4 + if (args.size() < 4) + { + this->SetError("must be called with at least 4 arguments."); + return false; + } + + std::vector::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 options_map; + typedef std::map single_map; + typedef std::map > multi_map; + options_map options; + single_map single; + multi_map multi; + + // anything else is put into a vector of unparsed strings + std::vector unparsed; + + // the second argument is a (cmake) list of options without argument + std::vector list; + cmSystemTools::ExpandListArgument(*argIter++, list); + for (std::vector::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::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::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; +} diff --git a/Source/cmParseArgumentsCommand.h b/Source/cmParseArgumentsCommand.h new file mode 100644 index 000000000..7fbf64296 --- /dev/null +++ b/Source/cmParseArgumentsCommand.h @@ -0,0 +1,54 @@ +/*============================================================================ + CMake - Cross Platform Makefile Generator + Copyright 2015 Matthias Maennich + + 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 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 diff --git a/Tests/RunCMake/cmake_parse_arguments/CornerCases.cmake b/Tests/RunCMake/cmake_parse_arguments/CornerCases.cmake index 7337b71f5..9a727dd00 100644 --- a/Tests/RunCMake/cmake_parse_arguments/CornerCases.cmake +++ b/Tests/RunCMake/cmake_parse_arguments/CornerCases.cmake @@ -1,5 +1,4 @@ include(${CMAKE_CURRENT_LIST_DIR}/test_utils.cmake) -include(CMakeParseArguments) # example from the documentation # OPTIONAL is a keyword and therefore terminates the definition of diff --git a/Tests/RunCMake/cmake_parse_arguments/Errors-stderr.txt b/Tests/RunCMake/cmake_parse_arguments/Errors-stderr.txt index 5976fdc7e..5394eaf1b 100644 --- a/Tests/RunCMake/cmake_parse_arguments/Errors-stderr.txt +++ b/Tests/RunCMake/cmake_parse_arguments/Errors-stderr.txt @@ -1,17 +1,14 @@ -CMake Error at Errors.cmake:3 \(cmake_parse_arguments\): - CMAKE_PARSE_ARGUMENTS Function invoked with incorrect arguments for - function named: CMAKE_PARSE_ARGUMENTS +CMake Error at Errors\.cmake:1 \(cmake_parse_arguments\): + cmake_parse_arguments must be called with at least 4 arguments\. Call Stack \(most recent call first\): - CMakeLists.txt:3 \(include\) + CMakeLists\.txt:3 \(include\) + -CMake Error at Errors.cmake:4 \(cmake_parse_arguments\): - CMAKE_PARSE_ARGUMENTS Function invoked with incorrect arguments for - function named: CMAKE_PARSE_ARGUMENTS +CMake Error at Errors\.cmake:2 \(cmake_parse_arguments\): + cmake_parse_arguments must be called with at least 4 arguments\. Call Stack \(most recent call first\): - CMakeLists.txt:3 \(include\) + CMakeLists\.txt:3 \(include\) + -CMake Error at Errors.cmake:5 \(cmake_parse_arguments\): - CMAKE_PARSE_ARGUMENTS Function invoked with incorrect arguments for - function named: CMAKE_PARSE_ARGUMENTS +CMake Error at Errors\.cmake:3 \(cmake_parse_arguments\): + cmake_parse_arguments must be called with at least 4 arguments\. Call Stack \(most recent call first\): - CMakeLists.txt:3 \(include\) + CMakeLists\.txt:3 \(include\) diff --git a/Tests/RunCMake/cmake_parse_arguments/Errors.cmake b/Tests/RunCMake/cmake_parse_arguments/Errors.cmake index 2db3bb158..98e22e94d 100644 --- a/Tests/RunCMake/cmake_parse_arguments/Errors.cmake +++ b/Tests/RunCMake/cmake_parse_arguments/Errors.cmake @@ -1,5 +1,3 @@ -include(CMakeParseArguments) - cmake_parse_arguments() cmake_parse_arguments(prefix OPT) cmake_parse_arguments(prefix OPT SINGLE) diff --git a/Tests/RunCMake/cmake_parse_arguments/Initialization.cmake b/Tests/RunCMake/cmake_parse_arguments/Initialization.cmake index 8729bc618..462f923db 100644 --- a/Tests/RunCMake/cmake_parse_arguments/Initialization.cmake +++ b/Tests/RunCMake/cmake_parse_arguments/Initialization.cmake @@ -1,5 +1,4 @@ include(${CMAKE_CURRENT_LIST_DIR}/test_utils.cmake) -include(CMakeParseArguments) # unparsed arguments cmake_parse_arguments(pref "" "" "") diff --git a/Tests/RunCMake/cmake_parse_arguments/Mix.cmake b/Tests/RunCMake/cmake_parse_arguments/Mix.cmake index c14fdfd91..b3eff3947 100644 --- a/Tests/RunCMake/cmake_parse_arguments/Mix.cmake +++ b/Tests/RunCMake/cmake_parse_arguments/Mix.cmake @@ -1,5 +1,4 @@ include(${CMAKE_CURRENT_LIST_DIR}/test_utils.cmake) -include(CMakeParseArguments) # specify two keywords for each category and set the first keyword of each # within ARGN