Browse Source

Merge topic 'build-test-presets'

9b5289ebd8 Help: Add release note for build and test presets
c8a5cd6871 Tests: Update RunCMake/CMakePresets --list-presets
69a5cf23a2 Tests: Update RunCMake/CommandLine BuildDir
b500935b94 Tests: Add test preset tests
74a86566f0 Tests: Add build preset tests
56751c83aa Tests: Factor out RunCMake.CMakePresets schema validation
676ecf0d37 cmake-presets: Add build and test presets
4f4f2028b8 Help: Add documentation for buildPresets and testPresets
...

Acked-by: Kitware Robot <[email protected]>
Merge-request: !5681
Brad King 4 years ago
parent
commit
2c2eb777f4
95 changed files with 3460 additions and 372 deletions
  1. 494 1
      Help/manual/cmake-presets.7.rst
  2. 19 3
      Help/manual/cmake.1.rst
  3. 10 0
      Help/manual/ctest.1.rst
  4. 472 13
      Help/manual/presets/schema.json
  5. 4 0
      Help/release/dev/build-test-presets.rst
  6. 7 6
      Source/QtDialog/QCMake.cxx
  7. 840 195
      Source/cmCMakePresetsFile.cxx
  8. 251 56
      Source/cmCMakePresetsFile.h
  9. 339 1
      Source/cmCTest.cxx
  10. 6 0
      Source/cmCTest.h
  11. 170 54
      Source/cmake.cxx
  12. 5 5
      Source/cmake.h
  13. 41 8
      Source/cmakemain.cxx
  14. 2 0
      Source/ctest.cxx
  15. 2 0
      Tests/RunCMake/CMakeLists.txt
  16. 1 1
      Tests/RunCMake/CMakePresets/ListPresets-stdout.txt
  17. 1 1
      Tests/RunCMake/CMakePresets/ListPresetsHidden-stdout.txt
  18. 1 1
      Tests/RunCMake/CMakePresets/ListPresetsNoSuchPreset-stdout.txt
  19. 1 1
      Tests/RunCMake/CMakePresets/ListPresetsWorkingDir-stdout.txt
  20. 1 15
      Tests/RunCMake/CMakePresets/RunCMakeTest.cmake
  21. 19 0
      Tests/RunCMake/CMakePresets/validate_schema.cmake
  22. 3 0
      Tests/RunCMake/CMakePresetsBuild/CMakeLists.txt.in
  23. 5 0
      Tests/RunCMake/CMakePresetsBuild/Good-build-build-other-check.cmake
  24. 14 0
      Tests/RunCMake/CMakePresetsBuild/Good-build-macros-check.cmake
  25. 6 0
      Tests/RunCMake/CMakePresetsBuild/Good-build-noEnvironment-check.cmake
  26. 8 0
      Tests/RunCMake/CMakePresetsBuild/Good-build-withEnvironment-check.cmake
  27. 3 0
      Tests/RunCMake/CMakePresetsBuild/Good.cmake
  28. 78 0
      Tests/RunCMake/CMakePresetsBuild/Good.json.in
  29. 1 0
      Tests/RunCMake/CMakePresetsBuild/Invalid-build-badConfigurePreset-result.txt
  30. 2 0
      Tests/RunCMake/CMakePresetsBuild/Invalid-build-badConfigurePreset-stderr.txt
  31. 1 0
      Tests/RunCMake/CMakePresetsBuild/Invalid-build-hidden-result.txt
  32. 2 0
      Tests/RunCMake/CMakePresetsBuild/Invalid-build-hidden-stderr.txt
  33. 1 0
      Tests/RunCMake/CMakePresetsBuild/Invalid-build-vendorMacro-result.txt
  34. 1 0
      Tests/RunCMake/CMakePresetsBuild/Invalid-build-vendorMacro-stderr.txt
  35. 0 0
      Tests/RunCMake/CMakePresetsBuild/Invalid.cmake
  36. 27 0
      Tests/RunCMake/CMakePresetsBuild/Invalid.json.in
  37. 5 0
      Tests/RunCMake/CMakePresetsBuild/ListPresets-build-x-stdout.txt
  38. 0 0
      Tests/RunCMake/CMakePresetsBuild/ListPresets.cmake
  39. 31 0
      Tests/RunCMake/CMakePresetsBuild/ListPresets.json.in
  40. 1 0
      Tests/RunCMake/CMakePresetsBuild/NoConfigurePreset-build-noConfigurePreset-result.txt
  41. 2 0
      Tests/RunCMake/CMakePresetsBuild/NoConfigurePreset-build-noConfigurePreset-stderr.txt
  42. 0 0
      Tests/RunCMake/CMakePresetsBuild/NoConfigurePreset.cmake
  43. 15 0
      Tests/RunCMake/CMakePresetsBuild/NoConfigurePreset.json.in
  44. 1 0
      Tests/RunCMake/CMakePresetsBuild/PresetsUnsupported-build-x-result.txt
  45. 2 0
      Tests/RunCMake/CMakePresetsBuild/PresetsUnsupported-build-x-stderr.txt
  46. 7 0
      Tests/RunCMake/CMakePresetsBuild/PresetsUnsupported.json.in
  47. 67 0
      Tests/RunCMake/CMakePresetsBuild/RunCMakeTest.cmake
  48. 14 0
      Tests/RunCMake/CMakePresetsBuild/TestVariable.cmake
  49. 3 0
      Tests/RunCMake/CMakePresetsBuild/check.cmake
  50. 3 0
      Tests/RunCMake/CMakePresetsTest/CMakeLists.txt.in
  51. 1 0
      Tests/RunCMake/CMakePresetsTest/Good-indexFile.txt
  52. 5 0
      Tests/RunCMake/CMakePresetsTest/Good-test-config-debug-stdout.txt
  53. 5 0
      Tests/RunCMake/CMakePresetsTest/Good-test-config-release-stdout.txt
  54. 7 0
      Tests/RunCMake/CMakePresetsTest/Good-test-exclude-stdout.txt
  55. 7 0
      Tests/RunCMake/CMakePresetsTest/Good-test-index-stdout.txt
  56. 7 0
      Tests/RunCMake/CMakePresetsTest/Good-test-indexFile-stdout.txt
  57. 8 0
      Tests/RunCMake/CMakePresetsTest/Good-test-noEnvironment-stdout.txt
  58. 8 0
      Tests/RunCMake/CMakePresetsTest/Good-test-showOnly-stdout.txt
  59. 8 0
      Tests/RunCMake/CMakePresetsTest/Good-withEnvironment-check.cmake
  60. 27 0
      Tests/RunCMake/CMakePresetsTest/Good.cmake
  61. 184 0
      Tests/RunCMake/CMakePresetsTest/Good.json.in
  62. 1 0
      Tests/RunCMake/CMakePresetsTest/Invalid-test-badConfigurePreset-result.txt
  63. 2 0
      Tests/RunCMake/CMakePresetsTest/Invalid-test-badConfigurePreset-stderr.txt
  64. 1 0
      Tests/RunCMake/CMakePresetsTest/Invalid-test-hidden-result.txt
  65. 2 0
      Tests/RunCMake/CMakePresetsTest/Invalid-test-hidden-stderr.txt
  66. 1 0
      Tests/RunCMake/CMakePresetsTest/Invalid-test-vendorMacro-result.txt
  67. 1 0
      Tests/RunCMake/CMakePresetsTest/Invalid-test-vendorMacro-stderr.txt
  68. 0 0
      Tests/RunCMake/CMakePresetsTest/Invalid.cmake
  69. 27 0
      Tests/RunCMake/CMakePresetsTest/Invalid.json.in
  70. 12 0
      Tests/RunCMake/CMakePresetsTest/ListPresets-test-x-stdout.txt
  71. 0 0
      Tests/RunCMake/CMakePresetsTest/ListPresets.cmake
  72. 1 0
      Tests/RunCMake/CMakePresetsTest/NoConfigurePreset-test-noConfigurePreset-result.txt
  73. 2 0
      Tests/RunCMake/CMakePresetsTest/NoConfigurePreset-test-noConfigurePreset-stderr.txt
  74. 0 0
      Tests/RunCMake/CMakePresetsTest/NoConfigurePreset.cmake
  75. 15 0
      Tests/RunCMake/CMakePresetsTest/NoConfigurePreset.json.in
  76. 1 0
      Tests/RunCMake/CMakePresetsTest/NoTestsAction-test-noTestsAction-result.txt
  77. 1 0
      Tests/RunCMake/CMakePresetsTest/NoTestsAction-test-noTestsAction-stderr.txt
  78. 0 0
      Tests/RunCMake/CMakePresetsTest/NoTestsAction.cmake
  79. 19 0
      Tests/RunCMake/CMakePresetsTest/NoTestsAction.json.in
  80. 1 0
      Tests/RunCMake/CMakePresetsTest/PresetsUnsupported-test-x-result.txt
  81. 2 0
      Tests/RunCMake/CMakePresetsTest/PresetsUnsupported-test-x-stderr.txt
  82. 7 0
      Tests/RunCMake/CMakePresetsTest/PresetsUnsupported.json.in
  83. 94 0
      Tests/RunCMake/CMakePresetsTest/RunCMakeTest.cmake
  84. 3 0
      Tests/RunCMake/CMakePresetsTest/check.cmake
  85. 1 1
      Tests/RunCMake/CommandLine/BuildDir--build--parallel-bad-number-stderr.txt
  86. 1 1
      Tests/RunCMake/CommandLine/BuildDir--build--parallel-large-stderr.txt
  87. 1 1
      Tests/RunCMake/CommandLine/BuildDir--build--parallel-no-space-bad-number-stderr.txt
  88. 1 1
      Tests/RunCMake/CommandLine/BuildDir--build--parallel-zero-stderr.txt
  89. 1 1
      Tests/RunCMake/CommandLine/BuildDir--build-jobs-bad-number-stderr.txt
  90. 1 1
      Tests/RunCMake/CommandLine/BuildDir--build-jobs-large-stderr.txt
  91. 1 1
      Tests/RunCMake/CommandLine/BuildDir--build-jobs-no-space-bad-number-stderr.txt
  92. 1 1
      Tests/RunCMake/CommandLine/BuildDir--build-jobs-zero-stderr.txt
  93. 1 1
      Tests/RunCMake/CommandLine/BuildDir--build-multiple-targets-with-clean-first-stderr.txt
  94. 1 1
      Tests/RunCMake/CommandLine/BuildDir--build-multiple-targets-with-clean-second-stderr.txt
  95. 1 1
      Tests/RunCMake/CommandLine/build-no-dir-stderr.txt

+ 494 - 1
Help/manual/cmake-presets.7.rst

@@ -71,6 +71,14 @@ The root object recognizes the following fields:
 
   An optional array of `Configure Preset`_ objects.
 
+``buildPresets``
+
+  An optional array of `Build Preset`_ objects.
+
+``testPresets``
+
+  An optional array of `Test Preset`_ objects.
+
 Configure Preset
 ^^^^^^^^^^^^^^^^
 
@@ -289,6 +297,489 @@ that may contain the following fields:
     An optional boolean. Setting this to ``true`` is equivalent to passing
     ``--debug-find`` on the command line.
 
