[Cmake-commits] CMake branch, master, updated. v3.15.3-1197-g5fa625d

Kitware Robot kwrobot at kitware.com
Thu Sep 26 09:31:09 EDT 2019


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "CMake".

The branch, master has been updated
       via  5fa625d611414ac567fd7a6bb5b3b416ea93eca2 (commit)
       via  67e6b55c5885c445b5b78b41a66529553dcab069 (commit)
       via  33c7ea513dc8a7e1322c5c37003c7c515bd8ad99 (commit)
       via  53be31e19c0dc0f517825d1be056c55d7729a4b7 (commit)
       via  0aa8a2ab8b00c0889df3d0fa1be47ad5a0b9db22 (commit)
       via  d5a6a133680e4d5798522a4b29ae6bced1f2db3a (commit)
       via  b2c67a7703836a0015a7e4bf3837dbb613061a74 (commit)
      from  b42cb1ff80dc056da4036c7b65109d1a77d84bf4 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=5fa625d611414ac567fd7a6bb5b3b416ea93eca2
commit 5fa625d611414ac567fd7a6bb5b3b416ea93eca2
Merge: 67e6b55 33c7ea5
Author:     Kyle Edwards <kyle.edwards at kitware.com>
AuthorDate: Thu Sep 26 13:22:18 2019 +0000
Commit:     Kitware Robot <kwrobot at kitware.com>
CommitDate: Thu Sep 26 09:23:31 2019 -0400

    Merge topic 'cpack-DEB-use-CPACK_PACKAGE_DESCRIPTION_FILE'
    
    33c7ea513d CPackDeb: Use `CPACK_PACKAGE_DESCRIPTION_FILE`
    53be31e19c Refactor: Use `list` commands instead of old-way string ops
    b2c67a7703 Style: Remove spaces after command call and `(`
    
    Acked-by: Kitware Robot <kwrobot at kitware.com>
    Merge-request: !3541


https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=67e6b55c5885c445b5b78b41a66529553dcab069
commit 67e6b55c5885c445b5b78b41a66529553dcab069
Merge: b42cb1f 0aa8a2a
Author:     Kyle Edwards <kyle.edwards at kitware.com>
AuthorDate: Thu Sep 26 13:22:00 2019 +0000
Commit:     Kitware Robot <kwrobot at kitware.com>
CommitDate: Thu Sep 26 09:22:13 2019 -0400

    Merge topic 'ctest-argument-parser'
    
    0aa8a2ab8b cmCTest*Command: Port to cmArgumentParser
    d5a6a13368 cmArgumentParser: Record parsed keywords
    
    Acked-by: Kitware Robot <kwrobot at kitware.com>
    Merge-request: !3835

diff --cc Source/CTest/cmCTestCoverageCommand.h
index 75aefdf,f7b6315..fcffa75
--- a/Source/CTest/cmCTestCoverageCommand.h
+++ b/Source/CTest/cmCTestCoverageCommand.h
@@@ -8,11 -8,11 +8,11 @@@
  #include "cmCTestHandlerCommand.h"
  #include "cmCommand.h"
  
- #include <set>
  #include <string>
  #include <utility>
+ #include <vector>
  
 -#include "cm_memory.hxx"
 +#include <cm/memory>
  
  class cmCTestGenericHandler;
  
diff --cc Source/CTest/cmCTestMemCheckCommand.h
index 5dad4e7,200cc2e..8f4ffb8
--- a/Source/CTest/cmCTestMemCheckCommand.h
+++ b/Source/CTest/cmCTestMemCheckCommand.h
@@@ -5,9 -5,10 +5,10 @@@
  
  #include "cmConfigure.h" // IWYU pragma: keep
  
+ #include <string>
  #include <utility>
  
 -#include "cm_memory.hxx"
 +#include <cm/memory>
  
  #include "cmCTestTestCommand.h"
  #include "cmCommand.h"
diff --cc Source/CTest/cmCTestUploadCommand.h
index 39314f2,a962497..f78f0ec
--- a/Source/CTest/cmCTestUploadCommand.h
+++ b/Source/CTest/cmCTestUploadCommand.h
@@@ -8,11 -8,11 +8,11 @@@
  #include "cmCTestHandlerCommand.h"
  #include "cmCommand.h"
  
- #include <set>
  #include <string>
  #include <utility>
+ #include <vector>
  
 -#include "cm_memory.hxx"
 +#include <cm/memory>
  
  class cmCTestGenericHandler;
  

https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=33c7ea513dc8a7e1322c5c37003c7c515bd8ad99
commit 33c7ea513dc8a7e1322c5c37003c7c515bd8ad99
Author:     Alex Turbov <i.zaufi at gmail.com>
AuthorDate: Thu Jul 11 19:31:59 2019 +0300
Commit:     Alex Turbov <i.zaufi at gmail.com>
CommitDate: Sun Sep 22 00:31:24 2019 +0200

    CPackDeb: Use `CPACK_PACKAGE_DESCRIPTION_FILE`
    
    Also, handle per-component description nicely.

diff --git a/Help/cpack_gen/deb.rst b/Help/cpack_gen/deb.rst
index 23f0515..db71c87 100644
--- a/Help/cpack_gen/deb.rst
+++ b/Help/cpack_gen/deb.rst
@@ -179,16 +179,24 @@ List of CPack DEB generator specific variables:
  * Default   : ``CPACK_PACKAGE_CONTACT``
 
 .. variable:: CPACK_DEBIAN_PACKAGE_DESCRIPTION
-              CPACK_COMPONENT_<COMPONENT>_DESCRIPTION
+              CPACK_DEBIAN_<COMPONENT>_DESCRIPTION
 
  The Debian package description
 
  * Mandatory : YES
  * Default   :
 
-   - :variable:`CPACK_DEBIAN_PACKAGE_DESCRIPTION` if set or
-   - :variable:`CPACK_PACKAGE_DESCRIPTION_SUMMARY`
+   - :variable:`CPACK_DEBIAN_<COMPONENT>_DESCRIPTION` (component
+     based installers only) if set, or :variable:`CPACK_DEBIAN_PACKAGE_DESCRIPTION` if set, or
+   - :variable:`CPACK_COMPONENT_<compName>_DESCRIPTION` (component
+     based installers only) if set, or :variable:`CPACK_PACKAGE_DESCRIPTION` if set, or
+   - content of the file specified in :variable:`CPACK_PACKAGE_DESCRIPTION_FILE` if set
 
+ If after that description is not set, :variable:`CPACK_PACKAGE_DESCRIPTION_SUMMARY` going to be
+ used if set. Otherwise, :variable:`CPACK_PACKAGE_DESCRIPTION_SUMMARY` will be added as the first
+ line of description as defined in `Debian Policy Manual`_.
+
+.. _Debian Policy Manual: https://www.debian.org/doc/debian-policy/ch-controlfields.html#description
 
 .. variable:: CPACK_DEBIAN_PACKAGE_SECTION
               CPACK_DEBIAN_<COMPONENT>_PACKAGE_SECTION
diff --git a/Modules/Internal/CPack/CPackDeb.cmake b/Modules/Internal/CPack/CPackDeb.cmake
index e22f623..ad8e078 100644
--- a/Modules/Internal/CPack/CPackDeb.cmake
+++ b/Modules/Internal/CPack/CPackDeb.cmake
@@ -56,6 +56,67 @@ function(extract_so_info shared_object libname version)
   endif()
 endfunction()
 
+function(cpack_deb_check_description SUMMARY LINES RESULT_VARIABLE)
+  set(_result TRUE)
+
+  # Get the summary line
+  if(NOT SUMMARY MATCHES "^[^\\s].*$")
+    set(_result FALSE)
+    set(${RESULT_VARIABLE} ${_result} PARENT_SCOPE)
+    return()
+  endif()
+
+  foreach(_line IN LISTS LINES)
+    if(NOT _line MATCHES "^ +[^ ]+.*$")
+      set(_result FALSE)
+      break()
+    endif()
+  endforeach()
+
+  set(${RESULT_VARIABLE} ${_result} PARENT_SCOPE)
+endfunction()
+
+function(cpack_deb_format_package_description TEXT OUTPUT_VAR)
+  # Turn the possible multi-line string into a list
+  string(UUID uuid NAMESPACE 00000000-0000-0000-0000-000000000000 TYPE SHA1)
+  string(REPLACE ";" "${uuid}" _text "${TEXT}")
+  string(REPLACE "\n" ";" _lines "${_text}")
+  list(POP_FRONT _lines _summary)
+
+  # Check if reformatting required
+  cpack_deb_check_description("${_summary}" "${_lines}" _result)
+  if(_result)
+    # Ok, no formatting required
+    set(${OUTPUT_VAR} "${TEXT}" PARENT_SCOPE)
+    return()
+  endif()
+
+  # Format the summary line
+  string(STRIP "${_summary}" _summary)
+
+  # Make sure the rest formatted properly
+  set(_result)
+  foreach(_line IN LISTS _lines)
+    string(STRIP "${_line}" _line_strip)
+    if(NOT _line_strip)
+      # Replace empty lines w/ a _single full stop character_
+      set(_line " .")
+    else()
+      # Prepend the normal lines w/ a single space.
+      # If the line already starts w/ at least one space,
+      # it'll become _verbatim_ (assuming it supposed to be
+      # verbatim in the original text).
+      string(PREPEND _line " ")
+    endif()
+    list(APPEND _result "${_line}")
+  endforeach()
+
+  list(PREPEND _result "${_summary}")
+  list(JOIN _result "\n" _result)
+  string(REPLACE "${uuid}"  ";" _result "${_result}")
+  set(${OUTPUT_VAR} "${_result}" PARENT_SCOPE)
+endfunction()
+
 function(cpack_deb_prepare_package_vars)
   # CPACK_DEBIAN_PACKAGE_SHLIBDEPS
   # If specify OFF, only user depends are used
@@ -436,26 +497,53 @@ function(cpack_deb_prepare_package_vars)
   endif()
 
   # Description: (mandatory)
-  if(NOT CPACK_DEB_PACKAGE_COMPONENT)
-    if(NOT CPACK_DEBIAN_PACKAGE_DESCRIPTION)
-      if(NOT CPACK_PACKAGE_DESCRIPTION_SUMMARY)
-        message(FATAL_ERROR "CPackDeb: Debian package requires a summary for a package, set CPACK_PACKAGE_DESCRIPTION_SUMMARY or CPACK_DEBIAN_PACKAGE_DESCRIPTION")
-      endif()
-      set(CPACK_DEBIAN_PACKAGE_DESCRIPTION ${CPACK_PACKAGE_DESCRIPTION_SUMMARY})
-    endif()
+  # Try package description first
+  if(CPACK_DEB_PACKAGE_COMPONENT)
+    cpack_deb_variable_fallback("CPACK_DEBIAN_PACKAGE_DESCRIPTION"
+      "CPACK_DEBIAN_${_local_component_name}_DESCRIPTION"
+      "CPACK_COMPONENT_${_local_component_name}_DESCRIPTION")
   else()
-    set(component_description_var CPACK_COMPONENT_${_local_component_name}_DESCRIPTION)
-
-    # component description overrides package description
-    if(${component_description_var})
-      set(CPACK_DEBIAN_PACKAGE_DESCRIPTION ${${component_description_var}})
-    elseif(NOT CPACK_DEBIAN_PACKAGE_DESCRIPTION)
-      if(NOT CPACK_PACKAGE_DESCRIPTION_SUMMARY)
-        message(FATAL_ERROR "CPackDeb: Debian package requires a summary for a package, set CPACK_PACKAGE_DESCRIPTION_SUMMARY or CPACK_DEBIAN_PACKAGE_DESCRIPTION or ${component_description_var}")
-      endif()
+    cpack_deb_variable_fallback("CPACK_DEBIAN_PACKAGE_DESCRIPTION"
+      "CPACK_DEBIAN_PACKAGE_DESCRIPTION"
+      "CPACK_PACKAGE_DESCRIPTION")
+  endif()
+
+  # Still no description? ... and description file has set ...
+  if(NOT CPACK_DEBIAN_PACKAGE_DESCRIPTION AND CPACK_PACKAGE_DESCRIPTION_FILE)
+    # Read `CPACK_PACKAGE_DESCRIPTION_FILE` then...
+    file(READ ${CPACK_PACKAGE_DESCRIPTION_FILE} CPACK_DEBIAN_PACKAGE_DESCRIPTION)
+  endif()
+
+  # Still no description? #2
+  if(NOT CPACK_DEBIAN_PACKAGE_DESCRIPTION)
+    # Try to get `CPACK_PACKAGE_DESCRIPTION_SUMMARY` as the last hope
+    if(CPACK_PACKAGE_DESCRIPTION_SUMMARY)
       set(CPACK_DEBIAN_PACKAGE_DESCRIPTION ${CPACK_PACKAGE_DESCRIPTION_SUMMARY})
+    else()
+      # Giving up! Report an error...
+      set(_description_failure_message
+        "CPackDeb: Debian package requires a summary for a package, set CPACK_PACKAGE_DESCRIPTION_SUMMARY or CPACK_DEBIAN_PACKAGE_DESCRIPTION")
+      if(CPACK_DEB_PACKAGE_COMPONENT)
+        string(APPEND _description_failure_message
+          " or CPACK_DEBIAN_${_local_component_name}_DESCRIPTION")
+      endif()
+      message(FATAL_ERROR _description_failure_message)
     endif()
+
+  # Ok, description has set. According to the `Debian Policy Manual`_ the frist
+  # line is a pacakge summary.  Try to get it as well...
+  # See also: https://www.debian.org/doc/debian-policy/ch-controlfields.html#description
+  elseif(CPACK_PACKAGE_DESCRIPTION_SUMMARY)
+    # Merge summary w/ the detailed description
+    string(PREPEND CPACK_DEBIAN_PACKAGE_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}\n")
   endif()
+  # assert(CPACK_DEBIAN_PACKAGE_DESCRIPTION)
+
+  # Make sure description is properly formatted
+  cpack_deb_format_package_description(
+    "${CPACK_DEBIAN_PACKAGE_DESCRIPTION}"
+    CPACK_DEBIAN_PACKAGE_DESCRIPTION
+  )
 
   # Homepage: (optional)
   if(NOT CPACK_DEBIAN_PACKAGE_HOMEPAGE AND CMAKE_PROJECT_HOMEPAGE_URL)
