Hi,

This is my pull request [1] brought to the list.

This adds a test suite for the CMakeParseArguments module. In addition
the second change implements cmake_parse_arguments(...) as native
command.

In our project we saw that cmake_parse_arguments is actually called a
lot of times due to macro expansion and we could almost halve our
configure time by using a native implementation of
cmake_parse_arguments. This implementation follows the implementation
of the former macro and both implementations of course pass the added
test suite.

I added a third change that makes cmake_parse_arguments() emit a
warning in case keywords are specified more than once. An error
instead actually would break compatibility (which is why I also made
it a separate commit) for some probably unused corner cases. But in my
opinion this improves the implementation as I assume that most of the
duplicate specifications are actual errors that else get silently
ignored. In a later CMake version one could even emit an error in this
case but as it would break compatibility it should be introduced along
with a policy.

Best regards,

Matthias

---

[1] https://github.com/Kitware/CMake/pull/202
From d34efe4001c289f20aee922881ec5c7f11c8b79d Mon Sep 17 00:00:00 2001
From: Matthias Maennich <matth...@maennich.net>
Date: Sat, 5 Dec 2015 19:02:19 +0100
Subject: [PATCH 3/3] cmake_parse_arguments: consider duplicate keyword as
 warning

The behaviour of double specified keywords is rather undefined or at
least not clearly documented. This change introduces a strict check and
emits a warning in case a keyword has been specified more than once.
---
 Help/command/cmake_parse_arguments.rst             |  7 ++++
 Source/cmParseArgumentsCommand.cxx                 | 16 +++++++++
 .../cmake_parse_arguments/Errors-stderr.txt        | 42 ++++++++++++++++++++--
 Tests/RunCMake/cmake_parse_arguments/Errors.cmake  | 10 ++++++
 4 files changed, 72 insertions(+), 3 deletions(-)

diff --git a/Help/command/cmake_parse_arguments.rst b/Help/command/cmake_parse_arguments.rst
index e497cae..134f72c 100644
--- a/Help/command/cmake_parse_arguments.rst
+++ b/Help/command/cmake_parse_arguments.rst
@@ -24,6 +24,13 @@ 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.
 
+.. note::
+
+   All keywords shall be unique. I.e. every keyword shall only be specified
+   once in either ``<options>``, ``<one_value_keywords>`` or
+   ``<multi_value_keywords>``. A warning will be emitted if uniqueness is
+   violated.
+
 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>``
diff --git a/Source/cmParseArgumentsCommand.cxx b/Source/cmParseArgumentsCommand.cxx
index f5492a3..0264e24 100644
--- a/Source/cmParseArgumentsCommand.cxx
+++ b/Source/cmParseArgumentsCommand.cxx
@@ -43,6 +43,10 @@ bool cmParseArgumentsCommand
   // anything else is put into a vector of unparsed strings
   std::vector<std::string> unparsed;
 
+  // remember already defined keywords
+  std::set<std::string> used_keywords;
+  const std::string dup_warning = "keyword defined more than once: ";
+
   // the second argument is a (cmake) list of options without argument
   std::vector<std::string> list;
   cmSystemTools::ExpandListArgument(*argIter++, list);
@@ -50,6 +54,10 @@ bool cmParseArgumentsCommand
                                                 end   = list.end();
                                                 iter != end; ++iter)
     {
+    if (!used_keywords.insert(*iter).second)
+      {
+      this->GetMakefile()->IssueMessage(cmake::WARNING, dup_warning + *iter);
+      }
     options[*iter]; // default initialize
     }
 
@@ -60,6 +68,10 @@ bool cmParseArgumentsCommand
                                                 end   = list.end();
                                                 iter != end; ++iter)
     {
+    if (!used_keywords.insert(*iter).second)
+      {
+      this->GetMakefile()->IssueMessage(cmake::WARNING, dup_warning + *iter);
+      }
     single[*iter]; // default initialize
     }
 
@@ -70,6 +82,10 @@ bool cmParseArgumentsCommand
                                                 end   = list.end();
                                                 iter != end; ++iter)
     {
+    if (!used_keywords.insert(*iter).second)
+      {
+      this->GetMakefile()->IssueMessage(cmake::WARNING, dup_warning + *iter);
+      }
     multi[*iter]; // default initialize
     }
 