+Build Preset
+^^^^^^^^^^^^
+
+Each entry of the ``buildPresets`` array is a JSON object
+that may contain the following fields:
+
+``name``
+
+  A required string representing the machine-friendly name of the preset.
+  This identifier is used in the ``--preset`` argument. There must not be
+  two presets (configure, build, or test) in the union of
+  ``CMakePresets.json`` and ``CMakeUserPresets.json`` in the same
+  directory with the same name.
+
+``hidden``
+
+  An optional boolean specifying whether or not a preset should be hidden.
+  If a preset is hidden, it cannot be used in the ``--preset`` argument
+  and does not have to have a valid ``configurePreset``, even from
+  inheritance. ``hidden`` presets are intended to be used as a base for
+  other presets to inherit via the ``inherits`` field.
+
+``inherits``
+
+  An optional array of strings representing the names of presets to
+  inherit from. The preset will inherit all of the fields from the
+  ``inherits`` presets by default (except ``name``, ``hidden``,
+  ``inherits``, ``description``, and ``displayName``), but can override
+  them as desired. If multiple ``inherits`` presets provide conflicting
+  values for the same field, the earlier preset in the ``inherits`` list
+  will be preferred. Presets in ``CMakePresets.json`` may not inherit from
+  presets in ``CMakeUserPresets.json``.
+
+  This field can also be a string, which is equivalent to an array
+  containing one string.
+
+``vendor``
+
+  An optional map containing vendor-specific information. CMake does not
+  interpret the contents of this field except to verify that it is a map
+  if it does exist. However, it should follow the same conventions as the
+  root-level ``vendor`` field. If vendors use their own per-preset
+  ``vendor`` field, they should implement inheritance in a sensible manner
+  when appropriate.
+
+``displayName``
+
+  An optional string with a human-friendly name of the preset.
+
+``description``
+
+  An optional string with a human-friendly description of the preset.
+
+``environment``
+
+  An optional map of environment variables. The key is the variable name
+  (which may not be an empty string), and the value is either ``null`` or
+  a string representing the value of the variable. Each variable is set
+  regardless of whether or not a value was given to it by the process's
+  environment. This field supports macro expansion, and environment
+  variables in this map may reference each other, and may be listed in any
+  order, as long as such references do not cause a cycle (for example, if
+  ``ENV_1`` is ``$env{ENV_2}``, ``ENV_2`` may not be ``$env{ENV_1}``.)
+
+  Environment variables are inherited through the ``inherits`` field, and
+  the preset's environment will be the union of its own ``environment``
+  and the ``environment`` from all its parents. If multiple presets in
+  this union define the same variable, the standard rules of ``inherits``
+  are applied. Setting a variable to ``null`` causes it to not be set,
+  even if a value was inherited from another preset.
+
+``configurePreset``
+
+  An optional string specifying the name of a configure preset to
+  associate with this build preset. If ``configurePreset`` is not
+  specified, it must be inherited from the inherits preset (unless this
+  preset is hidden). The build tree directory is inferred from the
+  configure preset.
+
+``inheritConfigureEnvironment``
+
+  An optional boolean that defaults to true. If true, the environment
+  variables from the associated configure preset are inherited after all
+  inherited build preset environments, but before environment variables
+  explicitly specified in this build preset.
+
+``jobs``
+
+  An optional integer. Equivalent to passing ``--parallel`` or ``-j`` on
+  the command line.
+
+``targets``
+
+  An optional string or array of strings. Equivalent to passing
+  ``--target`` or ``-t`` on the command line. Vendors may ignore the
+  targets property or hide build presets that explicitly specify targets.
+  This field supports macro expansion.
+
+``configuration``
+
+  An optional string. Equivalent to passing ``--config`` on the command
+  line.
+
+``cleanFirst``
+
+  An optional bool. If true, equivalent to passing ``--clean-first`` on
+  the command line.
+
+``verbose``
+
+  An optional bool. If true, equivalent to passing ``--verbose`` on the
+  command line.
+
+``nativeToolOptions``
+
+  An optional array of strings. Equivalent to passing options after ``--``
+  on the command line. The array values support macro expansion.
+
+Test Preset
+^^^^^^^^^^^
+
+Each entry of the ``testPresets`` array is a JSON object
+that may contain the following fields:
+
+``name``
+
+  A required string representing the machine-friendly name of the preset.
+  This identifier is used in the ``--preset`` argument. There must not be
+  two presets (configure, build, or test) in the union of
+  ``CMakePresets.json`` and ``CMakeUserPresets.json`` in the same
+  directory with the same name.
+
+``hidden``
+
+  An optional boolean specifying whether or not a preset should be hidden.
+  If a preset is hidden, it cannot be used in the ``--preset`` argument
+  and does not have to have a valid ``configurePreset``, even from
+  inheritance. ``hidden`` presets are intended to be used as a base for
+  other presets to inherit via the ``inherits`` field.
+
+``inherits``
+
+  An optional array of strings representing the names of presets to
+  inherit from. The preset will inherit all of the fields from the
+  ``inherits`` presets by default (except ``name``, ``hidden``,
+  ``inherits``, ``description``, and ``displayName``), but can override
+  them as desired. If multiple ``inherits`` presets provide conflicting
+  values for the same field, the earlier preset in the ``inherits`` list
+  will be preferred. Presets in ``CMakePresets.json`` may not inherit from
+  presets in ``CMakeUserPresets.json``.
+
+  This field can also be a string, which is equivalent to an array
+  containing one string.
+
+``vendor``
+
+  An optional map containing vendor-specific information. CMake does not
+  interpret the contents of this field except to verify that it is a map
+  if it does exist. However, it should follow the same conventions as the
+  root-level ``vendor`` field. If vendors use their own per-preset
+  ``vendor`` field, they should implement inheritance in a sensible manner
+  when appropriate.
+
+``displayName``
+
+  An optional string with a human-friendly name of the preset.
+
+``description``
+
+  An optional string with a human-friendly description of the preset.
+
+``environment``
+
+  An optional map of environment variables. The key is the variable name
+  (which may not be an empty string), and the value is either ``null`` or
+  a string representing the value of the variable. Each variable is set
+  regardless of whether or not a value was given to it by the process's
+  environment. This field supports macro expansion, and environment
+  variables in this map may reference each other, and may be listed in any
+  order, as long as such references do not cause a cycle (for example, if
+  ``ENV_1`` is ``$env{ENV_2}``, ``ENV_2`` may not be ``$env{ENV_1}``.)
+
+  Environment variables are inherited through the ``inherits`` field, and
+  the preset's environment will be the union of its own ``environment``
+  and the ``environment`` from all its parents. If multiple presets in
+  this union define the same variable, the standard rules of ``inherits``
+  are applied. Setting a variable to ``null`` causes it to not be set,
+  even if a value was inherited from another preset.
+
+``configurePreset``
+
+  An optional string specifying the name of a configure preset to
+  associate with this test preset. If ``configurePreset`` is not
+  specified, it must be inherited from the inherits preset (unless this
+  preset is hidden). The build tree directory is inferred from the
+  configure preset.
+
+``inheritConfigureEnvironment``
+
+  An optional boolean that defaults to true. If true, the environment
+  variables from the associated configure preset are inherited after all
+  inherited test preset environments, but before environment variables
+  explicitly specified in this test preset.
+
+``configuration``
+
+  An optional string. Equivalent to passing ``--build-config`` on the
+  command line.
+
+``overwriteConfigurationFile``
+
+  An optional array of configuration options to overwrite options
+  specified in the CTest configuration file. Equivalent to passing
+  ``--overwrite`` for each value in the array. The array values
+  support macro expansion.
+
+``output``
+
+  An optional object specifying output options. The object may contain the
+  following fields.
+
+  ``shortProgress``
+
+    An optional bool. If true, equivalent to passing ``--progress`` on the
+    command line.
+
+  ``verbosity``
+
+    An optional string specifying verbosity level. Must be one of the
+    following:
+
+    ``default``
+
+      Equivalent to passing no verbosity flags on the command line.
+
+    ``verbose``
+
+      Equivalent to passing ``--verbose`` on the command line.
+
+    ``extra``
+
+      Equivalent to passing ``--extra-verbose`` on the command line.
+
+  ``debug``
+
+    An optional bool. If true, equivalent to passing ``--debug`` on the
+    command line.
+
+  ``outputOnFailure``
+
+    An optional bool. If true, equivalent to passing
+    ``--output-on-failure`` on the command line.
+
+  ``quiet``
+
+    An optional bool. If true, equivalent to passing ``--quiet`` on the
+    command line.
+
+  ``outputLogFile``
+
+    An optional string specifying a path to a log file. Equivalent to
+    passing ``--output-log`` on the command line. This field supports
+    macro expansion.
+
+  ``labelSummary``
+
+    An optional bool. If false, equivalent to passing
+    ``--no-label-summary`` on the command line.
+
+  ``subprojectSummary``
+
+    An optional bool. If false, equivalent to passing
+    ``--no-subproject-summary`` on the command line.
+
+  ``maxPassedTestOutputSize``
+
+    An optional integer specifying the maximum output for passed tests in
+    bytes. Equivalent to passing ``--test-output-size-passed`` on the
+    command line.
+
+  ``maxFailedTestOutputSize``
+
+    An optional integer specifying the maximum output for failed tests in
+    bytes. Equivalent to passing ``--test-output-size-failed`` on the
+    command line.
+
+  ``maxTestNameWidth``
+
+    An optional integer specifying the maximum width of a test name to
+    output. Equivalent to passing ``--max-width`` on the command line.
+
+``filter``
+
+  An optional object specifying how to filter the tests to run. The object
+  may contain the following fields.
+
+  ``include``
+
+    An optional object specifying which tests to include. The object may
+    contain the following fields.
+
+    ``name``
+
+      An optional string specifying a regex for test names. Equivalent to
+      passing ``--tests-regex`` on the command line. This field supports
+      macro expansion.
+
+
+    ``label``
+
+      An optional string specifying a regex for test labels. Equivalent to
+      passing ``--label-regex`` on the command line. This field supports
+      macro expansion.
+
+    ``useUnion``
+
+      An optional bool. Equivalent to passing ``--union`` on the command
+      line.
+
+    ``index``
+
+      An optional object specifying tests to include by test index. The
+      object may contain the following fields. Can also be an optional
+      string specifying a file with the command line syntax for
+      ``--tests-information``. If specified as a string, this field
+      supports macro expansion.
+
+      ``start``
+
+        An optional integer specifying a test index to start testing at.
+
+      ``end``
+
+        An optional integer specifying a test index to stop testing at.
+
+      ``stride``
+
+        An optional integer specifying the increment.
+
+      ``specificTests``
+
+        An optional array of integers specifying specific test indices to
+        run.
+
+  ``exclude``
+
+    An optional object specifying which tests to exclude. The object may
+    contain the following fields.
+
+    ``name``
+
+      An optional string specifying a regex for test names. Equivalent to
+      passing ``--exclude-regex`` on the command line. This field supports
+      macro expansion.
+
+    ``label``
+
+      An optional string specifying a regex for test labels. Equivalent to
+      passing ``--label-exclude`` on the command line. This field supports
+      macro expansion.
+
+    ``fixtures``
+
+      An optional object specifying which fixtures to exclude from adding
+      tests. The object may contain the following fields.
+
+      ``any``
+
+        An optional string specifying a regex for text fixtures to exclude
+        from adding any tests. Equivalent to ``--fixture-exclude-any`` on
+        the command line. This field supports macro expansion.
+
+      ``setup``
+
+        An optional string specifying a regex for text fixtures to exclude
+        from adding setup tests. Equivalent to ``--fixture-exclude-setup``
+        on the command line. This field supports macro expansion.
+
+      ``cleanup``
+
+        An optional string specifying a regex for text fixtures to exclude
+        from adding cleanup tests. Equivalent to
+        ``--fixture-exclude-cleanup`` on the command line. This field
+        supports macro expansion.
+
+``execution``
+
+  An optional object specifying options for test execution. The object may
+  contain the following fields.
+
+  ``stopOnFailure``
+
+    An optional bool. If true, equivalent to passing ``--stop-on-failure``
+    on the command line.
+
+  ``enableFailover``
+
+    An optional bool. If true, equivalent to passing ``-F`` on the command
+    line.
+
+  ``jobs``
+
+    An optional integer. Equivalent to passing ``--parallel`` on the
+    command line.
+
+  ``resourceSpecFile``
+
+    An optional string. Equivalent to passing ``--resource-spec-file`` on
+    the command line. This field supports macro expansion.
+
+  ``testLoad``
+
+    An optional integer. Equivalent to passing ``--test-load`` on the
+    command line.
+
+  ``showOnly``
+
+    An optional string. Equivalent to passing ``--show-only`` on the
+    command line. The string must be one of the following values:
+
+    ``human``
+
+    ``json-v1``
+
+  ``rerunFailed``
+
+    An optional bool. If true, equivalent to passing ``--rerun-failed`` on
+    the command line.
+
+  ``repeat``
+
+    An optional object specifying how to repeat tests. Equivalent to
+    passing ``--repeat`` on the command line. The object must have the
+    following fields.
+
+    ``mode``
+
+      A required string. Must be one of the following values:
+
+      ``until-fail``
+
+      ``until-pass``
+
+      ``after-timeout``
+
+    ``count``
+
+      A required integer.
+
+  ``interactiveDebugging``
+
+    An optional bool. If true, equivalent to passing
+    ``--interactive-debug-mode 1`` on the command line. If false,
+    equivalent to passing ``--interactive-debug-mode 0`` on the command
+    line.
+
+  ``scheduleRandom``
+
+    An optional bool. If true, equivalent to passing ``--schedule-random``
+    on the command line.
+
+  ``timeout``
+
+    An optional integer. Equivalent to passing ``--timeout`` on the
+    command line.
+
+  ``noTestsAction``
+
+    An optional string specifying the behavior if no tests are found. Must
+    be one of the following values:
+
+    ``default``
+
+      Equivalent to not passing any value on the command line.
+
+    ``error``
+
+      Equivalent to passing ``--no-tests=error`` on the command line.
+
+    ``ignore``
+
+      Equivalent to passing ``--no-tests=ignore`` on the command line.
+
 Macro Expansion
 ^^^^^^^^^^^^^^^
 
@@ -326,7 +817,9 @@ Recognized macros include:
 
 ``${generator}``
 
-  Generator specified in the preset's ``generator`` field.
+  Generator specified in the preset's ``generator`` field. For build and
+  test presets, this will evaluate to the generator specified by
+  ``configurePreset``.
 
 ``${dollar}``
 

+ 19 - 3
Help/manual/cmake.1.rst

@@ -382,7 +382,8 @@ Options
  ``<path-to-source>/CMakePresets.json`` and
  ``<path-to-source>/CMakeUserPresets.json``. The preset specifies the
  generator and the build directory, and optionally a list of variables and
- other arguments to pass to CMake. The :manual:`CMake GUI <cmake-gui(1)>` can
+ other arguments to pass to CMake. The current working directory must contain
+ CMake preset files. The :manual:`CMake GUI <cmake-gui(1)>` can
  also recognize ``CMakePresets.json`` and ``CMakeUserPresets.json`` files. For
  full details on these files, see :manual:`cmake-presets(7)`.
 
@@ -392,6 +393,10 @@ Options
  a variable called ``MYVAR`` to ``1``, but the user sets it to ``2`` with a
  ``-D`` argument, the value ``2`` is preferred.
 
+``--list-presets, --list-presets=<[configure | build | test | all]>``
+ Lists the available presets. If no option is specified only configure presets
+ will be listed. The current working directory must contain CMake preset files.
+
 .. _`Build Tool Mode`:
 
 Build a Project
@@ -402,13 +407,24 @@ project binary tree:
 
 .. code-block:: shell
 
-  cmake --build <dir> [<options>] [-- <build-tool-options>]
+  cmake --build [<dir> | --preset <preset>] [<options>] [-- <build-tool-options>]
 
 This abstracts a native build tool's command-line interface with the
 following options:
 
 ``--build <dir>``
-  Project binary directory to be built.  This is required and must be first.
+  Project binary directory to be built.  This is required (unless a preset
+  is specified) and must be first.
+
+``--preset <preset>``
+  Use a build preset to specify build options. The project binary directory
+  is inferred from the ``configurePreset`` key. The current working directory
+  must contain CMake preset files.
+  See :manual:`preset <cmake-presets(7)>` for more details.
+
+``--list-presets``
+  Lists the available build presets. The current working directory must
+  contain CMake preset files.
 
 ``--parallel [<jobs>], -j [<jobs>]``
   The maximum number of concurrent processes to use when building.

+ 10 - 0
Help/manual/ctest.1.rst

@@ -28,6 +28,16 @@ This program will run the tests and report results.
 Options
 =======
 
+``--preset <preset>``
+ Use a test preset to specify test options. The project binary directory
+ is inferred from the ``configurePreset`` key. The current working directory
+ must contain CMake preset files.
+ See :manual:`preset <cmake-presets(7)>` for more details.
+
+``--list-presets``
+ Lists the available test presets. The current working directory must contain
+ CMake preset files.
+
 ``-C <cfg>, --build-config <cfg>``
  Choose configuration to test.
 

+ 472 - 13
Help/manual/presets/schema.json

@@ -2,13 +2,38 @@
   "$schema": "http://json-schema.org/draft-07/schema#",
   "type": "object",
   "description": "The presets specify the generator and the build directory, and optionally a list of variables and other arguments to pass to CMake.",