diff --git a/Tests/CPackComponentsDEB/MyLibCPackConfig-components-description1.cmake.in b/Tests/CPackComponentsDEB/MyLibCPackConfig-components-description1.cmake.in
index 74d816c..67b108b 100644
--- a/Tests/CPackComponentsDEB/MyLibCPackConfig-components-description1.cmake.in
+++ b/Tests/CPackComponentsDEB/MyLibCPackConfig-components-description1.cmake.in
@@ -15,8 +15,9 @@ set(CPACK_COMPONENTS_IGNORE_GROUPS 1)
 #set(CPACK_COMPONENTS_ALL_IN_ONE_PACKAGE 1)
 
 # overriding previous descriptions
-set(CPACK_PACKAGE_DESCRIPTION_SUMMARY           "main description")
-set(CPACK_COMPONENT_APPLICATIONS_DESCRIPTION    "applications_description")
-set(CPACK_COMPONENT_HEADERS_DESCRIPTION         "headers_description")
+set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "main description") # This become a summary line (the first one) of all descriptions
+set(CPACK_COMPONENT_APPLICATIONS_DESCRIPTION "applications_description")
+set(CPACK_COMPONENT_HEADERS_DESCRIPTION "headers_description")
 # libraries does not have any description and should inherit from CPACK_PACKAGE_DESCRIPTION_SUMMARY
+# plus content of the `CPACK_PACKAGE_DESCRIPTION_FILE`.
 unset(CPACK_COMPONENT_LIBRARIES_DESCRIPTION)
diff --git a/Tests/CPackComponentsDEB/MyLibCPackConfig-components-description2.cmake.in b/Tests/CPackComponentsDEB/MyLibCPackConfig-components-description2.cmake.in
index cda79bc..d877325 100644
--- a/Tests/CPackComponentsDEB/MyLibCPackConfig-components-description2.cmake.in
+++ b/Tests/CPackComponentsDEB/MyLibCPackConfig-components-description2.cmake.in
@@ -15,12 +15,12 @@ set(CPACK_COMPONENTS_IGNORE_GROUPS 1)
 #set(CPACK_COMPONENTS_ALL_IN_ONE_PACKAGE 1)
 
 # overriding previous descriptions
-set(CPACK_PACKAGE_DESCRIPTION_SUMMARY           "main description 2")
+set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "main description 2")
 
-# Components do not have any description
+# Components do not have any description.
+# So, content of `CPACK_PACKAGE_DESCRIPTION_FILE` gonna used
+# after summary line.
 unset(CPACK_COMPONENT_APPLICATIONS_DESCRIPTION)
 unset(CPACK_COMPONENT_HEADERS_DESCRIPTION)
-unset(CPACK_COMPONENT_LIBRARIES_DESCRIPTION)
 
-
-set(CPACK_COMPONENT_LIBRARIES_DESCRIPTION       "library description")
+set(CPACK_COMPONENT_LIBRARIES_DESCRIPTION "library description")
diff --git a/Tests/CPackComponentsDEB/RunCPackVerifyResult-components-description1.cmake b/Tests/CPackComponentsDEB/RunCPackVerifyResult-components-description1.cmake
index f74137c..86a74b2 100644
--- a/Tests/CPackComponentsDEB/RunCPackVerifyResult-components-description1.cmake
+++ b/Tests/CPackComponentsDEB/RunCPackVerifyResult-components-description1.cmake
@@ -48,26 +48,26 @@ if(DPKGDEB_EXECUTABLE)
                                       DPKGDEB_OUTPUT "${dpkg_output}"
                                       METAENTRY "Package:")
 
-    dpkgdeb_return_specific_metaentry(dpkg_description
-                                      DPKGDEB_OUTPUT "${dpkg_output}"
-                                      METAENTRY "Description:")
+    get_package_description("${dpkg_output}" dpkg_description)
 
     message(STATUS "package='${dpkg_package_name}', description='${dpkg_description}'")
 
     if(dpkg_package_name STREQUAL "mylib-applications")
-      if(NOT "${dpkg_description}" STREQUAL "applications_description")
+      set(expected_description "main description\n  applications_description")
+      if(NOT dpkg_description STREQUAL expected_description)
         set(dpkgdeb_output_errors_all ${dpkgdeb_output_errors_all}
-                                      "dpkg-deb: ${_f}: Incorrect description for package ${dpkg_package_name}: ${dpkg_description} != applications_description")
+                                      "dpkg-deb: ${_f}: Incorrect description for package ${dpkg_package_name}: `${dpkg_description}` != `${expected_description}`")
       endif()
     elseif(dpkg_package_name STREQUAL "mylib-headers")
-      if(NOT dpkg_description STREQUAL "headers_description")
+      set(expected_description "main description\n  headers_description")
+      if(NOT dpkg_description STREQUAL expected_description)
         set(dpkgdeb_output_errors_all ${dpkgdeb_output_errors_all}
-                                      "dpkg-deb: ${_f}: Incorrect description for package ${dpkg_package_name}: ${dpkg_description} != headers_description")
+                                      "dpkg-deb: ${_f}: Incorrect description for package ${dpkg_package_name}: `${dpkg_description}` != `${expected_description}`")
       endif()
     elseif(dpkg_package_name STREQUAL "mylib-libraries")
-      if(NOT dpkg_description STREQUAL "main description")
+      if(NOT dpkg_description MATCHES "main description\n.*")
         set(dpkgdeb_output_errors_all ${dpkgdeb_output_errors_all}
-                                      "dpkg-deb: ${_f}: Incorrect description for package ${dpkg_package_name}: ${dpkg_description} != 'main description'")
+                                      "dpkg-deb: ${_f}: Incorrect description for package ${dpkg_package_name}: `${dpkg_description}` =~ `main description.*`")
       endif()
     else()
       set(dpkgdeb_output_errors_all ${dpkgdeb_output_errors_all}
diff --git a/Tests/CPackComponentsDEB/RunCPackVerifyResult-components-description2.cmake b/Tests/CPackComponentsDEB/RunCPackVerifyResult-components-description2.cmake
index 241dda5..d53c73d 100644
--- a/Tests/CPackComponentsDEB/RunCPackVerifyResult-components-description2.cmake
+++ b/Tests/CPackComponentsDEB/RunCPackVerifyResult-components-description2.cmake
@@ -48,26 +48,20 @@ if(DPKGDEB_EXECUTABLE)
                                       DPKGDEB_OUTPUT "${dpkg_output}"
                                       METAENTRY "Package:")
 
-    dpkgdeb_return_specific_metaentry(dpkg_description
-                                      DPKGDEB_OUTPUT "${dpkg_output}"
-                                      METAENTRY "Description:")
+    get_package_description("${dpkg_output}" dpkg_description)
 
     message(STATUS "package='${dpkg_package_name}', description='${dpkg_description}'")
 
-    if(dpkg_package_name STREQUAL "mylib-applications")
-      if(NOT dpkg_description STREQUAL "main description 2")
-        set(dpkgdeb_output_errors_all ${dpkgdeb_output_errors_all}
-                                      "dpkg-deb: ${_f}: Incorrect description for package ${dpkg_package_name}: ${dpkg_description} != applications_description")
-      endif()
-    elseif(dpkg_package_name STREQUAL "mylib-headers")
-      if(NOT dpkg_description STREQUAL "main description 2")
+    if(dpkg_package_name STREQUAL "mylib-applications" OR dpkg_package_name STREQUAL "mylib-headers")
+      if(NOT dpkg_description MATCHES "main description 2\n.*")
         set(dpkgdeb_output_errors_all ${dpkgdeb_output_errors_all}
-                                      "dpkg-deb: ${_f}: Incorrect description for package ${dpkg_package_name}: ${dpkg_description} != headers_description")
+                                      "dpkg-deb: ${_f}: Incorrect description for package ${dpkg_package_name}: `${dpkg_description}` =~ `main description 2`")
       endif()
     elseif(dpkg_package_name STREQUAL "mylib-libraries")
