Просмотр исходного кода

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.
Matthias Maennich 10 лет назад
Родитель
Сommit
e8b148318f

+ 78 - 0
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(<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``.

+ 1 - 0
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

+ 6 - 0
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.

+ 4 - 144
Modules/CMakeParseArguments.cmake

@@ -2,86 +2,10 @@
 # CMakeParseArguments
 # -------------------
 #
-#
-#
-# CMAKE_PARSE_ARGUMENTS(<prefix> <options> <one_value_keywords>
-# <multi_value_keywords> 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 <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.
+# 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 <[email protected]>
@@ -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()

+ 2 - 0
Source/cmBootstrapCommands1.cxx

@@ -54,6 +54,7 @@
 #include "cmFunctionCommand.cxx"
 #include "cmPathLabel.cxx"
 #include "cmSearchPath.cxx"
+#include "cmParseArgumentsCommand.cxx"
 
 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 cmForEachCommand);
   commands.push_back(new cmFunctionCommand);
+  commands.push_back(new cmParseArgumentsCommand);
 }

+ 176 - 0
Source/cmParseArgumentsCommand.cxx

@@ -0,0 +1,176 @@
+/*============================================================================
+  CMake - Cross Platform Makefile Generator
+  Copyright 2015 Matthias Maennich <[email protected]>
+  Copyright 2010 Alexander Neundorf <[email protected]>
+
+  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;
+}

+ 54 - 0
Source/cmParseArgumentsCommand.h

@@ -0,0 +1,54 @@
+/*============================================================================
+  CMake - Cross Platform Makefile Generator
+  Copyright 2015 Matthias Maennich <[email protected]>
+
+  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

+ 0 - 1
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

+ 9 - 12
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\)

+ 0 - 2
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)

+ 0 - 1
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 "" "" "")

+ 0 - 1
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