-  "properties": {
-    "version": {
-      "type": "integer",
-      "description": "A required integer representing the version of the JSON schema. Currently, the only supported version is 1.",
-      "minimum": 1,
-      "maximum": 1
+  "oneOf": [
+    {
+      "properties": {
+        "version": {
+          "const": 1,
+          "description": "A required integer representing the version of the JSON schema."
+        },
+        "cmakeMinimumRequired": { "$ref": "#/definitions/cmakeMinimumRequired"},
+        "vendor": { "$ref": "#/definitions/vendor" },
+        "configurePresets": { "$ref": "#/definitions/configurePresets"}
+      },
+      "additionalProperties": false
     },
+    {
+      "properties": {
+        "version": {
+          "const": 2,
+          "description": "A required integer representing the version of the JSON schema."
+        },
+        "cmakeMinimumRequired": { "$ref": "#/definitions/cmakeMinimumRequired"},
+        "vendor": { "$ref": "#/definitions/vendor" },
+        "configurePresets": { "$ref": "#/definitions/configurePresets"},
+        "buildPresets": { "$ref": "#/definitions/buildPresets"},
+        "testPresets": { "$ref": "#/definitions/testPresets"}
+      },
+      "additionalProperties": false
+    }
+  ],
+  "required": [
+    "version"
+  ],
+  "definitions": {
     "cmakeMinimumRequired": {
       "type": "object",
       "description": "An optional object representing the minimum version of CMake needed to build this project.",
@@ -47,7 +72,7 @@
           },
           "hidden": {
             "type": "boolean",
-            "description": "An optional boolean specifying whether or not a preset should be hidden. If a preset is hidden, it cannot be used in the --preset= argument, will not show up in the CMake GUI, and does not have to have a valid generator or binaryDir, even from inheritance. hidden presets are intended to be used as a base for other presets to inherit via the inherits field."
+            "description": "An optional boolean specifying whether or not a preset should be hidden. If a preset is hidden, it cannot be used in the --preset= argument, will not show up in the CMake GUI, and does not have to have a valid generator or binaryDir, even from inheritance. Hidden presets are intended to be used as a base for other presets to inherit via the inherits field."
           },
           "inherits": {
             "anyOf": [
@@ -58,7 +83,7 @@
               },
               {
                 "type": "array",
-                "description": "An optional array of strings representing the names of presets to inherit from. The preset will inherit all of the fields from the inherits presets by default (except name, hidden, inherits, description, and longDescription), but can override them as desired. If multiple inherits presets provide conflicting values for the same field, the earlier preset in the inherits list will be preferred. Presets in CMakePresets.json may not inherit from presets in CMakeUserPresets.json.",
+                "description": "An optional array of strings representing the names of presets to inherit from. The preset will inherit all of the fields from the inherits presets by default (except name, hidden, inherits, description, and displayName), but can override them as desired. If multiple inherits presets provide conflicting values for the same field, the earlier preset in the inherits list will be preferred. Presets in CMakePresets.json may not inherit from presets in CMakeUserPresets.json.",
                 "items": {
                   "type": "string",
                   "description": "An optional string representing the name of the preset to inherit from.",
@@ -283,10 +308,444 @@
         ],
         "additionalProperties": false
       }
+    },
+    "buildPresets": {
+      "type": "array",
+      "description": "An optional array of build preset objects. Used to specify arguments to cmake --build. Available in version 2 and higher.",
+      "items": {
+        "type": "object",
+        "properties": {
+          "name": {
+            "type": "string",
+            "description": "A required string representing the machine-friendly name of the preset. This identifier is used in the --preset argument. There must not be two presets (configure, build, or test) in the union of CMakePresets.json and CMakeUserPresets.json in the same directory with the same name.",
+            "minLength": 1
+          },
+          "hidden": {
+            "type": "boolean",
+            "description": "An optional boolean specifying whether or not a preset should be hidden. If a preset is hidden, it cannot be used in the --preset argument, will not show up in the CMake GUI, and does not have to have a valid configurePreset, even from inheritance. Hidden presets are intended to be used as a base for other presets to inherit via the inherits field."
+          },
+          "inherits": {
+            "anyOf": [
+              {
+                "type": "string",
+                "description": "An optional string representing the name of the build preset to inherit from.",
+                "minLength": 1
+              },
+              {
+                "type": "array",
+                "description": "An optional array of strings representing the names of build presets to inherit from. The preset will inherit all of the fields from the inherits presets by default (except name, hidden, inherits, description, and displayName), but can override them as desired. If multiple inherits presets provide conflicting values for the same field, the earlier preset in the inherits list will be preferred. Presets in CMakePresets.json may not inherit from presets in CMakeUserPresets.json.",
+                "items": {
+                  "type": "string",
+                  "description": "An optional string representing the name of the preset to inherit from.",
+                  "minLength": 1
+                }
+              }
+            ]
+          },
+          "configurePreset": {
+            "type": "string",
+            "description": "An optional string specifying the name of a configure preset to associate with this build preset. If configurePreset is not specified, it must be inherited from the inherits preset (unless this preset is hidden). The build tree directory is inferred from the configure preset.",
+            "minLength": 1
+          },
+          "vendor": {
+            "type": "object",
+            "description": "An optional map containing vendor-specific information. CMake does not interpret the contents of this field except to verify that it is a map if it does exist. However, it should follow the same conventions as the root-level vendor field. If vendors use their own per-preset vendor field, they should implement inheritance in a sensible manner when appropriate.",
+            "properties": {}
+          },
+          "displayName": {
+            "type": "string",
+            "description": "An optional string with a human-friendly name of the preset."
+          },
+          "description": {
+            "type": "string",
+            "description": "An optional string with a human-friendly description of the preset."
+          },
+          "inheritConfigureEnvironment": {
+            "type": "boolean",
+            "description": "An optional Boolean that defaults to true. If true, the environment variables from the associated configure preset are inherited after all inherited build preset environments, but before environment variables explicitly specified in this build preset."
+          },
+          "environment": {
+            "type": "object",
+            "description": "An optional map of environment variables. The key is the variable name (which must not be an empty string). Each variable is set regardless of whether or not a value was given to it by the process's environment. This field supports macro expansion, and environment variables in this map may reference each other, and may be listed in any order, as long as such references do not cause a cycle (for example,if ENV_1 is $env{ENV_2}, ENV_2 may not be $env{ENV_1}.) Environment variables are inherited through the inherits field, and the preset's environment will be the union of its own environment and the environment from all its parents. If multiple presets in this union define the same variable, the standard rules of inherits are applied. Setting a variable to null causes it to not be set, even if a value was inherited from another preset.",
+            "properties": {},
+            "additionalProperties": {
+              "anyOf": [
+                {
+                  "type": "null",
+                  "description": "Setting a variable to null causes it to not be set, even if a value was inherited from another preset."
+                },
+                {
+                  "type": "string",
+                  "description": "A string representing the value of the variable."
+                }
+              ]
+            },
+            "propertyNames": {
+              "pattern": "^.+$"
+            }
+          },
+          "jobs": {
+            "type": "integer",
+            "description": "An optional integer. Equivalent to passing --parallel or -j on the command line."
+          },
+          "targets": {
+            "anyOf": [
+              {
+                "type": "string",
+                "description": "An optional string. Equivalent to passing --target or -t on the command line. Vendors may ignore the targets property or hide build presets that explicitly specify targets."
+              },
+              {
+                "type": "array",
+                "description": "An optional array of strings. Equivalent to passing --target or -t on the command line. Vendors may ignore the targets property or hide build presets that explicitly specify targets.",
+                "items": {
+                  "type": "string",
+                  "description": "An optional string. Equivalent to passing --target or -t on the command line. Vendors may ignore the targets property or hide build presets that explicitly specify targets."
+                }
+              }
+            ]
+          },
+          "configuration": {
+            "type": "string",
+            "description": "An optional string. Equivalent to passing --config on the command line."
+          },
+          "cleanFirst": {
+            "type": "boolean",
+            "description": "An optional bool. If true, equivalent to passing --clean-first on the command line."
+          },
+          "verbose": {
+            "type": "boolean",
+            "description": "An optional bool. If true, equivalent to passing --verbose on the command line."
+          },
+          "nativeToolOptions": {
+            "type": "array",
+            "description": "An optional array of strings. Equivalent to passing options after -- on the command line.",
+            "items": {
+              "type": "string",
+              "description": "An optional string representing an option to pass after -- on the command line."
+            }
+          }
+        },
+        "required": [
+          "name"
+        ],
+        "additionalProperties": false
+      }
+    },
+    "testPresets": {
+      "type": "array",
+      "description": "An optional array of test preset objects. Used to specify arguments to ctest. Available in version 2 and higher.",
+      "items": {
+        "type": "object",
+        "properties": {
+          "name": {
+            "type": "string",
+            "description": "A required string representing the machine-friendly name of the preset. This identifier is used in the --preset argument. There must not be two presets (configure, build, or test) in the union of CMakePresets.json and CMakeUserPresets.json in the same directory with the same name.",
+            "minLength": 1
+          },
+          "hidden": {
+            "type": "boolean",
+            "description": "An optional boolean specifying whether or not a preset should be hidden. If a preset is hidden, it cannot be used in the --preset argument, will not show up in the CMake GUI, and does not have to have a valid configurePreset, even from inheritance. Hidden presets are intended to be used as a base for other presets to inherit via the inherits field."
+          },
+          "inherits": {
+            "anyOf": [
+              {
+                "type": "string",
+                "description": "An optional string representing the name of the test preset to inherit from.",
+                "minLength": 1
+              },
+              {
+                "type": "array",
+                "description": "An optional array of strings representing the names of test presets to inherit from. The preset will inherit all of the fields from the inherits presets by default (except name, hidden, inherits, description, and displayName), but can override them as desired. If multiple inherits presets provide conflicting values for the same field, the earlier preset in the inherits list will be preferred. Presets in CMakePresets.json may not inherit from presets in CMakeUserPresets.json.",
+                "items": {
+                  "type": "string",
+                  "description": "An optional string representing the name of the preset to inherit from.",
+                  "minLength": 1
+                }
+              }
+            ]
+          },
+          "configurePreset": {
+            "type": "string",
+            "description": "An optional string specifying the name of a configure preset to associate with this test preset. If configurePreset is not specified, it must be inherited from the inherits preset (unless this preset is hidden). The build tree directory is inferred from the configure preset.",
+            "minLength": 1
+          },
+          "vendor": {
+            "type": "object",
+            "description": "An optional map containing vendor-specific information. CMake does not interpret the contents of this field except to verify that it is a map if it does exist. However, it should follow the same conventions as the root-level vendor field. If vendors use their own per-preset vendor field, they should implement inheritance in a sensible manner when appropriate.",
+            "properties": {}
+          },
+          "displayName": {
+            "type": "string",
+            "description": "An optional string with a human-friendly name of the preset."
+          },
+          "description": {
+            "type": "string",
+            "description": "An optional string with a human-friendly description of the preset."
+          },
+          "inheritConfigureEnvironment": {
+            "type": "boolean",
+            "description": "An optional Boolean that defaults to true. If true, the environment variables from the associated configure preset are inherited after all inherited test preset environments, but before environment variables explicitly specified in this test preset."
+          },
+          "environment": {
+            "type": "object",
+            "description": "An optional map of environment variables. The key is the variable name (which must not be an empty string). Each variable is set regardless of whether or not a value was given to it by the process's environment. This field supports macro expansion, and environment variables in this map may reference each other, and may be listed in any order, as long as such references do not cause a cycle (for example,if ENV_1 is $env{ENV_2}, ENV_2 may not be $env{ENV_1}.) Environment variables are inherited through the inherits field, and the preset's environment will be the union of its own environment and the environment from all its parents. If multiple presets in this union define the same variable, the standard rules of inherits are applied. Setting a variable to null causes it to not be set, even if a value was inherited from another preset.",
+            "properties": {},
+            "additionalProperties": {
+              "anyOf": [
+                {
+                  "type": "null",
+                  "description": "Setting a variable to null causes it to not be set, even if a value was inherited from another preset."
+                },
+                {
+                  "type": "string",
+                  "description": "A string representing the value of the variable."
+                }
+              ]
+            },
+            "propertyNames": {
+              "pattern": "^.+$"
+            }
+          },
+          "configuration": {
+            "type": "string",
+            "description": "An optional string. Equivalent to passing --build-config on the command line."
+          },
+          "overwriteConfigurationFile": {
+            "type": "array",
+            "description": "An optional array of configuration options to overwrite options specified in the CTest configuration file. Equivalent to passing ``--overwrite`` for each value in the array.",
+            "items": {
+              "type": "string",
+              "description": "An option written as a key-value pair in the form \"key=value\"."
+            }
+          },
+          "output": {
+            "type": "object",
+            "description": "An optional object specifying output options.",
+            "properties": {
+              "shortProgress": {
+                "type": "boolean",
+                "description": "An optional bool. If true, equivalent to passing --progress on the command line."
+              },
+              "verbosity": {
+                "type": "string",
+                "description": "An optional string specifying verbosity level. Valid values are \"default\" (equivalent to passing no verbosity flags on the command line), \"verbose\" (equivalent to passing --verbose on the command line), and \"extra\" (equivalent to passing --extra-verbose on the command line).",
+                "enum": [
+                  "default", "verbose", "extra"
+                ]
+              },
+              "debug": {
+                "type": "boolean",
+                "description": "An optional bool. If true, equivalent to passing --debug on the command line."
+              },
+              "outputOnFailure": {
+                "type": "boolean",
+                "description": "An optional bool. If true, equivalent to passing --output-on-failure on the command line."
+              },
+              "quiet": {
+                "type": "boolean",
+                "description": "An optional bool. If true, equivalent to passing --quiet on the command line."
+              },
+              "outputLogFile": {
+                "type": "string",
+                "description": "An optional string specifying a path to a log file. Equivalent to passing --output-log on the command line."
+              },
+              "labelSummary": {
+                "type": "boolean",
+                "description": "An optional bool. If false, equivalent to passing --no-label-summary on the command line."
+              },
+              "subprojectSummary": {
+                "type": "boolean",
+                "description": "An optional bool. If false, equivalent to passing --no-subproject-summary on the command line."
+              },
+              "maxPassedTestOutputSize": {
+                "type": "integer",
+                "description": "An optional integer specifying the maximum output for passed tests in bytes. Equivalent to passing --test-output-size-passed on the command line."
+              },
+              "maxFailedTestOutputSize": {
+                "type": "integer",
+                "description": "An optional integer specifying the maximum output for failed tests in bytes. Equivalent to passing --test-output-size-failed on the command line."
+              },
+              "maxTestNameWidth": {
+                "type": "integer",
+                "description": "An optional integer specifying the maximum width of a test name to output. Equivalent to passing --max-width on the command line."
+              }
+            },
+            "additionalProperties": false
+          },
+          "filter": {
+            "type": "object",
+            "description": "An optional object specifying how to filter the tests to run.",
+            "properties": {
+              "include": {
+                "type": "object",
+                "description": "An optional object specifying which tests to include.",
+                "properties": {
+                  "name": {
+                    "type": "string",
+                    "description": "An optional string specifying a regex for test names. Equivalent to passing --tests-regex on the command line."
+                  },
+                  "label": {
+                    "type": "string",
+                    "description": "An optional string specifying a regex for test labels. Equivalent to passing --label-regex on the command line."
+                  },
+                  "index": {
+                    "anyOf": [
+                      {
+                        "type": "object",
+                        "description": "An optional object specifying tests to include by test index.",
+                        "properties": {
+                          "start": {
+                            "type": "integer",
+                            "description": "An optional integer specifying a test index to start testing at."
+                          },
+                          "end": {
+                            "type": "integer",
+                            "description": "An optional integer specifying a test index to stop testing at."
+                          },
+                          "stride": {
+                            "type": "integer",
+                            "description": "An optional integer specifying the increment."
+                          },
+                          "specificTests": {
+                            "type": "array",
+                            "description": "An optional array of integers specifying specific test indices to run.",
+                            "items": {
+                              "type": "integer",
+                              "description": "An integer specifying the test to run by index."
+                            }
+                          }
+                        },
+                        "additionalProperties": false
+                      },
+                      {
+                        "type": "string",
+                        "description": "An optional string specifying a file with the command line syntax for --tests-information."
+                      }
+                    ]
+                  },
+                  "useUnion": {
+                    "type": "boolean",
+                    "description": "An optional bool. Equivalent to passing --union on the command line."
+                  }
+                },
+                "additionalProperties": false
+              },
+              "exclude": {
+                "type": "object",
+                "description": "An optional object specifying which tests to exclude.",
+                "properties": {
+                  "name": {
+                    "type": "string",
+                    "description": "An optional string specifying a regex for test names. Equivalent to passing --exclude-regex on the command line."
+                  },
+                  "label": {
+                    "type": "string",
+                    "description": "An optional string specifying a regex for test labels. Equivalent to passing --label-exclude on the command line."
+                  },
+                  "fixtures": {
+                    "type": "object",
+                    "description": "An optional object specifying which fixtures to exclude from adding tests.",
+                    "properties": {
+                      "any": {
+                        "type": "string",
+                        "description": "An optional string specifying a regex for text fixtures to exclude from adding any tests. Equivalent to --fixture-exclude-any on the command line."
+                      },
+                      "setup": {
+                        "type": "string",
+                        "description": "An optional string specifying a regex for text fixtures to exclude from adding setup tests. Equivalent to --fixture-exclude-setup on the command line."
+                      },
+                      "cleanup": {
+                        "type": "string",
+                        "description": "An optional string specifying a regex for text fixtures to exclude from adding cleanup tests. Equivalent to --fixture-exclude-cleanup on the command line."
+                      }
+                    },
+                    "additionalProperties": false
+                  }
+                }
+              }
+            },
+            "additionalProperties": false
+          },
+          "execution": {
+            "type": "object",
+            "description": "An optional object specifying options for test execution.",
+            "properties": {
+              "stopOnFailure": {
+                "type": "boolean",
+                "description": "An optional bool. If true, equivalent to passing --stop-on-failure on the command line."
+              },
+              "enableFailover": {
+                "type": "boolean",
+                "description": "An optional bool. If true, equivalent to passing -F on the command line."
+              },
+              "jobs": {
+                "type": "integer",
+                "description": "An optional integer. Equivalent to passing --parallel on the command line."
+              },
+              "resourceSpecFile": {
+                "type": "string",
+                "description": "An optional string. Equivalent to passing --resource-spec-file on the command line."
+              },
+              "testLoad": {
+                "type": "integer",
+                "description": "An optional integer. Equivalent to passing --test-load on the command line."
+              },
+              "showOnly": {
+                "type": "string",
+                "description": "An optional string. Equivalent to passing --show-only on the command line. Value must be \"human\" or \"json-v1\".",
+                "enum": [
+                  "human", "json-v1"
+                ]
+              },
+              "repeat": {
+                "type": "object",
+                "description": "An optional object specifying how to repeat tests. Equivalent to passing --repeat on the command line.",
+                "properties": {
+                  "mode": {
+                    "type": "string",
+                    "description": "A required string. Must be one of the following values: \"until-fail\", \"until-pass\", or \"after-timeout\".",
+                    "enum": [
+                      "until-fail", "until-pass", "after-timeout"
+                    ]
+                  },
+                  "count": {
+                    "type": "integer",
+                    "description": "A required integer."
+                  }
+                },
+                "required": [
+                  "mode", "count"
+                ],
+                "additionalProperties": false
+              },
+              "interactiveDebugging": {
+                "type": "boolean",
+                "description": "An optional bool. If true, equivalent to passing --interactive-debug-mode 1 on the command line. If false, equivalent to passing --interactive-debug-mode 0 on the command line."
+              },
+              "scheduleRandom": {
+                "type": "boolean",
+                "description": "An optional bool. If true, equivalent to passing --schedule-random on the command line."
+              },
+              "timeout": {
+                "type": "integer",
+                "description": "An optional integer. Equivalent to passing --timeout on the command line."
+              },
+              "noTestsAction": {
+                "type": "string",
+                "description": "An optional string specifying the behavior if no tests are found. Must be one of the following values: \"default\" (equivalent to not passing any value on the command line), \"error\" (equivalent to passing --no-tests=error on the command line), or \"ignore\" (equivalent to passing --no-tests-ignore on the command line).",
+                "enum": [
+                  "default", "error", "ignore"
+                ]
+              }
+            },
+            "additionalProperties": false
+          }
+        },
+        "required": [
+          "name"
+        ],
+        "additionalProperties": false
+      }
     }
-  },
-  "required": [
-    "version"
-  ],
-  "additionalProperties": false
+  }
 }

+ 4 - 0
Help/release/dev/build-test-presets.rst

@@ -0,0 +1,4 @@
+build-test-presets
+------------------
+
+* :manual:`cmake-presets(7)` gained support for build and test presets.

+ 7 - 6
Source/QtDialog/QCMake.cxx