-      if(NOT dpkg_description STREQUAL "library description")
+      set(expected_description "main description 2\n  library description")
+      if(NOT dpkg_description STREQUAL expected_description)
         set(dpkgdeb_output_errors_all ${dpkgdeb_output_errors_all}
-                                      "dpkg-deb: ${_f}: Incorrect description for package ${dpkg_package_name}: ${dpkg_description} != 'main description'")
+                                      "dpkg-deb: ${_f}: Incorrect description for package ${dpkg_package_name}: `${dpkg_description}` != `${expected_description}`")
       endif()
     else()
       set(dpkgdeb_output_errors_all ${dpkgdeb_output_errors_all}
diff --git a/Tests/CPackComponentsDEB/RunCPackVerifyResult.cmake b/Tests/CPackComponentsDEB/RunCPackVerifyResult.cmake
index 2093e7e..b172da2 100644
--- a/Tests/CPackComponentsDEB/RunCPackVerifyResult.cmake
+++ b/Tests/CPackComponentsDEB/RunCPackVerifyResult.cmake
@@ -200,4 +200,29 @@ function(dpkgdeb_return_specific_metaentry output)
   endif()
 endfunction()
 
+function(get_package_description DPKG_OUTPUT RESULT_VAR)
+  string(UUID uuid NAMESPACE 00000000-0000-0000-0000-000000000000 TYPE SHA1)
+  string(REPLACE ";" "${uuid}" DPKG_OUTPUT "${DPKG_OUTPUT}")
+  string(REPLACE "\n" ";" DPKG_OUTPUT "${DPKG_OUTPUT}")
+
+  unset(_actual_description)
+  set(_parse_description FALSE)
+  foreach(_line IN LISTS DPKG_OUTPUT)
+    if(_line MATCHES " Description:.*")
+      set(_parse_description TRUE)
+      string(REPLACE " Description: " "" _line "${_line}")
+      list(APPEND _actual_description "${_line}")
+    elseif(_parse_description)
+      if(_line MATCHES " [A-Z][A-Za-z\-]+: .*")
+        set(_parse_description FALSE)
+      else()
+        list(APPEND _actual_description "${_line}")
+      endif()
+    endif()
+  endforeach()
+  list(JOIN _actual_description "\n" _actual_description)
+
+  set(${RESULT_VAR} "${_actual_description}" PARENT_SCOPE)
+endfunction()
+
 cmake_policy(POP)
diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt
index 8fd5234..13fbecf 100644
--- a/Tests/RunCMake/CMakeLists.txt
+++ b/Tests/RunCMake/CMakeLists.txt
@@ -510,6 +510,7 @@ set(cpack_tests
   DEB.TIMESTAMPS
   DEB.MD5SUMS
   DEB.DEB_PACKAGE_VERSION_BACK_COMPATIBILITY
+  DEB.DEB_DESCRIPTION
 
   RPM.CUSTOM_BINARY_SPEC_FILE
   RPM.CUSTOM_NAMES
diff --git a/Tests/RunCMake/CPack/RunCMakeTest.cmake b/Tests/RunCMake/CPack/RunCMakeTest.cmake
index 8e98961..b0b7a99 100644
--- a/Tests/RunCMake/CPack/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CPack/RunCMakeTest.cmake
@@ -39,3 +39,10 @@ run_cpack_test_subtests(EXTERNAL "none;good;good_multi;bad_major;bad_minor;inval
 if(RunCMake_GENERATOR MATCHES "Visual Studio|Xcode")
   run_cpack_test(CPACK_INSTALL_CMAKE_CONFIGURATIONS "ZIP" false "MONOLITHIC")
 endif()
+run_cpack_test_subtests(
+  DEB_DESCRIPTION
+  "CPACK_DEBIAN_PACKAGE_DESCRIPTION;CPACK_PACKAGE_DESCRIPTION;CPACK_PACKAGE_DESCRIPTION_FILE"
+  "DEB.DEB_DESCRIPTION"
+  false
+  "MONOLITHIC;COMPONENT"
+)
diff --git a/Tests/RunCMake/CPack/tests/DEB_DESCRIPTION/ExpectedFiles.cmake b/Tests/RunCMake/CPack/tests/DEB_DESCRIPTION/ExpectedFiles.cmake
new file mode 100644
index 0000000..39f18a3
--- /dev/null
+++ b/Tests/RunCMake/CPack/tests/DEB_DESCRIPTION/ExpectedFiles.cmake
@@ -0,0 +1,16 @@
+set(EXPECTED_FILES_COUNT_MONOLITHIC "1")
+set(EXPECTED_FILES_COUNT_COMPONENT "2")
+set(EXPECTED_FILES_COUNT "${EXPECTED_FILES_COUNT_${PACKAGING_TYPE}}")
+
+if(PACKAGING_TYPE STREQUAL "COMPONENT")
+  set(EXPECTED_FILE_1 "deb_description-0.1.1-*-satu.deb")
+  set(EXPECTED_FILE_2 "deb_description-0.1.1-*-dua.deb")
+  set(EXPECTED_FILE_CONTENT_1_LIST "/satu;/satu/CMakeLists.txt")
+  set(EXPECTED_FILE_CONTENT_2_LIST "/dua;/dua/CMakeLists.txt")
+
+elseif(PACKAGING_TYPE STREQUAL "MONOLITHIC")
+  set(EXPECTED_FILE_CONTENT_1_LIST "/dua;/dua/CMakeLists.txt;/satu;/satu/CMakeLists.txt")
+
+endif()
+
+# kate: indent-width 2;
diff --git a/Tests/RunCMake/CPack/tests/DEB_DESCRIPTION/VerifyResult.cmake b/Tests/RunCMake/CPack/tests/DEB_DESCRIPTION/VerifyResult.cmake
new file mode 100644
index 0000000..e9ac13a
--- /dev/null
+++ b/Tests/RunCMake/CPack/tests/DEB_DESCRIPTION/VerifyResult.cmake
@@ -0,0 +1,65 @@
+function(checkPackageDescription FILE EXPECTED_DESCRIPTION)
+  getPackageInfo("${FILE}" "_file_info")
+  string(UUID uuid NAMESPACE 00000000-0000-0000-0000-000000000000 TYPE SHA1)
+  string(REPLACE ";" "${uuid}" _file_info "${_file_info}")
+  string(REPLACE ";" "${uuid}" EXPECTED_DESCRIPTION "${EXPECTED_DESCRIPTION}")
+  string(REPLACE "\n" ";" _file_info "${_file_info}")
+
+  set(_actual_description)
+  set(_parse_description FALSE)
+  foreach(_line IN LISTS _file_info)
+    if(_line MATCHES " Description:.*")
+      set(_parse_description TRUE)
+      list(APPEND _actual_description "${_line}")
+    elseif(_parse_description)
+      if(_line MATCHES " [A-Z][A-Za-z\-]+: .*")
+        set(_parse_description FALSE)
+      else()
+        list(APPEND _actual_description "${_line}")
+      endif()
+    endif()
+  endforeach()
+  list(JOIN _actual_description "\n" _actual_description)
+
+  if(NOT _actual_description STREQUAL EXPECTED_DESCRIPTION)
+    set(_error "---[BEGIN Expected description]---\n${EXPECTED_DESCRIPTION}---[END Expected description]---\n")
+    string(APPEND _error "---[BEGIN Actual description]---\n${_actual_description}---[END Actual description]---\n")
+    string(REPLACE "${uuid}"  ";" _error "${_error}")
+    message(FATAL_ERROR "${_error}")
+  endif()
+endfunction()
+
+# ALERT The output of `dpkg -I *.deb` indented by one space
+set(_expected_description [[ Description: This is the summary line
+  This is the Debian package multiline description.
+  .
+  It must be formatted properly! Otherwise, the result `*.deb`
+  package become broken and cant be installed!
+  .
+  It may contains `;` characters (even like this `;;;;`). Example:
+  .
+    - one;
+    - two;
+    - three;
+  .
+  ... and they are properly handled by the automatic description formatter!
+  .
+  See also: https://www.debian.org/doc/debian-policy/ch-controlfields.html#description]])
+
+# ATTENTION The code in `cmCPackGenerator.cxx` to read `CPACK_PACKAGE_DESCRIPTION_FILE`
+# has a BUG: it appends the `\n` character to every line of the
+# input, even if there was no EOL (e.g. at the last line of the file).
+# That is WHY for this sub-test the one more pre-formatted "empty"
+# line required!
+# NOTE For component based installers content of the file gonna read by
+# `CPackDeb` module and the `file(READ...)` command so no the mentioned
+# workaround required!
+if(RunCMake_SUBTEST_SUFFIX STREQUAL "CPACK_PACKAGE_DESCRIPTION_FILE" AND PACKAGING_TYPE STREQUAL "MONOLITHIC")
+  string(APPEND _expected_description "\n  ." )
+endif()
+
+foreach(_file_no RANGE 1 ${EXPECTED_FILES_COUNT})
+  checkPackageDescription("${FOUND_FILE_${_file_no}}" "${_expected_description}")
+endforeach()
+
+# kate: indent-width 2;
diff --git a/Tests/RunCMake/CPack/tests/DEB_DESCRIPTION/test.cmake b/Tests/RunCMake/CPack/tests/DEB_DESCRIPTION/test.cmake
new file mode 100644
index 0000000..ce3f651
--- /dev/null
+++ b/Tests/RunCMake/CPack/tests/DEB_DESCRIPTION/test.cmake
@@ -0,0 +1,45 @@
+install(FILES CMakeLists.txt DESTINATION satu COMPONENT satu)
+install(FILES CMakeLists.txt DESTINATION dua COMPONENT dua)
+
+set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "This is the summary line")
+set(_description [[This is the Debian package multiline description.
+
+It must be formatted properly! Otherwise, the result `*.deb`
+package become broken and cant be installed!
+
+It may contains `;` characters (even like this `;;;;`). Example:
+
+  - one;
+  - two;
+  - three;
+
+... and they are properly handled by the automatic description formatter!
+
+See also: https://www.debian.org/doc/debian-policy/ch-controlfields.html#description]])
+
+if(RunCMake_SUBTEST_SUFFIX STREQUAL "CPACK_DEBIAN_PACKAGE_DESCRIPTION")
+  if(PACKAGING_TYPE STREQUAL "COMPONENT")
+    set(CPACK_DEBIAN_SATU_DESCRIPTION "${_description}")
+    set(CPACK_DEBIAN_DUA_DESCRIPTION "${_description}")
+  else()
+    set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "${_description}")
+  endif()
+
+elseif(RunCMake_SUBTEST_SUFFIX STREQUAL "CPACK_PACKAGE_DESCRIPTION")
+  # NOTE Documented fallback variable
+  if(PACKAGING_TYPE STREQUAL "COMPONENT")
+    set(CPACK_COMPONENT_SATU_DESCRIPTION "${_description}")
+    set(CPACK_COMPONENT_DUA_DESCRIPTION "${_description}")
+  else()
+    set(CPACK_PACKAGE_DESCRIPTION "${_description}")
+  endif()
+
+elseif(RunCMake_SUBTEST_SUFFIX STREQUAL "CPACK_PACKAGE_DESCRIPTION_FILE")
+  # NOTE Getting the description from the file
+  set(_file "${CMAKE_CURRENT_BINARY_DIR}/description.txt")
+  file(WRITE "${_file}" "${_description}")
+  set(CPACK_PACKAGE_DESCRIPTION_FILE "${_file}")
+
+endif()
+
+# kate: indent-width 2;
diff --git a/Tests/RunCMake/CPack/tests/PER_COMPONENT_FIELDS/VerifyResult.cmake b/Tests/RunCMake/CPack/tests/PER_COMPONENT_FIELDS/VerifyResult.cmake
index b4bdb61..c47b40e 100644
--- a/Tests/RunCMake/CPack/tests/PER_COMPONENT_FIELDS/VerifyResult.cmake
+++ b/Tests/RunCMake/CPack/tests/PER_COMPONENT_FIELDS/VerifyResult.cmake
@@ -8,6 +8,9 @@ endfunction()
 if(GENERATOR_TYPE STREQUAL "DEB")
   set(name_ "Package")
   set(group_ "Section")
+  # NOTE For a Debian package the first line of the `Description`
+  # field is generated by CMake and gonna be ignored
+  set(ignore_rest_cond_ ".*\n")
 elseif(GENERATOR_TYPE STREQUAL "RPM")
   set(name_ "Name")
   set(group_ "Group")
@@ -33,6 +36,6 @@ if(GENERATOR_TYPE STREQUAL "RPM")
 endif()
 
 # check package description
-checkPackageInfo_("description" "${FOUND_FILE_1}" ".*Description${whitespaces_}:${whitespaces_}Description for pkg_1")
-checkPackageInfo_("description" "${FOUND_FILE_2}" ".*Description${whitespaces_}:${whitespaces_}Description for pkg_2")
-checkPackageInfo_("description" "${FOUND_FILE_3}" ".*Description${whitespaces_}:${whitespaces_}Description for pkg_3")
+checkPackageInfo_("description" "${FOUND_FILE_1}" ".*Description${whitespaces_}:${ignore_rest_cond_}${whitespaces_}Description for pkg_1")
+checkPackageInfo_("description" "${FOUND_FILE_2}" ".*Description${whitespaces_}:${ignore_rest_cond_}${whitespaces_}Description for pkg_2")
+checkPackageInfo_("description" "${FOUND_FILE_3}" ".*Description${whitespaces_}:${ignore_rest_cond_}${whitespaces_}Description for pkg_3")

https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=53be31e19c0dc0f517825d1be056c55d7729a4b7
commit 53be31e19c0dc0f517825d1be056c55d7729a4b7
Author:     Alex Turbov <i.zaufi at gmail.com>
AuthorDate: Thu Jul 11 02:26:48 2019 +0300
Commit:     Alex Turbov <i.zaufi at gmail.com>
CommitDate: Sat Sep 21 23:06:30 2019 +0200

    Refactor: Use `list` commands instead of old-way string ops

diff --git a/Modules/Internal/CPack/CPackDeb.cmake b/Modules/Internal/CPack/CPackDeb.cmake
index 51e505b..e22f623 100644
--- a/Modules/Internal/CPack/CPackDeb.cmake
+++ b/Modules/Internal/CPack/CPackDeb.cmake
@@ -210,7 +210,7 @@ function(cpack_deb_prepare_package_vars)
       if(_TMP_VERSION MATCHES "dpkg-shlibdeps version ([0-9]+\\.[0-9]+\\.[0-9]+)")
         set(SHLIBDEPS_EXECUTABLE_VERSION "${CMAKE_MATCH_1}")
       else()
-        set(SHLIBDEPS_EXECUTABLE_VERSION "")
+        unset(SHLIBDEPS_EXECUTABLE_VERSION)
       endif()
 
       if(CPACK_DEBIAN_PACKAGE_DEBUG)
@@ -388,7 +388,7 @@ function(cpack_deb_prepare_package_vars)
   # if per-component variable, overrides the global CPACK_DEBIAN_PACKAGE_${variable_type_}
   # automatic dependency discovery will be performed afterwards.
   if(CPACK_DEB_PACKAGE_COMPONENT)
-    foreach(value_type_ DEPENDS RECOMMENDS SUGGESTS PREDEPENDS ENHANCES BREAKS CONFLICTS PROVIDES REPLACES SOURCE SECTION PRIORITY NAME)
+    foreach(value_type_ IN ITEMS DEPENDS RECOMMENDS SUGGESTS PREDEPENDS ENHANCES BREAKS CONFLICTS PROVIDES REPLACES SOURCE SECTION PRIORITY NAME)
       set(_component_var "CPACK_DEBIAN_${_local_component_name}_PACKAGE_${value_type_}")
 
       # if set, overrides the global variable
@@ -402,21 +402,15 @@ function(cpack_deb_prepare_package_vars)
     endforeach()
 
     if(CPACK_DEBIAN_ENABLE_COMPONENT_DEPENDS)
-      set(COMPONENT_DEPENDS "")
-      foreach(_PACK ${CPACK_COMPONENT_${_local_component_name}_DEPENDS})
+      unset(COMPONENT_DEPENDS)
+      foreach(_PACK IN LISTS CPACK_COMPONENT_${_local_component_name}_DEPENDS)
         get_component_package_name(_PACK_NAME "${_PACK}")
-        if(COMPONENT_DEPENDS)
-          set(COMPONENT_DEPENDS "${_PACK_NAME} (= ${CPACK_DEBIAN_PACKAGE_VERSION}), ${COMPONENT_DEPENDS}")
-        else()
-          set(COMPONENT_DEPENDS "${_PACK_NAME} (= ${CPACK_DEBIAN_PACKAGE_VERSION})")
-        endif()
+        list(PREPEND COMPONENT_DEPENDS "${_PACK_NAME} (= ${CPACK_DEBIAN_PACKAGE_VERSION})")
       endforeach()
+      list(JOIN COMPONENT_DEPENDS ", " COMPONENT_DEPENDS)
       if(COMPONENT_DEPENDS)
-        if(CPACK_DEBIAN_PACKAGE_DEPENDS)
-          set(CPACK_DEBIAN_PACKAGE_DEPENDS "${COMPONENT_DEPENDS}, ${CPACK_DEBIAN_PACKAGE_DEPENDS}")
-        else()
-          set(CPACK_DEBIAN_PACKAGE_DEPENDS "${COMPONENT_DEPENDS}")
-        endif()
+        list(PREPEND CPACK_DEBIAN_PACKAGE_DEPENDS ${COMPONENT_DEPENDS})
+        list(JOIN CPACK_DEBIAN_PACKAGE_DEPENDS ", " CPACK_DEBIAN_PACKAGE_DEPENDS)
       endif()
     endif()
   endif()
@@ -424,12 +418,9 @@ function(cpack_deb_prepare_package_vars)
   # at this point, the CPACK_DEBIAN_PACKAGE_DEPENDS is properly set
   # to the minimal dependency of the package
   # Append automatically discovered dependencies .
-  if(NOT "${CPACK_DEBIAN_PACKAGE_AUTO_DEPENDS}" STREQUAL "")
-    if(CPACK_DEBIAN_PACKAGE_DEPENDS)
-      set (CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}, ${CPACK_DEBIAN_PACKAGE_AUTO_DEPENDS}")
-    else()
-      set (CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_AUTO_DEPENDS}")
-    endif()
+  if(CPACK_DEBIAN_PACKAGE_AUTO_DEPENDS)
+    list(APPEND CPACK_DEBIAN_PACKAGE_DEPENDS ${CPACK_DEBIAN_PACKAGE_AUTO_DEPENDS})
+    list(JOIN CPACK_DEBIAN_PACKAGE_DEPENDS ", " CPACK_DEBIAN_PACKAGE_DEPENDS)
   endif()
 
   if(NOT CPACK_DEBIAN_PACKAGE_DEPENDS)
@@ -519,7 +510,7 @@ function(cpack_deb_prepare_package_vars)
   # Are we packaging components ?
   if(CPACK_DEB_PACKAGE_COMPONENT)
     # override values with per component version if set
-    foreach(VAR_NAME_ "PACKAGE_CONTROL_EXTRA" "PACKAGE_CONTROL_STRICT_PERMISSION")
+    foreach(VAR_NAME_ IN ITEMS PACKAGE_CONTROL_EXTRA PACKAGE_CONTROL_STRICT_PERMISSION)
       if(CPACK_DEBIAN_${_local_component_name}_${VAR_NAME_})
         set(CPACK_DEBIAN_${VAR_NAME_} "${CPACK_DEBIAN_${_local_component_name}_${VAR_NAME_}}")
       endif()
@@ -527,12 +518,12 @@ function(cpack_deb_prepare_package_vars)
     get_component_package_name(CPACK_DEBIAN_PACKAGE_NAME ${_local_component_name})
   endif()
 
-  set(CPACK_DEBIAN_PACKAGE_SHLIBS_LIST "")
-
   if(NOT CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS_POLICY)
     set(CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS_POLICY "=")
   endif()
 
+  unset(CPACK_DEBIAN_PACKAGE_SHLIBS_LIST)
+
   if(CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS)
     if(READELF_EXECUTABLE)
       foreach(_FILE IN LISTS CPACK_DEB_SHARED_OBJECT_FILES)
@@ -544,9 +535,7 @@ function(cpack_deb_prepare_package_vars)
           message(AUTHOR_WARNING "Shared library '${_FILE}' is missing soname or soversion. Library will not be added to DEBIAN/shlibs control file.")
         endif()
       endforeach()
-      if(CPACK_DEBIAN_PACKAGE_SHLIBS_LIST)
-        string(REPLACE ";" "\n" CPACK_DEBIAN_PACKAGE_SHLIBS_LIST "${CPACK_DEBIAN_PACKAGE_SHLIBS_LIST}")
-      endif()
+      list(JOIN CPACK_DEBIAN_PACKAGE_SHLIBS_LIST "\n" CPACK_DEBIAN_PACKAGE_SHLIBS_LIST)
     else()
       message(FATAL_ERROR "Readelf utility is not available. CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS option is not available.")
     endif()
@@ -554,7 +543,7 @@ function(cpack_deb_prepare_package_vars)
 
   # add ldconfig call in default postrm and postint
   set(CPACK_ADD_LDCONFIG_CALL 0)
-  foreach(_FILE ${CPACK_DEB_SHARED_OBJECT_FILES})
+  foreach(_FILE IN LISTS CPACK_DEB_SHARED_OBJECT_FILES)
     get_filename_component(_DIR ${_FILE} DIRECTORY)
     # all files in CPACK_DEB_SHARED_OBJECT_FILES have dot at the beginning
     if(_DIR STREQUAL "./lib" OR _DIR STREQUAL "./usr/lib")
@@ -565,12 +554,12 @@ function(cpack_deb_prepare_package_vars)
   if(CPACK_ADD_LDCONFIG_CALL)
     set(CPACK_DEBIAN_GENERATE_POSTINST 1)
     set(CPACK_DEBIAN_GENERATE_POSTRM 1)
-    foreach(f ${PACKAGE_CONTROL_EXTRA})
+    foreach(f IN LISTS PACKAGE_CONTROL_EXTRA)
       get_filename_component(n "${f}" NAME)
-      if("${n}" STREQUAL "postinst")
+      if(n STREQUAL "postinst")
         set(CPACK_DEBIAN_GENERATE_POSTINST 0)
       endif()
-      if("${n}" STREQUAL "postrm")
+      if(n STREQUAL "postrm")
         set(CPACK_DEBIAN_GENERATE_POSTRM 0)
       endif()
     endforeach()
@@ -671,7 +660,7 @@ function(cpack_deb_prepare_package_vars)
   if(BUILD_IDS)
     set(GEN_DBGSYMDIR "${DBGSYMDIR}" PARENT_SCOPE)
     set(GEN_CPACK_DBGSYM_OUTPUT_FILE_NAME "${CPACK_DBGSYM_OUTPUT_FILE_NAME}" PARENT_SCOPE)
-    string(REPLACE ";" " " BUILD_IDS "${BUILD_IDS}")
+    list(JOIN BUILD_IDS " " BUILD_IDS)
     set(GEN_BUILD_IDS "${BUILD_IDS}" PARENT_SCOPE)
   endif()
 endfunction()

https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=0aa8a2ab8b00c0889df3d0fa1be47ad5a0b9db22
commit 0aa8a2ab8b00c0889df3d0fa1be47ad5a0b9db22
Author:     Regina Pfeifer <regina at mailbox.org>
AuthorDate: Thu Sep 19 13:11:55 2019 +0200
Commit:     Regina Pfeifer <regina at mailbox.org>
CommitDate: Fri Sep 20 13:27:30 2019 +0200

    cmCTest*Command: Port to cmArgumentParser

diff --git a/Source/CTest/cmCTestBuildCommand.cxx b/Source/CTest/cmCTestBuildCommand.cxx
index 02b00e3..ce690f9 100644
--- a/Source/CTest/cmCTestBuildCommand.cxx
+++ b/Source/CTest/cmCTestBuildCommand.cxx
@@ -9,6 +9,7 @@
 #include "cmMessageType.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
+#include "cm_static_string_view.hxx"
 #include "cmake.h"
 
 #include <cstring>
@@ -16,17 +17,15 @@
 
 class cmExecutionStatus;
 
-cmCTestBuildCommand::cmCTestBuildCommand()
+void cmCTestBuildCommand::BindArguments()
 {
-  this->GlobalGenerator = nullptr;
-  this->Arguments[ctb_NUMBER_ERRORS] = "NUMBER_ERRORS";
-  this->Arguments[ctb_NUMBER_WARNINGS] = "NUMBER_WARNINGS";
-  this->Arguments[ctb_TARGET] = "TARGET";
-  this->Arguments[ctb_CONFIGURATION] = "CONFIGURATION";
-  this->Arguments[ctb_FLAGS] = "FLAGS";
-  this->Arguments[ctb_PROJECT_NAME] = "PROJECT_NAME";
-  this->Arguments[ctb_LAST] = nullptr;
-  this->Last = ctb_LAST;
+  this->cmCTestHandlerCommand::BindArguments();
+  this->Bind("NUMBER_ERRORS"_s, this->NumberErrors);
+  this->Bind("NUMBER_WARNINGS"_s, this->NumberWarnings);
+  this->Bind("TARGET"_s, this->Target);
+  this->Bind("CONFIGURATION"_s, this->Configuration);
+  this->Bind("FLAGS"_s, this->Flags);
+  this->Bind("PROJECT_NAME"_s, this->ProjectName);
 }
 
 cmCTestBuildCommand::~cmCTestBuildCommand()
@@ -60,20 +59,17 @@ cmCTestGenericHandler* cmCTestBuildCommand::InitializeHandler()
     //
     const char* ctestBuildConfiguration =
       this->Makefile->GetDefinition("CTEST_BUILD_CONFIGURATION");
-    const char* cmakeBuildConfiguration =
-      (this->Values[ctb_CONFIGURATION] && *this->Values[ctb_CONFIGURATION])
-      ? this->Values[ctb_CONFIGURATION]
+    const char* cmakeBuildConfiguration = !this->Configuration.empty()
+      ? this->Configuration.c_str()
       : ((ctestBuildConfiguration && *ctestBuildConfiguration)
            ? ctestBuildConfiguration
            : this->CTest->GetConfigType().c_str());
 
-    const char* cmakeBuildAdditionalFlags =
-      (this->Values[ctb_FLAGS] && *this->Values[ctb_FLAGS])
-      ? this->Values[ctb_FLAGS]
+    const char* cmakeBuildAdditionalFlags = !this->Flags.empty()
+      ? this->Flags.c_str()
       : this->Makefile->GetDefinition("CTEST_BUILD_FLAGS");
-    const char* cmakeBuildTarget =
-      (this->Values[ctb_TARGET] && *this->Values[ctb_TARGET])
-      ? this->Values[ctb_TARGET]
+    const char* cmakeBuildTarget = !this->Target.empty()
+      ? this->Target.c_str()
       : this->Makefile->GetDefinition("CTEST_BUILD_TARGET");
 
     if (cmakeGeneratorName && *cmakeGeneratorName) {
@@ -153,16 +149,13 @@ bool cmCTestBuildCommand::InitialPass(std::vector<std::string> const& args,
                                       cmExecutionStatus& status)
 {
   bool ret = cmCTestHandlerCommand::InitialPass(args, status);
-  if (this->Values[ctb_NUMBER_ERRORS] && *this->Values[ctb_NUMBER_ERRORS]) {
+  if (!this->NumberErrors.empty()) {
     this->Makefile->AddDefinition(
-      this->Values[ctb_NUMBER_ERRORS],
-      std::to_string(this->Handler->GetTotalErrors()));
+      this->NumberErrors, std::to_string(this->Handler->GetTotalErrors()));
   }
-  if (this->Values[ctb_NUMBER_WARNINGS] &&
-      *this->Values[ctb_NUMBER_WARNINGS]) {
+  if (!this->NumberWarnings.empty()) {
     this->Makefile->AddDefinition(
-      this->Values[ctb_NUMBER_WARNINGS],
-      std::to_string(this->Handler->GetTotalWarnings()));
+      this->NumberWarnings, std::to_string(this->Handler->GetTotalWarnings()));
   }
   return ret;
 }
diff --git a/Source/CTest/cmCTestBuildCommand.h b/Source/CTest/cmCTestBuildCommand.h
index a62c301..2c1f9fb 100644
--- a/Source/CTest/cmCTestBuildCommand.h
+++ b/Source/CTest/cmCTestBuildCommand.h
@@ -27,7 +27,6 @@ class cmGlobalGenerator;
 class cmCTestBuildCommand : public cmCTestHandlerCommand
 {
 public:
-  cmCTestBuildCommand();
   ~cmCTestBuildCommand() override;
 
   /**
@@ -49,23 +48,19 @@ public:
   bool InitialPass(std::vector<std::string> const& args,
                    cmExecutionStatus& status) override;
 
-  cmGlobalGenerator* GlobalGenerator;
+  cmGlobalGenerator* GlobalGenerator = nullptr;
 
 protected:
   cmCTestBuildHandler* Handler;
-  enum
-  {
-    ctb_BUILD = ct_LAST,
-    ctb_NUMBER_ERRORS,
-    ctb_NUMBER_WARNINGS,
-    ctb_TARGET,
-    ctb_CONFIGURATION,
-    ctb_FLAGS,
-    ctb_PROJECT_NAME,
-    ctb_LAST
-  };
-
+  void BindArguments() override;
   cmCTestGenericHandler* InitializeHandler() override;
+
+  std::string NumberErrors;
+  std::string NumberWarnings;
+  std::string Target;
+  std::string Configuration;
+  std::string Flags;
+  std::string ProjectName;
 };
 
 #endif
diff --git a/Source/CTest/cmCTestConfigureCommand.cxx b/Source/CTest/cmCTestConfigureCommand.cxx
index 8a56f3d..948b9fb 100644
--- a/Source/CTest/cmCTestConfigureCommand.cxx
+++ b/Source/CTest/cmCTestConfigureCommand.cxx
@@ -8,25 +8,25 @@
 #include "cmMakefile.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
+#include "cm_static_string_view.hxx"
 #include "cmake.h"
 
 #include <cstring>
 #include <sstream>
 #include <vector>
 
-cmCTestConfigureCommand::cmCTestConfigureCommand()
+void cmCTestConfigureCommand::BindArguments()
 {
-  this->Arguments[ctc_OPTIONS] = "OPTIONS";
-  this->Arguments[ctc_LAST] = nullptr;
-  this->Last = ctc_LAST;
+  this->cmCTestHandlerCommand::BindArguments();
+  this->Bind("OPTIONS"_s, this->Options);
 }
 
 cmCTestGenericHandler* cmCTestConfigureCommand::InitializeHandler()
 {
   std::vector<std::string> options;
 
-  if (this->Values[ctc_OPTIONS]) {
-    cmExpandList(this->Values[ctc_OPTIONS], options);
+  if (!this->Options.empty()) {
+    cmExpandList(this->Options, options);
   }
 
   if (this->CTest->GetCTestConfiguration("BuildDirectory").empty()) {
diff --git a/Source/CTest/cmCTestConfigureCommand.h b/Source/CTest/cmCTestConfigureCommand.h
index 4677c83..f42f192 100644
--- a/Source/CTest/cmCTestConfigureCommand.h
+++ b/Source/CTest/cmCTestConfigureCommand.h
@@ -23,8 +23,6 @@ class cmCTestGenericHandler;
 class cmCTestConfigureCommand : public cmCTestHandlerCommand
 {
 public:
-  cmCTestConfigureCommand();
-
   /**
    * This is a virtual constructor for the command.
    */
@@ -42,14 +40,10 @@ public:
   std::string GetName() const override { return "ctest_configure"; }
 
 protected:
+  void BindArguments() override;
   cmCTestGenericHandler* InitializeHandler() override;
 
-  enum
-  {
-    ctc_FIRST = ct_LAST,
-    ctc_OPTIONS,
-    ctc_LAST
-  };
+  std::string Options;
 };
 
 #endif
diff --git a/Source/CTest/cmCTestCoverageCommand.cxx b/Source/CTest/cmCTestCoverageCommand.cxx
index 07aae76..b66bba7 100644
--- a/Source/CTest/cmCTestCoverageCommand.cxx
+++ b/Source/CTest/cmCTestCoverageCommand.cxx
@@ -2,14 +2,26 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmCTestCoverageCommand.h"
 
+#include <set>
+
+#include "cmAlgorithms.h"
 #include "cmCTest.h"
 #include "cmCTestCoverageHandler.h"
+#include "cm_static_string_view.hxx"
 
 class cmCTestGenericHandler;
 
-cmCTestCoverageCommand::cmCTestCoverageCommand()
+void cmCTestCoverageCommand::BindArguments()
+{
+  this->cmCTestHandlerCommand::BindArguments();
+  this->Bind("LABELS"_s, this->Labels);
+}
+
+void cmCTestCoverageCommand::CheckArguments(
+  std::vector<std::string> const& keywords)
 {
-  this->LabelsMentioned = false;
+  this->LabelsMentioned =
+    !this->Labels.empty() || cmContains(keywords, "LABELS");
 }
 
 cmCTestGenericHandler* cmCTestCoverageCommand::InitializeHandler()
@@ -24,34 +36,10 @@ cmCTestGenericHandler* cmCTestCoverageCommand::InitializeHandler()
 
   // If a LABELS option was given, select only files with the labels.
   if (this->LabelsMentioned) {
-    handler->SetLabelFilter(this->Labels);
+    handler->SetLabelFilter(
+      std::set<std::string>(this->Labels.begin(), this->Labels.end()));
   }
 
   handler->SetQuiet(this->Quiet);
   return handler;
 }
-
-bool cmCTestCoverageCommand::CheckArgumentKeyword(std::string const& arg)
-{
-  // Look for arguments specific to this command.
-  if (arg == "LABELS") {
-    this->ArgumentDoing = ArgumentDoingLabels;
-    this->LabelsMentioned = true;
-    return true;
-  }
-
-  // Look for other arguments.
-  return this->Superclass::CheckArgumentKeyword(arg);
-}
-
-bool cmCTestCoverageCommand::CheckArgumentValue(std::string const& arg)
-{
-  // Handle states specific to this command.
-  if (this->ArgumentDoing == ArgumentDoingLabels) {
-    this->Labels.insert(arg);
-    return true;
-  }
-
-  // Look for other arguments.
-  return this->Superclass::CheckArgumentValue(arg);
-}
diff --git a/Source/CTest/cmCTestCoverageCommand.h b/Source/CTest/cmCTestCoverageCommand.h
index 24b96c0..f7b6315 100644
--- a/Source/CTest/cmCTestCoverageCommand.h
+++ b/Source/CTest/cmCTestCoverageCommand.h
@@ -8,9 +8,9 @@
 #include "cmCTestHandlerCommand.h"
 #include "cmCommand.h"
 
-#include <set>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "cm_memory.hxx"
 
@@ -24,8 +24,6 @@ class cmCTestGenericHandler;
 class cmCTestCoverageCommand : public cmCTestHandlerCommand
 {
 public:
-  cmCTestCoverageCommand();
-
   /**
    * This is a virtual constructor for the command.
    */
@@ -42,22 +40,13 @@ public:
    */
   std::string GetName() const override { return "ctest_coverage"; }
 
-  using Superclass = cmCTestHandlerCommand;
-
 protected:
+  void BindArguments() override;
+  void CheckArguments(std::vector<std::string> const& keywords) override;
   cmCTestGenericHandler* InitializeHandler() override;
 
-  bool CheckArgumentKeyword(std::string const& arg) override;
-  bool CheckArgumentValue(std::string const& arg) override;
-
-  enum
-  {
-    ArgumentDoingLabels = Superclass::ArgumentDoingLast1,
-    ArgumentDoingLast2
-  };
-
   bool LabelsMentioned;
-  std::set<std::string> Labels;
+  std::vector<std::string> Labels;
 };
 
 #endif
diff --git a/Source/CTest/cmCTestHandlerCommand.cxx b/Source/CTest/cmCTestHandlerCommand.cxx
index 9c5425d..3f9ce4e 100644
--- a/Source/CTest/cmCTestHandlerCommand.cxx
+++ b/Source/CTest/cmCTestHandlerCommand.cxx
@@ -7,31 +7,16 @@
 #include "cmExecutionStatus.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
+#include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmWorkingDirectory.h"
+#include "cm_static_string_view.hxx"
 
+#include <algorithm>
 #include <cstdlib>
 #include <cstring>
 #include <sstream>
 
-cmCTestHandlerCommand::cmCTestHandlerCommand()
-{
-  const size_t INIT_SIZE = 100;
-  size_t cc;
-  this->Arguments.reserve(INIT_SIZE);
-  for (cc = 0; cc < INIT_SIZE; ++cc) {
-    this->Arguments.push_back(nullptr);
-  }
-  this->Arguments[ct_RETURN_VALUE] = "RETURN_VALUE";
-  this->Arguments[ct_CAPTURE_CMAKE_ERROR] = "CAPTURE_CMAKE_ERROR";
-  this->Arguments[ct_SOURCE] = "SOURCE";
-  this->Arguments[ct_BUILD] = "BUILD";
-  this->Arguments[ct_SUBMIT_INDEX] = "SUBMIT_INDEX";
-  this->Last = ct_LAST;
-  this->AppendXML = false;
-  this->Quiet = false;
-}
-
 namespace {
 // class to save and restore the error state for ctest_* commands
 // if a ctest_* command has a CAPTURE_CMAKE_ERROR then put the error
@@ -90,30 +75,30 @@ bool cmCTestHandlerCommand::InitialPass(std::vector<std::string> const& args,
   // save error state and restore it if needed
   SaveRestoreErrorState errorState;
   // Allocate space for argument values.
-  this->Values.clear();
-  this->Values.resize(this->Last, nullptr);
+  this->BindArguments();
 
   // Process input arguments.
-  this->ArgumentDoing = ArgumentDoingNone;
-  // look at all arguments and do not short circuit on the first
-  // bad one so that CAPTURE_CMAKE_ERROR can override setting the
-  // global error state
-  bool foundBadArgument = false;
-  for (std::string const& arg : args) {
-    // Check this argument.
-    if (!this->CheckArgumentKeyword(arg) && !this->CheckArgumentValue(arg)) {
-      std::ostringstream e;
-      e << "called with unknown argument \"" << arg << "\".";
-      this->SetError(e.str());
-      foundBadArgument = true;
-    }
-    // note bad argument
-    if (this->ArgumentDoing == ArgumentDoingError) {
-      foundBadArgument = true;
-    }
+  std::vector<std::string> unparsedArguments;
+  std::vector<std::string> keywordsMissingValue;
+  std::vector<std::string> parsedKeywords;
+  this->Parse(args, &unparsedArguments, &keywordsMissingValue,
+              &parsedKeywords);
+  this->CheckArguments(keywordsMissingValue);
+
+  std::sort(parsedKeywords.begin(), parsedKeywords.end());
+  auto it = std::adjacent_find(parsedKeywords.begin(), parsedKeywords.end());
+  if (it != parsedKeywords.end()) {
+    this->Makefile->IssueMessage(
+      MessageType::FATAL_ERROR,
+      cmStrCat("Called with more than one value for ", *it));
+  }
+
+  bool const foundBadArgument = !unparsedArguments.empty();
+  if (foundBadArgument) {
+    this->SetError(cmStrCat("called with unknown argument \"",
+                            unparsedArguments.front(), "\"."));
   }
-  bool captureCMakeError = (this->Values[ct_CAPTURE_CMAKE_ERROR] &&
-                            *this->Values[ct_CAPTURE_CMAKE_ERROR]);
+  bool const captureCMakeError = !this->CaptureCMakeError.empty();
   // now that arguments are parsed check to see if there is a
   // CAPTURE_CMAKE_ERROR specified let the errorState object know.
   if (captureCMakeError) {
@@ -123,8 +108,7 @@ bool cmCTestHandlerCommand::InitialPass(std::vector<std::string> const& args,
   if (foundBadArgument) {
     // store the cmake error
     if (captureCMakeError) {
-      this->Makefile->AddDefinition(this->Values[ct_CAPTURE_CMAKE_ERROR],
-                                    "-1");
+      this->Makefile->AddDefinition(this->CaptureCMakeError, "-1");
       std::string const err = this->GetName() + " " + status.GetError();
       if (!cmSystemTools::FindLastString(err.c_str(), "unknown error.")) {
         cmCTestLog(this->CTest, ERROR_MESSAGE, err << " error from command\n");
@@ -146,10 +130,9 @@ bool cmCTestHandlerCommand::InitialPass(std::vector<std::string> const& args,
     this->CTest->SetConfigType(ctestConfigType);
   }
 
-  if (this->Values[ct_BUILD]) {
+  if (!this->Build.empty()) {
     this->CTest->SetCTestConfiguration(
-      "BuildDirectory",
-      cmSystemTools::CollapseFullPath(this->Values[ct_BUILD]).c_str(),
+      "BuildDirectory", cmSystemTools::CollapseFullPath(this->Build).c_str(),
       this->Quiet);
   } else {
     std::string const& bdir =
@@ -163,13 +146,11 @@ bool cmCTestHandlerCommand::InitialPass(std::vector<std::string> const& args,
                  "CTEST_BINARY_DIRECTORY not set" << std::endl;);
     }
   }
-  if (this->Values[ct_SOURCE]) {
+  if (!this->Source.empty()) {
     cmCTestLog(this->CTest, DEBUG,
-               "Set source directory to: " << this->Values[ct_SOURCE]
-                                           << std::endl);
+               "Set source directory to: " << this->Source << std::endl);
     this->CTest->SetCTestConfiguration(
-      "SourceDirectory",
-      cmSystemTools::CollapseFullPath(this->Values[ct_SOURCE]).c_str(),
+      "SourceDirectory", cmSystemTools::CollapseFullPath(this->Source).c_str(),
       this->Quiet);
   } else {
     this->CTest->SetCTestConfiguration(
@@ -192,8 +173,7 @@ bool cmCTestHandlerCommand::InitialPass(std::vector<std::string> const& args,
                "Cannot instantiate test handler " << this->GetName()
                                                   << std::endl);
     if (captureCMakeError) {
-      this->Makefile->AddDefinition(this->Values[ct_CAPTURE_CMAKE_ERROR],
-                                    "-1");
+      this->Makefile->AddDefinition(this->CaptureCMakeError, "-1");
       std::string const& err = status.GetError();
       if (!cmSystemTools::FindLastString(err.c_str(), "unknown error.")) {
         cmCTestLog(this->CTest, ERROR_MESSAGE, err << " error from command\n");
@@ -203,11 +183,11 @@ bool cmCTestHandlerCommand::InitialPass(std::vector<std::string> const& args,
     return false;
   }
 
-  handler->SetAppendXML(this->AppendXML);
+  handler->SetAppendXML(this->Append);
 
   handler->PopulateCustomVectors(this->Makefile);
-  if (this->Values[ct_SUBMIT_INDEX]) {
-    handler->SetSubmitIndex(atoi(this->Values[ct_SUBMIT_INDEX]));
+  if (!this->SubmitIndex.empty()) {
+    handler->SetSubmitIndex(atoi(this->SubmitIndex.c_str()));
   }
   cmWorkingDirectory workdir(
     this->CTest->GetCTestConfiguration("BuildDirectory"));
@@ -216,8 +196,7 @@ bool cmCTestHandlerCommand::InitialPass(std::vector<std::string> const& args,
                    this->CTest->GetCTestConfiguration("BuildDirectory") +
                    " : " + std::strerror(workdir.GetLastResult()));
     if (captureCMakeError) {
-      this->Makefile->AddDefinition(this->Values[ct_CAPTURE_CMAKE_ERROR],
-                                    "-1");
+      this->Makefile->AddDefinition(this->CaptureCMakeError, "-1");
       cmCTestLog(this->CTest, ERROR_MESSAGE,
                  this->GetName() << " " << status.GetError() << "\n");
       // return success because failure is recorded in CAPTURE_CMAKE_ERROR
@@ -227,9 +206,8 @@ bool cmCTestHandlerCommand::InitialPass(std::vector<std::string> const& args,
   }
 
   int res = handler->ProcessHandler();
-  if (this->Values[ct_RETURN_VALUE] && *this->Values[ct_RETURN_VALUE]) {
-    this->Makefile->AddDefinition(this->Values[ct_RETURN_VALUE],
-                                  std::to_string(res));
+  if (!this->ReturnValue.empty()) {
+    this->Makefile->AddDefinition(this->ReturnValue, std::to_string(res));
   }
   this->ProcessAdditionalValues(handler);
   // log the error message if there was an error
@@ -245,8 +223,7 @@ bool cmCTestHandlerCommand::InitialPass(std::vector<std::string> const& args,
       }
     }
     // store the captured cmake error state 0 or -1
-    this->Makefile->AddDefinition(this->Values[ct_CAPTURE_CMAKE_ERROR],
-                                  returnString);
+    this->Makefile->AddDefinition(this->CaptureCMakeError, returnString);
   }
   return true;
 }
@@ -255,47 +232,17 @@ void cmCTestHandlerCommand::ProcessAdditionalValues(cmCTestGenericHandler*)
 {
 }
 
-bool cmCTestHandlerCommand::CheckArgumentKeyword(std::string const& arg)
+void cmCTestHandlerCommand::BindArguments()
 {
-  // Look for non-value arguments common to all commands.
-  if (arg == "APPEND") {
-    this->ArgumentDoing = ArgumentDoingNone;
-    this->AppendXML = true;
-    return true;
-  }
-  if (arg == "QUIET") {
-    this->ArgumentDoing = ArgumentDoingNone;
-    this->Quiet = true;
-    return true;
-  }
-
-  // Check for a keyword in our argument/value table.
-  for (unsigned int k = 0; k < this->Arguments.size(); ++k) {
-    if (this->Arguments[k] && arg == this->Arguments[k]) {
-      this->ArgumentDoing = ArgumentDoingKeyword;
-      this->ArgumentIndex = k;
-      return true;
-    }
-  }
-  return false;
+  this->Bind("APPEND"_s, this->Append);
+  this->Bind("QUIET"_s, this->Quiet);
+  this->Bind("RETURN_VALUE"_s, this->ReturnValue);
+  this->Bind("CAPTURE_CMAKE_ERROR"_s, this->CaptureCMakeError);
+  this->Bind("SOURCE"_s, this->Source);
+  this->Bind("BUILD"_s, this->Build);
+  this->Bind("SUBMIT_INDEX"_s, this->SubmitIndex);
 }
 
-bool cmCTestHandlerCommand::CheckArgumentValue(std::string const& arg)
+void cmCTestHandlerCommand::CheckArguments(std::vector<std::string> const&)
 {
-  if (this->ArgumentDoing == ArgumentDoingKeyword) {
-    this->ArgumentDoing = ArgumentDoingNone;
-    unsigned int k = this->ArgumentIndex;
-    if (this->Values[k]) {
-      std::ostringstream e;
-      e << "Called with more than one value for " << this->Arguments[k];
-      this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
-      this->ArgumentDoing = ArgumentDoingError;
-      return true;
-    }
-    this->Values[k] = arg.c_str();
-    cmCTestLog(this->CTest, DEBUG,
-               "Set " << this->Arguments[k] << " to " << arg << "\n");
-    return true;
-  }
-  return false;
 }
diff --git a/Source/CTest/cmCTestHandlerCommand.h b/Source/CTest/cmCTestHandlerCommand.h
index 79d61f3..5bbc569 100644
--- a/Source/CTest/cmCTestHandlerCommand.h
+++ b/Source/CTest/cmCTestHandlerCommand.h
@@ -5,9 +5,9 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
+#include "cmArgumentParser.h"
 #include "cmCTestCommand.h"
 
-#include <stddef.h>
 #include <string>
 #include <vector>
 
@@ -19,11 +19,11 @@ class cmExecutionStatus;
  *
  * cmCTestHandlerCommand defineds the command to test the project.
  */
-class cmCTestHandlerCommand : public cmCTestCommand
+class cmCTestHandlerCommand
+  : public cmCTestCommand
+  , public cmArgumentParser<void>
 {
 public:
-  cmCTestHandlerCommand();
-
   /**
    * The name of the command as specified in CMakeList.txt.
    */
@@ -36,42 +36,22 @@ public:
   bool InitialPass(std::vector<std::string> const& args,
                    cmExecutionStatus& status) override;
 
-  enum
-  {
-    ct_NONE,
-    ct_RETURN_VALUE,
-    ct_CAPTURE_CMAKE_ERROR,
-    ct_BUILD,
-    ct_SOURCE,
-    ct_SUBMIT_INDEX,
-    ct_LAST
-  };
-
 protected:
   virtual cmCTestGenericHandler* InitializeHandler() = 0;
 
   virtual void ProcessAdditionalValues(cmCTestGenericHandler* handler);
 
   // Command argument handling.
-  virtual bool CheckArgumentKeyword(std::string const& arg);
-  virtual bool CheckArgumentValue(std::string const& arg);
-  enum
-  {
-    ArgumentDoingNone,
-    ArgumentDoingError,
-    ArgumentDoingKeyword,
-    ArgumentDoingLast1
-  };
-  int ArgumentDoing;
-  unsigned int ArgumentIndex;
-
-  bool AppendXML;
-  bool Quiet;
-
-  std::string ReturnVariable;
-  std::vector<const char*> Arguments;
-  std::vector<const char*> Values;
-  size_t Last;
+  virtual void BindArguments();
+  virtual void CheckArguments(std::vector<std::string> const& keywords);
+
+  bool Append = false;
+  bool Quiet = false;
+  std::string CaptureCMakeError;
+  std::string ReturnValue;
+  std::string Build;
+  std::string Source;
+  std::string SubmitIndex;
 };
 
 #define CTEST_COMMAND_APPEND_OPTION_DOCS                                      \
diff --git a/Source/CTest/cmCTestMemCheckCommand.cxx b/Source/CTest/cmCTestMemCheckCommand.cxx
index 804efa5..abad5fc 100644
--- a/Source/CTest/cmCTestMemCheckCommand.cxx
+++ b/Source/CTest/cmCTestMemCheckCommand.cxx
@@ -2,18 +2,15 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmCTestMemCheckCommand.h"
 
-#include <string>
-#include <vector>
-
 #include "cmCTest.h"
 #include "cmCTestMemCheckHandler.h"
 #include "cmMakefile.h"
+#include "cm_static_string_view.hxx"
 
-cmCTestMemCheckCommand::cmCTestMemCheckCommand()
+void cmCTestMemCheckCommand::BindArguments()
 {
-  this->Arguments[ctm_DEFECT_COUNT] = "DEFECT_COUNT";
-  this->Arguments[ctm_LAST] = nullptr;
-  this->Last = ctm_LAST;
+  this->cmCTestTestCommand::BindArguments();
+  this->Bind("DEFECT_COUNT"_s, this->DefectCount);
 }
 
 cmCTestGenericHandler* cmCTestMemCheckCommand::InitializeActualHandler()
@@ -43,9 +40,9 @@ cmCTestGenericHandler* cmCTestMemCheckCommand::InitializeActualHandler()
 void cmCTestMemCheckCommand::ProcessAdditionalValues(
   cmCTestGenericHandler* handler)
 {
-  if (this->Values[ctm_DEFECT_COUNT] && *this->Values[ctm_DEFECT_COUNT]) {
+  if (!this->DefectCount.empty()) {
     this->Makefile->AddDefinition(
-      this->Values[ctm_DEFECT_COUNT],
+      this->DefectCount,
       std::to_string(
         static_cast<cmCTestMemCheckHandler*>(handler)->GetDefectCount()));
   }
diff --git a/Source/CTest/cmCTestMemCheckCommand.h b/Source/CTest/cmCTestMemCheckCommand.h
index 837a687..200cc2e 100644
--- a/Source/CTest/cmCTestMemCheckCommand.h
+++ b/Source/CTest/cmCTestMemCheckCommand.h
@@ -5,6 +5,7 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
+#include <string>
 #include <utility>
 
 #include "cm_memory.hxx"
@@ -22,8 +23,6 @@ class cmCTestGenericHandler;
 class cmCTestMemCheckCommand : public cmCTestTestCommand
 {
 public:
-  cmCTestMemCheckCommand();
-
   /**
    * This is a virtual constructor for the command.
    */
@@ -36,15 +35,13 @@ public:
   }
 
 protected:
+  void BindArguments() override;
+
   cmCTestGenericHandler* InitializeActualHandler() override;
 
   void ProcessAdditionalValues(cmCTestGenericHandler* handler) override;
 
-  enum
-  {
-    ctm_DEFECT_COUNT = ctt_LAST,
-    ctm_LAST
-  };
+  std::string DefectCount;
 };
 
 #endif
diff --git a/Source/CTest/cmCTestSubmitCommand.cxx b/Source/CTest/cmCTestSubmitCommand.cxx
index d16aac0..2368276 100644
--- a/Source/CTest/cmCTestSubmitCommand.cxx
+++ b/Source/CTest/cmCTestSubmitCommand.cxx
@@ -2,14 +2,18 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmCTestSubmitCommand.h"
 
+#include "cmAlgorithms.h"
 #include "cmCTest.h"
 #include "cmCTestSubmitHandler.h"
 #include "cmCommand.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
+#include "cmRange.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
+#include "cm_static_string_view.hxx"
 
+#include <set>
 #include <sstream>
 #include <utility>
 
@@ -17,18 +21,6 @@
 
 class cmExecutionStatus;
 
-cmCTestSubmitCommand::cmCTestSubmitCommand()
-{
-  this->PartsMentioned = false;
-  this->FilesMentioned = false;
-  this->InternalTest = false;
-  this->RetryCount = "";
-  this->RetryDelay = "";
-  this->CDashUpload = false;
-  this->Arguments[cts_BUILD_ID] = "BUILD_ID";
-  this->Last = cts_LAST;
-}
-
 /**
  * This is a virtual constructor for the command.
  */
@@ -106,13 +98,18 @@ cmCTestGenericHandler* cmCTestSubmitCommand::InitializeHandler()
     // without any of the default parts.
     //
     handler->SelectParts(std::set<cmCTest::Part>());
-    handler->SelectFiles(this->Files);
+    handler->SelectFiles(
+      std::set<std::string>(this->Files.begin(), this->Files.end()));
   }
 
   // If a PARTS option was given, select only the named parts for submission.
   //
   if (this->PartsMentioned) {
-    handler->SelectParts(this->Parts);
+    auto parts =
+      cmMakeRange(this->Parts).transform([this](std::string const& arg) {
+        return this->CTest->GetPartFromName(arg.c_str());
+      });
+    handler->SelectParts(std::set<cmCTest::Part>(parts.begin(), parts.end()));
   }
 
   // Pass along any HTTPHEADER to the handler if this option was given.
@@ -140,133 +137,61 @@ bool cmCTestSubmitCommand::InitialPass(std::vector<std::string> const& args,
 
   bool ret = this->cmCTestHandlerCommand::InitialPass(args, status);
 
-  if (this->Values[cts_BUILD_ID] && *this->Values[cts_BUILD_ID]) {
-    this->Makefile->AddDefinition(this->Values[cts_BUILD_ID],
-                                  this->CTest->GetBuildID());
+  if (!this->BuildID.empty()) {
+    this->Makefile->AddDefinition(this->BuildID, this->CTest->GetBuildID());
   }
 
   return ret;
 }
 
-bool cmCTestSubmitCommand::CheckArgumentKeyword(std::string const& arg)
+void cmCTestSubmitCommand::BindArguments()
 {
   if (this->CDashUpload) {
     // Arguments specific to the CDASH_UPLOAD signature.
-    if (arg == "CDASH_UPLOAD") {
-      this->ArgumentDoing = ArgumentDoingCDashUpload;
-      return true;
-    }
-
-    if (arg == "CDASH_UPLOAD_TYPE") {
-      this->ArgumentDoing = ArgumentDoingCDashUploadType;
-      return true;
-    }
+    this->Bind("CDASH_UPLOAD", this->CDashUploadFile);
+    this->Bind("CDASH_UPLOAD_TYPE", this->CDashUploadType);
   } else {
     // Arguments that cannot be used with CDASH_UPLOAD.
-    if (arg == "PARTS") {
-      this->ArgumentDoing = ArgumentDoingParts;
-      this->PartsMentioned = true;
-      return true;
-    }
-
-    if (arg == "FILES") {
-      this->ArgumentDoing = ArgumentDoingFiles;
-      this->FilesMentioned = true;
-      return true;
-    }
+    this->Bind("PARTS"_s, this->Parts);
+    this->Bind("FILES"_s, this->Files);
   }
   // Arguments used by both modes.
-  if (arg == "HTTPHEADER") {
-    this->ArgumentDoing = ArgumentDoingHttpHeader;
-    return true;
-  }
-
-  if (arg == "RETRY_COUNT") {
-    this->ArgumentDoing = ArgumentDoingRetryCount;
-    return true;
-  }
-
-  if (arg == "RETRY_DELAY") {
-    this->ArgumentDoing = ArgumentDoingRetryDelay;
-    return true;
-  }
-
-  if (arg == "SUBMIT_URL") {
-    this->ArgumentDoing = ArgumentDoingSubmitURL;
-    return true;
-  }
-
-  if (arg == "INTERNAL_TEST_CHECKSUM") {
-    this->InternalTest = true;
-    return true;
-  }
+  this->Bind("BUILD_ID"_s, this->BuildID);
+  this->Bind("HTTPHEADER"_s, this->HttpHeaders);
+  this->Bind("RETRY_COUNT"_s, this->RetryCount);
+  this->Bind("RETRY_DELAY"_s, this->RetryDelay);
+  this->Bind("SUBMIT_URL"_s, this->SubmitURL);
+  this->Bind("INTERNAL_TEST_CHECKSUM", this->InternalTest);
 
   // Look for other arguments.
-  return this->Superclass::CheckArgumentKeyword(arg);
+  this->cmCTestHandlerCommand::BindArguments();
 }
 
-bool cmCTestSubmitCommand::CheckArgumentValue(std::string const& arg)
+void cmCTestSubmitCommand::CheckArguments(
+  std::vector<std::string> const& keywords)
 {
-  // Handle states specific to this command.
-  if (this->ArgumentDoing == ArgumentDoingParts) {
+  this->PartsMentioned = !this->Parts.empty() || cmContains(keywords, "PARTS");
+  this->FilesMentioned = !this->Files.empty() || cmContains(keywords, "FILES");
+
+  cmEraseIf(this->Parts, [this](std::string const& arg) -> bool {
     cmCTest::Part p = this->CTest->GetPartFromName(arg.c_str());
-    if (p != cmCTest::PartCount) {
-      this->Parts.insert(p);
-    } else {
+    if (p == cmCTest::PartCount) {
       std::ostringstream e;
       e << "Part name \"" << arg << "\" is invalid.";
       this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
-      this->ArgumentDoing = ArgumentDoingError;
+      return true;
     }
-    return true;
-  }
+    return false;
+  });
 
-  if (this->ArgumentDoing == ArgumentDoingFiles) {
-    if (cmSystemTools::FileExists(arg)) {
-      this->Files.insert(arg);
-    } else {
+  cmEraseIf(this->Files, [this](std::string const& arg) -> bool {
+    if (!cmSystemTools::FileExists(arg)) {
       std::ostringstream e;
       e << "File \"" << arg << "\" does not exist. Cannot submit "
         << "a non-existent file.";
       this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
-      this->ArgumentDoing = ArgumentDoingError;
+      return true;
     }
-    return true;
-  }
-
-  if (this->ArgumentDoing == ArgumentDoingHttpHeader) {
-    this->HttpHeaders.push_back(arg);
-    return true;
-  }
-
-  if (this->ArgumentDoing == ArgumentDoingRetryCount) {
-    this->RetryCount = arg;
-    return true;
-  }
-
-  if (this->ArgumentDoing == ArgumentDoingRetryDelay) {
-    this->RetryDelay = arg;
-    return true;
-  }
-
-  if (this->ArgumentDoing == ArgumentDoingCDashUpload) {
-    this->ArgumentDoing = ArgumentDoingNone;
-    this->CDashUploadFile = arg;
-    return true;
-  }
-
-  if (this->ArgumentDoing == ArgumentDoingCDashUploadType) {
-    this->ArgumentDoing = ArgumentDoingNone;
-    this->CDashUploadType = arg;
-    return true;
-  }
-
-  if (this->ArgumentDoing == ArgumentDoingSubmitURL) {
-    this->ArgumentDoing = ArgumentDoingNone;
-    this->SubmitURL = arg;
-    return true;
-  }
-
-  // Look for other arguments.
-  return this->Superclass::CheckArgumentValue(arg);
+    return false;
+  });
 }
diff --git a/Source/CTest/cmCTestSubmitCommand.h b/Source/CTest/cmCTestSubmitCommand.h
index 8562207..249f844 100644
--- a/Source/CTest/cmCTestSubmitCommand.h
+++ b/Source/CTest/cmCTestSubmitCommand.h
@@ -5,11 +5,9 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
-#include "cmCTest.h"
 #include "cmCTestHandlerCommand.h"
 
 #include <memory>
-#include <set>
 #include <string>
 #include <vector>
 
@@ -26,7 +24,6 @@ class cmExecutionStatus;
 class cmCTestSubmitCommand : public cmCTestHandlerCommand
 {
 public:
-  cmCTestSubmitCommand();
   std::unique_ptr<cmCommand> Clone() override;
 
   bool InitialPass(std::vector<std::string> const& args,
@@ -37,45 +34,26 @@ public:
    */
   std::string GetName() const override { return "ctest_submit"; }
 
-  using Superclass = cmCTestHandlerCommand;
-
 protected:
+  void BindArguments() override;
+  void CheckArguments(std::vector<std::string> const& keywords) override;
   cmCTestGenericHandler* InitializeHandler() override;
 
-  bool CheckArgumentKeyword(std::string const& arg) override;
-  bool CheckArgumentValue(std::string const& arg) override;
-
-  enum
-  {
-    ArgumentDoingParts = Superclass::ArgumentDoingLast1,
-    ArgumentDoingFiles,
-    ArgumentDoingRetryDelay,
-    ArgumentDoingRetryCount,
-    ArgumentDoingCDashUpload,
-    ArgumentDoingCDashUploadType,
-    ArgumentDoingHttpHeader,
-    ArgumentDoingSubmitURL,
-    ArgumentDoingLast2
-  };
-
-  enum
-  {
-    cts_BUILD_ID = ct_LAST,
-    cts_LAST
-  };
+  bool CDashUpload = false;
+  bool FilesMentioned = false;
+  bool InternalTest = false;
+  bool PartsMentioned = false;
 
-  bool PartsMentioned;
-  std::set<cmCTest::Part> Parts;
-  bool FilesMentioned;
-  bool InternalTest;
-  std::set<std::string> Files;
-  std::string RetryCount;
-  std::string RetryDelay;
-  bool CDashUpload;
+  std::string BuildID;
   std::string CDashUploadFile;
   std::string CDashUploadType;
-  std::vector<std::string> HttpHeaders;
+  std::string RetryCount;
+  std::string RetryDelay;
   std::string SubmitURL;
+
+  std::vector<std::string> Files;
+  std::vector<std::string> HttpHeaders;
+  std::vector<std::string> Parts;
 };
 
 #endif
diff --git a/Source/CTest/cmCTestTestCommand.cxx b/Source/CTest/cmCTestTestCommand.cxx
index 3a29ad3..c277db8 100644
--- a/Source/CTest/cmCTestTestCommand.cxx
+++ b/Source/CTest/cmCTestTestCommand.cxx
@@ -8,30 +8,29 @@
 #include "cmDuration.h"
 #include "cmMakefile.h"
 #include "cmStringAlgorithms.h"
+#include "cm_static_string_view.hxx"
 
 #include <chrono>
 #include <cstdlib>
 #include <sstream>
-#include <vector>
 
-cmCTestTestCommand::cmCTestTestCommand()
+void cmCTestTestCommand::BindArguments()
 {
-  this->Arguments[ctt_START] = "START";
-  this->Arguments[ctt_END] = "END";
-  this->Arguments[ctt_STRIDE] = "STRIDE";
-  this->Arguments[ctt_EXCLUDE] = "EXCLUDE";
-  this->Arguments[ctt_INCLUDE] = "INCLUDE";
-  this->Arguments[ctt_EXCLUDE_LABEL] = "EXCLUDE_LABEL";
-  this->Arguments[ctt_INCLUDE_LABEL] = "INCLUDE_LABEL";
-  this->Arguments[ctt_EXCLUDE_FIXTURE] = "EXCLUDE_FIXTURE";
-  this->Arguments[ctt_EXCLUDE_FIXTURE_SETUP] = "EXCLUDE_FIXTURE_SETUP";
-  this->Arguments[ctt_EXCLUDE_FIXTURE_CLEANUP] = "EXCLUDE_FIXTURE_CLEANUP";
-  this->Arguments[ctt_PARALLEL_LEVEL] = "PARALLEL_LEVEL";
-  this->Arguments[ctt_SCHEDULE_RANDOM] = "SCHEDULE_RANDOM";
-  this->Arguments[ctt_STOP_TIME] = "STOP_TIME";
-  this->Arguments[ctt_TEST_LOAD] = "TEST_LOAD";
-  this->Arguments[ctt_LAST] = nullptr;
-  this->Last = ctt_LAST;
+  this->cmCTestHandlerCommand::BindArguments();
+  this->Bind("START"_s, this->Start);
+  this->Bind("END"_s, this->End);
+  this->Bind("STRIDE"_s, this->Stride);
+  this->Bind("EXCLUDE"_s, this->Exclude);
+  this->Bind("INCLUDE"_s, this->Include);
+  this->Bind("EXCLUDE_LABEL"_s, this->ExcludeLabel);
+  this->Bind("INCLUDE_LABEL"_s, this->IncludeLabel);
+  this->Bind("EXCLUDE_FIXTURE"_s, this->ExcludeFixture);
+  this->Bind("EXCLUDE_FIXTURE_SETUP"_s, this->ExcludeFixtureSetup);
+  this->Bind("EXCLUDE_FIXTURE_CLEANUP"_s, this->ExcludeFixtureCleanup);
+  this->Bind("PARALLEL_LEVEL"_s, this->ParallelLevel);
+  this->Bind("SCHEDULE_RANDOM"_s, this->ScheduleRandom);
+  this->Bind("STOP_TIME"_s, this->StopTime);
+  this->Bind("TEST_LOAD"_s, this->TestLoad);
 }
 
 cmCTestGenericHandler* cmCTestTestCommand::InitializeHandler()
@@ -51,57 +50,44 @@ cmCTestGenericHandler* cmCTestTestCommand::InitializeHandler()
   }
   this->CTest->SetTimeOut(timeout);
   cmCTestGenericHandler* handler = this->InitializeActualHandler();
-  if (this->Values[ctt_START] || this->Values[ctt_END] ||
-      this->Values[ctt_STRIDE]) {
-    std::ostringstream testsToRunString;
-    if (this->Values[ctt_START]) {
-      testsToRunString << this->Values[ctt_START];
-    }
-    testsToRunString << ",";
-    if (this->Values[ctt_END]) {
-      testsToRunString << this->Values[ctt_END];
-    }
-    testsToRunString << ",";
-    if (this->Values[ctt_STRIDE]) {
-      testsToRunString << this->Values[ctt_STRIDE];
-    }
-    handler->SetOption("TestsToRunInformation",
-                       testsToRunString.str().c_str());
+  if (!this->Start.empty() || !this->End.empty() || !this->Stride.empty()) {
+    handler->SetOption(
+      "TestsToRunInformation",
+      cmStrCat(this->Start, ',', this->End, ',', this->Stride).c_str());
   }
-  if (this->Values[ctt_EXCLUDE]) {
-    handler->SetOption("ExcludeRegularExpression", this->Values[ctt_EXCLUDE]);
+  if (!this->Exclude.empty()) {
+    handler->SetOption("ExcludeRegularExpression", this->Exclude.c_str());
   }
-  if (this->Values[ctt_INCLUDE]) {
-    handler->SetOption("IncludeRegularExpression", this->Values[ctt_INCLUDE]);
+  if (!this->Include.empty()) {
+    handler->SetOption("IncludeRegularExpression", this->Include.c_str());
   }
-  if (this->Values[ctt_EXCLUDE_LABEL]) {
+  if (!this->ExcludeLabel.empty()) {
     handler->SetOption("ExcludeLabelRegularExpression",
-                       this->Values[ctt_EXCLUDE_LABEL]);
+                       this->ExcludeLabel.c_str());
   }
-  if (this->Values[ctt_INCLUDE_LABEL]) {
-    handler->SetOption("LabelRegularExpression",
-                       this->Values[ctt_INCLUDE_LABEL]);
+  if (!this->IncludeLabel.empty()) {
+    handler->SetOption("LabelRegularExpression", this->IncludeLabel.c_str());
   }
-  if (this->Values[ctt_EXCLUDE_FIXTURE]) {
+  if (!this->ExcludeFixture.empty()) {
     handler->SetOption("ExcludeFixtureRegularExpression",
-                       this->Values[ctt_EXCLUDE_FIXTURE]);
+                       this->ExcludeFixture.c_str());
   }
-  if (this->Values[ctt_EXCLUDE_FIXTURE_SETUP]) {
+  if (!this->ExcludeFixtureSetup.empty()) {
     handler->SetOption("ExcludeFixtureSetupRegularExpression",
-                       this->Values[ctt_EXCLUDE_FIXTURE_SETUP]);
+                       this->ExcludeFixtureSetup.c_str());
   }
-  if (this->Values[ctt_EXCLUDE_FIXTURE_CLEANUP]) {
+  if (!this->ExcludeFixtureCleanup.empty()) {
     handler->SetOption("ExcludeFixtureCleanupRegularExpression",
-                       this->Values[ctt_EXCLUDE_FIXTURE_CLEANUP]);
+                       this->ExcludeFixtureCleanup.c_str());
   }
-  if (this->Values[ctt_PARALLEL_LEVEL]) {
-    handler->SetOption("ParallelLevel", this->Values[ctt_PARALLEL_LEVEL]);
+  if (!this->ParallelLevel.empty()) {
+    handler->SetOption("ParallelLevel", this->ParallelLevel.c_str());
   }
-  if (this->Values[ctt_SCHEDULE_RANDOM]) {
-    handler->SetOption("ScheduleRandom", this->Values[ctt_SCHEDULE_RANDOM]);
+  if (!this->ScheduleRandom.empty()) {
+    handler->SetOption("ScheduleRandom", this->ScheduleRandom.c_str());
   }
-  if (this->Values[ctt_STOP_TIME]) {
-    this->CTest->SetStopTime(this->Values[ctt_STOP_TIME]);
+  if (!this->StopTime.empty()) {
+    this->CTest->SetStopTime(this->StopTime);
   }
 
   // Test load is determined by: TEST_LOAD argument,
@@ -109,12 +95,12 @@ cmCTestGenericHandler* cmCTestTestCommand::InitializeHandler()
   // command line argument... in that order.
   unsigned long testLoad;
   const char* ctestTestLoad = this->Makefile->GetDefinition("CTEST_TEST_LOAD");
-  if (this->Values[ctt_TEST_LOAD] && *this->Values[ctt_TEST_LOAD]) {
-    if (!cmStrToULong(this->Values[ctt_TEST_LOAD], &testLoad)) {
+  if (!this->TestLoad.empty()) {
+    if (!cmStrToULong(this->TestLoad.c_str(), &testLoad)) {
       testLoad = 0;
       cmCTestLog(this->CTest, WARNING,
-                 "Invalid value for 'TEST_LOAD' : "
-                   << this->Values[ctt_TEST_LOAD] << std::endl);
+                 "Invalid value for 'TEST_LOAD' : " << this->TestLoad
+                                                    << std::endl);
     }
   } else if (ctestTestLoad && *ctestTestLoad) {
     if (!cmStrToULong(ctestTestLoad, &testLoad)) {
diff --git a/Source/CTest/cmCTestTestCommand.h b/Source/CTest/cmCTestTestCommand.h
index d74136c..3d9b63e 100644
--- a/Source/CTest/cmCTestTestCommand.h
+++ b/Source/CTest/cmCTestTestCommand.h
@@ -23,8 +23,6 @@ class cmCTestGenericHandler;
 class cmCTestTestCommand : public cmCTestHandlerCommand
 {
 public:
-  cmCTestTestCommand();
-
   /**
    * This is a virtual constructor for the command.
    */
@@ -42,29 +40,24 @@ public:
   std::string GetName() const override { return "ctest_test"; }
 
 protected:
+  void BindArguments() override;
   virtual cmCTestGenericHandler* InitializeActualHandler();
   cmCTestGenericHandler* InitializeHandler() override;
 
-  enum
-  {
-    ctt_BUILD = ct_LAST,
-    ctt_RETURN_VALUE,
-    ctt_START,
-    ctt_END,
-    ctt_STRIDE,
-    ctt_EXCLUDE,
-    ctt_INCLUDE,
-    ctt_EXCLUDE_LABEL,
-    ctt_INCLUDE_LABEL,
-    ctt_EXCLUDE_FIXTURE,
-    ctt_EXCLUDE_FIXTURE_SETUP,
-    ctt_EXCLUDE_FIXTURE_CLEANUP,
-    ctt_PARALLEL_LEVEL,
-    ctt_SCHEDULE_RANDOM,
-    ctt_STOP_TIME,
-    ctt_TEST_LOAD,
-    ctt_LAST
-  };
+  std::string Start;
+  std::string End;
+  std::string Stride;
+  std::string Exclude;
+  std::string Include;
+  std::string ExcludeLabel;
+  std::string IncludeLabel;
+  std::string ExcludeFixture;
+  std::string ExcludeFixtureSetup;
+  std::string ExcludeFixtureCleanup;
+  std::string ParallelLevel;
+  std::string ScheduleRandom;
+  std::string StopTime;
+  std::string TestLoad;
 };
 
 #endif
diff --git a/Source/CTest/cmCTestUpdateCommand.cxx b/Source/CTest/cmCTestUpdateCommand.cxx
index 65dc921..673eb9a 100644
--- a/Source/CTest/cmCTestUpdateCommand.cxx
+++ b/Source/CTest/cmCTestUpdateCommand.cxx
@@ -7,14 +7,11 @@
 #include "cmMakefile.h"
 #include "cmSystemTools.h"
 
-#include <vector>
-
 cmCTestGenericHandler* cmCTestUpdateCommand::InitializeHandler()
 {
-  if (this->Values[ct_SOURCE]) {
+  if (!this->Source.empty()) {
     this->CTest->SetCTestConfiguration(
-      "SourceDirectory",
-      cmSystemTools::CollapseFullPath(this->Values[ct_SOURCE]).c_str(),
+      "SourceDirectory", cmSystemTools::CollapseFullPath(this->Source).c_str(),
       this->Quiet);
   } else {
     this->CTest->SetCTestConfiguration(
diff --git a/Source/CTest/cmCTestUpdateCommand.h b/Source/CTest/cmCTestUpdateCommand.h
index 55c4b80..35db88f 100644
--- a/Source/CTest/cmCTestUpdateCommand.h
+++ b/Source/CTest/cmCTestUpdateCommand.h
@@ -23,8 +23,6 @@ class cmCTestGenericHandler;
 class cmCTestUpdateCommand : public cmCTestHandlerCommand
 {
 public:
-  cmCTestUpdateCommand() {}
-
   /**
    * This is a virtual constructor for the command.
    */
diff --git a/Source/CTest/cmCTestUploadCommand.cxx b/Source/CTest/cmCTestUploadCommand.cxx
index 59fbf37..9180821 100644
--- a/Source/CTest/cmCTestUploadCommand.cxx
+++ b/Source/CTest/cmCTestUploadCommand.cxx
@@ -2,61 +2,45 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmCTestUploadCommand.h"
 
+#include <set>
 #include <sstream>
 #include <vector>
 
+#include "cmAlgorithms.h"
 #include "cmCTest.h"
 #include "cmCTestUploadHandler.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
 #include "cmSystemTools.h"
+#include "cm_static_string_view.hxx"
 
-cmCTestGenericHandler* cmCTestUploadCommand::InitializeHandler()
-{
-  cmCTestUploadHandler* handler = this->CTest->GetUploadHandler();
-  handler->Initialize();
-  handler->SetFiles(this->Files);
-  handler->SetQuiet(this->Quiet);
-  return handler;
-}
-
-bool cmCTestUploadCommand::CheckArgumentKeyword(std::string const& arg)
+void cmCTestUploadCommand::BindArguments()
 {
-  if (arg == "FILES") {
-    this->ArgumentDoing = ArgumentDoingFiles;
-    return true;
-  }
-  if (arg == "QUIET") {
-    this->ArgumentDoing = ArgumentDoingNone;
-    this->Quiet = true;
-    return true;
-  }
-  if (arg == "CAPTURE_CMAKE_ERROR") {
-    this->ArgumentDoing = ArgumentDoingCaptureCMakeError;
-    return true;
-  }
-  return false;
+  this->Bind("FILES"_s, this->Files);
+  this->Bind("QUIET"_s, this->Quiet);
+  this->Bind("CAPTURE_CMAKE_ERROR"_s, this->CaptureCMakeError);
 }
 
-bool cmCTestUploadCommand::CheckArgumentValue(std::string const& arg)
+void cmCTestUploadCommand::CheckArguments(std::vector<std::string> const&)
 {
-  if (this->ArgumentDoing == ArgumentDoingCaptureCMakeError) {
-    this->Values[ct_CAPTURE_CMAKE_ERROR] = arg.c_str();
-    return true;
-  }
-  if (this->ArgumentDoing == ArgumentDoingFiles) {
-    if (cmSystemTools::FileExists(arg)) {
-      this->Files.insert(arg);
+  cmEraseIf(this->Files, [this](std::string const& arg) -> bool {
+    if (!cmSystemTools::FileExists(arg)) {
+      std::ostringstream e;
+      e << "File \"" << arg << "\" does not exist. Cannot submit "
+        << "a non-existent file.";
+      this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
       return true;
     }
-    std::ostringstream e;
-    e << "File \"" << arg << "\" does not exist. Cannot submit "
-      << "a non-existent file.";
-    this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
-    this->ArgumentDoing = ArgumentDoingError;
     return false;
-  }
+  });
+}
 
-  // Look for other arguments.
-  return this->Superclass::CheckArgumentValue(arg);
+cmCTestGenericHandler* cmCTestUploadCommand::InitializeHandler()
+{
+  cmCTestUploadHandler* handler = this->CTest->GetUploadHandler();
+  handler->Initialize();
+  handler->SetFiles(
+    std::set<std::string>(this->Files.begin(), this->Files.end()));
+  handler->SetQuiet(this->Quiet);
+  return handler;
 }
diff --git a/Source/CTest/cmCTestUploadCommand.h b/Source/CTest/cmCTestUploadCommand.h
index 9e49769..a962497 100644
--- a/Source/CTest/cmCTestUploadCommand.h
+++ b/Source/CTest/cmCTestUploadCommand.h
@@ -8,9 +8,9 @@
 #include "cmCTestHandlerCommand.h"
 #include "cmCommand.h"
 
-#include <set>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "cm_memory.hxx"
 
@@ -41,22 +41,12 @@ public:
    */
   std::string GetName() const override { return "ctest_upload"; }
 
-  using Superclass = cmCTestHandlerCommand;
-
 protected:
+  void BindArguments() override;
+  void CheckArguments(std::vector<std::string> const&) override;
   cmCTestGenericHandler* InitializeHandler() override;
 
-  bool CheckArgumentKeyword(std::string const& arg) override;
-  bool CheckArgumentValue(std::string const& arg) override;
-
-  enum
-  {
-    ArgumentDoingFiles = Superclass::ArgumentDoingLast1,
-    ArgumentDoingCaptureCMakeError,
-    ArgumentDoingLast2
-  };
-
-  std::set<std::string> Files;
+  std::vector<std::string> Files;
 };
 
 #endif

https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=d5a6a133680e4d5798522a4b29ae6bced1f2db3a
commit d5a6a133680e4d5798522a4b29ae6bced1f2db3a
Author:     Regina Pfeifer <regina at mailbox.org>
AuthorDate: Fri Sep 20 13:27:02 2019 +0200
Commit:     Regina Pfeifer <regina at mailbox.org>
CommitDate: Fri Sep 20 13:27:30 2019 +0200

    cmArgumentParser: Record parsed keywords

diff --git a/Source/cmArgumentParser.cxx b/Source/cmArgumentParser.cxx
index 751d117..4c87177 100644
--- a/Source/cmArgumentParser.cxx
+++ b/Source/cmArgumentParser.cxx
@@ -61,10 +61,14 @@ void Instance::Bind(MultiStringList& val)
 
 void Instance::Consume(cm::string_view arg, void* result,
                        std::vector<std::string>* unparsedArguments,
-                       std::vector<std::string>* keywordsMissingValue)
+                       std::vector<std::string>* keywordsMissingValue,
+                       std::vector<std::string>* parsedKeywords)
 {
   auto const it = this->Bindings.Find(arg);
   if (it != this->Bindings.end()) {
+    if (parsedKeywords != nullptr) {
+      parsedKeywords->emplace_back(arg);
+    }
     it->second(*this, result);
     if (this->ExpectValue && keywordsMissingValue != nullptr) {
       keywordsMissingValue->emplace_back(arg);
diff --git a/Source/cmArgumentParser.h b/Source/cmArgumentParser.h
index 6cfe946..e1773b4 100644
--- a/Source/cmArgumentParser.h
+++ b/Source/cmArgumentParser.h
@@ -45,7 +45,8 @@ public:
 
   void Consume(cm::string_view arg, void* result,
                std::vector<std::string>* unparsedArguments,
-               std::vector<std::string>* keywordsMissingValue);
+               std::vector<std::string>* keywordsMissingValue,
+               std::vector<std::string>* parsedKeywords);
 
 private:
   ActionMap const& Bindings;
@@ -79,21 +80,25 @@ public:
   template <typename Range>
   void Parse(Result& result, Range const& args,
              std::vector<std::string>* unparsedArguments = nullptr,
-             std::vector<std::string>* keywordsMissingValue = nullptr) const
+             std::vector<std::string>* keywordsMissingValue = nullptr,
+             std::vector<std::string>* parsedKeywords = nullptr) const
   {
     ArgumentParser::Instance instance(this->Bindings);
     for (cm::string_view arg : args) {
-      instance.Consume(arg, &result, unparsedArguments, keywordsMissingValue);
+      instance.Consume(arg, &result, unparsedArguments, keywordsMissingValue,
+                       parsedKeywords);
     }
   }
 
   template <typename Range>
   Result Parse(Range const& args,
                std::vector<std::string>* unparsedArguments = nullptr,
-               std::vector<std::string>* keywordsMissingValue = nullptr) const
+               std::vector<std::string>* keywordsMissingValue = nullptr,
+               std::vector<std::string>* parsedKeywords = nullptr) const
   {
     Result result;
-    this->Parse(result, args, unparsedArguments, keywordsMissingValue);
+    this->Parse(result, args, unparsedArguments, keywordsMissingValue,
+                parsedKeywords);
     return result;
   }
 
@@ -116,11 +121,13 @@ public:
   template <typename Range>
   void Parse(Range const& args,
              std::vector<std::string>* unparsedArguments = nullptr,
-             std::vector<std::string>* keywordsMissingValue = nullptr) const
+             std::vector<std::string>* keywordsMissingValue = nullptr,
+             std::vector<std::string>* parsedKeywords = nullptr) const
   {
     ArgumentParser::Instance instance(this->Bindings);
     for (cm::string_view arg : args) {
-      instance.Consume(arg, nullptr, unparsedArguments, keywordsMissingValue);
+      instance.Consume(arg, nullptr, unparsedArguments, keywordsMissingValue,
+                       parsedKeywords);
     }
   }
 

https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=b2c67a7703836a0015a7e4bf3837dbb613061a74
commit b2c67a7703836a0015a7e4bf3837dbb613061a74
Author:     Alex Turbov <i.zaufi at gmail.com>
AuthorDate: Thu Jul 11 02:08:10 2019 +0300
Commit:     Alex Turbov <i.zaufi at gmail.com>
CommitDate: Sun Sep 8 20:29:36 2019 +0300

    Style: Remove spaces after command call and `(`

diff --git a/Modules/Internal/CPack/CPackDeb.cmake b/Modules/Internal/CPack/CPackDeb.cmake
index 7791822..51e505b 100644
--- a/Modules/Internal/CPack/CPackDeb.cmake
+++ b/Modules/Internal/CPack/CPackDeb.cmake
@@ -102,7 +102,7 @@ function(cpack_deb_prepare_package_vars)
         RESULT_VARIABLE FILE_RESULT_
         OUTPUT_VARIABLE INSTALL_FILE_)
       if(NOT FILE_RESULT_ EQUAL 0)
-        message (FATAL_ERROR "CPackDeb: execution of command: '${FILE_EXECUTABLE} ./${FILE_}' failed with exit code: ${FILE_RESULT_}")
+        message(FATAL_ERROR "CPackDeb: execution of command: '${FILE_EXECUTABLE} ./${FILE_}' failed with exit code: ${FILE_RESULT_}")
       endif()
       list(APPEND CPACK_DEB_INSTALL_FILES "${INSTALL_FILE_}")
     endforeach()
@@ -253,7 +253,7 @@ function(cpack_deb_prepare_package_vars)
           message( "CPackDeb Debug: dpkg-shlibdeps warnings \n${SHLIBDEPS_ERROR}")
         endif()
         if(NOT SHLIBDEPS_RESULT EQUAL 0)
-          message (FATAL_ERROR "CPackDeb: dpkg-shlibdeps: '${SHLIBDEPS_ERROR}';\n"
+          message(FATAL_ERROR "CPackDeb: dpkg-shlibdeps: '${SHLIBDEPS_ERROR}';\n"
               "executed command: '${SHLIBDEPS_EXECUTABLE} ${IGNORE_MISSING_INFO_FLAG} -O ${CPACK_DEB_BINARY_FILES}';\n"
               "found files: '${INSTALL_FILE_}';\n"
               "files info: '${CPACK_DEB_INSTALL_FILES}';\n"
@@ -403,7 +403,7 @@ function(cpack_deb_prepare_package_vars)
 
     if(CPACK_DEBIAN_ENABLE_COMPONENT_DEPENDS)
       set(COMPONENT_DEPENDS "")
-      foreach (_PACK ${CPACK_COMPONENT_${_local_component_name}_DEPENDS})
+      foreach(_PACK ${CPACK_COMPONENT_${_local_component_name}_DEPENDS})
         get_component_package_name(_PACK_NAME "${_PACK}")
         if(COMPONENT_DEPENDS)
           set(COMPONENT_DEPENDS "${_PACK_NAME} (= ${CPACK_DEBIAN_PACKAGE_VERSION}), ${COMPONENT_DEPENDS}")
@@ -425,11 +425,11 @@ function(cpack_deb_prepare_package_vars)
   # to the minimal dependency of the package
   # Append automatically discovered dependencies .
   if(NOT "${CPACK_DEBIAN_PACKAGE_AUTO_DEPENDS}" STREQUAL "")
-    if (CPACK_DEBIAN_PACKAGE_DEPENDS)
+    if(CPACK_DEBIAN_PACKAGE_DEPENDS)
       set (CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}, ${CPACK_DEBIAN_PACKAGE_AUTO_DEPENDS}")
-    else ()
+    else()
       set (CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_AUTO_DEPENDS}")
-    endif ()
+    endif()
   endif()
 
   if(NOT CPACK_DEBIAN_PACKAGE_DEPENDS)
@@ -529,7 +529,7 @@ function(cpack_deb_prepare_package_vars)
 
   set(CPACK_DEBIAN_PACKAGE_SHLIBS_LIST "")
 
-  if (NOT CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS_POLICY)
+  if(NOT CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS_POLICY)
     set(CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS_POLICY "=")
   endif()
 
@@ -544,7 +544,7 @@ function(cpack_deb_prepare_package_vars)
           message(AUTHOR_WARNING "Shared library '${_FILE}' is missing soname or soversion. Library will not be added to DEBIAN/shlibs control file.")
         endif()
       endforeach()
-      if (CPACK_DEBIAN_PACKAGE_SHLIBS_LIST)
+      if(CPACK_DEBIAN_PACKAGE_SHLIBS_LIST)
         string(REPLACE ";" "\n" CPACK_DEBIAN_PACKAGE_SHLIBS_LIST "${CPACK_DEBIAN_PACKAGE_SHLIBS_LIST}")
       endif()
     else()

-----------------------------------------------------------------------

Summary of changes:
 Help/cpack_gen/deb.rst                             |  14 +-
 Modules/Internal/CPack/CPackDeb.cmake              | 177 +++++++++++++++------
 Source/CTest/cmCTestBuildCommand.cxx               |  45 +++---
 Source/CTest/cmCTestBuildCommand.h                 |  23 ++-
 Source/CTest/cmCTestConfigureCommand.cxx           |  12 +-
 Source/CTest/cmCTestConfigureCommand.h             |  10 +-
 Source/CTest/cmCTestCoverageCommand.cxx            |  44 ++---
 Source/CTest/cmCTestCoverageCommand.h              |  19 +--
 Source/CTest/cmCTestHandlerCommand.cxx             | 147 ++++++-----------
 Source/CTest/cmCTestHandlerCommand.h               |  48 ++----
 Source/CTest/cmCTestMemCheckCommand.cxx            |  15 +-
 Source/CTest/cmCTestMemCheckCommand.h              |  11 +-
 Source/CTest/cmCTestSubmitCommand.cxx              | 155 +++++-------------
 Source/CTest/cmCTestSubmitCommand.h                |  48 ++----
 Source/CTest/cmCTestTestCommand.cxx                | 104 ++++++------
 Source/CTest/cmCTestTestCommand.h                  |  37 ++---
 Source/CTest/cmCTestUpdateCommand.cxx              |   7 +-
 Source/CTest/cmCTestUpdateCommand.h                |   2 -
 Source/CTest/cmCTestUploadCommand.cxx              |  64 +++-----
 Source/CTest/cmCTestUploadCommand.h                |  18 +--
 Source/cmArgumentParser.cxx                        |   6 +-
 Source/cmArgumentParser.h                          |  21 ++-
 ...LibCPackConfig-components-description1.cmake.in |   7 +-
 ...LibCPackConfig-components-description2.cmake.in |  10 +-
 ...CPackVerifyResult-components-description1.cmake |  18 +--
 ...CPackVerifyResult-components-description2.cmake |  20 +--
 .../CPackComponentsDEB/RunCPackVerifyResult.cmake  |  25 +++
 Tests/RunCMake/CMakeLists.txt                      |   1 +
 Tests/RunCMake/CPack/RunCMakeTest.cmake            |   7 +
 .../tests/DEB_DESCRIPTION/ExpectedFiles.cmake      |  16 ++
 .../CPack/tests/DEB_DESCRIPTION/VerifyResult.cmake |  65 ++++++++
 .../CPack/tests/DEB_DESCRIPTION/test.cmake         |  45 ++++++
 .../tests/PER_COMPONENT_FIELDS/VerifyResult.cmake  |   9 +-
 33 files changed, 617 insertions(+), 633 deletions(-)
 create mode 100644 Tests/RunCMake/CPack/tests/DEB_DESCRIPTION/ExpectedFiles.cmake
 create mode 100644 Tests/RunCMake/CPack/tests/DEB_DESCRIPTION/VerifyResult.cmake
 create mode 100644 Tests/RunCMake/CPack/tests/DEB_DESCRIPTION/test.cmake


hooks/post-receive
-- 
CMake


More information about the Cmake-commits mailing list