diff --git a/Tests/RunCMake/cmake_parse_arguments/Errors-stderr.txt b/Tests/RunCMake/cmake_parse_arguments/Errors-stderr.txt
index 9835a22..748d1d0 100644
--- a/Tests/RunCMake/cmake_parse_arguments/Errors-stderr.txt
+++ b/Tests/RunCMake/cmake_parse_arguments/Errors-stderr.txt
@@ -1,16 +1,52 @@
-CMake Error at Errors\.cmake:1 \(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\)
 
 
-CMake Error at Errors\.cmake:2 \(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\)
 
 
-CMake Error at Errors\.cmake:3 \(cmake_parse_arguments\):
+CMake Error at Errors\.cmake:4 \(cmake_parse_arguments\):
   cmake_parse_arguments must be called with at least 4 arguments\.
 Call Stack \(most recent call first\):
   CMakeLists\.txt:3 \(include\)
+
+
+CMake Warning at Errors\.cmake:8 \(cmake_parse_arguments\):
+  keyword defined more than once: OPT
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:3 \(include\)
+
+
+CMake Warning at Errors\.cmake:9 \(cmake_parse_arguments\):
+  keyword defined more than once: OPT
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:3 \(include\)
+
+
+CMake Warning at Errors\.cmake:10 \(cmake_parse_arguments\):
+  keyword defined more than once: OPT
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:3 \(include\)
+
+
+CMake Warning at Errors\.cmake:12 \(cmake_parse_arguments\):
+  keyword defined more than once: OPT
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:3 \(include\)
+
+
+CMake Warning at Errors\.cmake:13 \(cmake_parse_arguments\):
+  keyword defined more than once: OPT
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:3 \(include\)
+
+
+CMake Warning at Errors\.cmake:14 \(cmake_parse_arguments\):
+  keyword defined more than once: OPT
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:3 \(include\)
diff --git a/Tests/RunCMake/cmake_parse_arguments/Errors.cmake b/Tests/RunCMake/cmake_parse_arguments/Errors.cmake
index 98e22e9..6a38081 100644
--- a/Tests/RunCMake/cmake_parse_arguments/Errors.cmake
+++ b/Tests/RunCMake/cmake_parse_arguments/Errors.cmake
@@ -1,4 +1,14 @@
+# wrong argument count
 cmake_parse_arguments()
 cmake_parse_arguments(prefix OPT)
 cmake_parse_arguments(prefix OPT SINGLE)
 cmake_parse_arguments(prefix OPT SINGLE MULTI) # not an error
+
+# duplicate keywords
+cmake_parse_arguments(prefix "OPT;OPT" "" "")
+cmake_parse_arguments(prefix "" "OPT;OPT" "")
+cmake_parse_arguments(prefix "" "" "OPT;OPT")
+
+cmake_parse_arguments(prefix "OPT" "OPT" "")
+cmake_parse_arguments(prefix "" "OPT" "OPT")
+cmake_parse_arguments(prefix "OPT" "" "OPT")
-- 
2.6.3

From be738a61b66e818933845ed340b4bc51ec8966b5 Mon Sep 17 00:00:00 2001
From: Matthias Maennich <matth...@maennich.net>
Date: Sat, 5 Dec 2015 19:01:12 +0100
Subject: [PATCH 2/3] 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.

deprecate the CMakeParseArguments module but leave the empty module for
compatibility.
---
 Help/command/cmake_parse_arguments.rst             |  82 ++++++++++
 Help/manual/cmake-commands.7.rst                   |   1 +
 Modules/CMakeParseArguments.cmake                  | 146 +----------------
 Source/cmBootstrapCommands1.cxx                    |   2 +
 Source/cmParseArgumentsCommand.cxx                 | 176 +++++++++++++++++++++
 Source/cmParseArgumentsCommand.h                   |  54 +++++++
 .../cmake_parse_arguments/CornerCases.cmake        |   1 -
 .../cmake_parse_arguments/Errors-stderr.txt        |  21 ++-
 Tests/RunCMake/cmake_parse_arguments/Errors.cmake  |   2 -
 .../cmake_parse_arguments/Initialization.cmake     |   1 -
 Tests/RunCMake/cmake_parse_arguments/Mix.cmake     |   1 -
 11 files changed, 328 insertions(+), 159 deletions(-)
 create mode 100644 Help/command/cmake_parse_arguments.rst
 create mode 100644 Source/cmParseArgumentsCommand.cxx
 create mode 100644 Source/cmParseArgumentsCommand.h