@@ -68,9 +68,9 @@ QCMake::QCMake(QObject* p)
   connect(&this->LoadPresetsTimer, &QTimer::timeout, this, [this]() {
     this->loadPresets();
     if (!this->PresetName.isEmpty() &&
-        this->CMakePresetsFile.Presets.find(
+        this->CMakePresetsFile.ConfigurePresets.find(
           std::string(this->PresetName.toLocal8Bit())) ==
-          this->CMakePresetsFile.Presets.end()) {
+          this->CMakePresetsFile.ConfigurePresets.end()) {
       this->setPreset(QString{});
     }
   });
@@ -158,7 +158,7 @@ void QCMake::setPreset(const QString& name, bool setBinary)
     if (!name.isNull()) {
       std::string presetName(name.toLocal8Bit());
       auto const& expandedPreset =
-        this->CMakePresetsFile.Presets[presetName].Expanded;
+        this->CMakePresetsFile.ConfigurePresets[presetName].Expanded;
       if (expandedPreset) {
         if (setBinary) {
           QString binaryDir =
@@ -420,7 +420,8 @@ QCMakePropertyList QCMake::properties() const
 
   if (!this->PresetName.isNull()) {
     std::string presetName(this->PresetName.toLocal8Bit());
-    auto const& p = this->CMakePresetsFile.Presets.at(presetName).Expanded;
+    auto const& p =
+      this->CMakePresetsFile.ConfigurePresets.at(presetName).Expanded;
     if (p) {
       for (auto const& v : p->CacheVariables) {
         if (!v.second) {
@@ -535,8 +536,8 @@ void QCMake::loadPresets()
   this->LastLoadPresetsResult = result;
 
   QVector<QCMakePreset> presets;
-  for (auto const& name : this->CMakePresetsFile.PresetOrder) {
-    auto const& it = this->CMakePresetsFile.Presets[name];
+  for (auto const& name : this->CMakePresetsFile.ConfigurePresetOrder) {
+    auto const& it = this->CMakePresetsFile.ConfigurePresets[name];
     auto const& p = it.Unexpanded;
     if (p.Hidden) {
       continue;

File diff suppressed because it is too large
+ 840 - 195
Source/cmCMakePresetsFile.cxx


+ 251 - 56
Source/cmCMakePresetsFile.h

@@ -2,6 +2,7 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #pragma once
 
+#include <functional>
 #include <map>
 #include <string>
 #include <utility>
@@ -12,6 +13,27 @@
 class cmCMakePresetsFile
 {
 public:
+  enum class ReadFileResult
+  {
+    READ_OK,
+    FILE_NOT_FOUND,
+    JSON_PARSE_ERROR,
+    INVALID_ROOT,
+    NO_VERSION,
+    INVALID_VERSION,
+    UNRECOGNIZED_VERSION,
+    INVALID_CMAKE_VERSION,
+    UNRECOGNIZED_CMAKE_VERSION,
+    INVALID_PRESETS,
+    INVALID_PRESET,
+    INVALID_VARIABLE,
+    DUPLICATE_PRESETS,
+    CYCLIC_PRESET_INHERITANCE,
+    USER_PRESET_INHERITANCE,
+    INVALID_MACRO_EXPANSION,
+    BUILD_TEST_PRESETS_UNSUPPORTED,
+  };
+
   enum class ArchToolsetStrategy
   {
     Set,
@@ -29,25 +51,51 @@ public:
   {
   public:
 #if __cplusplus < 201703L && (!defined(_MSVC_LANG) || _MSVC_LANG < 201703L)
-    Preset() = default;
-    Preset(const Preset& /*other*/) = default;
-    Preset(Preset&& /*other*/) = default;
-
-    Preset& operator=(const Preset& /*other*/) = default;
-
     // The move assignment operators for several STL classes did not become
     // noexcept until C++17, which causes some tools to warn about this move
     // assignment operator throwing an exception when it shouldn't. Disable the
     // move assignment operator until C++17 is enabled.
-    Preset& operator=(Preset&& /*other*/) = delete;
+    // Explicitly defining a copy assignment operator prevents the compiler
+    // from automatically generating a move assignment operator.
+    Preset& operator=(const Preset& /*other*/) = default;
 #endif
 
+    virtual ~Preset() = default;
+
     std::string Name;
     std::vector<std::string> Inherits;
     bool Hidden;
     bool User;
     std::string DisplayName;
     std::string Description;
+
+    std::map<std::string, cm::optional<std::string>> Environment;
+
+    virtual ReadFileResult VisitPresetInherit(const Preset& parent) = 0;
+    virtual ReadFileResult VisitPresetBeforeInherit()
+    {
+      return ReadFileResult::READ_OK;
+    }
+
+    virtual ReadFileResult VisitPresetAfterInherit()
+    {
+      return ReadFileResult::READ_OK;
+    }
+  };
+
+  class ConfigurePreset : public Preset
+  {
+  public:
+#if __cplusplus < 201703L && (!defined(_MSVC_LANG) || _MSVC_LANG < 201703L)
+    // The move assignment operators for several STL classes did not become
+    // noexcept until C++17, which causes some tools to warn about this move
+    // assignment operator throwing an exception when it shouldn't. Disable the
+    // move assignment operator until C++17 is enabled.
+    // Explicitly defining a copy assignment operator prevents the compiler
+    // from automatically generating a move assignment operator.
+    ConfigurePreset& operator=(const ConfigurePreset& /*other*/) = default;
+#endif
+
     std::string Generator;
     std::string Architecture;
     cm::optional<ArchToolsetStrategy> ArchitectureStrategy;
@@ -56,7 +104,6 @@ public:
     std::string BinaryDir;
 
     std::map<std::string, cm::optional<CacheVariable>> CacheVariables;
-    std::map<std::string, cm::optional<std::string>> Environment;
 
     cm::optional<bool> WarnDev;
     cm::optional<bool> ErrorDev;
@@ -69,70 +116,183 @@ public:
     cm::optional<bool> DebugOutput;
     cm::optional<bool> DebugTryCompile;
     cm::optional<bool> DebugFind;
+
+    ReadFileResult VisitPresetInherit(const Preset& parent) override;
+    ReadFileResult VisitPresetBeforeInherit() override;
+    ReadFileResult VisitPresetAfterInherit() override;
   };
 
-  class UnexpandedPreset : public Preset
+  class BuildPreset : public Preset
   {
   public:
-    using Preset::Preset;
+#if __cplusplus < 201703L && (!defined(_MSVC_LANG) || _MSVC_LANG < 201703L)
+    // The move assignment operators for several STL classes did not become
+    // noexcept until C++17, which causes some tools to warn about this move
+    // assignment operator throwing an exception when it shouldn't. Disable the
+    // move assignment operator until C++17 is enabled.
+    // Explicitly defining a copy assignment operator prevents the compiler
+    // from automatically generating a move assignment operator.
+    BuildPreset& operator=(const BuildPreset& /*other*/) = default;
+#endif
 
-    UnexpandedPreset() = default;
-    UnexpandedPreset(const Preset& preset)
-      : Preset(preset)
-    {
-    }
-    UnexpandedPreset(Preset&& preset)
-      : Preset(std::move(preset))
-    {
-    }
+    std::string ConfigurePreset;
+    cm::optional<bool> InheritConfigureEnvironment;
+    cm::optional<int> Jobs;
+    std::vector<std::string> Targets;
+    std::string Configuration;
+    cm::optional<bool> CleanFirst;
+    cm::optional<bool> Verbose;
+    std::vector<std::string> NativeToolOptions;
+
+    ReadFileResult VisitPresetInherit(const Preset& parent) override;
+    ReadFileResult VisitPresetAfterInherit() override;
   };
 
-  class ExpandedPreset : public Preset
+  class TestPreset : public Preset
   {
   public:
-    using Preset::Preset;
+#if __cplusplus < 201703L && (!defined(_MSVC_LANG) || _MSVC_LANG < 201703L)
+    // The move assignment operators for several STL classes did not become
+    // noexcept until C++17, which causes some tools to warn about this move
+    // assignment operator throwing an exception when it shouldn't. Disable the
+    // move assignment operator until C++17 is enabled.
+    // Explicitly defining a copy assignment operator prevents the compiler
+    // from automatically generating a move assignment operator.
+    TestPreset& operator=(const TestPreset& /*other*/) = default;
+#endif
 
-    ExpandedPreset() = default;
-    ExpandedPreset(const Preset& preset)
-      : Preset(preset)
+    struct OutputOptions
     {
-    }
-    ExpandedPreset(Preset&& preset)
-      : Preset(std::move(preset))
+      enum class VerbosityEnum
+      {
+        Default,
+        Verbose,
+        Extra
+      };
+
+      cm::optional<bool> ShortProgress;
+      cm::optional<VerbosityEnum> Verbosity;
+      cm::optional<bool> Debug;
+      cm::optional<bool> OutputOnFailure;
+      cm::optional<bool> Quiet;
+      std::string OutputLogFile;
+      cm::optional<bool> LabelSummary;
+      cm::optional<bool> SubprojectSummary;
+      cm::optional<int> MaxPassedTestOutputSize;
+      cm::optional<int> MaxFailedTestOutputSize;
+      cm::optional<int> MaxTestNameWidth;
+    };
+
+    struct IncludeOptions
     {
-    }
+      struct IndexOptions
+      {
+        cm::optional<int> Start;
+        cm::optional<int> End;
+        cm::optional<int> Stride;
+        std::vector<int> SpecificTests;
+
+        std::string IndexFile;
+      };
+
+      std::string Name;
+      std::string Label;
+      cm::optional<IndexOptions> Index;
+      cm::optional<bool> UseUnion;
+    };
+
+    struct ExcludeOptions
+    {
+      struct FixturesOptions
+      {
+        std::string Any;
+        std::string Setup;
+        std::string Cleanup;
+      };
+
+      std::string Name;
+      std::string Label;
+      cm::optional<FixturesOptions> Fixtures;
+    };
+
+    struct FilterOptions
+    {
+      cm::optional<IncludeOptions> Include;
+      cm::optional<ExcludeOptions> Exclude;
+    };
+
+    struct ExecutionOptions
+    {
+      enum class ShowOnlyEnum
+      {
+        Human,
+        JsonV1
+      };
+
+      struct RepeatOptions
+      {
+        enum class ModeEnum
+        {
+          UntilFail,
+          UntilPass,
+          AfterTimeout
+        };
+
+        ModeEnum Mode;
+        int Count;
+      };
+
+      enum class NoTestsActionEnum
+      {
+        Default,
+        Error,
+        Ignore
+      };
+
+      cm::optional<bool> StopOnFailure;
+      cm::optional<bool> EnableFailover;
+      cm::optional<int> Jobs;
+      std::string ResourceSpecFile;
+      cm::optional<int> TestLoad;
+      cm::optional<ShowOnlyEnum> ShowOnly;
+      cm::optional<bool> RerunFailed;
+
+      cm::optional<RepeatOptions> Repeat;
+      cm::optional<bool> InteractiveDebugging;
+      cm::optional<bool> ScheduleRandom;
+      cm::optional<int> Timeout;
+      cm::optional<NoTestsActionEnum> NoTestsAction;
+    };
+
+    std::string ConfigurePreset;
+    cm::optional<bool> InheritConfigureEnvironment;
+    std::string Configuration;
+    std::vector<std::string> OverwriteConfigurationFile;
+    cm::optional<OutputOptions> Output;
+    cm::optional<FilterOptions> Filter;
+    cm::optional<ExecutionOptions> Execution;
+
+    ReadFileResult VisitPresetInherit(const Preset& parent) override;
+    ReadFileResult VisitPresetAfterInherit() override;
   };
 
+  template <class T>
   class PresetPair
   {
   public:
-    UnexpandedPreset Unexpanded;
-    cm::optional<ExpandedPreset> Expanded;
+    T Unexpanded;
+    cm::optional<T> Expanded;
   };
 
-  std::string SourceDir;
-  std::map<std::string, PresetPair> Presets;
-  std::vector<std::string> PresetOrder;
+  std::map<std::string, PresetPair<ConfigurePreset>> ConfigurePresets;
+  std::map<std::string, PresetPair<BuildPreset>> BuildPresets;
+  std::map<std::string, PresetPair<TestPreset>> TestPresets;
 
-  enum class ReadFileResult
-  {
-    READ_OK,
-    FILE_NOT_FOUND,
-    JSON_PARSE_ERROR,
-    INVALID_ROOT,
-    NO_VERSION,
-    INVALID_VERSION,
-    UNRECOGNIZED_VERSION,
-    INVALID_CMAKE_VERSION,
-    UNRECOGNIZED_CMAKE_VERSION,
-    INVALID_PRESETS,
-    INVALID_PRESET,
-    INVALID_VARIABLE,
-    DUPLICATE_PRESETS,
-    CYCLIC_PRESET_INHERITANCE,
-    USER_PRESET_INHERITANCE,
-    INVALID_MACRO_EXPANSION,
-  };
+  std::vector<std::string> ConfigurePresetOrder;
+  std::vector<std::string> BuildPresetOrder;
+  std::vector<std::string> TestPresetOrder;
+
+  std::string SourceDir;
 
   static std::string GetFilename(const std::string& sourceDir);
   static std::string GetUserFilename(const std::string& sourceDir);
@@ -140,9 +300,44 @@ public:
                                     bool allowNoFiles = false);
   static const char* ResultToString(ReadFileResult result);
 
+  std::string GetGeneratorForPreset(const std::string& presetName) const
+  {
+    auto configurePresetName = presetName;
+
+    auto buildPresetIterator = this->BuildPresets.find(presetName);
+    if (buildPresetIterator != this->BuildPresets.end()) {
+      configurePresetName =
+        buildPresetIterator->second.Unexpanded.ConfigurePreset;
+    } else {
+      auto testPresetIterator = this->TestPresets.find(presetName);
+      if (testPresetIterator != this->TestPresets.end()) {
+        configurePresetName =
+          testPresetIterator->second.Unexpanded.ConfigurePreset;
+      }
+    }
+
+    auto configurePresetIterator =
+      this->ConfigurePresets.find(configurePresetName);
+    if (configurePresetIterator != this->ConfigurePresets.end()) {
+      return configurePresetIterator->second.Unexpanded.Generator;
+    }
+
+    // This should only happen if the preset is hidden
+    // or (for build or test presets) if ConfigurePreset is invalid.
+    return "";
+  }
+
+  static void PrintPresets(
+    const std::vector<const cmCMakePresetsFile::Preset*>& presets);
+  void PrintConfigurePresetList() const;
+  void PrintConfigurePresetList(
+    const std::function<bool(const ConfigurePreset&)>& filter) const;
+  void PrintBuildPresetList() const;
+  void PrintTestPresetList() const;
+  void PrintAllPresets() const;
+
 private:
-  ReadFileResult ReadJSONFile(const std::string& filename,
-                              std::vector<std::string>& presetOrder,
-                              std::map<std::string, PresetPair>& presetMap,
-                              bool user);
+  ReadFileResult ReadProjectPresetsInternal(bool allowNoFiles);
+  ReadFileResult ReadJSONFile(const std::string& filename, bool user);
+  void ClearPresets();
 };

+ 339 - 1
Source/cmCTest.cxx

@@ -18,6 +18,7 @@
 #include <vector>
 
 #include <cm/memory>
+#include <cm/optional>
 #include <cm/string_view>
 #include <cmext/algorithm>
 #include <cmext/string_view>
@@ -38,6 +39,7 @@
 #  include <unistd.h> // IWYU pragma: keep
 #endif
 
+#include "cmCMakePresetsFile.h"
 #include "cmCTestBuildAndTestHandler.h"
 #include "cmCTestBuildHandler.h"
 #include "cmCTestConfigureHandler.h"
@@ -2257,6 +2259,311 @@ bool cmCTest::AddVariableDefinition(const std::string& arg)
   return false;
 }
 
+void cmCTest::SetPersistentOptionIfNotEmpty(const std::string& value,
+                                            const std::string& optionName)
+{
+  if (!value.empty()) {
+    this->GetTestHandler()->SetPersistentOption(optionName, value.c_str());
+    this->GetMemCheckHandler()->SetPersistentOption(optionName, value.c_str());
+  }
+}
+
+bool cmCTest::SetArgsFromPreset(const std::string& presetName,
+                                bool listPresets)
+{
+  const auto workingDirectory = cmSystemTools::GetCurrentWorkingDirectory();
+
+  cmCMakePresetsFile settingsFile;
+  auto result = settingsFile.ReadProjectPresets(workingDirectory);
+  if (result != cmCMakePresetsFile::ReadFileResult::READ_OK) {
+    cmSystemTools::Error(cmStrCat("Could not read presets from ",
+                                  workingDirectory, ": ",
+                                  cmCMakePresetsFile::ResultToString(result)));
+    return false;
+  }
+
+  if (listPresets) {
+    settingsFile.PrintTestPresetList();
+    return true;
+  }
+
+  auto presetPair = settingsFile.TestPresets.find(presetName);
+  if (presetPair == settingsFile.TestPresets.end()) {
+    cmSystemTools::Error(cmStrCat("No such test preset in ", workingDirectory,
+                                  ": \"", presetName, '"'));
+    settingsFile.PrintTestPresetList();
+    return false;
+  }
+
+  if (presetPair->second.Unexpanded.Hidden) {
+    cmSystemTools::Error(cmStrCat("Cannot use hidden test preset in ",
+                                  workingDirectory, ": \"", presetName, '"'));
+    settingsFile.PrintTestPresetList();
+    return false;
+  }
+
+  auto const& expandedPreset = presetPair->second.Expanded;
+  if (!expandedPreset) {
+    cmSystemTools::Error(cmStrCat("Could not evaluate test preset \"",
+                                  presetName, "\": Invalid macro expansion"));
+    settingsFile.PrintTestPresetList();
+    return false;
+  }
+
+  auto configurePresetPair =
+    settingsFile.ConfigurePresets.find(expandedPreset->ConfigurePreset);
+  if (configurePresetPair == settingsFile.ConfigurePresets.end()) {
+    cmSystemTools::Error(cmStrCat("No such configure preset in ",
+                                  workingDirectory, ": \"",
+                                  expandedPreset->ConfigurePreset, '"'));
+    settingsFile.PrintConfigurePresetList();
+    return false;
+  }
+
+  if (configurePresetPair->second.Unexpanded.Hidden) {
+    cmSystemTools::Error(cmStrCat("Cannot use hidden configure preset in ",
+                                  workingDirectory, ": \"",
+                                  expandedPreset->ConfigurePreset, '"'));
+    settingsFile.PrintConfigurePresetList();
+    return false;
+  }
+
+  auto const& expandedConfigurePreset = configurePresetPair->second.Expanded;
+  if (!expandedConfigurePreset) {
+    cmSystemTools::Error(cmStrCat("Could not evaluate configure preset \"",
+                                  expandedPreset->ConfigurePreset,
+                                  "\": Invalid macro expansion"));
+    return false;
+  }
+
+  auto presetEnvironment = expandedPreset->Environment;
+  for (auto const& var : presetEnvironment) {
+    if (var.second) {
+      cmSystemTools::PutEnv(cmStrCat(var.first, '=', *var.second));
+    }
+  }
+
+  if (!expandedPreset->Configuration.empty()) {
+    this->SetConfigType(expandedPreset->Configuration);
+  }
+
+  // Set build directory to value specified by the configure preset.
+  this->AddCTestConfigurationOverwrite(
+    cmStrCat("BuildDirectory=", expandedConfigurePreset->BinaryDir));
+  for (const auto& kvp : expandedPreset->OverwriteConfigurationFile) {
+    this->AddCTestConfigurationOverwrite(kvp);
+  }
+
+  if (expandedPreset->Output) {
+    this->Impl->TestProgressOutput =
+      expandedPreset->Output->ShortProgress.value_or(false);
+
+    if (expandedPreset->Output->Verbosity) {
+      const auto& verbosity = *expandedPreset->Output->Verbosity;
+      switch (verbosity) {
+        case cmCMakePresetsFile::TestPreset::OutputOptions::VerbosityEnum::
+          Extra:
+          this->Impl->ExtraVerbose = true;
+          // intentional fallthrough
+        case cmCMakePresetsFile::TestPreset::OutputOptions::VerbosityEnum::
+          Verbose:
+          this->Impl->Verbose = true;
+          break;
+        case cmCMakePresetsFile::TestPreset::OutputOptions::VerbosityEnum::
+          Default:
+        default:
+          // leave default settings
+          break;
+      }
+    }
+
+    this->Impl->Debug = expandedPreset->Output->Debug.value_or(false);
+    this->Impl->ShowLineNumbers =
+      expandedPreset->Output->Debug.value_or(false);
+    this->Impl->OutputTestOutputOnTestFailure =
+      expandedPreset->Output->OutputOnFailure.value_or(false);
+    this->Impl->Quiet = expandedPreset->Output->Quiet.value_or(false);
+
+    if (!expandedPreset->Output->OutputLogFile.empty()) {
+      this->SetOutputLogFileName(expandedPreset->Output->OutputLogFile);
+    }
+
+    this->Impl->LabelSummary =
+      expandedPreset->Output->LabelSummary.value_or(true);
+    this->Impl->SubprojectSummary =
+      expandedPreset->Output->SubprojectSummary.value_or(true);
+
+    if (expandedPreset->Output->MaxPassedTestOutputSize) {
+      this->Impl->TestHandler.SetTestOutputSizePassed(
+        *expandedPreset->Output->MaxPassedTestOutputSize);
+    }
+
+    if (expandedPreset->Output->MaxFailedTestOutputSize) {
+      this->Impl->TestHandler.SetTestOutputSizeFailed(
+        *expandedPreset->Output->MaxFailedTestOutputSize);
+    }
+
+    if (expandedPreset->Output->MaxTestNameWidth) {
+      this->Impl->MaxTestNameWidth = *expandedPreset->Output->MaxTestNameWidth;
+    }
+  }
+
+  if (expandedPreset->Filter) {
+    if (expandedPreset->Filter->Include) {
+      this->SetPersistentOptionIfNotEmpty(
+        expandedPreset->Filter->Include->Name, "IncludeRegularExpression");
+      this->SetPersistentOptionIfNotEmpty(
+        expandedPreset->Filter->Include->Label, "LabelRegularExpression");
+
+      if (expandedPreset->Filter->Include->Index) {
+        if (expandedPreset->Filter->Include->Index->IndexFile.empty()) {
+          const auto& start = expandedPreset->Filter->Include->Index->Start;
+          const auto& end = expandedPreset->Filter->Include->Index->End;
+          const auto& stride = expandedPreset->Filter->Include->Index->Stride;
+          std::string indexOptions;
+          indexOptions += (start ? std::to_string(*start) : "") + ",";
+          indexOptions += (end ? std::to_string(*end) : "") + ",";
+          indexOptions += (stride ? std::to_string(*stride) : "") + ",";
+          indexOptions +=
+            cmJoin(expandedPreset->Filter->Include->Index->SpecificTests, ",");
+
+          this->SetPersistentOptionIfNotEmpty(indexOptions,
+                                              "TestsToRunInformation");
+        } else {
+          this->SetPersistentOptionIfNotEmpty(
+            expandedPreset->Filter->Include->Index->IndexFile,
+            "TestsToRunInformation");
+        }
+      }
+
+      if (expandedPreset->Filter->Include->UseUnion.value_or(false)) {
+        this->GetTestHandler()->SetPersistentOption("UseUnion", "true");
+        this->GetMemCheckHandler()->SetPersistentOption("UseUnion", "true");
+      }
+    }
+
+    if (expandedPreset->Filter->Exclude) {
+      this->SetPersistentOptionIfNotEmpty(
+        expandedPreset->Filter->Exclude->Name, "ExcludeRegularExpression");
+      this->SetPersistentOptionIfNotEmpty(
+        expandedPreset->Filter->Exclude->Label,
+        "ExcludeLabelRegularExpression");
+
+      if (expandedPreset->Filter->Exclude->Fixtures) {
+        this->SetPersistentOptionIfNotEmpty(
+          expandedPreset->Filter->Exclude->Fixtures->Any,
+          "ExcludeFixtureRegularExpression");
+        this->SetPersistentOptionIfNotEmpty(
+          expandedPreset->Filter->Exclude->Fixtures->Setup,
+          "ExcludeFixtureSetupRegularExpression");
+        this->SetPersistentOptionIfNotEmpty(
+          expandedPreset->Filter->Exclude->Fixtures->Cleanup,
+          "ExcludeFixtureCleanupRegularExpression");
+      }
+    }
+  }
+
+  if (expandedPreset->Execution) {
+    this->Impl->StopOnFailure =
+      expandedPreset->Execution->StopOnFailure.value_or(false);
+    this->Impl->Failover =
+      expandedPreset->Execution->EnableFailover.value_or(false);
+
+    if (expandedPreset->Execution->Jobs) {
+      auto jobs = *expandedPreset->Execution->Jobs;
+      this->SetParallelLevel(jobs);
+      this->Impl->ParallelLevelSetInCli = true;
+    }
+
+    this->SetPersistentOptionIfNotEmpty(
+      expandedPreset->Execution->ResourceSpecFile, "ResourceSpecFile");
+
+    if (expandedPreset->Execution->TestLoad) {
+      auto testLoad = *expandedPreset->Execution->TestLoad;
+      this->SetTestLoad(testLoad);
+    }
+
+    if (expandedPreset->Execution->ShowOnly) {
+      this->Impl->ShowOnly = true;
+
+      switch (*expandedPreset->Execution->ShowOnly) {
+        case cmCMakePresetsFile::TestPreset::ExecutionOptions::ShowOnlyEnum::
+          JsonV1:
+          this->Impl->Quiet = true;
+          this->Impl->OutputAsJson = true;
+          this->Impl->OutputAsJsonVersion = 1;
+          break;
+        case cmCMakePresetsFile::TestPreset::ExecutionOptions::ShowOnlyEnum::
+          Human:
+          // intentional fallthrough (human is the default)
+        default:
+          break;
+      }
+    }
+
+    if (expandedPreset->Execution->RerunFailed.value_or(false)) {
+      this->GetTestHandler()->SetPersistentOption("RerunFailed", "true");
+      this->GetMemCheckHandler()->SetPersistentOption("RerunFailed", "true");
+    }
+
+    if (expandedPreset->Execution->Repeat) {
+      this->Impl->RepeatCount = expandedPreset->Execution->Repeat->Count;
+      switch (expandedPreset->Execution->Repeat->Mode) {
+        case cmCMakePresetsFile::TestPreset::ExecutionOptions::RepeatOptions::
+          ModeEnum::UntilFail:
+          this->Impl->RepeatMode = cmCTest::Repeat::UntilFail;
+          break;
+        case cmCMakePresetsFile::TestPreset::ExecutionOptions::RepeatOptions::
+          ModeEnum::UntilPass:
+          this->Impl->RepeatMode = cmCTest::Repeat::UntilPass;
+          break;
+        case cmCMakePresetsFile::TestPreset::ExecutionOptions::RepeatOptions::
+          ModeEnum::AfterTimeout:
+          this->Impl->RepeatMode = cmCTest::Repeat::AfterTimeout;
+          break;
+        default:
+          // should never default since mode is required
+          return false;
+      }
+    }
+
+    if (expandedPreset->Execution->InteractiveDebugging) {
+      this->Impl->InteractiveDebugMode =
+        *expandedPreset->Execution->InteractiveDebugging;
+    }
+
+    if (expandedPreset->Execution->ScheduleRandom.value_or(false)) {
+      this->Impl->ScheduleType = "Random";
+    }
+
+    if (expandedPreset->Execution->Timeout) {
+      this->Impl->GlobalTimeout =
+        cmDuration(*expandedPreset->Execution->Timeout);
+    }
+
+    if (expandedPreset->Execution->NoTestsAction) {
+      switch (*expandedPreset->Execution->NoTestsAction) {
+        case cmCMakePresetsFile::TestPreset::ExecutionOptions::
+          NoTestsActionEnum::Error:
+          this->Impl->NoTestsMode = cmCTest::NoTests::Error;
+          break;
+        case cmCMakePresetsFile::TestPreset::ExecutionOptions::
+          NoTestsActionEnum::Ignore:
+          this->Impl->NoTestsMode = cmCTest::NoTests::Ignore;
+          break;
+        case cmCMakePresetsFile::TestPreset::ExecutionOptions::
+          NoTestsActionEnum::Default:
+          break;
+        default:
+          // should never default
+          return false;
+      }
+    }
+  }
+
+  return true;
+}
+
 // the main entry point of ctest, called from main
 int cmCTest::Run(std::vector<std::string>& args, std::string* output)
 {
@@ -2268,6 +2575,37 @@ int cmCTest::Run(std::vector<std::string>& args, std::string* output)
   // copy the command line
   cm::append(this->Impl->InitialCommandLineArguments, args);
 
+  // check if a test preset was specified
+
+  bool listPresets =
+    find(args.begin(), args.end(), "--list-presets") != args.end();
+  auto it = find(args.begin(), args.end(), "--preset");
+  if (listPresets || it != args.end()) {
+    std::string errormsg;
+    bool success;
+
+    if (listPresets) {
+      // If listing presets we don't need a presetName
+      success = this->SetArgsFromPreset("", listPresets);
+    } else {
+      if (++it != args.end()) {
+        auto presetName = *it;
+        success = this->SetArgsFromPreset(presetName, listPresets);
+      } else {
+        cmSystemTools::Error("'--preset' requires an argument");
+        success = false;
+      }
+    }
+
+    if (listPresets) {
+      return success ? 0 : 1;
+    }
+
+    if (!success) {
+      return 1;
+    }
+  }
+
   // process the command line arguments
   for (size_t i = 1; i < args.size(); ++i) {
     // handle the simple commandline arguments
@@ -2339,7 +2677,7 @@ int cmCTest::Run(std::vector<std::string>& args, std::string* output)
       this->Impl->ScheduleType = "Random";
     }
 
-    // pass the argument to all the handlers as well, but i may no longer be
+    // pass the argument to all the handlers as well, but it may no longer be
     // set to what it was originally so I'm not sure this is working as
     // intended
     for (auto& handler : this->Impl->GetTestingHandlers()) {

+ 6 - 0
Source/cmCTest.h

@@ -461,6 +461,9 @@ public:
   void SetRunCurrentScript(bool value);
 
 private:
+  void SetPersistentOptionIfNotEmpty(const std::string& value,
+                                     const std::string& optionName);
+
   int GenerateNotesFile(const std::string& files);
 
   void BlockTestErrorDiagnostics();
@@ -484,6 +487,9 @@ private:
   /** add a variable definition from a command line -D value */
   bool AddVariableDefinition(const std::string& arg);
 
+  /** set command line arguments read from a test preset */
+  bool SetArgsFromPreset(const std::string& presetName, bool listPresets);
+
   /** parse and process most common command line arguments */
   bool HandleCommandLineArguments(size_t& i, std::vector<std::string>& args,
                                   std::string& errormsg);

+ 170 - 54
Source/cmake.cxx

@@ -726,6 +726,17 @@ void cmake::LoadEnvironmentPresets()
   readGeneratorVar("CMAKE_GENERATOR_TOOLSET", this->GeneratorToolset);
 }
 
+namespace {
+enum class ListPresets
+{
+  None,
+  Configure,
+  Build,
+  Test,
+  All,
+};
+}
+
 // Parse the args
 void cmake::SetArgs(const std::vector<std::string>& args)
 {
@@ -738,7 +749,8 @@ void cmake::SetArgs(const std::vector<std::string>& args)
   std::string profilingFormat;
   std::string profilingOutput;
   std::string presetName;
-  bool listPresets = false;
+
+  ListPresets listPresets = ListPresets::None;
 #endif
 
   auto SourceArgLambda = [](std::string const& value, cmake* state) -> bool {
@@ -995,11 +1007,27 @@ void cmake::SetArgs(const std::vector<std::string>& args)
                            presetName = value;
                            return true;
                          });
-  arguments.emplace_back("--list-presets", CommandArgument::Values::Zero,
-                         [&](std::string const&, cmake*) -> bool {
-                           listPresets = true;
-                           return true;
-                         });
+  arguments.emplace_back(
+    "--list-presets", CommandArgument::Values::ZeroOrOne,
+    [&](std::string const& value, cmake*) -> bool {
+      if (value.empty() || value == "configure") {
+        listPresets = ListPresets::Configure;
+      } else if (value == "build") {
+        listPresets = ListPresets::Build;
+      } else if (value == "test") {
+        listPresets = ListPresets::Test;
+      } else if (value == "all") {
+        listPresets = ListPresets::All;
+      } else {
+        cmSystemTools::Error(
+          "Invalid value specified for --list-presets.\n"
+          "Valid values are configure, build, test, or all. "
+          "When no value is passed the default is configure.");
+        return false;
+      }
+
+      return true;
+    });
 
 #endif
 
@@ -1119,7 +1147,7 @@ void cmake::SetArgs(const std::vector<std::string>& args)
   }
 
 #if !defined(CMAKE_BOOTSTRAP)
-  if (listPresets || !presetName.empty()) {
+  if (listPresets != ListPresets::None || !presetName.empty()) {
     cmCMakePresetsFile settingsFile;
     auto result = settingsFile.ReadProjectPresets(this->GetHomeDirectory());
     if (result != cmCMakePresetsFile::ReadFileResult::READ_OK) {
@@ -1128,12 +1156,24 @@ void cmake::SetArgs(const std::vector<std::string>& args)
                  ": ", cmCMakePresetsFile::ResultToString(result)));
       return;
     }
-    if (listPresets) {
-      this->PrintPresetList(settingsFile);
+
+    if (listPresets != ListPresets::None) {
+      if (listPresets == ListPresets::Configure) {
+        this->PrintPresetList(settingsFile);
+      } else if (listPresets == ListPresets::Build) {
+        settingsFile.PrintBuildPresetList();
+      } else if (listPresets == ListPresets::Test) {
+        settingsFile.PrintTestPresetList();
+      } else if (listPresets == ListPresets::All) {
+        settingsFile.PrintAllPresets();
+      }
+
+      this->SetWorkingMode(WorkingMode::HELP_MODE);
       return;
     }
-    auto preset = settingsFile.Presets.find(presetName);
-    if (preset == settingsFile.Presets.end()) {
+
+    auto preset = settingsFile.ConfigurePresets.find(presetName);
+    if (preset == settingsFile.ConfigurePresets.end()) {
       cmSystemTools::Error(cmStrCat("No such preset in ",
                                     this->GetHomeDirectory(), ": \"",
                                     presetName, '"'));
@@ -1562,44 +1602,16 @@ void cmake::PrintPresetList(const cmCMakePresetsFile& file) const
 {
   std::vector<GeneratorInfo> generators;
   this->GetRegisteredGenerators(generators, false);
+  auto filter =
+    [&generators](const cmCMakePresetsFile::ConfigurePreset& preset) -> bool {
+    auto condition = [&preset](const GeneratorInfo& info) -> bool {
+      return info.name == preset.Generator;
+    };
+    auto it = std::find_if(generators.begin(), generators.end(), condition);
+    return it != generators.end();
+  };
 
-  std::vector<cmCMakePresetsFile::UnexpandedPreset> presets;
-  for (auto const& p : file.PresetOrder) {
-    auto const& preset = file.Presets.at(p);
-    if (!preset.Unexpanded.Hidden && preset.Expanded &&
-        std::find_if(generators.begin(), generators.end(),
-                     [&preset](const GeneratorInfo& info) {
-                       return info.name == preset.Unexpanded.Generator;
-                     }) != generators.end()) {
-      presets.push_back(preset.Unexpanded);
-    }
-  }
-
-  if (presets.empty()) {
-    return;
-  }
-
-  std::cout << "Available presets:\n\n";
-
-  auto longestPresetName =
-    std::max_element(presets.begin(), presets.end(),
-                     [](const cmCMakePresetsFile::UnexpandedPreset& a,
-                        const cmCMakePresetsFile::UnexpandedPreset& b) {
-                       return a.Name.length() < b.Name.length();
-                     });
-  auto longestLength = longestPresetName->Name.length();
-
-  for (auto const& preset : presets) {
-    std::cout << "  \"" << preset.Name << '"';
-    auto const& description = preset.DisplayName;
-    if (!description.empty()) {
-      for (std::size_t i = 0; i < longestLength - preset.Name.length(); ++i) {
-        std::cout << ' ';
-      }
-      std::cout << " - " << description;
-    }
-    std::cout << '\n';
-  }
+  file.PrintConfigurePresetList(filter);
 }
 #endif
 
@@ -3068,15 +3080,119 @@ std::vector<std::string> cmake::GetDebugConfigs()
   return configs;
 }
 
-int cmake::Build(int jobs, const std::string& dir,
-                 const std::vector<std::string>& targets,
-                 const std::string& config,
-                 const std::vector<std::string>& nativeOptions, bool clean,
-                 bool verbose)
+int cmake::Build(int jobs, std::string dir, std::vector<std::string> targets,
+                 std::string config, std::vector<std::string> nativeOptions,
+                 bool clean, bool verbose, const std::string& presetName,
+                 bool listPresets)
 {
-
   this->SetHomeDirectory("");
   this->SetHomeOutputDirectory("");
+
+#if !defined(CMAKE_BOOTSTRAP)
+  if (!presetName.empty() || listPresets) {
+    this->SetHomeDirectory(cmSystemTools::GetCurrentWorkingDirectory());
+    this->SetHomeOutputDirectory(cmSystemTools::GetCurrentWorkingDirectory());
+
+    cmCMakePresetsFile settingsFile;
+    auto result = settingsFile.ReadProjectPresets(this->GetHomeDirectory());
+    if (result != cmCMakePresetsFile::ReadFileResult::READ_OK) {
+      cmSystemTools::Error(
+        cmStrCat("Could not read presets from ", this->GetHomeDirectory(),
+                 ": ", cmCMakePresetsFile::ResultToString(result)));
+      return 1;
+    }
+
+    if (listPresets) {
+      settingsFile.PrintBuildPresetList();
+      return 0;
+    }
+
+    auto presetPair = settingsFile.BuildPresets.find(presetName);
+    if (presetPair == settingsFile.BuildPresets.end()) {
+      cmSystemTools::Error(cmStrCat("No such build preset in ",
+                                    this->GetHomeDirectory(), ": \"",
+                                    presetName, '"'));
+      settingsFile.PrintBuildPresetList();
+      return 1;
+    }
+
+    if (presetPair->second.Unexpanded.Hidden) {
+      cmSystemTools::Error(cmStrCat("Cannot use hidden build preset in ",
+                                    this->GetHomeDirectory(), ": \"",
+                                    presetName, '"'));
+      settingsFile.PrintBuildPresetList();
+      return 1;
+    }
+
+    auto const& expandedPreset = presetPair->second.Expanded;
+    if (!expandedPreset) {
+      cmSystemTools::Error(cmStrCat("Could not evaluate build preset \"",
+                                    presetName,
+                                    "\": Invalid macro expansion"));
+      settingsFile.PrintBuildPresetList();
+      return 1;
+    }
+
+    auto configurePresetPair =
+      settingsFile.ConfigurePresets.find(expandedPreset->ConfigurePreset);
+    if (configurePresetPair == settingsFile.ConfigurePresets.end()) {
+      cmSystemTools::Error(cmStrCat("No such configure preset in ",
+                                    this->GetHomeDirectory(), ": \"",
+                                    expandedPreset->ConfigurePreset, '"'));
+      this->PrintPresetList(settingsFile);
+      return 1;
+    }
+
+    if (configurePresetPair->second.Unexpanded.Hidden) {
+      cmSystemTools::Error(cmStrCat("Cannot use hidden configure preset in ",
+                                    this->GetHomeDirectory(), ": \"",
+                                    expandedPreset->ConfigurePreset, '"'));
+      this->PrintPresetList(settingsFile);
+      return 1;
+    }
+
+    auto const& expandedConfigurePreset = configurePresetPair->second.Expanded;
+    if (!expandedConfigurePreset) {
+      cmSystemTools::Error(cmStrCat("Could not evaluate configure preset \"",
+                                    expandedPreset->ConfigurePreset,
+                                    "\": Invalid macro expansion"));
+      return 1;
+    }
+
+    dir = expandedConfigurePreset->BinaryDir;
+
+    this->UnprocessedPresetEnvironment = expandedPreset->Environment;
+    this->ProcessPresetEnvironment();
+
+    if (jobs == cmake::DEFAULT_BUILD_PARALLEL_LEVEL && expandedPreset->Jobs) {
+      jobs = *expandedPreset->Jobs;
+    }
+
+    if (targets.empty()) {
+      targets.insert(targets.begin(), expandedPreset->Targets.begin(),
+                     expandedPreset->Targets.end());
+    }
+
+    if (config.empty()) {
+      config = expandedPreset->Configuration;
+    }
+
+    if (!clean && expandedPreset->CleanFirst) {
+      clean = *expandedPreset->CleanFirst;
+    }
+
+    if (!verbose && expandedPreset->Verbose) {
+      verbose = *expandedPreset->Verbose;
+    }
+
+    if (nativeOptions.empty()) {
+      nativeOptions.insert(nativeOptions.begin(),
+                           expandedPreset->NativeToolOptions.begin(),
+                           expandedPreset->NativeToolOptions.end());
+    }
+  }
+#endif
+
   if (!cmSystemTools::FileIsDirectory(dir)) {
     std::cerr << "Error: " << dir << " is not a directory\n";
     return 1;

+ 5 - 5
Source/cmake.h

@@ -238,7 +238,7 @@ public:
   bool CreateAndSetGlobalGenerator(const std::string& name, bool allowArch);
 
 #ifndef CMAKE_BOOTSTRAP
-  //! Print list of presets
+  //! Print list of configure presets
   void PrintPresetList(const cmCMakePresetsFile& file) const;
 #endif
 
@@ -556,10 +556,10 @@ public:
     cmListFileBacktrace const& backtrace = cmListFileBacktrace()) const;
 
   //! run the --build option
-  int Build(int jobs, const std::string& dir,
-            const std::vector<std::string>& targets, const std::string& config,
-            const std::vector<std::string>& nativeOptions, bool clean,
-            bool verbose);
+  int Build(int jobs, std::string dir, std::vector<std::string> targets,
+            std::string config, std::vector<std::string> nativeOptions,
+            bool clean, bool verbose, const std::string& presetName,
+            bool listPresets);
 
   //! run the --open option
   bool Open(const std::string& dir, bool dryRun);

+ 41 - 8
Source/cmakemain.cxx

@@ -425,6 +425,8 @@ int do_build(int ac, char const* const* av)
   bool foundClean = false;
   bool foundNonClean = false;
   bool verbose = cmSystemTools::HasEnv("VERBOSE");
+  std::string presetName;
+  bool listPresets = false;
 
   auto jLambda = [&](std::string const& value) -> bool {
     jobs = extract_job_number("-j", value);
@@ -464,6 +466,16 @@ int do_build(int ac, char const* const* av)
     cmCommandLineArgument<bool(std::string const& value)>;
 
   std::vector<CommandArgument> arguments = {
+    CommandArgument{ "--preset", CommandArgument::Values::One,
+                     [&](std::string const& value) -> bool {
+                       presetName = value;
+                       return true;
+                     } },
+    CommandArgument{ "--list-presets", CommandArgument::Values::Zero,
+                     [&](std::string const&) -> bool {
+                       listPresets = true;
+                       return true;
+                     } },
     CommandArgument{ "-j", CommandArgument::Values::ZeroOrOne, jLambda },
     CommandArgument{ "--parallel", CommandArgument::Values::ZeroOrOne,
                      parallelLambda },
@@ -494,11 +506,26 @@ int do_build(int ac, char const* const* av)
   };
 
   if (ac >= 3) {
-    dir = cmSystemTools::CollapseFullPath(av[2]);
-
     std::vector<std::string> inputArgs;
-    inputArgs.reserve(ac - 3);
-    cm::append(inputArgs, av + 3, av + ac);
+
+    bool hasPreset = false;
+    for (int i = 2; i < ac; ++i) {
+      if (strcmp(av[i], "--list-presets") == 0 ||
+          strcmp(av[i], "--preset") == 0) {
+        hasPreset = true;
+        break;
+      }
+    }
+
+    if (hasPreset) {
+      inputArgs.reserve(ac - 2);
+      cm::append(inputArgs, av + 2, av + ac);
+    } else {
+      dir = cmSystemTools::CollapseFullPath(av[2]);
+
+      inputArgs.reserve(ac - 3);
+      cm::append(inputArgs, av + 3, av + ac);
+    }
 
     decltype(inputArgs.size()) i = 0;
     for (; i < inputArgs.size() && !nativeOptionsPassed; ++i) {
@@ -551,12 +578,16 @@ int do_build(int ac, char const* const* av)
     }
   }
 
-  if (dir.empty()) {
+  if (dir.empty() && presetName.empty() && !listPresets) {
     /* clang-format off */
     std::cerr <<
-      "Usage: cmake --build <dir> [options] [-- [native-options]]\n"
+      "Usage: cmake --build [<dir> | --preset <preset>] [options] [-- [native-options]]\n"
       "Options:\n"
       "  <dir>          = Project binary directory to be built.\n"
+      "  --preset <preset>\n"
+      "                 = Specify a build preset.\n"
+      "  --list-presets\n"
+      "                 = List available build presets.\n"
       "  --parallel [<jobs>], -j [<jobs>]\n"
       "                 = Build in parallel using the given number of jobs. \n"
       "                   If <jobs> is omitted the native build tool's \n"
@@ -587,8 +618,10 @@ int do_build(int ac, char const* const* av)
   cm.SetProgressCallback([&cm](const std::string& msg, float prog) {
     cmakemainProgressCallback(msg, prog, &cm);
   });
-  return cm.Build(jobs, dir, targets, config, nativeOptions, cleanFirst,
-                  verbose);
+
+  return cm.Build(jobs, std::move(dir), std::move(targets), std::move(config),
+                  std::move(nativeOptions), cleanFirst, verbose, presetName,
+                  listPresets);
 #endif
 }
 

+ 2 - 0
Source/ctest.cxx

@@ -26,6 +26,8 @@ static const char* cmDocumentationUsage[][2] = { { nullptr,
                                                  { nullptr, nullptr } };
 
 static const char* cmDocumentationOptions[][2] = {
+  { "--preset <preset>", "Read arguments from a test preset." },
+  { "--list-presets", "List available test presets." },
   { "-C <cfg>, --build-config <cfg>", "Choose configuration to test." },
   { "--progress", "Enable short progress output from tests." },
   { "-V,--verbose", "Enable verbose output from tests." },

+ 2 - 0
Tests/RunCMake/CMakeLists.txt

@@ -805,6 +805,8 @@ add_RunCMake_test(PrecompileHeaders -DCMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID}
 
 add_RunCMake_test("UnityBuild")
 add_RunCMake_test(CMakePresets -DPYTHON_EXECUTABLE=${PYTHON_EXECUTABLE} -DCMake_TEST_JSON_SCHEMA=${CMake_TEST_JSON_SCHEMA})
+add_RunCMake_test(CMakePresetsBuild -DPYTHON_EXECUTABLE=${PYTHON_EXECUTABLE} -DCMake_TEST_JSON_SCHEMA=${CMake_TEST_JSON_SCHEMA})
+add_RunCMake_test(CMakePresetsTest -DPYTHON_EXECUTABLE=${PYTHON_EXECUTABLE} -DCMake_TEST_JSON_SCHEMA=${CMake_TEST_JSON_SCHEMA})
 
 if(${CMAKE_GENERATOR} MATCHES "Make|Ninja")
   add_RunCMake_test(TransformDepfile)

+ 1 - 1
Tests/RunCMake/CMakePresets/ListPresets-stdout.txt

@@ -1,5 +1,5 @@
 ^Not searching for unused variables given on the command line\.
-Available presets:
+Available configure presets:
 
   "zzzzzz"   - Sleepy
   "aaaaaaaa" - Screaming

+ 1 - 1
Tests/RunCMake/CMakePresets/ListPresetsHidden-stdout.txt

@@ -1,5 +1,5 @@
 ^Not searching for unused variables given on the command line\.
-Available presets:
+Available configure presets:
 
   "zzzzzz"   - Sleepy
   "aaaaaaaa" - Screaming

+ 1 - 1
Tests/RunCMake/CMakePresets/ListPresetsNoSuchPreset-stdout.txt

@@ -1,5 +1,5 @@
 ^Not searching for unused variables given on the command line\.
-Available presets:
+Available configure presets:
 
   "zzzzzz"   - Sleepy
   "aaaaaaaa" - Screaming

+ 1 - 1
Tests/RunCMake/CMakePresets/ListPresetsWorkingDir-stdout.txt

@@ -1,5 +1,5 @@
 ^Not searching for unused variables given on the command line\.
-Available presets:
+Available configure presets:
 
   "zzzzzz"   - Sleepy
   "aaaaaaaa" - Screaming

+ 1 - 15
Tests/RunCMake/CMakePresets/RunCMakeTest.cmake

@@ -9,21 +9,7 @@ endif()
 
 set(RunCMake-check-file check.cmake)
 
-function(validate_schema file expected_result)
-  execute_process(
-    COMMAND "${PYTHON_EXECUTABLE}" "${RunCMake_SOURCE_DIR}/validate_schema.py" "${file}"
-    RESULT_VARIABLE _result
-    OUTPUT_VARIABLE _output
-    ERROR_VARIABLE _error
-    )
-  if(NOT _result STREQUAL expected_result)
-    string(REPLACE "\n" "\n" _output_p "${_output}")
-    string(REPLACE "\n" "\n" _error_p "${_error}")
-    string(APPEND RunCMake_TEST_FAILED "Expected result of validating ${file}: ${expected_result}\nActual result: ${_result}\nOutput:\n${_output_p}\nError:\n${_error_p}")
-  endif()
-
-  set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}" PARENT_SCOPE)
-endfunction()
+include("${RunCMake_SOURCE_DIR}/validate_schema.cmake")
 
 function(run_cmake_presets name)
   set(RunCMake_TEST_SOURCE_DIR "${RunCMake_BINARY_DIR}/${name}")

+ 19 - 0
Tests/RunCMake/CMakePresets/validate_schema.cmake

@@ -0,0 +1,19 @@
+function(validate_schema file expected_result)
+  if (NOT CMakePresets_VALIDATE_SCRIPT_PATH)
+    set(CMakePresets_VALIDATE_SCRIPT_PATH "${RunCMake_SOURCE_DIR}/validate_schema.py")
+  endif()
+
+  execute_process(
+    COMMAND "${PYTHON_EXECUTABLE}" "${CMakePresets_VALIDATE_SCRIPT_PATH}" "${file}"
+    RESULT_VARIABLE _result
+    OUTPUT_VARIABLE _output
+    ERROR_VARIABLE _error
+    )
+  if(NOT _result STREQUAL expected_result)
+    string(REPLACE "\n" "\n  " _output_p "${_output}")
+    string(REPLACE "\n" "\n  " _error_p "${_error}")
+    string(APPEND RunCMake_TEST_FAILED "Expected result of validating ${file}: ${expected_result}\nActual result: ${_result}\nOutput:\n  ${_output_p}\nError:\n  ${_error_p}\n")
+  endif()
+
+  set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}" PARENT_SCOPE)
+endfunction()

+ 3 - 0
Tests/RunCMake/CMakePresetsBuild/CMakeLists.txt.in

@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.19)
+project("@CASE_NAME@" NONE)
+include("@CASE_SOURCE_DIR@/@[email protected]")

+ 5 - 0
Tests/RunCMake/CMakePresetsBuild/Good-build-build-other-check.cmake

@@ -0,0 +1,5 @@
+include("${RunCMake_SOURCE_DIR}/TestVariable.cmake")
+
+test_environment_variable("TEST_ENV_" "other")
+
+include("${RunCMake_SOURCE_DIR}/check.cmake")

+ 14 - 0
Tests/RunCMake/CMakePresetsBuild/Good-build-macros-check.cmake

@@ -0,0 +1,14 @@
+include("${RunCMake_SOURCE_DIR}/TestVariable.cmake")
+
+if(RunCMake_GENERATOR STREQUAL "NMake Makefiles JOM")
+  # JOM removes the '$' and content following it.
+else()
+  test_environment_variable("TEST_DOLLAR" "x\\$x")
+endif()
+test_environment_variable("TEST_GENERATOR" "${RunCMake_GENERATOR}")
+test_environment_variable("TEST_PRESET_NAME" "xmacrosx")
+test_environment_variable("TEST_SOURCE_DIR_" "x[^\n]*[/\\\\]Tests[/\\\\]RunCMake[/\\\\]CMakePresetsBuild[/\\\\]Goodx")
+test_environment_variable("TEST_SOURCE_DIR_NAME" "xGoodx")
+test_environment_variable("TEST_SOURCE_PARENT_DIR" "x[^\n]*[/\\\\]Tests[/\\\\]RunCMake[/\\\\]CMakePresetsBuildx")
+
+include("${RunCMake_SOURCE_DIR}/check.cmake")

+ 6 - 0
Tests/RunCMake/CMakePresetsBuild/Good-build-noEnvironment-check.cmake

@@ -0,0 +1,6 @@
+string(FIND "${actual_stdout}" "TEST_ENV_" TEST_ENV_POS)
+if (NOT TEST_ENV_POS EQUAL -1)
+  message(FATAL_ERROR "Found TEST_ENV_ in environment")
+endif()
+
+include("${RunCMake_SOURCE_DIR}/check.cmake")

+ 8 - 0
Tests/RunCMake/CMakePresetsBuild/Good-build-withEnvironment-check.cmake

@@ -0,0 +1,8 @@
+include("${RunCMake_SOURCE_DIR}/TestVariable.cmake")
+
+test_environment_variable("TEST_ENV_" "Environment variable")
+test_environment_variable("TEST_ENV_OVERRIDE_" "Overridden")
+test_environment_variable("TEST_ENV_OVERRIDE_REF" "xOverriddenx")
+test_environment_variable("TEST_ENV_REF" "xEnvironment variablex")
+
+include("${RunCMake_SOURCE_DIR}/check.cmake")

+ 3 - 0
Tests/RunCMake/CMakePresetsBuild/Good.cmake

@@ -0,0 +1,3 @@
+add_custom_target(good ALL)
+add_custom_command(TARGET good PRE_BUILD
+    COMMAND ${CMAKE_COMMAND} -E environment)

+ 78 - 0
Tests/RunCMake/CMakePresetsBuild/Good.json.in

@@ -0,0 +1,78 @@
+{
+    "version": 2,
+    "configurePresets": [
+        {
+            "name": "default",
+            "generator": "@RunCMake_GENERATOR@",
+            "binaryDir": "${sourceDir}/build/${presetName}",
+            "environment": {
+                "TEST_ENV_": "Environment variable",
+                "TEST_ENV_OVERRIDE_": "Environment variable",
+                "TEST_ENV_OVERRIDE_REF": "x$env{TEST_ENV_OVERRIDE_}x"
+            }
+        },
+        {
+            "name": "other",
+            "inherits": "default",
+            "environment": {
+                "TEST_ENV_": "other"
+            }
+        }
+    ],
+    "buildPresets": [
+        {
+            "name": "build-default",
+            "hidden": true,
+            "inherits": [],
+            "configurePreset": "default",
+            "vendor": {},
+            "displayName": "",
+            "description": "",
+            "inheritConfigureEnvironment": true,
+            "environment": {},
+            "jobs": 0,
+            "targets": [],
+            "configuration": "",
+            "verbose": true,
+            "nativeToolOptions": []
+        },
+        {
+            "name": "build-other",
+            "configurePreset": "other"
+        },
+        {
+            "name": "withEnvironment",
+            "inherits": "build-default",
+            "environment": {
+                "TEST_ENV_OVERRIDE_": "Overridden",
+                "TEST_ENV_REF": "x$env{TEST_ENV_}x",
+                "TEST_ENV_OVERRIDE_REF": "x$env{TEST_ENV_OVERRIDE_}x"
+            }
+        },
+        {
+            "name": "noEnvironment",
+            "inherits": "build-default",
+            "inheritConfigureEnvironment": false
+        },
+        {
+            "name": "macros",
+            "inherits": "build-default",
+            "inheritConfigureEnvironment": false,
+            "environment": {
+                "TEST_SOURCE_DIR_": "x${sourceDir}x",
+                "TEST_SOURCE_PARENT_DIR": "x${sourceParentDir}x",
+                "TEST_SOURCE_DIR_NAME": "x${sourceDirName}x",
+                "TEST_PRESET_NAME": "x${presetName}x",
+                "TEST_GENERATOR": "x${generator}x",
+                "TEST_DOLLAR": "x${dollar}x"
+            }
+        },
+        {
+            "name": "vendorObject",
+            "configurePreset": "default",
+            "vendor": {
+                "example.com": "value"
+            }
+        }
+    ]
+}

+ 1 - 0
Tests/RunCMake/CMakePresetsBuild/Invalid-build-badConfigurePreset-result.txt

@@ -0,0 +1 @@
+1

+ 2 - 0
Tests/RunCMake/CMakePresetsBuild/Invalid-build-badConfigurePreset-stderr.txt

@@ -0,0 +1,2 @@
+CMake Error: No such configure preset in [^
+]*/Tests/RunCMake/CMakePresetsBuild/Invalid: "dne"

+ 1 - 0
Tests/RunCMake/CMakePresetsBuild/Invalid-build-hidden-result.txt

@@ -0,0 +1 @@
+1

+ 2 - 0
Tests/RunCMake/CMakePresetsBuild/Invalid-build-hidden-stderr.txt

@@ -0,0 +1,2 @@
+CMake Error: Cannot use hidden build preset in [^
+]*/Tests/RunCMake/CMakePresetsBuild/Invalid: "hidden"

+ 1 - 0
Tests/RunCMake/CMakePresetsBuild/Invalid-build-vendorMacro-result.txt

@@ -0,0 +1 @@
+1

+ 1 - 0
Tests/RunCMake/CMakePresetsBuild/Invalid-build-vendorMacro-stderr.txt

@@ -0,0 +1 @@
+CMake Error: Could not evaluate build preset "vendorMacro": Invalid macro expansion

+ 0 - 0
Tests/RunCMake/CMakePresetsBuild/Invalid.cmake


+ 27 - 0
Tests/RunCMake/CMakePresetsBuild/Invalid.json.in

@@ -0,0 +1,27 @@
+{
+    "version": 2,
+    "configurePresets": [
+        {
+            "name": "default",
+            "generator": "@RunCMake_GENERATOR@",
+            "binaryDir": "${sourceDir}/build/${presetName}"
+        }
+    ],
+    "buildPresets": [
+        {
+            "name": "hidden",
+            "hidden": true
+        },
+        {
+            "name": "vendorMacro",
+            "configurePreset": "default",
+            "environment": {
+                "TEST": "$vendor{bad.TEST}"
+            }
+        },
+        {
+            "name": "badConfigurePreset",
+            "configurePreset": "dne"
+        }
+    ]
+}

+ 5 - 0
Tests/RunCMake/CMakePresetsBuild/ListPresets-build-x-stdout.txt

@@ -0,0 +1,5 @@
+Available build presets:
+
+  "build-default" - build-default displayName
+  "empty"
+  "display"       - display displayName

+ 0 - 0
Tests/RunCMake/CMakePresetsBuild/ListPresets.cmake


+ 31 - 0
Tests/RunCMake/CMakePresetsBuild/ListPresets.json.in

@@ -0,0 +1,31 @@
+{
+    "version": 2,
+    "configurePresets": [
+        {
+            "name": "default",
+            "generator": "@RunCMake_GENERATOR@",
+            "binaryDir": "${sourceDir}/build/${presetName}"
+        }
+    ],
+    "buildPresets": [
+        {
+            "name": "build-default",
+            "configurePreset": "default",
+            "displayName": "build-default displayName",
+            "description": "build-default description"
+        },
+        {
+            "name": "empty",
+            "inherits": "build-default"
+        },
+        {
+            "name": "display",
+            "inherits": "build-default",
+            "displayName": "display displayName"
+        },
+        {
+            "name": "hidden",
+            "hidden": true
+        }
+    ]
+}

+ 1 - 0
Tests/RunCMake/CMakePresetsBuild/NoConfigurePreset-build-noConfigurePreset-result.txt

@@ -0,0 +1 @@
+1

+ 2 - 0
Tests/RunCMake/CMakePresetsBuild/NoConfigurePreset-build-noConfigurePreset-stderr.txt

@@ -0,0 +1,2 @@
+CMake Error: Could not read presets from [^
+]*/Tests/RunCMake/CMakePresetsBuild/NoConfigurePreset: Invalid preset

+ 0 - 0
Tests/RunCMake/CMakePresetsBuild/NoConfigurePreset.cmake


+ 15 - 0
Tests/RunCMake/CMakePresetsBuild/NoConfigurePreset.json.in

@@ -0,0 +1,15 @@
+{
+    "version": 2,
+    "configurePresets": [
+        {
+            "name": "default",
+            "generator": "@RunCMake_GENERATOR@",
+            "binaryDir": "${sourceDir}/build/${presetName}"
+        }
+    ],
+    "buildPresets": [
+        {
+            "name": "noConfigurePreset"
+        }
+    ]
+}

+ 1 - 0
Tests/RunCMake/CMakePresetsBuild/PresetsUnsupported-build-x-result.txt

@@ -0,0 +1 @@
+1

+ 2 - 0
Tests/RunCMake/CMakePresetsBuild/PresetsUnsupported-build-x-stderr.txt

@@ -0,0 +1,2 @@
+CMake Error: Could not read presets from [^
+]*Tests/RunCMake/CMakePresetsBuild/PresetsUnsupported: File version must be 2 or higher for build and test preset support.

+ 7 - 0
Tests/RunCMake/CMakePresetsBuild/PresetsUnsupported.json.in

@@ -0,0 +1,7 @@
+{
+    "version": 1,
+    "configurePresets": [
+    ],
+    "buildPresets": [
+    ]
+}

+ 67 - 0
Tests/RunCMake/CMakePresetsBuild/RunCMakeTest.cmake

@@ -0,0 +1,67 @@
+include(RunCMake)
+
+# Presets do not support legacy VS generator name architecture suffix.
+if(RunCMake_GENERATOR MATCHES "^(Visual Studio [0-9]+ [0-9]+) ")
+  set(RunCMake_GENERATOR "${CMAKE_MATCH_1}")
+endif()
+
+function(run_cmake_build_presets name CMakePresetsBuild_CONFIGURE_PRESETS CMakePresetsBuild_BUILD_PRESETS)
+  set(RunCMake_TEST_SOURCE_DIR "${RunCMake_BINARY_DIR}/${name}")
+  set(RunCMake_TEST_BINARY_DIR "${RunCMake_TEST_SOURCE_DIR}/build")
+  set(RunCMake_TEST_COMMAND_WORKING_DIRECTORY "${RunCMake_TEST_SOURCE_DIR}")
+
+  set(RunCMake_TEST_NO_CLEAN TRUE)
+
+  file(REMOVE_RECURSE "${RunCMake_TEST_SOURCE_DIR}")
+  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
+
+  set(CASE_NAME "${name}")
+  set(CASE_SOURCE_DIR "${RunCMake_SOURCE_DIR}")
+  configure_file("${RunCMake_SOURCE_DIR}/CMakeLists.txt.in" "${RunCMake_TEST_SOURCE_DIR}/CMakeLists.txt" @ONLY)
+
+  if(NOT CMakePresetsBuild_FILE)
+    set(CMakePresetsBuild_FILE "${RunCMake_SOURCE_DIR}/${name}.json.in")
+  endif()
+  if(EXISTS "${CMakePresetsBuild_FILE}")
+    configure_file("${CMakePresetsBuild_FILE}" "${RunCMake_TEST_SOURCE_DIR}/CMakePresets.json" @ONLY)
+  endif()
+
+  if(NOT CMakeUserPresets_FILE)
+    set(CMakeUserPresets_FILE "${RunCMake_SOURCE_DIR}/${name}User.json.in")
+  endif()
+  if(EXISTS "${CMakeUserPresets_FILE}")
+    configure_file("${CMakeUserPresets_FILE}" "${RunCMake_TEST_SOURCE_DIR}/CMakeUserPresets.json" @ONLY)
+  endif()
+
+  if (NOT CMakePresetsBuild_BUILD_ONLY)
+    foreach(CONFIGURE_PRESET ${CMakePresetsBuild_CONFIGURE_PRESETS})
+      run_cmake_command("${name}-configure-${CONFIGURE_PRESET}"
+        "${CMAKE_COMMAND}" "--preset" "${CONFIGURE_PRESET}")
+    endforeach()
+  endif()
+
+  foreach(BUILD_PRESET ${CMakePresetsBuild_BUILD_PRESETS})
+    if (EXISTS "${RunCMake_SOURCE_DIR}/${name}-build-${BUILD_PRESET}-check.cmake")
+      set(RunCMake-check-file "${name}-build-${BUILD_PRESET}-check.cmake")
+    else()
+      set(RunCMake-check-file "check.cmake")
+    endif()
+
+    run_cmake_command(${name}-build-${BUILD_PRESET}
+      ${CMAKE_COMMAND} "--build" "--preset" "${BUILD_PRESET}" ${ARGN})
+  endforeach()
+endfunction()
+
+set(CMakePresets_SCHEMA_EXPECTED_RESULT 0)
+
+run_cmake_build_presets(Good "default;other" "build-other;withEnvironment;noEnvironment;macros;vendorObject")
+
+set(CMakePresetsBuild_BUILD_ONLY 1)
+run_cmake_build_presets(ListPresets "x" "x" "--list-presets")
+run_cmake_build_presets(NoConfigurePreset "x" "noConfigurePreset")
+run_cmake_build_presets(Invalid "x" "hidden;vendorMacro;badConfigurePreset")
+
+set(CMakePresets_SCHEMA_EXPECTED_RESULT 1)
+run_cmake_build_presets(PresetsUnsupported "x" "x")
+set(CMakePresets_SCHEMA_EXPECTED_RESULT 0)
+set(CMakePresetsBuild_BUILD_ONLY 0)

+ 14 - 0
Tests/RunCMake/CMakePresetsBuild/TestVariable.cmake

@@ -0,0 +1,14 @@
+function(test_environment_variable name expected_value)
+  string(REGEX MATCH "${name}=([^\n]*)" REGEX_RESULT "${actual_stdout}")
+  if(NOT REGEX_RESULT)
+    string(APPEND RunCMake_TEST_FAILED "Environment variable '${name}' is not defined.\n")
+  else()
+    set(actual_value "${CMAKE_MATCH_1}")
+    if(NOT "${actual_value}" MATCHES "${expected_value}")
+      string(REPLACE "\n" "\n  " _actual "${expected_value}")
+      string(REPLACE "\n" "\n  " _expect "${actual_value}")
+      string(APPEND RunCMake_TEST_FAILED "Expected value of environment variable '${name}':\n  ${_expect}\nActual value:\n  ${_actual}\n")
+    endif()
+  endif()
+  set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}" PARENT_SCOPE)
+endfunction()

+ 3 - 0
Tests/RunCMake/CMakePresetsBuild/check.cmake

@@ -0,0 +1,3 @@
+set(CMakePresets_VALIDATE_SCRIPT_PATH "${RunCMake_SOURCE_DIR}/../CMakePresets/validate_schema.py")
+include("${RunCMake_SOURCE_DIR}/../CMakePresets/validate_schema.cmake")
+include("${RunCMake_SOURCE_DIR}/../CMakePresets/check.cmake")

+ 3 - 0
Tests/RunCMake/CMakePresetsTest/CMakeLists.txt.in

@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.19)
+project("@CASE_NAME@" NONE)
+include("@CASE_SOURCE_DIR@/@[email protected]")

+ 1 - 0
Tests/RunCMake/CMakePresetsTest/Good-indexFile.txt

@@ -0,0 +1 @@
+2,,2

+ 5 - 0
Tests/RunCMake/CMakePresetsTest/Good-test-config-debug-stdout.txt

@@ -0,0 +1,5 @@
+Test project [^
+]*/Tests/RunCMake/CMakePresetsTest/Good/build/default
+    Start 6: debug-only
+.*
+100% tests passed, 0 tests failed out of 1

+ 5 - 0
Tests/RunCMake/CMakePresetsTest/Good-test-config-release-stdout.txt

@@ -0,0 +1,5 @@
+Test project [^
+]*/Tests/RunCMake/CMakePresetsTest/Good/build/default
+    Start 6: release-only
+.*
+100% tests passed, 0 tests failed out of 1

+ 7 - 0
Tests/RunCMake/CMakePresetsTest/Good-test-exclude-stdout.txt

@@ -0,0 +1,7 @@
+Test project [^
+]*/Tests/RunCMake/CMakePresetsTest/Good/build/default
+    Start 3: testc
+.*
+    Start 4: testd
+.*
+100% tests passed, 0 tests failed out of 2

+ 7 - 0
Tests/RunCMake/CMakePresetsTest/Good-test-index-stdout.txt

@@ -0,0 +1,7 @@
+Test project [^
+]*/Tests/RunCMake/CMakePresetsTest/Good/build/default
+    Start 1: testa
+.*
+    Start 3: testc
+.*
+100% tests passed, 0 tests failed out of 2

+ 7 - 0
Tests/RunCMake/CMakePresetsTest/Good-test-indexFile-stdout.txt

@@ -0,0 +1,7 @@
+Test project [^
+]*/Tests/RunCMake/CMakePresetsTest/Good/build/default
+    Start 2: testb
+.*
+    Start 4: testd
+.*
+100% tests passed, 0 tests failed out of 2

+ 8 - 0
Tests/RunCMake/CMakePresetsTest/Good-test-noEnvironment-stdout.txt

@@ -0,0 +1,8 @@
+Test project [^
+]*/Tests/RunCMake/CMakePresetsTest/Good/build/default
+.*
+    Start 5: test-env
+.*
+5: TEST_ENV_REF=xx
+.*
+100% tests passed, 0 tests failed out of 1

+ 8 - 0
Tests/RunCMake/CMakePresetsTest/Good-test-showOnly-stdout.txt

@@ -0,0 +1,8 @@
+Test project [^
+]*/Tests/RunCMake/CMakePresetsTest/Good/build/default
+  Test #1: testa
+  Test #2: testb
+  Test #3: testc
+  Test #4: testd
+
+Total Tests: 4

+ 8 - 0
Tests/RunCMake/CMakePresetsTest/Good-withEnvironment-check.cmake

@@ -0,0 +1,8 @@
+include("${RunCMake_SOURCE_DIR}/../CMakePresetsBuild/TestVariable.cmake")
+
+test_environment_variable("TEST_ENV" "Environment variable")
+test_environment_variable("TEST_ENV_OVERRIDE" "Override")
+test_environment_variable("TEST_ENV_OVERRIDE_REF" "xOverridex")
+test_environment_variable("TEST_ENV_REF" "xEnvironment variablex")
+
+include("${RunCMake_SOURCE_DIR}/check.cmake")

+ 27 - 0
Tests/RunCMake/CMakePresetsTest/Good.cmake

@@ -0,0 +1,27 @@
+enable_testing()
+add_test(testa ${CMAKE_COMMAND} -E echo testa)
+add_test(testb ${CMAKE_COMMAND} -E echo testb)
+add_test(testc ${CMAKE_COMMAND} -E echo testc)
+add_test(testd ${CMAKE_COMMAND} -E echo testd)
+
+set_tests_properties(testa testb testc testd
+                     PROPERTIES LABELS "echo")
+set_property(TEST testa testb
+             APPEND PROPERTY LABELS ab)
+set_property(TEST testb testc
+             APPEND PROPERTY LABELS bc)
+set_property(TEST testc testd
+             APPEND PROPERTY LABELS cd)
+
+add_test(test-env ${CMAKE_COMMAND} -E environment | sort)
+set_tests_properties(test-env PROPERTIES LABELS "env")
+
+add_test(NAME debug-only
+         COMMAND ${CMAKE_COMMAND} -E echo debug-only
+         CONFIGURATIONS Debug)
+add_test(NAME release-only
+         COMMAND ${CMAKE_COMMAND} -E echo release-only
+         CONFIGURATIONS Release)
+
+set_tests_properties(debug-only release-only
+                     PROPERTIES LABELS "config")

+ 184 - 0
Tests/RunCMake/CMakePresetsTest/Good.json.in

@@ -0,0 +1,184 @@
+{
+    "version": 2,
+    "configurePresets": [
+        {
+            "name": "default",
+            "generator": "@RunCMake_GENERATOR@",
+            "binaryDir": "${sourceDir}/build/${presetName}",
+            "environment": {
+                "TEST_ENV": "Environment variable",
+                "TEST_ENV_OVERRIDE": "Overridden environment variable"
+            }
+        }
+    ],
+    "buildPresets": [
+        {
+            "name": "build-default-debug",
+            "configurePreset": "default",
+            "configuration": "Debug"
+        },
+        {
+            "name": "build-default-release",
+            "inherits": "build-default-debug",
+            "configuration": "Release"
+        }
+    ],
+    "testPresets": [
+        {
+            "name": "minimal",
+            "configurePreset": "default"
+        },
+        {
+            "name": "defaults",
+            "hidden": false,
+            "inherits": [],
+            "vendor": {},
+            "displayName": "",
+            "description": "",
+            "environment": {},
+            "configurePreset": "default",
+            "inheritConfigureEnvironment": true,
+            "configuration": "",
+            "overwriteConfigurationFile": [],
+            "output": {
+                "shortProgress": false,
+                "verbosity": "default",
+                "debug": false,
+                "outputOnFailure": false,
+                "quiet": false,
+                "outputLogFile": "",
+                "labelSummary": true,
+                "subprojectSummary": true
+                // "maxPassedTestOutputSize": 0
+                // "maxTestNameWidth": 0
+            },
+            "filter": {
+                "include": {
+                    "name": "",
+                    "label": "",
+                    "useUnion": false,
+                    "index": ""
+                    // "index": {
+                    //     "start": 0,
+                    //     "end": 0,
+                    //     "stride": 0
+                    // }
+                },
+                "exclude": {
+                    "name": "",
+                    "label": "",
+                    "fixtures": {
+                        "any": "",
+                        "setup": "",
+                        "cleanup": ""
+                    }
+                }
+            },
+            "execution": {
+                "stopOnFailure": false,
+                "enableFailover": false,
+                "jobs": 0,
+                "resourceSpecFile": "",
+                // "testLoad": 0,
+                "showOnly": "human",
+                "repeat": {
+                    "mode": "until-pass",
+                    "count": 1
+                },
+                "interactiveDebugging": false,
+                "scheduleRandom": false,
+                // "timeout": 0,
+                "noTestsAction": "default"
+            }
+        },
+        {
+            "name": "noEnvironment",
+            "configurePreset": "default",
+            "inheritConfigureEnvironment": false,
+            "environment": {
+                "TEST_ENV_REF": "x$env{TEST_ENV}x"
+            },
+            "filter": {
+                "include": {
+                    "name": "test-env"
+                }
+            },
+            "output": {
+                "verbosity": "verbose"
+            }
+        },
+        {
+            "name": "withEnvironment",
+            "inherits": "noEnvironment",
+            "inheritConfigureEnvironment": true,
+            "environment": {
+                "TEST_ENV_OVERRIDE": "Override",
+                "TEST_ENV_OVERRIDE_REF": "x$env{TEST_ENV_OVERRIDE}x",
+                "TEST_ENV_REF": "x$env{TEST_ENV}x"
+            }
+        },
+        {
+            "name": "config-debug",
+            "inherits": "minimal",
+            "configuration": "Debug",
+            "filter": {
+                "include": {
+                    "label": "config"
+                }
+            }
+        },
+        {
+            "name": "config-release",
+            "inherits": "minimal",
+            "configuration": "Release",
+            "filter": {
+                "include": {
+                    "label": "config"
+                }
+            }
+        },
+        {
+            "name": "exclude",
+            "inherits": "minimal",
+            "filter": {
+                "exclude": {
+                    "name": "test-env",
+                    "label": "(ab|config)"
+                }
+            }
+        },
+        {
+            "name": "index",
+            "inherits": "minimal",
+            "filter": {
+                "include": {
+                    "index": {
+                        "end": 4,
+                        "stride": 2
+                    }
+                }
+            }
+        },
+        {
+            "name": "indexFile",
+            "inherits": "minimal",
+            "filter": {
+                "include": {
+                    "index": "${sourceDir}/Good-indexFile.txt"
+                }
+            }
+        },
+        {
+            "name": "showOnly",
+            "inherits": "minimal",
+            "filter": {
+                "include": {
+                    "label": "echo"
+                }
+            },
+            "execution": {
+                "showOnly": "human"
+            }
+        }
+    ]
+}

+ 1 - 0
Tests/RunCMake/CMakePresetsTest/Invalid-test-badConfigurePreset-result.txt

@@ -0,0 +1 @@
+1

+ 2 - 0
Tests/RunCMake/CMakePresetsTest/Invalid-test-badConfigurePreset-stderr.txt

@@ -0,0 +1,2 @@
+CMake Error: No such configure preset in [^
+]*/Tests/RunCMake/CMakePresetsTest/Invalid: "dne"

+ 1 - 0
Tests/RunCMake/CMakePresetsTest/Invalid-test-hidden-result.txt

@@ -0,0 +1 @@
+1

+ 2 - 0
Tests/RunCMake/CMakePresetsTest/Invalid-test-hidden-stderr.txt

@@ -0,0 +1,2 @@
+CMake Error: Cannot use hidden test preset in [^
+]*/Tests/RunCMake/CMakePresetsTest/Invalid: "hidden"

+ 1 - 0
Tests/RunCMake/CMakePresetsTest/Invalid-test-vendorMacro-result.txt

@@ -0,0 +1 @@
+1

+ 1 - 0
Tests/RunCMake/CMakePresetsTest/Invalid-test-vendorMacro-stderr.txt

@@ -0,0 +1 @@
+CMake Error: Could not evaluate test preset "vendorMacro": Invalid macro expansion

+ 0 - 0
Tests/RunCMake/CMakePresetsTest/Invalid.cmake


+ 27 - 0
Tests/RunCMake/CMakePresetsTest/Invalid.json.in

@@ -0,0 +1,27 @@
+{
+    "version": 2,
+    "configurePresets": [
+        {
+            "name": "default",
+            "generator": "@RunCMake_GENERATOR@",
+            "binaryDir": "${sourceDir}/build/${presetName}"
+        }
+    ],
+    "testPresets": [
+        {
+            "name": "hidden",
+            "hidden": true
+        },
+        {
+            "name": "vendorMacro",
+            "configurePreset": "default",
+            "environment": {
+                "TEST": "$vendor{bad.TEST}"
+            }
+        },
+        {
+            "name": "badConfigurePreset",
+            "configurePreset": "dne"
+        }
+    ]
+}

+ 12 - 0
Tests/RunCMake/CMakePresetsTest/ListPresets-test-x-stdout.txt

@@ -0,0 +1,12 @@
+Available test presets:
+
+  "minimal"
+  "defaults"
+  "noEnvironment"
+  "withEnvironment"
+  "config-debug"
+  "config-release"
+  "exclude"
+  "index"
+  "indexFile"
+  "showOnly"

+ 0 - 0
Tests/RunCMake/CMakePresetsTest/ListPresets.cmake


+ 1 - 0
Tests/RunCMake/CMakePresetsTest/NoConfigurePreset-test-noConfigurePreset-result.txt

@@ -0,0 +1 @@
+1

+ 2 - 0
Tests/RunCMake/CMakePresetsTest/NoConfigurePreset-test-noConfigurePreset-stderr.txt

@@ -0,0 +1,2 @@
+CMake Error: Could not read presets from [^
+]*/Tests/RunCMake/CMakePresetsTest/NoConfigurePreset: Invalid preset

+ 0 - 0
Tests/RunCMake/CMakePresetsTest/NoConfigurePreset.cmake


+ 15 - 0
Tests/RunCMake/CMakePresetsTest/NoConfigurePreset.json.in

@@ -0,0 +1,15 @@
+{
+    "version": 2,
+    "configurePresets": [
+        {
+            "name": "default",
+            "generator": "@RunCMake_GENERATOR@",
+            "binaryDir": "${sourceDir}/build/${presetName}"
+        }
+    ],
+    "testPresets": [
+        {
+            "name": "noConfigurePreset"
+        }
+    ]
+}

+ 1 - 0
Tests/RunCMake/CMakePresetsTest/NoTestsAction-test-noTestsAction-result.txt

@@ -0,0 +1 @@
+8

+ 1 - 0
Tests/RunCMake/CMakePresetsTest/NoTestsAction-test-noTestsAction-stderr.txt

@@ -0,0 +1 @@
+No tests were found!!!

+ 0 - 0
Tests/RunCMake/CMakePresetsTest/NoTestsAction.cmake


+ 19 - 0
Tests/RunCMake/CMakePresetsTest/NoTestsAction.json.in

@@ -0,0 +1,19 @@
+{
+    "version": 2,
+    "configurePresets": [
+        {
+            "name": "default",
+            "generator": "@RunCMake_GENERATOR@",
+            "binaryDir": "${sourceDir}/build/${presetName}"
+        }
+    ],
+    "testPresets": [
+        {
+            "name": "noTestsAction",
+            "configurePreset": "default",
+            "execution": {
+                "noTestsAction": "error"
+            }
+        }
+    ]
+}

+ 1 - 0
Tests/RunCMake/CMakePresetsTest/PresetsUnsupported-test-x-result.txt

@@ -0,0 +1 @@
+1

+ 2 - 0
Tests/RunCMake/CMakePresetsTest/PresetsUnsupported-test-x-stderr.txt

@@ -0,0 +1,2 @@
+CMake Error: Could not read presets from [^
+]*Tests/RunCMake/CMakePresetsTest/PresetsUnsupported: File version must be 2 or higher for build and test preset support.

+ 7 - 0
Tests/RunCMake/CMakePresetsTest/PresetsUnsupported.json.in

@@ -0,0 +1,7 @@
+{
+    "version": 1,
+    "configurePresets": [
+    ],
+    "testPresets": [
+    ]
+}

+ 94 - 0
Tests/RunCMake/CMakePresetsTest/RunCMakeTest.cmake

@@ -0,0 +1,94 @@
+include(RunCMake)
+
+# Presets do not support legacy VS generator name architecture suffix.
+if(RunCMake_GENERATOR MATCHES "^(Visual Studio [0-9]+ [0-9]+) ")
+  set(RunCMake_GENERATOR "${CMAKE_MATCH_1}")
+endif()
+
+function(run_cmake_test_presets name CMakePresetsTest_CONFIGURE_PRESETS CMakePresetsTest_BUILD_PRESETS CMakePresetsTest_TEST_PRESETS)
+  set(RunCMake_TEST_SOURCE_DIR "${RunCMake_BINARY_DIR}/${name}")
+  set(RunCMake_TEST_BINARY_DIR "${RunCMake_TEST_SOURCE_DIR}/build")
+  set(RunCMake_TEST_COMMAND_WORKING_DIRECTORY "${RunCMake_TEST_SOURCE_DIR}")
+
+  set(RunCMake_TEST_NO_CLEAN TRUE)
+
+  file(REMOVE_RECURSE "${RunCMake_TEST_SOURCE_DIR}")
+  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
+
+  set(CASE_NAME "${name}")
+  set(CASE_SOURCE_DIR "${RunCMake_SOURCE_DIR}")
+  configure_file("${RunCMake_SOURCE_DIR}/CMakeLists.txt.in" "${RunCMake_TEST_SOURCE_DIR}/CMakeLists.txt" @ONLY)
+
+  if(NOT CMakePresetsTest_FILE)
+    set(CMakePresetsTest_FILE "${RunCMake_SOURCE_DIR}/${name}.json.in")
+  endif()
+  if(EXISTS "${CMakePresetsTest_FILE}")
+    configure_file("${CMakePresetsTest_FILE}" "${RunCMake_TEST_SOURCE_DIR}/CMakePresets.json" @ONLY)
+  endif()
+
+  if(NOT CMakeUserPresets_FILE)
+    set(CMakeUserPresets_FILE "${RunCMake_SOURCE_DIR}/${name}User.json.in")
+  endif()
+  if(EXISTS "${CMakeUserPresets_FILE}")
+    configure_file("${CMakeUserPresets_FILE}" "${RunCMake_TEST_SOURCE_DIR}/CMakeUserPresets.json" @ONLY)
+  endif()
+
+  foreach(ASSET ${CMakePresetsTest_ASSETS})
+    configure_file("${RunCMake_SOURCE_DIR}/${ASSET}" "${RunCMake_TEST_SOURCE_DIR}" COPYONLY)
+  endforeach()
+
+  if (NOT CMakePresetsTest_NO_CONFIGURE)
+    foreach(CONFIGURE_PRESET ${CMakePresetsTest_CONFIGURE_PRESETS})
+      run_cmake_command("${name}-configure-${CONFIGURE_PRESET}"
+        "${CMAKE_COMMAND}" "--preset" "${CONFIGURE_PRESET}")
+    endforeach()
+  endif()
+
+  if (NOT CMakePresetsTest_NO_BUILD)
+    foreach(BUILD_PRESET ${CMakePresetsTest_BUILD_PRESETS})
+      run_cmake_command("${name}-build-${BUILD_PRESET}"
+        "${CMAKE_COMMAND}" "--build" "--preset" "${BUILD_PRESET}")
+    endforeach()
+  endif()
+
+  foreach(TEST_PRESET ${CMakePresetsTest_TEST_PRESETS})
+    if (EXISTS "${RunCMake_SOURCE_DIR}/${name}-test-${TEST_PRESET}-check.cmake")
+      set(RunCMake-check-file "${name}-test-${TEST_PRESET}-check.cmake")
+    else()
+      set(RunCMake-check-file "check.cmake")
+    endif()
+
+    run_cmake_command(${name}-test-${TEST_PRESET}
+      ${CMAKE_CTEST_COMMAND} "--preset" "${TEST_PRESET}" ${ARGN})
+  endforeach()
+endfunction()
+
+set(CMakePresets_SCHEMA_EXPECTED_RESULT 0)
+set(CMakePresetsTest_NO_BUILD 1)
+
+set(CMakePresetsTest_ASSETS "Good-indexFile.txt")
+set(GoodTestPresets
+  "minimal;defaults;noEnvironment;withEnvironment"
+  "config-debug;config-release"
+  "exclude;index;indexFile;showOnly")
+run_cmake_test_presets(Good
+                       "default"
+                       ""
+                       "${GoodTestPresets}")
+unset(CMakePresetsTest_ASSETS)
+
+set(CMakePresetsTest_NO_CONFIGURE 1)
+set(CMakePresetsTest_FILE "${RunCMake_SOURCE_DIR}/Good.json.in")
+run_cmake_test_presets(ListPresets "" "" "x" "--list-presets")
+unset(CMakePresetsTest_FILE)
+
+run_cmake_test_presets(NoConfigurePreset "" "" "noConfigurePreset")
+run_cmake_test_presets(NoTestsAction "default" "" "noTestsAction")
+run_cmake_test_presets(Invalid "" "" "hidden;vendorMacro;badConfigurePreset")
+
+set(CMakePresets_SCHEMA_EXPECTED_RESULT 1)
+run_cmake_test_presets(PresetsUnsupported "" "" "x")
+set(CMakePresets_SCHEMA_EXPECTED_RESULT 0)
+set(CMakePresetsTest_NO_CONFIGURE 0)
+
+set(CMakePresetsTest_NO_BUILD 0)

+ 3 - 0
Tests/RunCMake/CMakePresetsTest/check.cmake

@@ -0,0 +1,3 @@
+set(CMakePresets_VALIDATE_SCRIPT_PATH "${RunCMake_SOURCE_DIR}/../CMakePresets/validate_schema.py")
+include("${RunCMake_SOURCE_DIR}/../CMakePresets/validate_schema.cmake")
+include("${RunCMake_SOURCE_DIR}/../CMakePresets/check.cmake")

+ 1 - 1
Tests/RunCMake/CommandLine/BuildDir--build--parallel-bad-number-stderr.txt

@@ -1,3 +1,3 @@
 ^'--parallel' invalid number '12ab' given\.
 +
-Usage: cmake --build <dir> \[options\] \[-- \[native-options\]\]
+Usage: cmake --build \[<dir> \| --preset <preset>\] \[options\] \[-- \[native-options\]\]

+ 1 - 1
Tests/RunCMake/CommandLine/BuildDir--build--parallel-large-stderr.txt

@@ -1,3 +1,3 @@
 ^The <jobs> value is too large\.
 +
-Usage: cmake --build <dir> \[options\] \[-- \[native-options\]\]
+Usage: cmake --build \[<dir> \| --preset <preset>\] \[options\] \[-- \[native-options\]\]

+ 1 - 1
Tests/RunCMake/CommandLine/BuildDir--build--parallel-no-space-bad-number-stderr.txt

@@ -1,3 +1,3 @@
 ^'--parallel' invalid number '12ab' given\.
 +
-Usage: cmake --build <dir> \[options\] \[-- \[native-options\]\]
+Usage: cmake --build \[<dir> \| --preset <preset>\] \[options\] \[-- \[native-options\]\]

+ 1 - 1
Tests/RunCMake/CommandLine/BuildDir--build--parallel-zero-stderr.txt

@@ -1,3 +1,3 @@
 ^The <jobs> value requires a positive integer argument\.
 +
-Usage: cmake --build <dir> \[options\] \[-- \[native-options\]\]
+Usage: cmake --build \[<dir> \| --preset <preset>\] \[options\] \[-- \[native-options\]\]

+ 1 - 1
Tests/RunCMake/CommandLine/BuildDir--build-jobs-bad-number-stderr.txt

@@ -1,3 +1,3 @@
 ^'-j' invalid number '12ab' given\.
 +
-Usage: cmake --build <dir> \[options\] \[-- \[native-options\]\]
+Usage: cmake --build \[<dir> \| --preset <preset>\] \[options\] \[-- \[native-options\]\]

+ 1 - 1
Tests/RunCMake/CommandLine/BuildDir--build-jobs-large-stderr.txt

@@ -1,3 +1,3 @@
 ^The <jobs> value is too large\.
 +
-Usage: cmake --build <dir> \[options\] \[-- \[native-options\]\]
+Usage: cmake --build \[<dir> \| --preset <preset>\] \[options\] \[-- \[native-options\]\]

+ 1 - 1
Tests/RunCMake/CommandLine/BuildDir--build-jobs-no-space-bad-number-stderr.txt

@@ -1,3 +1,3 @@
 ^'-j' invalid number '12ab' given\.
 +
-Usage: cmake --build <dir> \[options\] \[-- \[native-options\]\]
+Usage: cmake --build \[<dir> \| --preset <preset>\] \[options\] \[-- \[native-options\]\]

+ 1 - 1
Tests/RunCMake/CommandLine/BuildDir--build-jobs-zero-stderr.txt

@@ -1,3 +1,3 @@
 ^The <jobs> value requires a positive integer argument\.
 +
-Usage: cmake --build <dir> \[options\] \[-- \[native-options\]\]
+Usage: cmake --build \[<dir> \| --preset <preset>\] \[options\] \[-- \[native-options\]\]

+ 1 - 1
Tests/RunCMake/CommandLine/BuildDir--build-multiple-targets-with-clean-first-stderr.txt

@@ -1,2 +1,2 @@
 ^Error: Building 'clean' and other targets together is not supported\.
-Usage: cmake --build <dir> \[options\] \[-- \[native-options\]\]
+Usage: cmake --build \[<dir> \| --preset <preset>\] \[options\] \[-- \[native-options\]\]

+ 1 - 1
Tests/RunCMake/CommandLine/BuildDir--build-multiple-targets-with-clean-second-stderr.txt

@@ -1,2 +1,2 @@
 ^Error: Building 'clean' and other targets together is not supported\.
-Usage: cmake --build <dir> \[options\] \[-- \[native-options\]\]
+Usage: cmake --build \[<dir> \| --preset <preset>\] \[options\] \[-- \[native-options\]\]

+ 1 - 1
Tests/RunCMake/CommandLine/build-no-dir-stderr.txt

@@ -1 +1 @@
-^Usage: cmake --build <dir> \[options\] \[-- \[native-options\]\]
+^Usage: cmake --build \[<dir> \| --preset <preset>\] \[options\] \[-- \[native-options\]\]

Some files were not shown because too many files changed in this diff