diff --git a/Help/command/cmake_parse_arguments.rst b/Help/command/cmake_parse_arguments.rst
new file mode 100644
index 0000000..e497cae
--- /dev/null
+++ b/Help/command/cmake_parse_arguments.rst
@@ -0,0 +1,82 @@
+cmake_parse_arguments
+---------------------
+
+.. code-block:: cmake
+
+  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 :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 (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 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 5b92b51..d0c2986 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/Modules/CMakeParseArguments.cmake b/Modules/CMakeParseArguments.cmake
index 8553f38..2368b7a 100644
--- a/Modules/CMakeParseArguments.cmake
+++ b/Modules/CMakeParseArguments.cmake
@@ -2,86 +2,12 @@
 # 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()
+# The command :command:`cmake_parse_arguments` has previously been provided
+# by this module. :command:`cmake_parse_arguments` is now a native CMake
 # 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 is now deprecated but remains for compatibility reasons.
+# It will be removed in a future version of CMake.
 
 #=============================================================================
 # Copyright 2010 Alexander Neundorf <neund...@kde.org>
@@ -95,67 +21,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 1184514..0782b3b 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<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);
 }
diff --git a/Source/cmParseArgumentsCommand.cxx b/Source/cmParseArgumentsCommand.cxx
new file mode 100644
index 0000000..f5492a3
--- /dev/null
+++ b/Source/cmParseArgumentsCommand.cxx
@@ -0,0 +1,176 @@
+/*============================================================================
+  CMake - Cross Platform Makefile Generator
+  Copyright 2015 Matthias Maennich <matth...@maennich.net>
+  Copyright 2010 Alexander Neundorf <neund...@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;
+}
diff --git a/Source/cmParseArgumentsCommand.h b/Source/cmParseArgumentsCommand.h
new file mode 100644
index 0000000..7fbf642
--- /dev/null
+++ b/Source/cmParseArgumentsCommand.h
@@ -0,0 +1,54 @@
+/*============================================================================
+  CMake - Cross Platform Makefile Generator
+  Copyright 2015 Matthias Maennich <matth...@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
diff --git a/Tests/RunCMake/cmake_parse_arguments/CornerCases.cmake b/Tests/RunCMake/cmake_parse_arguments/CornerCases.cmake
index 7337b71..9a727dd 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 07902b5..9835a22 100644
--- a/Tests/RunCMake/cmake_parse_arguments/Errors-stderr.txt
+++ b/Tests/RunCMake/cmake_parse_arguments/Errors-stderr.txt
@@ -1,19 +1,16 @@
-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 2db3bb1..98e22e9 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 8729bc6..462f923 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 c14fdfd..b3eff39 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
-- 
2.6.3

From b35d9ada3f2bafd73199a79b56b592c701ffc6a6 Mon Sep 17 00:00:00 2001
From: Matthias Maennich <matth...@maennich.net>
Date: Sat, 5 Dec 2015 18:57:41 +0100
Subject: [PATCH 1/3] CMakeParseArguments: add a RunCMake test suite

---
 Tests/RunCMake/CMakeLists.txt                      |  1 +
 .../RunCMake/cmake_parse_arguments/CMakeLists.txt  |  4 ++
 .../cmake_parse_arguments/CornerCases.cmake        | 16 +++++
 .../cmake_parse_arguments/Errors-result.txt        |  1 +
 .../cmake_parse_arguments/Errors-stderr.txt        | 19 ++++++
 Tests/RunCMake/cmake_parse_arguments/Errors.cmake  |  6 ++
 .../cmake_parse_arguments/Initialization.cmake     | 69 ++++++++++++++++++++++
 Tests/RunCMake/cmake_parse_arguments/Mix.cmake     | 25 ++++++++
 .../cmake_parse_arguments/RunCMakeTest.cmake       |  7 +++
 Tests/RunCMake/cmake_parse_arguments/Utils.cmake   | 20 +++++++
 .../cmake_parse_arguments/test_utils.cmake         | 20 +++++++
 11 files changed, 188 insertions(+)
 create mode 100644 Tests/RunCMake/cmake_parse_arguments/CMakeLists.txt
 create mode 100644 Tests/RunCMake/cmake_parse_arguments/CornerCases.cmake
 create mode 100644 Tests/RunCMake/cmake_parse_arguments/Errors-result.txt
 create mode 100644 Tests/RunCMake/cmake_parse_arguments/Errors-stderr.txt
 create mode 100644 Tests/RunCMake/cmake_parse_arguments/Errors.cmake
 create mode 100644 Tests/RunCMake/cmake_parse_arguments/Initialization.cmake
 create mode 100644 Tests/RunCMake/cmake_parse_arguments/Mix.cmake
 create mode 100644 Tests/RunCMake/cmake_parse_arguments/RunCMakeTest.cmake
 create mode 100644 Tests/RunCMake/cmake_parse_arguments/Utils.cmake
 create mode 100644 Tests/RunCMake/cmake_parse_arguments/test_utils.cmake

diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt
index a6cbf86..0a388c5 100644
--- a/Tests/RunCMake/CMakeLists.txt
+++ b/Tests/RunCMake/CMakeLists.txt
@@ -177,6 +177,7 @@ add_RunCMake_test(build_command)
 add_RunCMake_test(execute_process)
 add_RunCMake_test(export)
 add_RunCMake_test(cmake_minimum_required)
+add_RunCMake_test(cmake_parse_arguments)
 add_RunCMake_test(continue)
 add_RunCMake_test(ctest_build)
 add_RunCMake_test(ctest_configure)
diff --git a/Tests/RunCMake/cmake_parse_arguments/CMakeLists.txt b/Tests/RunCMake/cmake_parse_arguments/CMakeLists.txt
new file mode 100644
index 0000000..bdb196b
--- /dev/null
+++ b/Tests/RunCMake/cmake_parse_arguments/CMakeLists.txt
@@ -0,0 +1,4 @@
+cmake_minimum_required(VERSION 3.4)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)
+
diff --git a/Tests/RunCMake/cmake_parse_arguments/CornerCases.cmake b/Tests/RunCMake/cmake_parse_arguments/CornerCases.cmake
new file mode 100644
index 0000000..7337b71
--- /dev/null
+++ b/Tests/RunCMake/cmake_parse_arguments/CornerCases.cmake
@@ -0,0 +1,16 @@
+include(${CMAKE_CURRENT_LIST_DIR}/test_utils.cmake)
+include(CMakeParseArguments)
+
+# example from the documentation
+# OPTIONAL is a keyword and therefore terminates the definition of
+# the multi-value DEFINITION before even a single value has been added
+
+set(options OPTIONAL FAST)
+set(oneValueArgs DESTINATION RENAME)
+set(multiValueArgs TARGETS CONFIGURATIONS)
+cmake_parse_arguments(MY_INSTALL "${options}" "${oneValueArgs}"
+                      "${multiValueArgs}"
+                      TARGETS foo DESTINATION OPTIONAL)
+
+TEST(MY_INSTALL_DESTINATION UNDEFINED)
+TEST(MY_INSTALL_OPTIONAL TRUE)
diff --git a/Tests/RunCMake/cmake_parse_arguments/Errors-result.txt b/Tests/RunCMake/cmake_parse_arguments/Errors-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_parse_arguments/Errors-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_parse_arguments/Errors-stderr.txt b/Tests/RunCMake/cmake_parse_arguments/Errors-stderr.txt
new file mode 100644
index 0000000..07902b5
--- /dev/null
+++ b/Tests/RunCMake/cmake_parse_arguments/Errors-stderr.txt
@@ -0,0 +1,19 @@
+CMake Error at Errors.cmake:3 \(cmake_parse_arguments\):
+  CMAKE_PARSE_ARGUMENTS Function invoked with incorrect arguments for
+  function named: CMAKE_PARSE_ARGUMENTS
+Call Stack \(most recent call first\):
+  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
+Call Stack \(most recent call first\):
+  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
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
diff --git a/Tests/RunCMake/cmake_parse_arguments/Errors.cmake b/Tests/RunCMake/cmake_parse_arguments/Errors.cmake
new file mode 100644
index 0000000..2db3bb1
--- /dev/null
+++ b/Tests/RunCMake/cmake_parse_arguments/Errors.cmake
@@ -0,0 +1,6 @@
+include(CMakeParseArguments)
+
+cmake_parse_arguments()
+cmake_parse_arguments(prefix OPT)
+cmake_parse_arguments(prefix OPT SINGLE)
+cmake_parse_arguments(prefix OPT SINGLE MULTI) # not an error
diff --git a/Tests/RunCMake/cmake_parse_arguments/Initialization.cmake b/Tests/RunCMake/cmake_parse_arguments/Initialization.cmake
new file mode 100644
index 0000000..8729bc6
--- /dev/null
+++ b/Tests/RunCMake/cmake_parse_arguments/Initialization.cmake
@@ -0,0 +1,69 @@
+include(${CMAKE_CURRENT_LIST_DIR}/test_utils.cmake)
+include(CMakeParseArguments)
+
+# unparsed arguments
+cmake_parse_arguments(pref "" "" "")
+TEST(pref_UNPARSED_ARGUMENTS UNDEFINED)
+
+cmake_parse_arguments(pref "" "" "" FOO)
+TEST(pref_UNPARSED_ARGUMENTS "FOO")
+cmake_parse_arguments(pref "" "" "" FOO BAR)
+TEST(pref_UNPARSED_ARGUMENTS "FOO;BAR")
+cmake_parse_arguments(pref "" "" "")
+TEST(pref_UNPARSED_ARGUMENTS UNDEFINED)
+
+
+# options
+cmake_parse_arguments(pref "OPT1" "" "")
+TEST(pref_OPT1 FALSE)
+
+cmake_parse_arguments(pref "OPT1;OPT2" "" "")
+TEST(pref_OPT1 FALSE)
+TEST(pref_OPT2 FALSE)
+
+cmake_parse_arguments(pref "OPT1" "" "" OPT1)
+TEST(pref_OPT1 TRUE)
+cmake_parse_arguments(pref "OPT1;OPT2" "" "" OPT1 OPT2)
+TEST(pref_OPT1 TRUE)
+TEST(pref_OPT2 TRUE)
+cmake_parse_arguments(pref "OPT1;OPT2" "" "")
+TEST(pref_OPT1 FALSE)
+TEST(pref_OPT2 FALSE)
+
+
+# single arguments
+cmake_parse_arguments(pref "" "SINGLE1" "")
+TEST(pref_SINGLE1 UNDEFINED)
+
+cmake_parse_arguments(pref "" "SINGLE1;SINGLE2" "")
+TEST(pref_SINGLE1 UNDEFINED)
+TEST(pref_SINGLE2 UNDEFINED)
+
+
+cmake_parse_arguments(pref "" "SINGLE1" "" SINGLE1 foo)
+TEST(pref_SINGLE1 foo)
+cmake_parse_arguments(pref "" "SINGLE1;SINGLE2" "" SINGLE1 foo SINGLE2 bar)
+TEST(pref_SINGLE1 foo)
+TEST(pref_SINGLE2 bar)
+cmake_parse_arguments(pref "" "SINGLE1;SINGLE2" "")
+TEST(pref_SINGLE1 UNDEFINED)
+TEST(pref_SINGLE2 UNDEFINED)
+
+
+# multi arguments
+
+cmake_parse_arguments(pref "" "" "MULTI1")
+TEST(pref_MULTI1 UNDEFINED)
+
+cmake_parse_arguments(pref "" "" "MULTI1;MULTI2")
+TEST(pref_MULTI1 UNDEFINED)
+TEST(pref_MULTI2 UNDEFINED)
+
+cmake_parse_arguments(pref "" "" "MULTI1" MULTI1 foo)
+TEST(pref_MULTI1 foo)
+cmake_parse_arguments(pref "" "" "MULTI1;MULTI2" MULTI1 foo bar MULTI2 bar foo)
+TEST(pref_MULTI1 foo bar)
+TEST(pref_MULTI2 bar foo)
+cmake_parse_arguments(pref "" "" "MULTI1;MULTI2")
+TEST(pref_MULTI1 UNDEFINED)
+TEST(pref_MULTI2 UNDEFINED)
diff --git a/Tests/RunCMake/cmake_parse_arguments/Mix.cmake b/Tests/RunCMake/cmake_parse_arguments/Mix.cmake
new file mode 100644
index 0000000..c14fdfd
--- /dev/null
+++ b/Tests/RunCMake/cmake_parse_arguments/Mix.cmake
@@ -0,0 +1,25 @@
+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
+cmake_parse_arguments(pref "OPT1;OPT2" "SINGLE1;SINGLE2" "MULTI1;MULTI2"
+                        OPT1 SINGLE1 foo MULTI1 bar foo bar)
+TEST(pref_OPT1 TRUE)
+TEST(pref_OPT2 FALSE)
+TEST(pref_SINGLE1 foo)
+TEST(pref_SINGLE2 UNDEFINED)
+TEST(pref_MULTI1 bar foo bar)
+TEST(pref_MULTI2 UNDEFINED)
+TEST(pref_UNPARSED_ARGUMENTS UNDEFINED)
+
+# same as above but reversed ARGN
+cmake_parse_arguments(pref "OPT1;OPT2" "SINGLE1;SINGLE2" "MULTI1;MULTI2"
+                        MULTI1 bar foo bar SINGLE1 foo OPT1)
+TEST(pref_OPT1 TRUE)
+TEST(pref_OPT2 FALSE)
+TEST(pref_SINGLE1 foo)
+TEST(pref_SINGLE2 UNDEFINED)
+TEST(pref_MULTI1 bar foo bar)
+TEST(pref_MULTI2 UNDEFINED)
+TEST(pref_UNPARSED_ARGUMENTS UNDEFINED)
diff --git a/Tests/RunCMake/cmake_parse_arguments/RunCMakeTest.cmake b/Tests/RunCMake/cmake_parse_arguments/RunCMakeTest.cmake
new file mode 100644
index 0000000..b89f1a5
--- /dev/null
+++ b/Tests/RunCMake/cmake_parse_arguments/RunCMakeTest.cmake
@@ -0,0 +1,7 @@
+include(RunCMake)
+
+run_cmake(Utils)
+run_cmake(Initialization)
+run_cmake(Mix)
+run_cmake(CornerCases)
+run_cmake(Errors)
diff --git a/Tests/RunCMake/cmake_parse_arguments/Utils.cmake b/Tests/RunCMake/cmake_parse_arguments/Utils.cmake
new file mode 100644
index 0000000..3bbf115
--- /dev/null
+++ b/Tests/RunCMake/cmake_parse_arguments/Utils.cmake
@@ -0,0 +1,20 @@
+include(${CMAKE_CURRENT_LIST_DIR}/test_utils.cmake)
+
+# test the TEST macro itself
+
+TEST(asdf UNDEFINED)
+
+SET (asdf FALSE)
+TEST(asdf FALSE)
+
+SET (asdf TRUE)
+TEST(asdf TRUE)
+
+SET (asdf TRUE)
+TEST(asdf TRUE)
+
+SET (asdf "some value")
+TEST(asdf "some value")
+
+SET (asdf some list)
+TEST(asdf "some;list")
diff --git a/Tests/RunCMake/cmake_parse_arguments/test_utils.cmake b/Tests/RunCMake/cmake_parse_arguments/test_utils.cmake
new file mode 100644
index 0000000..dd2fd0b
--- /dev/null
+++ b/Tests/RunCMake/cmake_parse_arguments/test_utils.cmake
@@ -0,0 +1,20 @@
+macro(TEST variable)
+    SET(expected "${ARGN}")
+    if ( "${expected}" STREQUAL "UNDEFINED" )
+        if (DEFINED ${variable})
+            message(FATAL_ERROR "'${variable}' shall be undefined but has value '${${variable}}'")
+        endif()
+    elseif( "${expected}" STREQUAL "FALSE" )
+        if (NOT ${variable} STREQUAL "FALSE")
+            message(FATAL_ERROR "'${variable}' shall be FALSE")
+        endif()
+    elseif( "${expected}" STREQUAL "TRUE" )
+        if (NOT ${variable} STREQUAL "TRUE")
+            message(FATAL_ERROR "'${variable}' shall be TRUE")
+        endif()
+    else()
+        if (NOT ${variable} STREQUAL "${expected}")
+            message(FATAL_ERROR "'${variable}' shall be '${expected}'")
+            endif()
+    endif()
+endmacro()
-- 
2.6.3

-- 

Powered by www.kitware.com

Please keep messages on-topic and check the CMake FAQ at: 
http://www.cmake.org/Wiki/CMake_FAQ

Kitware offers various services to support the CMake community. For more 
information on each offering, please visit:

CMake Support: http://cmake.org/cmake/help/support.html
CMake Consulting: http://cmake.org/cmake/help/consulting.html
CMake Training Courses: http://cmake.org/cmake/help/training.html

Visit other Kitware open-source projects at 
http://www.kitware.com/opensource/opensource.html

Follow this link to subscribe/unsubscribe:
http://public.kitware.com/mailman/listinfo/cmake-developers

Reply via email to