Răsfoiți Sursa

Merge topic 'cmake-presets'

6f8fdc686c .gitignore: Add CMakeUserPresets.json
1d25760198 Help: Add presets documentation and release notes
a4382f72d7 CMake GUI: Add presets functionality
8617479061 CMake: Add presets functionality
06128cf949 Presets: Add cmCMakePresetsFile class
5a36542086 Refactor: Add allowArch parameter to cmake::CreateGlobalGenerator()
3059e6aed7 cmJSONHelpers: Add new Bind() function
8682d1b7b2 CMake GUI Tests: Increase default sleep time

Acked-by: Kitware Robot <[email protected]>
Acked-by: Michael Hirsch, Ph.D. <[email protected]>
Merge-request: !5169
Brad King 5 ani în urmă
părinte
comite
8c4c658ab0
100 a modificat fișierele cu 4053 adăugiri și 83 ștergeri
  1. 1 0
      .gitignore
  2. 5 0
      Help/manual/cmake-gui.1.rst
  3. 368 0
      Help/manual/cmake.1.rst
  4. 5 0
      Help/release/dev/cmake-presets.rst
  5. 2 0
      Source/CMakeLists.txt
  6. 8 0
      Source/QtDialog/CMakeLists.txt
  7. 22 3
      Source/QtDialog/CMakeSetup.cxx
  8. 84 2
      Source/QtDialog/CMakeSetupDialog.cxx
  9. 12 0
      Source/QtDialog/CMakeSetupDialog.h
  10. 19 4
      Source/QtDialog/CMakeSetupDialog.ui
  11. 52 2
      Source/QtDialog/FirstConfigure.cxx
  12. 15 0
      Source/QtDialog/FirstConfigure.h
  13. 160 0
      Source/QtDialog/QCMake.cxx
  14. 23 0
      Source/QtDialog/QCMake.h
  15. 50 0
      Source/QtDialog/QCMakePreset.cxx
  16. 30 0
      Source/QtDialog/QCMakePreset.h
  17. 64 0
      Source/QtDialog/QCMakePresetComboBox.cxx
  18. 35 0
      Source/QtDialog/QCMakePresetComboBox.h
  19. 143 0
      Source/QtDialog/QCMakePresetItemModel.cxx
  20. 45 0
      Source/QtDialog/QCMakePresetItemModel.h
  21. 763 0
      Source/cmCMakePresetsFile.cxx
  22. 141 0
      Source/cmCMakePresetsFile.h
  23. 2 2
      Source/cmGlobalGeneratorFactory.h
  24. 2 2
      Source/cmGlobalVisualStudio10Generator.cxx
  25. 2 2
      Source/cmGlobalVisualStudio11Generator.cxx
  26. 2 2
      Source/cmGlobalVisualStudio12Generator.cxx
  27. 2 2
      Source/cmGlobalVisualStudio14Generator.cxx
  28. 2 2
      Source/cmGlobalVisualStudio9Generator.cxx
  29. 3 3
      Source/cmGlobalVisualStudioVersionedGenerator.cxx
  30. 2 1
      Source/cmGlobalXCodeGenerator.cxx
  31. 11 0
      Source/cmJSONHelpers.h
  32. 308 35
      Source/cmake.cxx
  33. 49 7
      Source/cmake.h
  34. 6 1
      Source/cmakemain.cxx
  35. 38 0
      Tests/CMakeGUI/CMakeGUITest.cmake
  36. 266 1
      Tests/CMakeGUI/CMakeGUITest.cxx
  37. 3 0
      Tests/CMakeGUI/CMakeGUITest.h
  38. 21 0
      Tests/CMakeGUI/CMakeLists.txt
  39. 80 0
      Tests/CMakeGUI/QCMakePresetComboBoxTest.cxx
  40. 13 0
      Tests/CMakeGUI/QCMakePresetComboBoxTest.h
  41. 162 0
      Tests/CMakeGUI/QCMakePresetItemModelTest.cxx
  42. 17 0
      Tests/CMakeGUI/QCMakePresetItemModelTest.h
  43. 82 0
      Tests/CMakeGUI/QCMakePresetTest.cxx
  44. 14 0
      Tests/CMakeGUI/QCMakePresetTest.h
  45. 33 0
      Tests/CMakeGUI/presetArg-noPresetBinaryChange/CMakePresets.json.in
  46. 33 0
      Tests/CMakeGUI/presetArg-preset/CMakePresets.json.in
  47. 33 0
      Tests/CMakeGUI/presetArg-presetBinary/CMakePresets.json.in
  48. 39 0
      Tests/CMakeGUI/presetArg-presetBinaryChange/CMakePresets.json.in
  49. 2 0
      Tests/CMakeGUI/presetArg-presetConfigExists/CMakeLists.txt.in
  50. 33 0
      Tests/CMakeGUI/presetArg-presetConfigExists/CMakePresets.json.in
  51. 2 0
      Tests/CMakeGUI/presetArg-presetConfigExists/CMakeSetup.ini.in
  52. 23 12
      Tests/CMakeLib/testJSONHelpers.cxx
  53. 1 0
      Tests/RunCMake/CMakeLists.txt
  54. 1 0
      Tests/RunCMake/CMakePresets/CMakeGeneratorConfigDefault-result.txt
  55. 11 0
      Tests/RunCMake/CMakePresets/CMakeGeneratorConfigDefault-stderr.txt
  56. 0 0
      Tests/RunCMake/CMakePresets/CMakeGeneratorConfigIgnore.cmake
  57. 1 0
      Tests/RunCMake/CMakePresets/CMakeGeneratorConfigNone-result.txt
  58. 11 0
      Tests/RunCMake/CMakePresets/CMakeGeneratorConfigNone-stderr.txt
  59. 4 0
      Tests/RunCMake/CMakePresets/CMakeLists.txt.in
  60. 481 0
      Tests/RunCMake/CMakePresets/CMakePresets.json.in
  61. 2 0
      Tests/RunCMake/CMakePresets/CacheOverride.cmake
  62. 1 0
      Tests/RunCMake/CMakePresets/CyclicInheritance0-result.txt
  63. 2 0
      Tests/RunCMake/CMakePresets/CyclicInheritance0-stderr.txt
  64. 13 0
      Tests/RunCMake/CMakePresets/CyclicInheritance0.json.in
  65. 1 0
      Tests/RunCMake/CMakePresets/CyclicInheritance1-result.txt
  66. 2 0
      Tests/RunCMake/CMakePresets/CyclicInheritance1-stderr.txt
  67. 21 0
      Tests/RunCMake/CMakePresets/CyclicInheritance1.json.in
  68. 1 0
      Tests/RunCMake/CMakePresets/CyclicInheritance2-result.txt
  69. 2 0
      Tests/RunCMake/CMakePresets/CyclicInheritance2-stderr.txt
  70. 29 0
      Tests/RunCMake/CMakePresets/CyclicInheritance2.json.in
  71. 1 0
      Tests/RunCMake/CMakePresets/Debug-stderr.txt
  72. 4 0
      Tests/RunCMake/CMakePresets/Debug-stdout.txt
  73. 4 0
      Tests/RunCMake/CMakePresets/Debug.cmake
  74. 19 0
      Tests/RunCMake/CMakePresets/Debug.json.in
  75. 3 0
      Tests/RunCMake/CMakePresets/DebugBase.cmake
  76. 1 0
      Tests/RunCMake/CMakePresets/DisableWarningFlags.cmake
  77. 1 0
      Tests/RunCMake/CMakePresets/DuplicatePresets-result.txt
  78. 2 0
      Tests/RunCMake/CMakePresets/DuplicatePresets-stderr.txt
  79. 15 0
      Tests/RunCMake/CMakePresets/DuplicatePresets.json.in
  80. 1 0
      Tests/RunCMake/CMakePresets/EmptyPresetName-result.txt
  81. 2 0
      Tests/RunCMake/CMakePresets/EmptyPresetName-stderr.txt
  82. 10 0
      Tests/RunCMake/CMakePresets/EmptyPresetName.json.in
  83. 1 0
      Tests/RunCMake/CMakePresets/EnvCycle-result.txt
  84. 1 0
      Tests/RunCMake/CMakePresets/EnvCycle-stderr.txt
  85. 1 0
      Tests/RunCMake/CMakePresets/ErrorDeprecated-result.txt
  86. 7 0
      Tests/RunCMake/CMakePresets/ErrorDeprecated-stderr.txt
  87. 1 0
      Tests/RunCMake/CMakePresets/ErrorDeprecated.cmake
  88. 1 0
      Tests/RunCMake/CMakePresets/ErrorDev-result.txt
  89. 8 0
      Tests/RunCMake/CMakePresets/ErrorDev-stderr.txt
  90. 1 0
      Tests/RunCMake/CMakePresets/ErrorDev.cmake
  91. 1 0
      Tests/RunCMake/CMakePresets/ErrorNoWarningDeprecated-result.txt
  92. 2 0
      Tests/RunCMake/CMakePresets/ErrorNoWarningDeprecated-stderr.txt
  93. 16 0
      Tests/RunCMake/CMakePresets/ErrorNoWarningDeprecated.json.in
  94. 1 0
      Tests/RunCMake/CMakePresets/ErrorNoWarningDev-result.txt
  95. 2 0
      Tests/RunCMake/CMakePresets/ErrorNoWarningDev-stderr.txt
  96. 16 0
      Tests/RunCMake/CMakePresets/ErrorNoWarningDev.json.in
  97. 1 0
      Tests/RunCMake/CMakePresets/ExtraPresetField-result.txt
  98. 2 0
      Tests/RunCMake/CMakePresets/ExtraPresetField-stderr.txt
  99. 11 0
      Tests/RunCMake/CMakePresets/ExtraPresetField.json.in
  100. 1 0
      Tests/RunCMake/CMakePresets/ExtraRootField-result.txt

+ 1 - 0
.gitignore

@@ -5,6 +5,7 @@
 
 *.pyc
 Testing
+CMakeUserPresets.json
 
 # Visual Studio work directory
 .vs/

+ 5 - 0
Help/manual/cmake-gui.1.rst

@@ -11,6 +11,7 @@ Synopsis
  cmake-gui [<options>]
  cmake-gui [<options>] {<path-to-source> | <path-to-existing-build>}
  cmake-gui [<options>] -S <path-to-source> -B <path-to-build>
+ cmake-gui [<options>] -S <path-to-source> --preset=<preset-name>
 
 Description
 ===========
@@ -36,6 +37,10 @@ Options
 
  If the directory doesn't already exist CMake will make it.
 
+``--preset=<preset-name>``
+ Name of the preset to use from the project's ``CMakePresets.json`` file, if it
+ has one.
+
 .. include:: OPTIONS_HELP.txt
 
 See Also

+ 368 - 0
Help/manual/cmake.1.rst

@@ -12,6 +12,7 @@ Synopsis
   cmake [<options>] <path-to-source>
   cmake [<options>] <path-to-existing-build>
   cmake [<options>] -S <path-to-source> -B <path-to-build>
+  cmake [<options>] -S <path-to-source> --preset=<preset-name>
 
  `Build a Project`_
   cmake --build <dir> [<options>] [-- <build-tool-options>]
@@ -148,6 +149,368 @@ source and build trees and generate a buildsystem:
 
     $ cmake -S src -B build
 
+``cmake [<options>] -S <path-to-source> --preset=<preset-name>``
+  Uses ``<path-to-source>`` as the source tree and reads a preset from
+  ``<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
+  also recognize ``CMakePresets.json`` and ``CMakeUserPresets.json`` files.
+
+  ``CMakePresets.json`` and ``CMakeUserPresets.json`` have exactly the same
+  format, and both are optional (though at least one must be present if
+  ``--preset`` is specified.) ``CMakePresets.json`` is meant to save
+  project-wide builds, while ``CMakeUserPresets.json`` is meant for developers
+  to save their own local builds. ``CMakePresets.json`` may be checked into a
+  version control system, and ``CMakeUserPresets.json`` should NOT be checked
+  in. For example, if a project is using Git, ``CMakePresets.json`` may be
+  tracked, and ``CMakeUserPresets.json`` should be added to the ``.gitignore``.
+
+  The presets are read before all other command line options. The options
+  specified by the preset (variables, generator, etc.) can all be overridden by
+  manually specifying them on the command line. For example, if the preset sets
+  a variable called ``MYVAR`` to ``1``, but the user sets it to ``2`` with a
+  ``-D`` argument, the value ``2`` is preferred.
+
+  The files are a JSON document with an object as the root:
+
+  .. code-block:: json
+
+    {
+      "version": 1,
+      "cmakeMinimumRequired": {
+        "major": 3,
+        "minor": 19,
+        "patch": 0
+      },
+      "configurePresets": [
+        {
+          "name": "default",
+          "displayName": "Default Config",
+          "description": "Default build using Ninja generator",
+          "generator": "Ninja",
+          "binaryDir": "${sourceDir}/build/default",
+          "cacheVariables": [
+            {
+              "name": "MY_CACHE_VARIABLE",
+              "type": "BOOL",
+              "value": "OFF"
+            }
+          ]
+        }
+      ]
+    }
+
+  The root object recognizes the following fields:
+
+  ``version``
+
+    A required integer representing the version of the JSON schema. Currently,
+    the only supported version is 1.
+
+  ``cmakeMinimumRequired``
+
+    An optional object representing the minimum version of CMake needed to
+    build this project. This object consists of the following fields:
+
+    ``major``
+
+      An optional integer representing the major version.
+
+    ``minor``
+
+      An optional integer representing the minor version.
+
+    ``patch``
+
+      An optional integer representing the patch version.
+
+  ``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, the keys should be a vendor-specific domain name
+    followed by a ``/``-separated path. For example, the Example IDE 1.0 could
+    use ``example.com/ExampleIDE/1.0``. The value of each field can be anything
+    desired by the vendor, though will typically be a map. For example:
+
+    .. code-block:: json
+
+      {
+        "version": 1,
+        "vendor": {
+          "example.com/ExampleIDE/1.0": {
+            "autoFormat": true
+          }
+        },
+        "configurePresets": []
+      }
+
+  ``configurePresets``
+
+    An optional array of configure preset objects. Each preset 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 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,
+      will not show up in the :manual:`CMake GUI <cmake-gui(1)>`, 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``
+
+      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``.
+
+      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.
+
+    ``generator``
+
+      An optional string representing the generator to use for the preset. If
+      ``generator`` is not specified, it must be inherited from the
+      ``inherits`` preset (unless this preset is ``hidden``).
+
+      Note that for Visual Studio generators, unlike in the command line ``-G``
+      argument, you cannot include the platform name in the generator name. Use
+      the ``architecture`` field instead.
+
+    ``architecture``
+
+      An optional string representing the platform name to use for Visual
+      Studio generators.
+
+    ``toolset``
+
+      An optional string representing the toolset name to use for Visual Studio
+      generators.
+
+    ``cmakeGeneratorConfig``
+
+      An optional string telling CMake how to handle the ``architecture`` and
+      ``toolset`` fields. Valid values are:
+
+      ``"default"``
+
+        Set the platform and toolset using the ``architecture`` and ``toolset``
+        fields respectively. On non-Visual Studio generators, this will result
+        in an error if ``architecture`` or ``toolset`` are set.
+
+      ``"ignore"``
+
+        Do not set the platform or toolset at all, even on Visual Studio
+        generators. This is useful if, for example, a preset uses the Ninja
+        generator, and an IDE knows how to set up the Visual C++ environment
+        from the ``architecture`` and ``toolset`` fields. In that case, CMake
+        will ignore ``architecture`` and ``toolset``, but the IDE can use them
+        to set up the environment before invoking CMake.
+
+    ``binaryDir``
+
+      An optional string representing the path to the output binary directory.
+      This field supports macro expansion. If a relative path is specified, it
+      is calculated relative to the source directory. If ``binaryDir`` is not
+      specified, it must be inherited from the ``inherits`` preset (unless this
+      preset is ``hidden``).
+
+    ``cmakeExecutable``
+
+      An optional string representing the path to the CMake executable to use
+      for this preset. This is reserved for use by IDEs, and is not used by
+      CMake itself. IDEs that use this field should expand any macros in it.
+
+    ``cacheVariables``
+
+      An optional map of cache variables. The key is the variable name, and the
+      value is either ``null``, a string representing the value of the variable
+      (which supports macro expansion), or an object with the following fields:
+
+      ``type``
+
+        An optional string representing the type of the variable.
+
+      ``value``
+
+        A required string representing the value of the variable. This field
+        supports macro expansion.
+
+      Cache variables are inherited through the ``inherits`` field, and the
+      preset's variables will be the union of its own ``cacheVariables`` and
+      the ``cacheVariables`` 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.
+
+    ``environment``
+
+      An optional map of environment variables. The key is the variable name,
+      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.
+
+    ``warnings``
+
+      An optional object specifying warnings. The object may contain the
+      following fields:
+
+      ``dev``
+
+        An optional boolean. Equivalent to passing ``-Wdev`` or ``-Wno-dev``
+        on the command line. This may not be set to ``false`` if ``errors.dev``
+        is set to ``true``.
+
+      ``deprecated``
+
+        An optional boolean. Equivalent to passing ``-Wdeprecated`` or
+        ``-Wno-deprecated`` on the command line. This may not be set to
+        ``false`` if ``errors.deprecated`` is set to ``true``.
+
+      ``uninitialized``
+
+        An optional boolean. Setting this to ``true`` is equivalent to passing
+        ``--warn-uninitialized`` on the command line.
+
+      ``unusedVars``
+
+        An optional boolean. Setting this to ``false`` is equivalent to passing
+        ``--no-warn-unused-cli`` on the command line.
+
+      ``systemVars``
+
+        An optional boolean. Setting this to ``true`` is equivalent to passing
+        ``--check-system-vars`` on the command line.
+
+    ``errors``
+
+      An optional object specifying errors. The object may contain the
+      following fields:
+
+      ``dev``
+
+        An optional boolean. Equivalent to passing ``-Werror=dev`` or
+        ``-Wno-error=dev`` on the command line. This may not be set to ``true``
+        if ``warnings.dev`` is set to ``false``.
+
+      ``deprecated``
+
+        An optional boolean. Equivalent to passing ``-Werror=deprecated`` or
+        ``-Wno-error=deprecated`` on the command line. This may not be set to
+        ``true`` if ``warnings.deprecated`` is set to ``false``.
+
+  As mentioned above, some fields support macro expansion. Macros are
+  recognized in the form ``$<macro-namespace>{<macro-name>}``. All macros are
+  evaluated in the context of the preset being used, even if the macro is in a
+  field that was inherited from another preset. For example, if the ``Base``
+  preset sets variable ``PRESET_NAME`` to ``${presetName}``, and the
+  ``Derived`` preset inherits from ``Base``, ``PRESET_NAME`` will be set to
+  ``Derived``.
+
+  It is an error to not put a closing brace at the end of a macro name. For
+  example, ``${sourceDir`` is invalid. A dollar sign (``$``) followed by
+  anything other than a left curly brace (``{``) with a possible namespace is
+  interpreted as a literal dollar sign.
+
+  Recognized macros include:
+
+  ``${sourceDir}``
+
+    Path to the project source directory.
+
+  ``${sourceParentDir}``
+
+    Path to the project source directory's parent directory.
+
+  ``${presetName}``
+
+    Name specified in the preset's ``name`` field.
+
+  ``${generator}``
+
+    Generator specified in the preset's ``generator`` field.
+
+  ``${dollar}``
+
+    A literal dollar sign (``$``).
+
+  ``$env{<variable-name>}``
+
+    Environment variable with name ``<variable-name>``. If the variable is
+    defined in the ``environment`` field, that value is used instead of the
+    value from the parent environment. If the environment variable is not
+    defined, this evaluates as an empty string.
+
+    Note that while Windows environment variable names are case-insensitive,
+    variable names within a preset are still case-sensitive. This may lead to
+    unexpected results when using inconsistent casing. For best results, keep
+    the casing of environment variable names consistent.
+
+  ``$penv{<variable-name>}``
+
+    Similar to ``$env{<variable-name>}``, except that the value only comes from
+    the parent environment, and never from the ``environment`` field. This
+    allows you to prepend or append values to existing environment variables.
+    For example, setting ``PATH`` to ``/path/to/ninja/bin:$penv{PATH}`` will
+    prepend ``/path/to/ninja/bin`` to the ``PATH`` environment variable. This
+    is needed because ``$env{<variable-name>}`` does not allow circular
+    references.
+
+  ``$vendor{<macro-name>}``
+
+    An extension point for vendors to insert their own macros. CMake will not
+    be able to use presets which have a ``$vendor{<macro-name>}`` macro, and
+    effectively ignores such presets. However, it will still be able to use
+    other presets from the same file.
+
+    CMake does not make any attempt to interpret ``$vendor{<macro-name>}``
+    macros. However, to avoid name collisions, IDE vendors should prefix
+    ``<macro-name>`` with a very short (preferably <= 4 characters) vendor
+    identifier prefix, followed by a ``.``, followed by the macro name. For
+    example, the Example IDE could have ``$vendor{xide.ideInstallDir}``.
+
 In all cases the ``<options>`` may be zero or more of the `Options`_ below.
 
 After generating a buildsystem one may use the corresponding native
@@ -813,6 +1176,11 @@ with one of the following options:
 
 .. include:: OPTIONS_HELP.txt
 
+To view the presets available for a project, use
+
+.. code-block::shell
+
+  cmake <source-dir> --list-presets
 
 See Also
 ========

+ 5 - 0
Help/release/dev/cmake-presets.rst

@@ -0,0 +1,5 @@
+cmake-presets
+-------------
+
+* :manual:`cmake(1)` and :manual:`cmake-gui(1)` now recognize
+  ``CMakePresets.json`` and ``CMakeUserPresets.json`` files.

+ 2 - 0
Source/CMakeLists.txt

@@ -184,6 +184,8 @@ set(SRCS
   cmCLocaleEnvironmentScope.cxx
   cmCMakePath.h
   cmCMakePath.cxx
+  cmCMakePresetsFile.cxx
+  cmCMakePresetsFile.h
   cmCommandArgumentParserHelper.cxx
   cmCommonTargetGenerator.cxx
   cmCommonTargetGenerator.h

+ 8 - 0
Source/QtDialog/CMakeLists.txt

@@ -92,6 +92,12 @@ set(SRCS
   QCMake.h
   QCMakeCacheView.cxx
   QCMakeCacheView.h
+  QCMakePreset.cxx
+  QCMakePreset.h
+  QCMakePresetComboBox.cxx
+  QCMakePresetComboBox.h
+  QCMakePresetItemModel.cxx
+  QCMakePresetItemModel.h
   QCMakeWidgets.cxx
   QCMakeWidgets.h
   RegexExplorer.cxx
@@ -116,6 +122,8 @@ qt5_wrap_cpp(MOC_SRCS
   FirstConfigure.h
   QCMake.h
   QCMakeCacheView.h
+  QCMakePresetComboBox.h
+  QCMakePresetItemModel.h
   QCMakeWidgets.h
   RegexExplorer.h
   WarningMessagesDialog.h

+ 22 - 3
Source/QtDialog/CMakeSetup.cxx

@@ -32,7 +32,8 @@ static const char* cmDocumentationUsage[][2] = {
     "  cmake-gui [options]\n"
     "  cmake-gui [options] <path-to-source>\n"
     "  cmake-gui [options] <path-to-existing-build>\n"
-    "  cmake-gui [options] -S <path-to-source> -B <path-to-build>\n" },
+    "  cmake-gui [options] -S <path-to-source> -B <path-to-build>\n"
+    "  cmake-gui [options] -S <path-to-source> --preset=<preset-name>\n" },
   { nullptr, nullptr }
 };
 
@@ -147,6 +148,7 @@ int main(int argc, char** argv)
   QStringList args = QApplication::arguments();
   std::string binaryDirectory;
   std::string sourceDirectory;
+  std::string presetName;
   for (int i = 1; i < args.size(); ++i) {
     const QString& arg = args[i];
     if (arg.startsWith("-S")) {
@@ -185,11 +187,28 @@ int main(int argc, char** argv)
       binaryDirectory =
         cmSystemTools::CollapseFullPath(path.toLocal8Bit().data());
       cmSystemTools::ConvertToUnixSlashes(binaryDirectory);
+    } else if (arg.startsWith("--preset=")) {
+      QString preset = arg.mid(cmStrLen("--preset="));
+      if (preset.isEmpty()) {
+        std::cerr << "No preset specified for --preset" << std::endl;
+        return 1;
+      }
+      presetName = preset.toLocal8Bit().data();
     }
   }
-  if (!sourceDirectory.empty() && !binaryDirectory.empty()) {
+  if (!sourceDirectory.empty() &&
+      (!binaryDirectory.empty() || !presetName.empty())) {
     dialog.setSourceDirectory(QString::fromLocal8Bit(sourceDirectory.c_str()));
-    dialog.setBinaryDirectory(QString::fromLocal8Bit(binaryDirectory.c_str()));
+    if (!binaryDirectory.empty()) {
+      dialog.setBinaryDirectory(
+        QString::fromLocal8Bit(binaryDirectory.c_str()));
+      if (!presetName.empty()) {
+        dialog.setStartupBinaryDirectory(true);
+      }
+    }
+    if (!presetName.empty()) {
+      dialog.setDeferredPreset(QString::fromLocal8Bit(presetName.c_str()));
+    }
   } else {
     if (args.count() == 2) {
       std::string filePath =

+ 84 - 2
Source/QtDialog/CMakeSetupDialog.cxx

@@ -21,8 +21,10 @@
 #include <QSettings>
 #include <QShortcut>
 #include <QStatusBar>
+#include <QString>
 #include <QToolButton>
 #include <QUrl>
+#include <QVector>
 
 #ifdef QT_WINEXTRAS
 #  include <QWinTaskbarButton>
@@ -263,6 +265,8 @@ void CMakeSetupDialog::initialize()
                    &CMakeSetupDialog::onBinaryDirectoryChanged);
   QObject::connect(this->SourceDirectory, &QLineEdit::textChanged, this,
                    &CMakeSetupDialog::onSourceDirectoryChanged);
+  QObject::connect(this->Preset, &QCMakePresetComboBox::presetChanged, this,
+                   &CMakeSetupDialog::onBuildPresetChanged);
 
   QObject::connect(this->CMakeThread->cmakeInstance(),
                    &QCMake::sourceDirChanged, this,
@@ -270,6 +274,13 @@ void CMakeSetupDialog::initialize()
   QObject::connect(this->CMakeThread->cmakeInstance(),
                    &QCMake::binaryDirChanged, this,
                    &CMakeSetupDialog::updateBinaryDirectory);
+  QObject::connect(this->CMakeThread->cmakeInstance(), &QCMake::presetsChanged,
+                   this, &CMakeSetupDialog::updatePresets);
+  QObject::connect(this->CMakeThread->cmakeInstance(), &QCMake::presetChanged,
+                   this, &CMakeSetupDialog::updatePreset);
+  QObject::connect(this->CMakeThread->cmakeInstance(),
+                   &QCMake::presetLoadError, this,
+                   &CMakeSetupDialog::showPresetLoadError);
 
   QObject::connect(this->CMakeThread->cmakeInstance(),
                    &QCMake::progressChanged, this,
@@ -314,9 +325,15 @@ void CMakeSetupDialog::initialize()
   QObject::connect(this->WarnUninitializedAction, &QAction::triggered,
                    this->CMakeThread->cmakeInstance(),
                    &QCMake::setWarnUninitializedMode);
+  QObject::connect(this->CMakeThread->cmakeInstance(),
+                   &QCMake::warnUninitializedModeChanged,
+                   this->WarnUninitializedAction, &QAction::setChecked);
 
-  if (!this->SourceDirectory->text().isEmpty() ||
-      !this->BinaryDirectory->lineEdit()->text().isEmpty()) {
+  if (!this->SourceDirectory->text().isEmpty() &&
+      !this->DeferredPreset.isNull()) {
+    this->onSourceDirectoryChanged(this->SourceDirectory->text());
+  } else if (!this->SourceDirectory->text().isEmpty() ||
+             !this->BinaryDirectory->lineEdit()->text().isEmpty()) {
     this->onSourceDirectoryChanged(this->SourceDirectory->text());
     this->onBinaryDirectoryChanged(this->BinaryDirectory->lineEdit()->text());
   } else {
@@ -671,6 +688,41 @@ void CMakeSetupDialog::updateBinaryDirectory(const QString& dir)
   }
 }
 
+void CMakeSetupDialog::updatePresets(const QVector<QCMakePreset>& presets)
+{
+  if (this->Preset->presets() != presets) {
+    this->Preset->blockSignals(true);
+    this->Preset->setPresets(presets);
+    this->Preset->blockSignals(false);
+  }
+
+  this->Preset->setHidden(presets.isEmpty());
+  this->PresetLabel->setHidden(presets.isEmpty());
+
+  if (!this->DeferredPreset.isNull()) {
+    this->Preset->setPresetName(this->DeferredPreset);
+    this->DeferredPreset = QString{};
+  }
+}
+
+void CMakeSetupDialog::updatePreset(const QString& name)
+{
+  if (this->Preset->presetName() != name) {
+    this->Preset->blockSignals(true);
+    this->Preset->setPresetName(name);
+    this->Preset->blockSignals(false);
+  }
+}
+
+void CMakeSetupDialog::showPresetLoadError(
+  const QString& dir, cmCMakePresetsFile::ReadFileResult result)
+{
+  QMessageBox::warning(
+    this, "Error Reading CMake Presets",
+    QString::fromLocal8Bit("Could not read presets from %1: %2")
+      .arg(dir, cmCMakePresetsFile::ResultToString(result)));
+}
+
 void CMakeSetupDialog::doBinaryBrowse()
 {
   QString dir = QFileDialog::getExistingDirectory(
@@ -686,6 +738,11 @@ void CMakeSetupDialog::setBinaryDirectory(const QString& dir)
   this->BinaryDirectory->setEditText(dir);
 }
 
+void CMakeSetupDialog::setStartupBinaryDirectory(bool startup)
+{
+  this->StartupBinaryDirectory = startup;
+}
+
 void CMakeSetupDialog::onSourceDirectoryChanged(const QString& dir)
 {
   this->Output->clear();
@@ -711,11 +768,24 @@ void CMakeSetupDialog::onBinaryDirectoryChanged(const QString& dir)
                             Q_ARG(QString, dir));
 }
 
+void CMakeSetupDialog::onBuildPresetChanged(const QString& name)
+{
+  QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(), "setPreset",
+                            Qt::QueuedConnection, Q_ARG(QString, name),
+                            Q_ARG(bool, !this->StartupBinaryDirectory));
+  this->StartupBinaryDirectory = false;
+}
+
 void CMakeSetupDialog::setSourceDirectory(const QString& dir)
 {
   this->SourceDirectory->setText(dir);
 }
 
+void CMakeSetupDialog::setDeferredPreset(const QString& preset)
+{
+  this->DeferredPreset = preset;
+}
+
 void CMakeSetupDialog::showProgress(const QString& /*msg*/, float percent)
 {
   percent = (percent * ProgressFactor) + ProgressOffset;
@@ -753,6 +823,7 @@ void CMakeSetupDialog::setEnabledState(bool enabled)
   this->CacheValues->cacheModel()->setEditEnabled(enabled);
   this->SourceDirectory->setEnabled(enabled);
   this->BrowseSourceDirectoryButton->setEnabled(enabled);
+  this->Preset->setEnabled(enabled);
   this->BinaryDirectory->setEnabled(enabled);
   this->BrowseBinaryDirectoryButton->setEnabled(enabled);
   this->ReloadCacheAction->setEnabled(enabled);
@@ -777,6 +848,17 @@ bool CMakeSetupDialog::setupFirstConfigure()
   // restore from settings
   dialog.loadFromSettings();
 
+  auto presetData = this->Preset->currentData();
+  if (presetData.isValid()) {
+    auto preset = presetData.value<QCMakePreset>();
+    dialog.setCurrentGenerator(preset.generator);
+    if (preset.setGenConfig) {
+      dialog.setPlatform(preset.architecture);
+      dialog.setToolset(preset.toolset);
+    }
+    dialog.setCompilerOption(CompilerOption::DefaultNative);
+  }
+
   if (dialog.exec() == QDialog::Accepted) {
     dialog.saveToSettings();
     this->CMakeThread->cmakeInstance()->setGenerator(dialog.getGenerator());

+ 12 - 0
Source/QtDialog/CMakeSetupDialog.h

@@ -5,12 +5,15 @@
 #include <memory>
 
 #include "QCMake.h"
+#include "QCMakePreset.h"
 #include <QEventLoop>
 #include <QMainWindow>
 #include <QThread>
+#include <QVector>
 
 #include "ui_CMakeSetupDialog.h"
 
+class QCMakePresetItemModel;
 class QCMakeThread;
 class CMakeCacheModel;
 class QProgressBar;
@@ -33,6 +36,8 @@ public:
 public slots:
   void setBinaryDirectory(const QString& dir);
   void setSourceDirectory(const QString& dir);
+  void setDeferredPreset(const QString& preset);
+  void setStartupBinaryDirectory(bool startup);
 
 protected slots:
   void initialize();
@@ -52,6 +57,10 @@ protected slots:
   void doDeleteCache();
   void updateSourceDirectory(const QString& dir);
   void updateBinaryDirectory(const QString& dir);
+  void updatePresets(const QVector<QCMakePreset>& presets);
+  void updatePreset(const QString& name);
+  void showPresetLoadError(const QString& dir,
+                           cmCMakePresetsFile::ReadFileResult result);
   void showProgress(const QString& msg, float percent);
   void setEnabledState(bool);
   bool setupFirstConfigure();
@@ -62,6 +71,7 @@ protected slots:
   void saveBuildPaths(const QStringList&);
   void onBinaryDirectoryChanged(const QString& dir);
   void onSourceDirectoryChanged(const QString& dir);
+  void onBuildPresetChanged(const QString& name);
   void setCacheModified();
   void removeSelectedCacheEntries();
   void selectionChanged();
@@ -113,6 +123,8 @@ protected:
   QAction* WarnUninitializedAction;
   QAction* InstallForCommandLineAction;
   State CurrentState;
+  QString DeferredPreset;
+  bool StartupBinaryDirectory = false;
 
   QTextCharFormat ErrorFormat;
   QTextCharFormat MessageFormat;

+ 19 - 4
Source/QtDialog/CMakeSetupDialog.ui

@@ -44,7 +44,7 @@
       <number>6</number>
      </property>
      <item row="0" column="0">
-      <widget class="QLabel" name="label">
+      <widget class="QLabel" name="SourceLabel">
        <property name="text">
         <string>Where is the source code:</string>
        </property>
@@ -61,13 +61,23 @@
       </widget>
      </item>
      <item row="1" column="0">
-      <widget class="QLabel" name="label_2">
+      <widget class="QLabel" name="PresetLabel">
        <property name="text">
-        <string>Where to build the binaries:</string>
+        <string>Preset:</string>
        </property>
       </widget>
      </item>
      <item row="1" column="1">
+      <widget class="QCMakePresetComboBox" name="Preset"/>
+     </item>
+     <item row="2" column="0">
+      <widget class="QLabel" name="BinaryLabel">
+       <property name="text">
+        <string>Where to build the binaries:</string>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="1">
       <widget class="QComboBox" name="BinaryDirectory">
        <property name="sizePolicy">
         <sizepolicy hsizetype="Ignored" vsizetype="Fixed">
@@ -83,7 +93,7 @@
        </property>
       </widget>
      </item>
-     <item row="1" column="2">
+     <item row="2" column="2">
       <widget class="QPushButton" name="BrowseBinaryDirectoryButton">
        <property name="text">
         <string>Browse &amp;Build...</string>
@@ -367,6 +377,11 @@
    <extends>QTreeView</extends>
    <header>QCMakeCacheView.h</header>
   </customwidget>
+  <customwidget>
+   <class>QCMakePresetComboBox</class>
+   <extends>QComboBox</extends>
+   <header>QCMakePresetComboBox.h</header>
+  </customwidget>
  </customwidgets>
  <resources>
   <include location="CMakeSetup.qrc"/>

+ 52 - 2
Source/QtDialog/FirstConfigure.cxx

@@ -145,6 +145,36 @@ void StartCompilerSetup::setCurrentGenerator(const QString& gen)
   }
 }
 
+void StartCompilerSetup::setPlatform(const QString& platform)
+{
+  this->PlatformOptions->setCurrentText(platform);
+}
+
+void StartCompilerSetup::setToolset(const QString& toolset)
+{
+  this->Toolset->setText(toolset);
+}
+
+void StartCompilerSetup::setCompilerOption(CompilerOption option)
+{
+  std::size_t index = 0;
+  switch (option) {
+    case CompilerOption::DefaultNative:
+      index = 0;
+      break;
+    case CompilerOption::SpecifyNative:
+      index = 1;
+      break;
+    case CompilerOption::ToolchainFile:
+      index = 2;
+      break;
+    case CompilerOption::Options:
+      index = 3;
+      break;
+  }
+  this->CompilerSetupOptions[index]->setChecked(true);
+}
+
 QString StartCompilerSetup::getGenerator() const
 {
   return this->GeneratorOptions->currentText();
@@ -482,6 +512,26 @@ void FirstConfigure::setGenerators(
   this->mStartCompilerSetupPage->setGenerators(gens);
 }
 
+void FirstConfigure::setCurrentGenerator(const QString& gen)
+{
+  this->mStartCompilerSetupPage->setCurrentGenerator(gen);
+}
+
+void FirstConfigure::setPlatform(const QString& platform)
+{
+  this->mStartCompilerSetupPage->setPlatform(platform);
+}
+
+void FirstConfigure::setToolset(const QString& toolset)
+{
+  this->mStartCompilerSetupPage->setToolset(toolset);
+}
+
+void FirstConfigure::setCompilerOption(CompilerOption option)
+{
+  this->mStartCompilerSetupPage->setCompilerOption(option);
+}
+
 QString FirstConfigure::getGenerator() const
 {
   return this->mStartCompilerSetupPage->getGenerator();
@@ -503,7 +553,7 @@ void FirstConfigure::loadFromSettings()
   // restore generator
   settings.beginGroup("Settings/StartPath");
   QString lastGen = settings.value("LastGenerator").toString();
-  this->mStartCompilerSetupPage->setCurrentGenerator(lastGen);
+  this->setCurrentGenerator(lastGen);
   settings.endGroup();
 
   // restore compiler setup
@@ -550,7 +600,7 @@ void FirstConfigure::loadFromSettings()
   //     this prevents them from being taken from environment, while the
   //     generator is taken from application settings
   if (!mDefaultGenerator.isEmpty()) {
-    this->mStartCompilerSetupPage->setCurrentGenerator(mDefaultGenerator);
+    this->setCurrentGenerator(mDefaultGenerator);
   }
 }
 

+ 15 - 0
Source/QtDialog/FirstConfigure.h

@@ -22,6 +22,14 @@ enum FirstConfigurePages
   Done
 };
 
+enum class CompilerOption
+{
+  DefaultNative,
+  SpecifyNative,
+  ToolchainFile,
+  Options,
+};
+
 //! the first page that gives basic options for what compilers setup to choose
 //! from
 class StartCompilerSetup : public QWizardPage
@@ -33,6 +41,9 @@ public:
   ~StartCompilerSetup();
   void setGenerators(std::vector<cmake::GeneratorInfo> const& gens);
   void setCurrentGenerator(const QString& gen);
+  void setToolset(const QString& toolset);
+  void setPlatform(const QString& platform);
+  void setCompilerOption(CompilerOption option);
   QString getGenerator() const;
   QString getToolset() const;
   QString getPlatform() const;
@@ -167,6 +178,10 @@ public:
   ~FirstConfigure();
 
   void setGenerators(std::vector<cmake::GeneratorInfo> const& gens);
+  void setCurrentGenerator(const QString& gen);
+  void setToolset(const QString& toolset);
+  void setPlatform(const QString& platform);
+  void setCompilerOption(CompilerOption option);
   QString getGenerator() const;
   QString getPlatform() const;
   QString getToolset() const;

+ 160 - 0
Source/QtDialog/QCMake.cxx

@@ -2,10 +2,14 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "QCMake.h"
 
+#include <algorithm>
+
 #include <cm/memory>
 
 #include <QCoreApplication>
 #include <QDir>
+#include <QString>
+#include <QVector>
 
 #include "cmExternalMakefileProjectGenerator.h"
 #include "cmGlobalGenerator.h"
@@ -19,12 +23,15 @@
 
 QCMake::QCMake(QObject* p)
   : QObject(p)
+  , StartEnvironment(QProcessEnvironment::systemEnvironment())
   , Environment(QProcessEnvironment::systemEnvironment())
 {
   this->WarnUninitializedMode = false;
   qRegisterMetaType<QCMakeProperty>();
   qRegisterMetaType<QCMakePropertyList>();
   qRegisterMetaType<QProcessEnvironment>();
+  qRegisterMetaType<QVector<QCMakePreset>>();
+  qRegisterMetaType<cmCMakePresetsFile::ReadFileResult>();
 
   cmSystemTools::DisableRunCommandOutput();
   cmSystemTools::SetRunCommandHideConsole(true);
@@ -57,6 +64,17 @@ QCMake::QCMake(QObject* p)
   for (cmake::GeneratorInfo const& gen : generators) {
     this->AvailableGenerators.push_back(gen);
   }
+
+  connect(&this->LoadPresetsTimer, &QTimer::timeout, this, [this]() {
+    this->loadPresets();
+    if (!this->PresetName.isEmpty() &&
+        this->CMakePresetsFile.Presets.find(
+          std::string(this->PresetName.toLocal8Bit())) ==
+          this->CMakePresetsFile.Presets.end()) {
+      this->setPreset(QString{});
+    }
+  });
+  this->LoadPresetsTimer.start(1000);
 }
 
 QCMake::~QCMake() = default;
@@ -73,6 +91,8 @@ void QCMake::setSourceDirectory(const QString& _dir)
   if (this->SourceDirectory != dir) {
     this->SourceDirectory = QDir::fromNativeSeparators(dir);
     emit this->sourceDirChanged(this->SourceDirectory);
+    this->loadPresets();
+    this->setPreset(QString{});
   }
 }
 
@@ -129,6 +149,56 @@ void QCMake::setBinaryDirectory(const QString& _dir)
   }
 }
 
+void QCMake::setPreset(const QString& name, bool setBinary)
+{
+  if (this->PresetName != name) {
+    this->PresetName = name;
+    emit this->presetChanged(this->PresetName);
+
+    if (!name.isNull()) {
+      std::string presetName(name.toLocal8Bit());
+      auto const& preset = this->CMakePresetsFile.Presets[presetName];
+      auto expandedPreset = this->CMakePresetsFile.ExpandMacros(preset);
+      if (expandedPreset) {
+        if (setBinary) {
+          QString binaryDir =
+            QString::fromLocal8Bit(expandedPreset->BinaryDir.data());
+          this->setBinaryDirectory(binaryDir);
+        }
+        if (expandedPreset->WarnDev) {
+          this->CMakeInstance->SetSuppressDevWarnings(
+            !*expandedPreset->WarnDev);
+        }
+        if (expandedPreset->ErrorDev) {
+          this->CMakeInstance->SetDevWarningsAsErrors(
+            *expandedPreset->ErrorDev);
+        }
+        if (expandedPreset->WarnDeprecated) {
+          this->CMakeInstance->SetSuppressDeprecatedWarnings(
+            !*expandedPreset->WarnDeprecated);
+        }
+        if (expandedPreset->ErrorDeprecated) {
+          this->CMakeInstance->SetDeprecatedWarningsAsErrors(
+            *expandedPreset->ErrorDeprecated);
+        }
+        if (expandedPreset->WarnUninitialized) {
+          this->WarnUninitializedMode = *expandedPreset->WarnUninitialized;
+          emit this->warnUninitializedModeChanged(
+            *expandedPreset->WarnUninitialized);
+        }
+        this->Environment = this->StartEnvironment;
+        for (auto const& v : expandedPreset->Environment) {
+          if (v.second) {
+            this->Environment.insert(QString::fromLocal8Bit(v.first.data()),
+                                     QString::fromLocal8Bit(v.second->data()));
+          }
+        }
+      }
+    }
+    emit this->propertiesChanged(this->properties());
+  }
+}
+
 void QCMake::setGenerator(const QString& gen)
 {
   if (this->Generator != gen) {
@@ -348,6 +418,56 @@ QCMakePropertyList QCMake::properties() const
     ret.append(prop);
   }
 
+  if (!this->PresetName.isNull()) {
+    std::string presetName(this->PresetName.toLocal8Bit());
+    auto p = this->CMakePresetsFile.ExpandMacros(
+      this->CMakePresetsFile.Presets.at(presetName));
+    if (p) {
+      for (auto const& v : p->CacheVariables) {
+        if (!v.second) {
+          continue;
+        }
+        QCMakeProperty prop;
+        prop.Key = QString::fromLocal8Bit(v.first.data());
+        prop.Value = QString::fromLocal8Bit(v.second->Value.data());
+        prop.Type = QCMakeProperty::STRING;
+        if (!v.second->Type.empty()) {
+          auto type = cmState::StringToCacheEntryType(v.second->Type);
+          switch (type) {
+            case cmStateEnums::BOOL:
+              prop.Type = QCMakeProperty::BOOL;
+              prop.Value = cmIsOn(v.second->Value);
+              break;
+            case cmStateEnums::PATH:
+              prop.Type = QCMakeProperty::PATH;
+              break;
+            case cmStateEnums::FILEPATH:
+              prop.Type = QCMakeProperty::FILEPATH;
+              break;
+            default:
+              prop.Type = QCMakeProperty::STRING;
+              break;
+          }
+        }
+
+        // QCMakeCacheModel prefers variables earlier in the list rather than
+        // later, so overwrite them if they already exist rather than simply
+        // appending
+        bool found = false;
+        for (auto& orig : ret) {
+          if (orig.Key == prop.Key) {
+            orig = prop;
+            found = true;
+            break;
+          }
+        }
+        if (!found) {
+          ret.append(prop);
+        }
+      }
+    }
+  }
+
   return ret;
 }
 
@@ -405,6 +525,46 @@ void QCMake::setUpEnvironment() const
   }
 }
 
+void QCMake::loadPresets()
+{
+  auto result = this->CMakePresetsFile.ReadProjectPresets(
+    this->SourceDirectory.toLocal8Bit().data(), true);
+  if (result != this->LastLoadPresetsResult &&
+      result != cmCMakePresetsFile::ReadFileResult::READ_OK) {
+    emit this->presetLoadError(this->SourceDirectory, result);
+  }
+  this->LastLoadPresetsResult = result;
+
+  QVector<QCMakePreset> presets;
+  for (auto const& name : this->CMakePresetsFile.PresetOrder) {
+    auto const& p = this->CMakePresetsFile.Presets[name];
+    if (p.Hidden) {
+      continue;
+    }
+
+    QCMakePreset preset;
+    preset.name = std::move(QString::fromLocal8Bit(p.Name.data()));
+    preset.displayName =
+      std::move(QString::fromLocal8Bit(p.DisplayName.data()));
+    preset.description =
+      std::move(QString::fromLocal8Bit(p.Description.data()));
+    preset.generator = std::move(QString::fromLocal8Bit(p.Generator.data()));
+    preset.architecture =
+      std::move(QString::fromLocal8Bit(p.Architecture.data()));
+    preset.toolset = std::move(QString::fromLocal8Bit(p.Toolset.data()));
+    preset.setGenConfig = !p.GeneratorConfig ||
+      p.GeneratorConfig == cmCMakePresetsFile::CMakeGeneratorConfig::Default;
+    preset.enabled = std::find_if(this->AvailableGenerators.begin(),
+                                  this->AvailableGenerators.end(),
+                                  [&p](const cmake::GeneratorInfo& g) {
+                                    return g.name == p.Generator;
+                                  }) != this->AvailableGenerators.end() &&
+      this->CMakePresetsFile.ExpandMacros(p);
+    presets.push_back(preset);
+  }
+  emit this->presetsChanged(presets);
+}
+
 QString QCMake::binaryDirectory() const
 {
   return this->BinaryDirectory;

+ 23 - 0
Source/QtDialog/QCMake.h

@@ -4,6 +4,7 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
+#include "cmCMakePresetsFile.h"
 #include "cmake.h"
 
 #ifdef _MSC_VER
@@ -14,6 +15,7 @@
 #include <memory>
 #include <vector>
 
+#include "QCMakePreset.h"
 #include <QAtomicInt>
 #include <QList>
 #include <QMetaType>
@@ -21,6 +23,7 @@
 #include <QProcessEnvironment>
 #include <QString>
 #include <QStringList>
+#include <QTimer>
 #include <QVariant>
 
 /// struct to represent cmake properties in Qt
@@ -57,6 +60,7 @@ using QCMakePropertyList = QList<QCMakeProperty>;
 Q_DECLARE_METATYPE(QCMakeProperty)
 Q_DECLARE_METATYPE(QCMakePropertyList)
 Q_DECLARE_METATYPE(QProcessEnvironment)
+Q_DECLARE_METATYPE(cmCMakePresetsFile::ReadFileResult)
 
 /// Qt API for CMake library.
 /// Wrapper like class allows for easier integration with
@@ -74,6 +78,8 @@ public slots:
   void setSourceDirectory(const QString& dir);
   /// set the binary directory to build in
   void setBinaryDirectory(const QString& dir);
+  /// set the preset name to use
+  void setPreset(const QString& name, bool setBinary = true);
   /// set the desired generator to use
   void setGenerator(const QString& generator);
   /// set the desired generator to use
@@ -147,6 +153,15 @@ signals:
   void sourceDirChanged(const QString& dir);
   /// signal when the binary directory changes
   void binaryDirChanged(const QString& dir);
+  /// signal when the preset list changes
+  void presetsChanged(const QVector<QCMakePreset>& presets);
+  /// signal when the selected preset changes
+  void presetChanged(const QString& name);
+  /// signal when there's an error reading the presets files
+  void presetLoadError(const QString& dir,
+                       cmCMakePresetsFile::ReadFileResult error);
+  /// signal when uninitialized warning changes
+  void warnUninitializedModeChanged(bool value);
   /// signal for progress events
   void progressChanged(const QString& msg, float percent);
   /// signal when configure is done
@@ -178,6 +193,8 @@ protected:
   void stderrCallback(std::string const& msg);
   void setUpEnvironment() const;
 
+  void loadPresets();
+
   bool WarnUninitializedMode;
   QString SourceDirectory;
   QString BinaryDirectory;
@@ -185,7 +202,13 @@ protected:
   QString Platform;
   QString Toolset;
   std::vector<cmake::GeneratorInfo> AvailableGenerators;
+  cmCMakePresetsFile CMakePresetsFile;
+  cmCMakePresetsFile::ReadFileResult LastLoadPresetsResult =
+    cmCMakePresetsFile::ReadFileResult::READ_OK;
+  QString PresetName;
   QString CMakeExecutable;
   QAtomicInt InterruptFlag;
+  QProcessEnvironment StartEnvironment;
   QProcessEnvironment Environment;
+  QTimer LoadPresetsTimer;
 };

+ 50 - 0
Source/QtDialog/QCMakePreset.cxx

@@ -0,0 +1,50 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "QCMakePreset.h"
+
+bool operator==(const QCMakePreset& lhs, const QCMakePreset& rhs)
+{
+  return lhs.name == rhs.name && lhs.displayName == rhs.displayName &&
+    lhs.description == rhs.description && lhs.generator == rhs.generator &&
+    lhs.architecture == rhs.architecture && lhs.toolset == rhs.toolset &&
+    lhs.setGenConfig == rhs.setGenConfig && lhs.enabled == rhs.enabled;
+}
+
+bool operator!=(const QCMakePreset& lhs, const QCMakePreset& rhs)
+{
+  return !(lhs == rhs);
+}
+
+bool operator<(const QCMakePreset& lhs, const QCMakePreset& rhs)
+{
+  return lhs.name < rhs.name ||
+    (lhs.name == rhs.name &&
+     (lhs.displayName < rhs.displayName ||
+      (lhs.displayName == rhs.displayName &&
+       (lhs.description < rhs.description ||
+        (lhs.description == rhs.description &&
+         (lhs.generator < rhs.generator ||
+          (lhs.generator == rhs.generator &&
+           (lhs.architecture < rhs.architecture ||
+            (lhs.architecture == rhs.architecture &&
+             (lhs.toolset < rhs.toolset ||
+              (lhs.toolset == rhs.toolset &&
+               (lhs.setGenConfig < rhs.setGenConfig ||
+                (lhs.setGenConfig == rhs.setGenConfig &&
+                 (lhs.enabled < rhs.enabled))))))))))))));
+}
+
+bool operator<=(const QCMakePreset& lhs, const QCMakePreset& rhs)
+{
+  return rhs >= lhs;
+}
+
+bool operator>(const QCMakePreset& lhs, const QCMakePreset& rhs)
+{
+  return rhs < lhs;
+}
+
+bool operator>=(const QCMakePreset& lhs, const QCMakePreset& rhs)
+{
+  return !(lhs < rhs);
+}

+ 30 - 0
Source/QtDialog/QCMakePreset.h

@@ -0,0 +1,30 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include <QString>
+#include <QVariant>
+
+#include "cmCMakePresetsFile.h"
+
+class QCMakePreset
+{
+public:
+  QString name;
+  QString displayName;
+  QString description;
+  QString generator;
+  QString architecture;
+  QString toolset;
+  bool setGenConfig;
+  bool enabled;
+};
+
+bool operator==(const QCMakePreset& lhs, const QCMakePreset& rhs);
+bool operator!=(const QCMakePreset& lhs, const QCMakePreset& rhs);
+bool operator<(const QCMakePreset& lhs, const QCMakePreset& rhs);
+bool operator<=(const QCMakePreset& lhs, const QCMakePreset& rhs);
+bool operator>(const QCMakePreset& lhs, const QCMakePreset& rhs);
+bool operator>=(const QCMakePreset& lhs, const QCMakePreset& rhs);
+
+Q_DECLARE_METATYPE(QCMakePreset)

+ 64 - 0
Source/QtDialog/QCMakePresetComboBox.cxx

@@ -0,0 +1,64 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "QCMakePresetComboBox.h"
+
+#include "QCMakePresetItemModel.h"
+
+QCMakePresetComboBox::QCMakePresetComboBox(QWidget* parent)
+  : QComboBox(parent)
+{
+  this->m_model = new QCMakePresetItemModel(this);
+  this->setModel(this->m_model);
+
+  QObject::connect(this->m_model, &QCMakePresetItemModel::modelAboutToBeReset,
+                   this, [this]() { this->m_resetting = true; });
+  QObject::connect(this->m_model, &QCMakePresetItemModel::modelReset, this,
+                   [this]() {
+                     this->setPresetName(this->m_lastPreset);
+                     this->m_resetting = false;
+                     this->emitPresetChanged();
+                   });
+  QObject::connect(
+    this,
+    static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
+    this, [this](int /*row*/) {
+      if (!this->m_resetting) {
+        this->emitPresetChanged();
+      }
+    });
+}
+
+const QVector<QCMakePreset>& QCMakePresetComboBox::presets() const
+{
+  return this->m_model->presets();
+}
+
+QString QCMakePresetComboBox::presetName() const
+{
+  auto preset = this->currentData();
+  if (preset.canConvert<QCMakePreset>()) {
+    return preset.value<QCMakePreset>().name;
+  }
+  return QString{};
+}
+
+void QCMakePresetComboBox::setPresets(const QVector<QCMakePreset>& presets)
+{
+  this->m_model->setPresets(presets);
+}
+
+void QCMakePresetComboBox::setPresetName(const QString& name)
+{
+  this->setCurrentIndex(this->m_model->presetNameToRow(name));
+  if (this->signalsBlocked()) {
+    this->m_lastPreset = this->presetName();
+  }
+}
+
+void QCMakePresetComboBox::emitPresetChanged()
+{
+  if (this->presetName() != this->m_lastPreset) {
+    emit this->presetChanged(this->presetName());
+    this->m_lastPreset = this->presetName();
+  }
+}

+ 35 - 0
Source/QtDialog/QCMakePresetComboBox.h

@@ -0,0 +1,35 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "QCMakePreset.h"
+#include <QComboBox>
+#include <QObject>
+#include <QString>
+#include <QVector>
+
+class QCMakePresetItemModel;
+
+class QCMakePresetComboBox : public QComboBox
+{
+  Q_OBJECT
+public:
+  QCMakePresetComboBox(QWidget* parent = nullptr);
+
+  const QVector<QCMakePreset>& presets() const;
+  QString presetName() const;
+
+public slots:
+  void setPresets(const QVector<QCMakePreset>& presets);
+  void setPresetName(const QString& name);
+
+signals:
+  void presetChanged(const QString& name);
+
+private:
+  QCMakePresetItemModel* m_model;
+  bool m_resetting = false;
+  QString m_lastPreset;
+
+  void emitPresetChanged();
+};

+ 143 - 0
Source/QtDialog/QCMakePresetItemModel.cxx

@@ -0,0 +1,143 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "QCMakePresetItemModel.h"
+
+#include <QFont>
+
+QCMakePresetItemModel::QCMakePresetItemModel(QObject* parent)
+  : QAbstractItemModel(parent)
+{
+}
+
+QVariant QCMakePresetItemModel::data(const QModelIndex& index, int role) const
+{
+  switch (role) {
+    case Qt::AccessibleDescriptionRole:
+      // Separators have to return "separator" for the
+      // AccessibleDescriptionRole. This was determined by looking at
+      // QComboBoxDelegate::isSeparator() (located in qcombobox_p.h.)
+      if (index.internalId() == SEPARATOR_INDEX) {
+        return QString::fromLocal8Bit("separator");
+      }
+      return QString{};
+    case Qt::DisplayRole: {
+      if (index.internalId() == CUSTOM_INDEX) {
+        return QString::fromLocal8Bit("<custom>");
+      }
+      if (index.internalId() == SEPARATOR_INDEX) {
+        return QVariant{};
+      }
+      auto const& preset = this->m_presets[index.internalId()];
+      return preset.displayName.isEmpty() ? preset.name : preset.displayName;
+    }
+    case Qt::ToolTipRole:
+      if (index.internalId() == CUSTOM_INDEX) {
+        return QString::fromLocal8Bit("Specify all settings manually");
+      }
+      if (index.internalId() == SEPARATOR_INDEX) {
+        return QVariant{};
+      }
+      return this->m_presets[index.internalId()].description;
+    case Qt::UserRole:
+      if (index.internalId() == CUSTOM_INDEX) {
+        return QVariant{};
+      }
+      if (index.internalId() == SEPARATOR_INDEX) {
+        return QVariant{};
+      }
+      return QVariant::fromValue(this->m_presets[index.internalId()]);
+    case Qt::FontRole:
+      if (index.internalId() == CUSTOM_INDEX) {
+        QFont font;
+        font.setItalic(true);
+        return font;
+      }
+      return QFont{};
+    default:
+      return QVariant{};
+  }
+}
+
+Qt::ItemFlags QCMakePresetItemModel::flags(const QModelIndex& index) const
+{
+  Qt::ItemFlags flags =
+    Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
+  if (index.internalId() != SEPARATOR_INDEX &&
+      (index.internalId() == CUSTOM_INDEX ||
+       this->m_presets[index.internalId()].enabled)) {
+    flags |= Qt::ItemIsSelectable | Qt::ItemIsEnabled;
+  }
+  return flags;
+}
+
+int QCMakePresetItemModel::rowCount(const QModelIndex& parent) const
+{
+  if (parent.isValid()) {
+    return 0;
+  }
+  if (this->m_presets.empty()) {
+    return 1;
+  }
+  return this->m_presets.size() + 2;
+}
+
+int QCMakePresetItemModel::columnCount(const QModelIndex& parent) const
+{
+  if (parent.isValid()) {
+    return 0;
+  }
+  return 1;
+}
+
+QModelIndex QCMakePresetItemModel::index(int row, int column,
+                                         const QModelIndex& parent) const
+{
+  if (parent.isValid() || column != 0 || row < 0 ||
+      row >= this->rowCount(QModelIndex{})) {
+    return QModelIndex{};
+  }
+
+  if (this->m_presets.empty() || row == this->m_presets.size() + 1) {
+    return this->createIndex(row, column, CUSTOM_INDEX);
+  }
+
+  if (row == this->m_presets.size()) {
+    return this->createIndex(row, column, SEPARATOR_INDEX);
+  }
+
+  return this->createIndex(row, column, static_cast<quintptr>(row));
+}
+
+QModelIndex QCMakePresetItemModel::parent(const QModelIndex& /*index*/) const
+{
+  return QModelIndex{};
+}
+
+QVector<QCMakePreset> const& QCMakePresetItemModel::presets() const
+{
+  return this->m_presets;
+}
+
+void QCMakePresetItemModel::setPresets(QVector<QCMakePreset> const& presets)
+{
+  this->beginResetModel();
+  this->m_presets = presets;
+  this->endResetModel();
+}
+
+int QCMakePresetItemModel::presetNameToRow(const QString& name) const
+{
+  if (this->m_presets.empty()) {
+    return 0;
+  }
+
+  int index = 0;
+  for (auto const& preset : this->m_presets) {
+    if (preset.name == name) {
+      return index;
+    }
+    index++;
+  }
+
+  return this->m_presets.size() + 1;
+}

+ 45 - 0
Source/QtDialog/QCMakePresetItemModel.h

@@ -0,0 +1,45 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include <cm/optional>
+
+#include "QCMakePreset.h"
+#include <QAbstractItemModel>
+#include <QModelIndex>
+#include <QString>
+#include <QVariant>
+#include <QVector>
+#include <QtGlobal>
+
+class QObject;
+
+class QCMakePresetItemModel : public QAbstractItemModel
+{
+  Q_OBJECT
+public:
+  QCMakePresetItemModel(QObject* parent = nullptr);
+
+  QVariant data(const QModelIndex& index, int role) const override;
+  Qt::ItemFlags flags(const QModelIndex& index) const override;
+
+  int rowCount(const QModelIndex& parent = QModelIndex{}) const override;
+  int columnCount(const QModelIndex& parent = QModelIndex{}) const override;
+
+  QModelIndex index(int row, int column,
+                    const QModelIndex& parent = QModelIndex{}) const override;
+  QModelIndex parent(const QModelIndex& index) const override;
+
+  QVector<QCMakePreset> const& presets() const;
+
+  int presetNameToRow(const QString& name) const;
+
+public slots:
+  void setPresets(QVector<QCMakePreset> const& presets);
+
+private:
+  QVector<QCMakePreset> m_presets;
+
+  static constexpr quintptr SEPARATOR_INDEX = static_cast<quintptr>(-2);
+  static constexpr quintptr CUSTOM_INDEX = static_cast<quintptr>(-1);
+};

+ 763 - 0
Source/cmCMakePresetsFile.cxx

@@ -0,0 +1,763 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmCMakePresetsFile.h"
+
+#include <cstdlib>
+#include <functional>
+#include <utility>
+
+#include <cmext/string_view>
+
+#include <cm3p/json/reader.h>
+#include <cm3p/json/value.h>
+
+#include "cmsys/FStream.hxx"
+
+#include "cmJSONHelpers.h"
+#include "cmStringAlgorithms.h"
+#include "cmSystemTools.h"
+#include "cmVersion.h"
+
+namespace {
+enum class CycleStatus
+{
+  Unvisited,
+  InProgress,
+  Verified,
+};
+
+using ReadFileResult = cmCMakePresetsFile::ReadFileResult;
+using CacheVariable = cmCMakePresetsFile::CacheVariable;
+using UnexpandedPreset = cmCMakePresetsFile::UnexpandedPreset;
+using CMakeGeneratorConfig = cmCMakePresetsFile::CMakeGeneratorConfig;
+
+constexpr int MIN_VERSION = 1;
+constexpr int MAX_VERSION = 1;
+
+struct CMakeVersion
+{
+  unsigned int Major = 0;
+  unsigned int Minor = 0;
+  unsigned int Patch = 0;
+};
+
+struct RootPresets
+{
+  CMakeVersion CMakeMinimumRequired;
+  std::vector<cmCMakePresetsFile::UnexpandedPreset> Presets;
+};
+
+cmJSONHelper<std::nullptr_t, ReadFileResult> VendorHelper(ReadFileResult error)
+{
+  return [error](std::nullptr_t& /*out*/,
+                 const Json::Value* value) -> ReadFileResult {
+    if (!value) {
+      return ReadFileResult::READ_OK;
+    }
+
+    if (!value->isObject()) {
+      return error;
+    }
+
+    return ReadFileResult::READ_OK;
+  };
+}
+
+auto const VersionIntHelper = cmJSONIntHelper<ReadFileResult>(
+  ReadFileResult::READ_OK, ReadFileResult::INVALID_VERSION);
+
+auto const VersionHelper = cmJSONRequiredHelper<int, ReadFileResult>(
+  ReadFileResult::NO_VERSION, VersionIntHelper);
+
+auto const RootVersionHelper =
+  cmJSONObjectHelper<int, ReadFileResult>(ReadFileResult::READ_OK,
+                                          ReadFileResult::INVALID_ROOT)
+    .Bind("version"_s, VersionHelper, false);
+
+auto const VariableStringHelper = cmJSONStringHelper<ReadFileResult>(
+  ReadFileResult::READ_OK, ReadFileResult::INVALID_VARIABLE);
+
+auto const VariableObjectHelper =
+  cmJSONObjectHelper<CacheVariable, ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_VARIABLE, false)
+    .Bind("type"_s, &CacheVariable::Type, VariableStringHelper, false)
+    .Bind("value"_s, &CacheVariable::Value, VariableStringHelper);
+
+ReadFileResult VariableHelper(cm::optional<CacheVariable>& out,
+                              const Json::Value* value)
+{
+  if (value->isString()) {
+    out = CacheVariable{
+      /*Type=*/"",
+      /*Value=*/value->asString(),
+    };
+    return ReadFileResult::READ_OK;
+  }
+  if (value->isObject()) {
+    out.emplace();
+    return VariableObjectHelper(*out, value);
+  }
+  if (value->isNull()) {
+    out = cm::nullopt;
+    return ReadFileResult::READ_OK;
+  }
+  return ReadFileResult::INVALID_VARIABLE;
+}
+
+auto const VariablesHelper =
+  cmJSONMapHelper<cm::optional<CacheVariable>, ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, VariableHelper);
+
+auto const PresetStringHelper = cmJSONStringHelper<ReadFileResult>(
+  ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET);
+
+ReadFileResult EnvironmentHelper(cm::optional<std::string>& out,
+                                 const Json::Value* value)
+{
+  if (!value || value->isNull()) {
+    out = cm::nullopt;
+    return ReadFileResult::READ_OK;
+  }
+  if (value->isString()) {
+    out = value->asString();
+    return ReadFileResult::READ_OK;
+  }
+  return ReadFileResult::INVALID_PRESET;
+}
+
+auto const EnvironmentMapHelper =
+  cmJSONMapHelper<cm::optional<std::string>, ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET,
+    EnvironmentHelper);
+
+auto const PresetVectorStringHelper =
+  cmJSONVectorHelper<std::string, ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET,
+    PresetStringHelper);
+
+ReadFileResult PresetInheritsHelper(std::vector<std::string>& out,
+                                    const Json::Value* value)
+{
+  out.clear();
+  if (!value) {
+    return ReadFileResult::READ_OK;
+  }
+
+  if (value->isString()) {
+    out.push_back(value->asString());
+    return ReadFileResult::READ_OK;
+  }
+
+  return PresetVectorStringHelper(out, value);
+}
+
+auto const PresetBoolHelper = cmJSONBoolHelper<ReadFileResult>(
+  ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET);
+
+auto const PresetOptionalBoolHelper =
+  cmJSONOptionalHelper<bool, ReadFileResult>(ReadFileResult::READ_OK,
+                                             PresetBoolHelper);
+
+auto const PresetWarningsHelper =
+  cmJSONObjectHelper<UnexpandedPreset, ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+    .Bind("dev"_s, &UnexpandedPreset::WarnDev, PresetOptionalBoolHelper, false)
+    .Bind("deprecated"_s, &UnexpandedPreset::WarnDeprecated,
+          PresetOptionalBoolHelper, false)
+    .Bind("uninitialized"_s, &UnexpandedPreset::WarnUninitialized,
+          PresetOptionalBoolHelper, false)
+    .Bind("unusedCli"_s, &UnexpandedPreset::WarnUnusedCli,
+          PresetOptionalBoolHelper, false)
+    .Bind("systemVars"_s, &UnexpandedPreset::WarnSystemVars,
+          PresetOptionalBoolHelper, false);
+
+auto const PresetErrorsHelper =
+  cmJSONObjectHelper<UnexpandedPreset, ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+    .Bind("dev"_s, &UnexpandedPreset::ErrorDev, PresetOptionalBoolHelper,
+          false)
+    .Bind("deprecated"_s, &UnexpandedPreset::ErrorDeprecated,
+          PresetOptionalBoolHelper, false);
+
+auto const PresetDebugHelper =
+  cmJSONObjectHelper<UnexpandedPreset, ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+    .Bind("output"_s, &UnexpandedPreset::DebugOutput, PresetOptionalBoolHelper,
+          false)
+    .Bind("tryCompile"_s, &UnexpandedPreset::DebugTryCompile,
+          PresetOptionalBoolHelper, false)
+    .Bind("find"_s, &UnexpandedPreset::DebugFind, PresetOptionalBoolHelper,
+          false);
+
+ReadFileResult CMakeGeneratorConfigHelper(
+  cm::optional<CMakeGeneratorConfig>& out, const Json::Value* value)
+{
+  if (!value) {
+    out = cm::nullopt;
+    return ReadFileResult::READ_OK;
+  }
+
+  if (!value->isString()) {
+    return ReadFileResult::INVALID_PRESET;
+  }
+
+  if (value->asString() == "default") {
+    out = CMakeGeneratorConfig::Default;
+    return ReadFileResult::READ_OK;
+  }
+
+  if (value->asString() == "ignore") {
+    out = CMakeGeneratorConfig::Ignore;
+    return ReadFileResult::READ_OK;
+  }
+
+  return ReadFileResult::INVALID_PRESET;
+}
+
+auto const PresetHelper =
+  cmJSONObjectHelper<UnexpandedPreset, ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+    .Bind("name"_s, &UnexpandedPreset::Name, PresetStringHelper)
+    .Bind("inherits"_s, &UnexpandedPreset::Inherits, PresetInheritsHelper,
+          false)
+    .Bind("hidden"_s, &UnexpandedPreset::Hidden, PresetBoolHelper, false)
+    .Bind<std::nullptr_t>("vendor"_s, nullptr,
+                          VendorHelper(ReadFileResult::INVALID_PRESET), false)
+    .Bind("displayName"_s, &UnexpandedPreset::DisplayName, PresetStringHelper,
+          false)
+    .Bind("description"_s, &UnexpandedPreset::Description, PresetStringHelper,
+          false)
+    .Bind("generator"_s, &UnexpandedPreset::Generator, PresetStringHelper,
+          false)
+    .Bind("architecture"_s, &UnexpandedPreset::Architecture,
+          PresetStringHelper, false)
+    .Bind("toolset"_s, &UnexpandedPreset::Toolset, PresetStringHelper, false)
+    .Bind("cmakeGeneratorConfig"_s, &UnexpandedPreset::GeneratorConfig,
+          CMakeGeneratorConfigHelper, false)
+    .Bind("binaryDir"_s, &UnexpandedPreset::BinaryDir, PresetStringHelper,
+          false)
+    .Bind<std::string>("cmakeExecutable"_s, nullptr, PresetStringHelper, false)
+    .Bind("cacheVariables"_s, &UnexpandedPreset::CacheVariables,
+          VariablesHelper, false)
+    .Bind("environment"_s, &UnexpandedPreset::Environment,
+          EnvironmentMapHelper, false)
+    .Bind("warnings"_s, PresetWarningsHelper, false)
+    .Bind("errors"_s, PresetErrorsHelper, false)
+    .Bind("debug"_s, PresetDebugHelper, false);
+
+auto const PresetsHelper =
+  cmJSONVectorHelper<UnexpandedPreset, ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESETS, PresetHelper);
+
+auto const CMakeVersionUIntHelper = cmJSONUIntHelper<ReadFileResult>(
+  ReadFileResult::READ_OK, ReadFileResult::INVALID_VERSION);
+
+auto const CMakeVersionHelper =
+  cmJSONObjectHelper<CMakeVersion, ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_CMAKE_VERSION, false)
+    .Bind("major"_s, &CMakeVersion::Major, CMakeVersionUIntHelper, false)
+    .Bind("minor"_s, &CMakeVersion::Minor, CMakeVersionUIntHelper, false)
+    .Bind("patch"_s, &CMakeVersion::Patch, CMakeVersionUIntHelper, false);
+
+auto const RootPresetsHelper =
+  cmJSONObjectHelper<RootPresets, ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_ROOT, false)
+    .Bind<int>("version"_s, nullptr, VersionHelper)
+    .Bind("configurePresets"_s, &RootPresets::Presets, PresetsHelper, false)
+    .Bind("cmakeMinimumRequired"_s, &RootPresets::CMakeMinimumRequired,
+          CMakeVersionHelper, false)
+    .Bind<std::nullptr_t>("vendor"_s, nullptr,
+                          VendorHelper(ReadFileResult::INVALID_ROOT), false);
+
+void InheritString(std::string& child, const std::string& parent)
+{
+  if (child.empty()) {
+    child = parent;
+  }
+}
+
+void InheritOptionalBool(cm::optional<bool>& child,
+                         const cm::optional<bool>& parent)
+{
+  if (!child) {
+    child = parent;
+  }
+}
+
+/**
+ * Check preset inheritance for cycles (using a DAG check algorithm) while
+ * also bubbling up fields through the inheritance hierarchy, then verify
+ * that each preset has the required fields, either directly or through
+ * inheritance.
+ */
+ReadFileResult VisitPreset(std::map<std::string, UnexpandedPreset>& presets,
+                           UnexpandedPreset& preset,
+                           std::map<std::string, CycleStatus> cycleStatus)
+{
+  switch (cycleStatus[preset.Name]) {
+    case CycleStatus::InProgress:
+      return ReadFileResult::CYCLIC_PRESET_INHERITANCE;
+    case CycleStatus::Verified:
+      return ReadFileResult::READ_OK;
+    default:
+      break;
+  }
+
+  cycleStatus[preset.Name] = CycleStatus::InProgress;
+
+  for (auto const& i : preset.Inherits) {
+    auto parent = presets.find(i);
+    if (parent == presets.end()) {
+      return ReadFileResult::INVALID_PRESET;
+    }
+
+    if (!preset.User && parent->second.User) {
+      return ReadFileResult::USER_PRESET_INHERITANCE;
+    }
+
+    auto result = VisitPreset(presets, parent->second, cycleStatus);
+    if (result != ReadFileResult::READ_OK) {
+      return result;
+    }
+
+    InheritString(preset.Generator, parent->second.Generator);
+    InheritString(preset.Architecture, parent->second.Architecture);
+    InheritString(preset.Toolset, parent->second.Toolset);
+    if (!preset.GeneratorConfig) {
+      preset.GeneratorConfig = parent->second.GeneratorConfig;
+    }
+    InheritString(preset.BinaryDir, parent->second.BinaryDir);
+    InheritOptionalBool(preset.WarnDev, parent->second.WarnDev);
+    InheritOptionalBool(preset.ErrorDev, parent->second.ErrorDev);
+    InheritOptionalBool(preset.WarnDeprecated, parent->second.WarnDeprecated);
+    InheritOptionalBool(preset.ErrorDeprecated,
+                        parent->second.ErrorDeprecated);
+    InheritOptionalBool(preset.WarnUninitialized,
+                        parent->second.WarnUninitialized);
+    InheritOptionalBool(preset.WarnUnusedCli, parent->second.WarnUnusedCli);
+    InheritOptionalBool(preset.WarnSystemVars, parent->second.WarnSystemVars);
+    for (auto const& v : parent->second.CacheVariables) {
+      preset.CacheVariables.insert(v);
+    }
+    for (auto const& v : parent->second.Environment) {
+      preset.Environment.insert(v);
+    }
+  }
+
+  if (!preset.Hidden) {
+    if (preset.Generator.empty()) {
+      return ReadFileResult::INVALID_PRESET;
+    }
+    if (preset.BinaryDir.empty()) {
+      return ReadFileResult::INVALID_PRESET;
+    }
+    if (preset.WarnDev == false && preset.ErrorDev == true) {
+      return ReadFileResult::INVALID_PRESET;
+    }
+    if (preset.WarnDeprecated == false && preset.ErrorDeprecated == true) {
+      return ReadFileResult::INVALID_PRESET;
+    }
+  }
+
+  cycleStatus[preset.Name] = CycleStatus::Verified;
+  return ReadFileResult::READ_OK;
+}
+
+ReadFileResult ComputePresetInheritance(
+  std::map<std::string, UnexpandedPreset>& presets)
+{
+  std::map<std::string, CycleStatus> cycleStatus;
+  for (auto const& it : presets) {
+    cycleStatus[it.first] = CycleStatus::Unvisited;
+  }
+
+  for (auto& it : presets) {
+    auto result = VisitPreset(presets, it.second, cycleStatus);
+    if (result != ReadFileResult::READ_OK) {
+      return result;
+    }
+  }
+
+  return ReadFileResult::READ_OK;
+}
+
+constexpr const char* ValidPrefixes[] = {
+  "",
+  "env",
+  "penv",
+  "vendor",
+};
+
+bool PrefixesValidMacroNamespace(const std::string& str)
+{
+  for (auto const& prefix : ValidPrefixes) {
+    if (cmHasPrefix(prefix, str)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool IsValidMacroNamespace(const std::string& str)
+{
+  for (auto const& prefix : ValidPrefixes) {
+    if (str == prefix) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool VisitEnv(const cmCMakePresetsFile& file,
+              cmCMakePresetsFile::ExpandedPreset& preset,
+              std::map<std::string, CycleStatus>& envCycles,
+              std::string& value, CycleStatus& status);
+bool ExpandMacros(const cmCMakePresetsFile& file,
+                  cmCMakePresetsFile::ExpandedPreset& preset,
+                  std::map<std::string, CycleStatus>& envCycles,
+                  std::string& out);
+bool ExpandMacro(const cmCMakePresetsFile& file,
+                 cmCMakePresetsFile::ExpandedPreset& preset,
+                 std::map<std::string, CycleStatus>& envCycles,
+                 std::string& out, const std::string& macroNamespace,
+                 const std::string& macroName);
+
+bool VisitEnv(const cmCMakePresetsFile& file,
+              cmCMakePresetsFile::ExpandedPreset& preset,
+              std::map<std::string, CycleStatus>& envCycles,
+              std::string& value, CycleStatus& status)
+{
+  if (status == CycleStatus::Verified) {
+    return true;
+  }
+  if (status == CycleStatus::InProgress) {
+    return false;
+  }
+
+  status = CycleStatus::InProgress;
+  if (!ExpandMacros(file, preset, envCycles, value)) {
+    return false;
+  }
+  status = CycleStatus::Verified;
+  return true;
+}
+
+bool ExpandMacros(const cmCMakePresetsFile& file,
+                  cmCMakePresetsFile::ExpandedPreset& preset,
+                  std::map<std::string, CycleStatus>& envCycles,
+                  std::string& out)
+{
+  std::string result;
+  std::string macroNamespace;
+  std::string macroName;
+
+  enum class State
+  {
+    Default,
+    MacroNamespace,
+    MacroName,
+  } state = State::Default;
+
+  for (auto c : out) {
+    switch (state) {
+      case State::Default:
+        if (c == '$') {
+          state = State::MacroNamespace;
+        } else {
+          result += c;
+        }
+        break;
+
+      case State::MacroNamespace:
+        if (c == '{') {
+          if (IsValidMacroNamespace(macroNamespace)) {
+            state = State::MacroName;
+          } else {
+            result += '$';
+            result += macroNamespace;
+            result += '{';
+            macroNamespace.clear();
+            state = State::Default;
+          }
+        } else {
+          macroNamespace += c;
+          if (!PrefixesValidMacroNamespace(macroNamespace)) {
+            result += '$';
+            result += macroNamespace;
+            macroNamespace.clear();
+            state = State::Default;
+          }
+        }
+        break;
+
+      case State::MacroName:
+        if (c == '}') {
+          if (!ExpandMacro(file, preset, envCycles, result, macroNamespace,
+                           macroName)) {
+            return false;
+          }
+          macroNamespace.clear();
+          macroName.clear();
+          state = State::Default;
+        } else {
+          macroName += c;
+        }
+        break;
+    }
+  }
+
+  switch (state) {
+    case State::Default:
+      break;
+    case State::MacroNamespace:
+      result += '$';
+      result += macroNamespace;
+      break;
+    case State::MacroName:
+      return false;
+  }
+
+  out = std::move(result);
+  return true;
+}
+
+bool ExpandMacro(const cmCMakePresetsFile& file,
+                 cmCMakePresetsFile::ExpandedPreset& preset,
+                 std::map<std::string, CycleStatus>& envCycles,
+                 std::string& out, const std::string& macroNamespace,
+                 const std::string& macroName)
+{
+  if (macroNamespace.empty()) {
+    if (macroName == "sourceDir") {
+      out += file.SourceDir;
+      return true;
+    }
+    if (macroName == "sourceParentDir") {
+      out += cmSystemTools::GetParentDirectory(file.SourceDir);
+      return true;
+    }
+    if (macroName == "presetName") {
+      out += preset.Name;
+      return true;
+    }
+    if (macroName == "generator") {
+      out += preset.Generator;
+      return true;
+    }
+    if (macroName == "dollar") {
+      out += '$';
+      return true;
+    }
+  }
+
+  if (macroNamespace == "env") {
+    auto v = preset.Environment.find(macroName);
+    if (v != preset.Environment.end() && v->second) {
+      if (!VisitEnv(file, preset, envCycles, *v->second,
+                    envCycles[macroName])) {
+        return false;
+      }
+      out += *v->second;
+      return true;
+    }
+  }
+
+  if (macroNamespace == "env" || macroNamespace == "penv") {
+    const char* value = std::getenv(macroName.c_str());
+    if (value) {
+      out += value;
+    }
+    return true;
+  }
+
+  // "vendor" falls through to here
+  return false;
+}
+}
+
+std::string cmCMakePresetsFile::GetFilename(const std::string& sourceDir)
+{
+  return cmStrCat(sourceDir, "/CMakePresets.json");
+}
+
+std::string cmCMakePresetsFile::GetUserFilename(const std::string& sourceDir)
+{
+  return cmStrCat(sourceDir, "/CMakeUserPresets.json");
+}
+
+cmCMakePresetsFile::ReadFileResult cmCMakePresetsFile::ReadProjectPresets(
+  const std::string& sourceDir, bool allowNoFiles)
+{
+  bool haveOneFile = false;
+  this->SourceDir = sourceDir;
+  this->Presets.clear();
+  this->PresetOrder.clear();
+
+  std::vector<std::string> presetOrder;
+  std::map<std::string, UnexpandedPreset> presetMap;
+
+  std::string filename = GetUserFilename(this->SourceDir);
+  if (cmSystemTools::FileExists(filename)) {
+    auto result = this->ReadJSONFile(filename, presetOrder, presetMap, true);
+    if (result != ReadFileResult::READ_OK) {
+      return result;
+    }
+    haveOneFile = true;
+  }
+
+  filename = GetFilename(this->SourceDir);
+  if (cmSystemTools::FileExists(filename)) {
+    auto result = this->ReadJSONFile(filename, presetOrder, presetMap, false);
+    if (result != ReadFileResult::READ_OK) {
+      return result;
+    }
+    haveOneFile = true;
+  }
+
+  if (!haveOneFile) {
+    return allowNoFiles ? ReadFileResult::READ_OK
+                        : ReadFileResult::FILE_NOT_FOUND;
+  }
+
+  auto result = ComputePresetInheritance(presetMap);
+  if (result != ReadFileResult::READ_OK) {
+    return result;
+  }
+
+  this->PresetOrder = std::move(presetOrder);
+  this->Presets = std::move(presetMap);
+  return ReadFileResult::READ_OK;
+}
+
+const char* cmCMakePresetsFile::ResultToString(ReadFileResult result)
+{
+  switch (result) {
+    case ReadFileResult::READ_OK:
+      return "OK";
+    case ReadFileResult::FILE_NOT_FOUND:
+      return "File not found";
+    case ReadFileResult::JSON_PARSE_ERROR:
+      return "JSON parse error";
+    case ReadFileResult::INVALID_ROOT:
+      return "Invalid root object";
+    case ReadFileResult::NO_VERSION:
+      return "No \"version\" field";
+    case ReadFileResult::INVALID_VERSION:
+      return "Invalid \"version\" field";
+    case ReadFileResult::UNRECOGNIZED_VERSION:
+      return "Unrecognized \"version\" field";
+    case ReadFileResult::INVALID_CMAKE_VERSION:
+      return "Invalid \"cmakeMinimumRequired\" field";
+    case ReadFileResult::UNRECOGNIZED_CMAKE_VERSION:
+      return "\"cmakeMinimumRequired\" version too new";
+    case ReadFileResult::INVALID_PRESETS:
+      return "Invalid \"configurePresets\" field";
+    case ReadFileResult::INVALID_PRESET:
+      return "Invalid preset";
+    case ReadFileResult::INVALID_VARIABLE:
+      return "Invalid CMake variable definition";
+    case ReadFileResult::DUPLICATE_PRESETS:
+      return "Duplicate presets";
+    case ReadFileResult::CYCLIC_PRESET_INHERITANCE:
+      return "Cyclic preset inheritance";
+    case ReadFileResult::USER_PRESET_INHERITANCE:
+      return "Project preset inherits from user preset";
+    default:
+      return "Unknown error";
+  }
+}
+
+cm::optional<cmCMakePresetsFile::ExpandedPreset>
+cmCMakePresetsFile::ExpandMacros(const UnexpandedPreset& preset) const
+{
+  ExpandedPreset retval{ preset };
+
+  std::map<std::string, CycleStatus> envCycles;
+  for (auto const& v : retval.Environment) {
+    envCycles[v.first] = CycleStatus::Unvisited;
+  }
+
+  for (auto& v : retval.Environment) {
+    if (v.second &&
+        !VisitEnv(*this, retval, envCycles, *v.second, envCycles[v.first])) {
+      return cm::nullopt;
+    }
+  }
+
+  std::string binaryDir = preset.BinaryDir;
+  if (!::ExpandMacros(*this, retval, envCycles, binaryDir)) {
+    return cm::nullopt;
+  }
+  if (!cmSystemTools::FileIsFullPath(binaryDir)) {
+    binaryDir = cmStrCat(this->SourceDir, '/', binaryDir);
+  }
+  retval.BinaryDir = cmSystemTools::CollapseFullPath(binaryDir);
+  cmSystemTools::ConvertToUnixSlashes(retval.BinaryDir);
+
+  for (auto& variable : retval.CacheVariables) {
+    if (variable.second &&
+        !::ExpandMacros(*this, retval, envCycles, variable.second->Value)) {
+      return cm::nullopt;
+    }
+  }
+
+  return cm::make_optional(retval);
+}
+
+cmCMakePresetsFile::ReadFileResult cmCMakePresetsFile::ReadJSONFile(
+  const std::string& filename, std::vector<std::string>& presetOrder,
+  std::map<std::string, UnexpandedPreset>& presetMap, bool user)
+{
+  cmsys::ifstream fin(filename.c_str());
+  if (!fin) {
+    return ReadFileResult::FILE_NOT_FOUND;
+  }
+  // If there's a BOM, toss it.
+  cmsys::FStream::ReadBOM(fin);
+
+  Json::Value root;
+  Json::CharReaderBuilder builder;
+  if (!Json::parseFromStream(builder, fin, &root, nullptr)) {
+    return ReadFileResult::JSON_PARSE_ERROR;
+  }
+
+  int v = 0;
+  auto result = RootVersionHelper(v, &root);
+  if (result != ReadFileResult::READ_OK) {
+    return result;
+  }
+  if (v < MIN_VERSION || v > MAX_VERSION) {
+    return ReadFileResult::UNRECOGNIZED_VERSION;
+  }
+
+  RootPresets presets;
+  if ((result = RootPresetsHelper(presets, &root)) !=
+      ReadFileResult::READ_OK) {
+    return result;
+  }
+
+  unsigned int currentMajor = cmVersion::GetMajorVersion();
+  unsigned int currentMinor = cmVersion::GetMinorVersion();
+  unsigned int currentPatch = cmVersion::GetPatchVersion();
+  auto const& required = presets.CMakeMinimumRequired;
+  if (required.Major > currentMajor ||
+      (required.Major == currentMajor &&
+       (required.Minor > currentMinor ||
+        (required.Minor == currentMinor &&
+         (required.Patch > currentPatch))))) {
+    return ReadFileResult::UNRECOGNIZED_CMAKE_VERSION;
+  }
+
+  for (auto& preset : presets.Presets) {
+    preset.User = user;
+    if (preset.Name.empty()) {
+      return ReadFileResult::INVALID_PRESET;
+    }
+    if (!presetMap.insert({ preset.Name, preset }).second) {
+      return ReadFileResult::DUPLICATE_PRESETS;
+    }
+    presetOrder.push_back(preset.Name);
+  }
+
+  return ReadFileResult::READ_OK;
+}

+ 141 - 0
Source/cmCMakePresetsFile.h

@@ -0,0 +1,141 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <cm/optional>
+
+class cmCMakePresetsFile
+{
+public:
+  enum class CMakeGeneratorConfig
+  {
+    Default,
+    Ignore,
+  };
+
+  class CacheVariable
+  {
+  public:
+    std::string Type;
+    std::string Value;
+  };
+
+  class Preset
+  {
+  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;
+#endif
+
+    std::string Name;
+    std::vector<std::string> Inherits;
+    bool Hidden;
+    bool User;
+    std::string DisplayName;
+    std::string Description;
+    std::string Generator;
+    std::string Architecture;
+    std::string Toolset;
+    cm::optional<CMakeGeneratorConfig> GeneratorConfig;
+    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;
+    cm::optional<bool> WarnDeprecated;
+    cm::optional<bool> ErrorDeprecated;
+    cm::optional<bool> WarnUninitialized;
+    cm::optional<bool> WarnUnusedCli;
+    cm::optional<bool> WarnSystemVars;
+
+    cm::optional<bool> DebugOutput;
+    cm::optional<bool> DebugTryCompile;
+    cm::optional<bool> DebugFind;
+  };
+
+  class UnexpandedPreset : public Preset
+  {
+  public:
+    using Preset::Preset;
+
+    UnexpandedPreset() = default;
+    UnexpandedPreset(const Preset& preset)
+      : Preset(preset)
+    {
+    }
+    UnexpandedPreset(Preset&& preset)
+      : Preset(std::move(preset))
+    {
+    }
+  };
+
+  class ExpandedPreset : public Preset
+  {
+  public:
+    using Preset::Preset;
+
+    ExpandedPreset() = default;
+    ExpandedPreset(const Preset& preset)
+      : Preset(preset)
+    {
+    }
+    ExpandedPreset(Preset&& preset)
+      : Preset(std::move(preset))
+    {
+    }
+  };
+
+  std::string SourceDir;
+  std::map<std::string, UnexpandedPreset> Presets;
+  std::vector<std::string> PresetOrder;
+
+  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,
+  };
+
+  static std::string GetFilename(const std::string& sourceDir);
+  static std::string GetUserFilename(const std::string& sourceDir);
+  ReadFileResult ReadProjectPresets(const std::string& sourceDir,
+                                    bool allowNoFiles = false);
+  static const char* ResultToString(ReadFileResult result);
+
+  cm::optional<ExpandedPreset> ExpandMacros(
+    const UnexpandedPreset& preset) const;
+
+private:
+  ReadFileResult ReadJSONFile(
+    const std::string& filename, std::vector<std::string>& presetOrder,
+    std::map<std::string, UnexpandedPreset>& presetMap, bool user);
+};

+ 2 - 2
Source/cmGlobalGeneratorFactory.h

@@ -25,7 +25,7 @@ public:
 
   /** Create a GlobalGenerator */
   virtual std::unique_ptr<cmGlobalGenerator> CreateGlobalGenerator(
-    const std::string& n, cmake* cm) const = 0;
+    const std::string& n, bool allowArch, cmake* cm) const = 0;
 
   /** Get the documentation entry for this factory */
   virtual void GetDocumentation(cmDocumentationEntry& entry) const = 0;
@@ -53,7 +53,7 @@ class cmGlobalGeneratorSimpleFactory : public cmGlobalGeneratorFactory
 public:
   /** Create a GlobalGenerator */
   std::unique_ptr<cmGlobalGenerator> CreateGlobalGenerator(
-    const std::string& name, cmake* cm) const override
+    const std::string& name, bool /*allowArch*/, cmake* cm) const override
   {
     if (name != T::GetActualName()) {
       return std::unique_ptr<cmGlobalGenerator>();

+ 2 - 2
Source/cmGlobalVisualStudio10Generator.cxx

@@ -59,7 +59,7 @@ class cmGlobalVisualStudio10Generator::Factory
 {
 public:
   std::unique_ptr<cmGlobalGenerator> CreateGlobalGenerator(
-    const std::string& name, cmake* cm) const override
+    const std::string& name, bool allowArch, cmake* cm) const override
   {
     std::string genName;
     const char* p = cmVS10GenName(name, genName);
@@ -70,7 +70,7 @@ public:
       return std::unique_ptr<cmGlobalGenerator>(
         new cmGlobalVisualStudio10Generator(cm, genName, ""));
     }
-    if (*p++ != ' ') {
+    if (!allowArch || *p++ != ' ') {
       return std::unique_ptr<cmGlobalGenerator>();
     }
     if (strcmp(p, "Win64") == 0) {

+ 2 - 2
Source/cmGlobalVisualStudio11Generator.cxx

@@ -31,7 +31,7 @@ class cmGlobalVisualStudio11Generator::Factory
 {
 public:
   std::unique_ptr<cmGlobalGenerator> CreateGlobalGenerator(
-    const std::string& name, cmake* cm) const override
+    const std::string& name, bool allowArch, cmake* cm) const override
   {
     std::string genName;
     const char* p = cmVS11GenName(name, genName);
@@ -42,7 +42,7 @@ public:
       return std::unique_ptr<cmGlobalGenerator>(
         new cmGlobalVisualStudio11Generator(cm, genName, ""));
     }
-    if (*p++ != ' ') {
+    if (!allowArch || *p++ != ' ') {
       return std::unique_ptr<cmGlobalGenerator>();
     }
     if (strcmp(p, "Win64") == 0) {

+ 2 - 2
Source/cmGlobalVisualStudio12Generator.cxx

@@ -29,7 +29,7 @@ class cmGlobalVisualStudio12Generator::Factory
 {
 public:
   std::unique_ptr<cmGlobalGenerator> CreateGlobalGenerator(
-    const std::string& name, cmake* cm) const override
+    const std::string& name, bool allowArch, cmake* cm) const override
   {
     std::string genName;
     const char* p = cmVS12GenName(name, genName);
@@ -40,7 +40,7 @@ public:
       return std::unique_ptr<cmGlobalGenerator>(
         new cmGlobalVisualStudio12Generator(cm, genName, ""));
     }
-    if (*p++ != ' ') {
+    if (!allowArch || *p++ != ' ') {
       return std::unique_ptr<cmGlobalGenerator>();
     }
     if (strcmp(p, "Win64") == 0) {

+ 2 - 2
Source/cmGlobalVisualStudio14Generator.cxx

@@ -30,7 +30,7 @@ class cmGlobalVisualStudio14Generator::Factory
 {
 public:
   std::unique_ptr<cmGlobalGenerator> CreateGlobalGenerator(
-    const std::string& name, cmake* cm) const override
+    const std::string& name, bool allowArch, cmake* cm) const override
   {
     std::string genName;
     const char* p = cmVS14GenName(name, genName);
@@ -41,7 +41,7 @@ public:
       return std::unique_ptr<cmGlobalGenerator>(
         new cmGlobalVisualStudio14Generator(cm, genName, ""));
     }
-    if (*p++ != ' ') {
+    if (!allowArch || *p++ != ' ') {
       return std::unique_ptr<cmGlobalGenerator>();
     }
     if (strcmp(p, "Win64") == 0) {

+ 2 - 2
Source/cmGlobalVisualStudio9Generator.cxx

@@ -16,7 +16,7 @@ class cmGlobalVisualStudio9Generator::Factory : public cmGlobalGeneratorFactory
 {
 public:
   std::unique_ptr<cmGlobalGenerator> CreateGlobalGenerator(
-    const std::string& name, cmake* cm) const override
+    const std::string& name, bool allowArch, cmake* cm) const override
   {
     if (strncmp(name.c_str(), vs9generatorName,
                 sizeof(vs9generatorName) - 1) != 0) {
@@ -29,7 +29,7 @@ public:
         new cmGlobalVisualStudio9Generator(cm, name, ""));
     }
 
-    if (p[0] != ' ') {
+    if (!allowArch || p[0] != ' ') {
       return std::unique_ptr<cmGlobalGenerator>();
     }
 

+ 3 - 3
Source/cmGlobalVisualStudioVersionedGenerator.cxx

@@ -140,7 +140,7 @@ class cmGlobalVisualStudioVersionedGenerator::Factory15
 {
 public:
   std::unique_ptr<cmGlobalGenerator> CreateGlobalGenerator(
-    const std::string& name, cmake* cm) const override
+    const std::string& name, bool allowArch, cmake* cm) const override
   {
     std::string genName;
     const char* p = cmVS15GenName(name, genName);
@@ -152,7 +152,7 @@ public:
         new cmGlobalVisualStudioVersionedGenerator(
           cmGlobalVisualStudioGenerator::VS15, cm, genName, ""));
     }
-    if (*p++ != ' ') {
+    if (!allowArch || *p++ != ' ') {
       return std::unique_ptr<cmGlobalGenerator>();
     }
     if (strcmp(p, "Win64") == 0) {
@@ -234,7 +234,7 @@ class cmGlobalVisualStudioVersionedGenerator::Factory16
 {
 public:
   std::unique_ptr<cmGlobalGenerator> CreateGlobalGenerator(
-    const std::string& name, cmake* cm) const override
+    const std::string& name, bool /*allowArch*/, cmake* cm) const override
   {
     std::string genName;
     const char* p = cmVS16GenName(name, genName);

+ 2 - 1
Source/cmGlobalXCodeGenerator.cxx

@@ -135,7 +135,7 @@ class cmGlobalXCodeGenerator::Factory : public cmGlobalGeneratorFactory
 {
 public:
   std::unique_ptr<cmGlobalGenerator> CreateGlobalGenerator(
-    const std::string& name, cmake* cm) const override;
+    const std::string& name, bool allowArch, cmake* cm) const override;
 
   void GetDocumentation(cmDocumentationEntry& entry) const override
   {
@@ -197,6 +197,7 @@ std::unique_ptr<cmGlobalGeneratorFactory> cmGlobalXCodeGenerator::NewFactory()
 
 std::unique_ptr<cmGlobalGenerator>
 cmGlobalXCodeGenerator::Factory::CreateGlobalGenerator(const std::string& name,
+                                                       bool /*allowArch*/,
                                                        cmake* cm) const
 {
   if (name != GetActualName()) {

+ 11 - 0
Source/cmJSONHelpers.h

@@ -29,6 +29,9 @@ public:
   template <typename M, typename F>
   cmJSONObjectHelper& Bind(const cm::string_view& name, std::nullptr_t, F func,
                            bool required = true);
+  template <typename F>
+  cmJSONObjectHelper& Bind(const cm::string_view& name, F func,
+                           bool required = true);
 
   E operator()(T& out, const Json::Value* value) const;
 
@@ -86,6 +89,14 @@ cmJSONObjectHelper<T, E>& cmJSONObjectHelper<T, E>::Bind(
                            required);
 }
 
+template <typename T, typename E>
+template <typename F>
+cmJSONObjectHelper<T, E>& cmJSONObjectHelper<T, E>::Bind(
+  const cm::string_view& name, F func, bool required)
+{
+  return this->BindPrivate(name, MemberFunction(func), required);
+}
+
 template <typename T, typename E>
 cmJSONObjectHelper<T, E>& cmJSONObjectHelper<T, E>::BindPrivate(
   const cm::string_view& name, MemberFunction&& func, bool required)

+ 308 - 35
Source/cmake.cxx

@@ -13,6 +13,7 @@
 #include <utility>
 
 #include <cm/memory>
+#include <cm/optional>
 #include <cm/string_view>
 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(CMAKE_BOOT_MINGW)
 #  include <cm/iterator>
@@ -27,6 +28,7 @@
 
 #include "cm_sys_stat.h"
 
+#include "cmCMakePresetsFile.h"
 #include "cmCommands.h"
 #include "cmDocumentation.h"
 #include "cmDocumentationEntry.h"
@@ -286,6 +288,97 @@ void cmake::CleanupCommandsAndMacros()
   this->CurrentSnapshot.SetDefaultDefinitions();
 }
 
+#ifndef CMAKE_BOOTSTRAP
+void cmake::SetWarningFromPreset(const std::string& name,
+                                 const cm::optional<bool>& warning,
+                                 const cm::optional<bool>& error)
+{
+  if (warning) {
+    if (*warning) {
+      this->DiagLevels[name] = std::max(this->DiagLevels[name], DIAG_WARN);
+    } else {
+      this->DiagLevels[name] = DIAG_IGNORE;
+    }
+  }
+  if (error) {
+    if (*error) {
+      this->DiagLevels[name] = DIAG_ERROR;
+    } else {
+      this->DiagLevels[name] = std::min(this->DiagLevels[name], DIAG_WARN);
+    }
+  }
+}
+
+void cmake::ProcessPresetVariables()
+{
+  for (auto const& var : this->UnprocessedPresetVariables) {
+    if (!var.second) {
+      continue;
+    }
+    cmStateEnums::CacheEntryType type = cmStateEnums::UNINITIALIZED;
+    if (!var.second->Type.empty()) {
+      type = cmState::StringToCacheEntryType(var.second->Type);
+    }
+    this->ProcessCacheArg(var.first, var.second->Value, type);
+  }
+}
+
+void cmake::PrintPresetVariables()
+{
+  bool first = true;
+  for (auto const& var : this->UnprocessedPresetVariables) {
+    if (!var.second) {
+      continue;
+    }
+    cmStateEnums::CacheEntryType type = cmStateEnums::UNINITIALIZED;
+    if (!var.second->Type.empty()) {
+      type = cmState::StringToCacheEntryType(var.second->Type);
+    }
+    if (first) {
+      std::cout << "Preset CMake variables:\n\n";
+      first = false;
+    }
+    std::cout << "  " << var.first;
+    if (type != cmStateEnums::UNINITIALIZED) {
+      std::cout << ':' << cmState::CacheEntryTypeToString(type);
+    }
+    std::cout << "=\"" << var.second->Value << "\"\n";
+  }
+  if (!first) {
+    std::cout << '\n';
+  }
+  this->UnprocessedPresetVariables.clear();
+}
+
+void cmake::ProcessPresetEnvironment()
+{
+  for (auto const& var : this->UnprocessedPresetEnvironment) {
+    if (var.second) {
+      cmSystemTools::PutEnv(cmStrCat(var.first, '=', *var.second));
+    }
+  }
+}
+
+void cmake::PrintPresetEnvironment()
+{
+  bool first = true;
+  for (auto const& var : this->UnprocessedPresetEnvironment) {
+    if (!var.second) {
+      continue;
+    }
+    if (first) {
+      std::cout << "Preset environment variables:\n\n";
+      first = false;
+    }
+    std::cout << "  " << var.first << "=\"" << *var.second << "\"\n";
+  }
+  if (!first) {
+    std::cout << '\n';
+  }
+  this->UnprocessedPresetEnvironment.clear();
+}
+#endif
+
 // Parse the args
 bool cmake::SetCacheArgs(const std::vector<std::string>& args)
 {
@@ -308,28 +401,10 @@ bool cmake::SetCacheArgs(const std::vector<std::string>& args)
       std::string value;
       cmStateEnums::CacheEntryType type = cmStateEnums::UNINITIALIZED;
       if (cmState::ParseCacheEntry(entry, var, value, type)) {
-        // The value is transformed if it is a filepath for example, so
-        // we can't compare whether the value is already in the cache until
-        // after we call AddCacheEntry.
-        bool haveValue = false;
-        std::string cachedValue;
-        if (this->WarnUnusedCli) {
-          if (cmProp v = this->State->GetInitializedCacheValue(var)) {
-            haveValue = true;
-            cachedValue = *v;
-          }
-        }
-
-        this->AddCacheEntry(var, value.c_str(),
-                            "No help, variable specified on the command line.",
-                            type);
-
-        if (this->WarnUnusedCli) {
-          if (!haveValue ||
-              cachedValue != *this->State->GetInitializedCacheValue(var)) {
-            this->WatchUnusedCli(var);
-          }
-        }
+#ifndef CMAKE_BOOTSTRAP
+        this->UnprocessedPresetVariables.erase(var);
+#endif
+        this->ProcessCacheArg(var, value, type);
       } else {
         cmSystemTools::Error("Parse error in command line argument: " + arg +
                              "\n" + "Should be: VAR:type=value\n");
@@ -409,6 +484,9 @@ bool cmake::SetCacheArgs(const std::vector<std::string>& args)
 
       // now remove them from the cache
       for (std::string const& currentEntry : entriesToDelete) {
+#ifndef CMAKE_BOOTSTRAP
+        this->UnprocessedPresetVariables.erase(currentEntry);
+#endif
         this->State->RemoveCacheEntry(currentEntry);
       }
     } else if (cmHasLiteralPrefix(arg, "-C")) {
@@ -462,6 +540,33 @@ bool cmake::SetCacheArgs(const std::vector<std::string>& args)
   return true;
 }
 
+void cmake::ProcessCacheArg(const std::string& var, const std::string& value,
+                            cmStateEnums::CacheEntryType type)
+{
+  // The value is transformed if it is a filepath for example, so
+  // we can't compare whether the value is already in the cache until
+  // after we call AddCacheEntry.
+  bool haveValue = false;
+  std::string cachedValue;
+  if (this->WarnUnusedCli) {
+    if (cmProp v = this->State->GetInitializedCacheValue(var)) {
+      haveValue = true;
+      cachedValue = *v;
+    }
+  }
+
+  this->AddCacheEntry(var, value.c_str(),
+                      "No help, variable specified on the command line.",
+                      type);
+
+  if (this->WarnUnusedCli) {
+    if (!haveValue ||
+        cachedValue != *this->State->GetInitializedCacheValue(var)) {
+      this->WatchUnusedCli(var);
+    }
+  }
+}
+
 void cmake::ReadListFile(const std::vector<std::string>& args,
                          const std::string& path)
 {
@@ -625,6 +730,8 @@ void cmake::SetArgs(const std::vector<std::string>& args)
 #if !defined(CMAKE_BOOTSTRAP)
   std::string profilingFormat;
   std::string profilingOutput;
+  std::string presetName;
+  bool listPresets = false;
 #endif
   for (unsigned int i = 1; i < args.size(); ++i) {
     std::string const& arg = args[i];
@@ -830,19 +937,9 @@ void cmake::SetArgs(const std::vector<std::string>& args)
         }
         value = args[i];
       }
-      auto gen = this->CreateGlobalGenerator(value);
-      if (!gen) {
-        std::string kdevError;
-        if (value.find("KDevelop3", 0) != std::string::npos) {
-          kdevError = "\nThe KDevelop3 generator is not supported anymore.";
-        }
-
-        cmSystemTools::Error(
-          cmStrCat("Could not create named generator ", value, kdevError));
-        this->PrintGeneratorList();
+      if (!this->CreateAndSetGlobalGenerator(value, true)) {
         return;
       }
-      this->SetGlobalGenerator(std::move(gen));
 #if !defined(CMAKE_BOOTSTRAP)
     } else if (cmHasLiteralPrefix(arg, "--profiling-format")) {
       profilingFormat = arg.substr(strlen("--profiling-format="));
@@ -856,6 +953,13 @@ void cmake::SetArgs(const std::vector<std::string>& args)
       if (profilingOutput.empty()) {
         cmSystemTools::Error("No path specified for --profiling-output");
       }
+    } else if (cmHasLiteralPrefix(arg, "--preset")) {
+      presetName = arg.substr(strlen("--preset="));
+      if (presetName.empty()) {
+        cmSystemTools::Error("No preset specified for --preset");
+      }
+    } else if (cmHasLiteralPrefix(arg, "--list-presets")) {
+      listPresets = true;
 #endif
     }
     // no option assume it is the path to the source or an existing build
@@ -915,6 +1019,91 @@ void cmake::SetArgs(const std::vector<std::string>& args)
   if (!haveBinaryDir) {
     this->SetHomeOutputDirectory(cmSystemTools::GetCurrentWorkingDirectory());
   }
+
+#if !defined(CMAKE_BOOTSTRAP)
+  if (listPresets || !presetName.empty()) {
+    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;
+    }
+    if (listPresets) {
+      this->PrintPresetList(settingsFile);
+      return;
+    }
+    auto preset = settingsFile.Presets.find(presetName);
+    if (preset == settingsFile.Presets.end()) {
+      cmSystemTools::Error(cmStrCat("No such preset in ",
+                                    this->GetHomeDirectory(), ": \"",
+                                    presetName, '"'));
+      this->PrintPresetList(settingsFile);
+      return;
+    }
+    if (preset->second.Hidden) {
+      cmSystemTools::Error(cmStrCat("Cannot use hidden preset in ",
+                                    this->GetHomeDirectory(), ": \"",
+                                    presetName, '"'));
+      this->PrintPresetList(settingsFile);
+      return;
+    }
+    auto expandedPreset = settingsFile.ExpandMacros(preset->second);
+    if (!expandedPreset) {
+      cmSystemTools::Error(cmStrCat("Could not evaluate preset \"",
+                                    preset->second.Name,
+                                    "\": Invalid macro expansion"));
+      return;
+    }
+
+    if (!haveBinaryDir) {
+      this->SetHomeOutputDirectory(expandedPreset->BinaryDir);
+    }
+    if (!this->GlobalGenerator) {
+      if (!this->CreateAndSetGlobalGenerator(expandedPreset->Generator,
+                                             false)) {
+        return;
+      }
+    }
+    this->UnprocessedPresetVariables = expandedPreset->CacheVariables;
+    this->UnprocessedPresetEnvironment = expandedPreset->Environment;
+
+    if (!expandedPreset->GeneratorConfig ||
+        expandedPreset->GeneratorConfig ==
+          cmCMakePresetsFile::CMakeGeneratorConfig::Default) {
+      if (!this->GeneratorPlatformSet) {
+        this->SetGeneratorPlatform(expandedPreset->Architecture);
+      }
+      if (!this->GeneratorToolsetSet) {
+        this->SetGeneratorToolset(expandedPreset->Toolset);
+      }
+    }
+
+    this->SetWarningFromPreset("dev", expandedPreset->WarnDev,
+                               expandedPreset->ErrorDev);
+    this->SetWarningFromPreset("deprecated", expandedPreset->WarnDeprecated,
+                               expandedPreset->ErrorDeprecated);
+    if (expandedPreset->WarnUninitialized == true) {
+      this->SetWarnUninitialized(true);
+    }
+    if (expandedPreset->WarnUnusedCli == false) {
+      this->SetWarnUnusedCli(false);
+    }
+    if (expandedPreset->WarnSystemVars == true) {
+      this->SetCheckSystemVars(true);
+    }
+    if (expandedPreset->DebugOutput == true) {
+      this->SetDebugOutputOn(true);
+    }
+    if (expandedPreset->DebugTryCompile == true) {
+      this->DebugTryCompileOn();
+    }
+    if (expandedPreset->DebugFind == true) {
+      this->SetDebugFindOutputOn(true);
+    }
+  }
+#endif
 }
 
 cmake::LogLevel cmake::StringToLogLevel(const std::string& levelStr)
@@ -1217,7 +1406,7 @@ createExtraGenerator(
 }
 
 std::unique_ptr<cmGlobalGenerator> cmake::CreateGlobalGenerator(
-  const std::string& gname)
+  const std::string& gname, bool allowArch)
 {
   std::pair<std::unique_ptr<cmExternalMakefileProjectGenerator>, std::string>
     extra = createExtraGenerator(this->ExtraGenerators, gname);
@@ -1227,7 +1416,7 @@ std::unique_ptr<cmGlobalGenerator> cmake::CreateGlobalGenerator(
 
   std::unique_ptr<cmGlobalGenerator> generator;
   for (const auto& g : this->Generators) {
-    generator = g->CreateGlobalGenerator(name, this);
+    generator = g->CreateGlobalGenerator(name, allowArch, this);
     if (generator) {
       break;
     }
@@ -1240,6 +1429,79 @@ std::unique_ptr<cmGlobalGenerator> cmake::CreateGlobalGenerator(
   return generator;
 }
 
+bool cmake::CreateAndSetGlobalGenerator(const std::string& name,
+                                        bool allowArch)
+{
+  auto gen = this->CreateGlobalGenerator(name, allowArch);
+  if (!gen) {
+    std::string kdevError;
+    std::string vsError;
+    if (name.find("KDevelop3", 0) != std::string::npos) {
+      kdevError = "\nThe KDevelop3 generator is not supported anymore.";
+    }
+    if (!allowArch && cmHasLiteralPrefix(name, "Visual Studio ") &&
+        name.length() >= cmStrLen("Visual Studio xx xxxx ")) {
+      vsError = "\nUsing platforms in Visual Studio generator names is not "
+                "supported in CMakePresets.json.";
+    }
+
+    cmSystemTools::Error(
+      cmStrCat("Could not create named generator ", name, kdevError, vsError));
+    this->PrintGeneratorList();
+    return false;
+  }
+
+  this->SetGlobalGenerator(std::move(gen));
+  return true;
+}
+
+#ifndef CMAKE_BOOTSTRAP
+void cmake::PrintPresetList(const cmCMakePresetsFile& file) const
+{
+  std::vector<GeneratorInfo> generators;
+  this->GetRegisteredGenerators(generators, false);
+
+  std::vector<cmCMakePresetsFile::UnexpandedPreset> presets;
+  for (auto const& p : file.PresetOrder) {
+    auto const& preset = file.Presets.at(p);
+    if (!preset.Hidden &&
+        std::find_if(generators.begin(), generators.end(),
+                     [&preset](const GeneratorInfo& info) {
+                       return info.name == preset.Generator;
+                     }) != generators.end() &&
+        file.ExpandMacros(preset)) {
+      presets.push_back(preset);
+    }
+  }
+
+  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';
+  }
+}
+#endif
+
 void cmake::SetHomeDirectory(const std::string& dir)
 {
   this->State->SetSourceDirectory(dir);
@@ -1803,6 +2065,9 @@ int cmake::Run(const std::vector<std::string>& args, bool noconfigure)
   if (cmSystemTools::GetErrorOccuredFlag()) {
     return -1;
   }
+  if (this->GetWorkingMode() == HELP_MODE) {
+    return 0;
+  }
 
   // Log the trace format version to the desired output
   if (this->GetTrace()) {
@@ -1831,11 +2096,19 @@ int cmake::Run(const std::vector<std::string>& args, bool noconfigure)
     this->AddCMakePaths();
   }
 
+#ifndef CMAKE_BOOTSTRAP
+  this->ProcessPresetVariables();
+  this->ProcessPresetEnvironment();
+#endif
   // Add any cache args
   if (!this->SetCacheArgs(args)) {
     cmSystemTools::Error("Problem processing arguments. Aborting.\n");
     return -1;
   }
+#ifndef CMAKE_BOOTSTRAP
+  this->PrintPresetVariables();
+  this->PrintPresetEnvironment();
+#endif
 
   // In script mode we terminate after running the script.
   if (this->GetWorkingMode() != NORMAL_MODE) {

+ 49 - 7
Source/cmake.h

@@ -27,7 +27,11 @@
 #include "cmStateTypes.h"
 
 #if !defined(CMAKE_BOOTSTRAP)
+#  include <cm/optional>
+
 #  include <cm3p/json/value.h>
+
+#  include "cmCMakePresetsFile.h"
 #endif
 
 class cmExternalMakefileProjectGeneratorFactory;
@@ -88,13 +92,22 @@ public:
   enum WorkingMode
   {
     NORMAL_MODE, ///< Cmake runs to create project files
-                 /** \brief Script mode (started by using -P).
-                  *
-                  * In script mode there is no generator and no cache. Also,
-                  * languages are not enabled, so add_executable and things do
-                  * nothing.
-                  */
+
+    /** \brief Script mode (started by using -P).
+     *
+     * In script mode there is no generator and no cache. Also,
+     * languages are not enabled, so add_executable and things do
+     * nothing.
+     */
     SCRIPT_MODE,
+
+    /** \brief Help mode
+     *
+     * Used to print help for things that can only be determined after finding
+     * the source directory, for example, the list of presets.
+     */
+    HELP_MODE,
+
     /** \brief A pkg-config like mode
      *
      * In this mode cmake just searches for a package and prints the results to
@@ -219,7 +232,15 @@ public:
 
   //! Create a GlobalGenerator
   std::unique_ptr<cmGlobalGenerator> CreateGlobalGenerator(
-    const std::string& name);
+    const std::string& name, bool allowArch = true);
+
+  //! Create a GlobalGenerator and set it as our own
+  bool CreateAndSetGlobalGenerator(const std::string& name, bool allowArch);
+
+#ifndef CMAKE_BOOTSTRAP
+  //! Print list of presets
+  void PrintPresetList(const cmCMakePresetsFile& file) const;
+#endif
 
   //! Return the global generator assigned to this instance of cmake
   cmGlobalGenerator* GetGlobalGenerator()
@@ -329,9 +350,22 @@ public:
   bool GetIsInTryCompile() const;
   void SetIsInTryCompile(bool b);
 
+#ifndef CMAKE_BOOTSTRAP
+  void SetWarningFromPreset(const std::string& name,
+                            const cm::optional<bool>& warning,
+                            const cm::optional<bool>& error);
+  void ProcessPresetVariables();
+  void PrintPresetVariables();
+  void ProcessPresetEnvironment();
+  void PrintPresetEnvironment();
+#endif
+
   //! Parse command line arguments that might set cache values
   bool SetCacheArgs(const std::vector<std::string>&);
 
+  void ProcessCacheArg(const std::string& var, const std::string& value,
+                       cmStateEnums::CacheEntryType type);
+
   using ProgressCallbackType = std::function<void(const std::string&, float)>;
   /**
    *  Set the function used by GUIs to receive progress updates
@@ -625,6 +659,12 @@ private:
   std::unique_ptr<cmFileTimeCache> FileTimeCache;
   std::string GraphVizFile;
   InstalledFilesMap InstalledFiles;
+#ifndef CMAKE_BOOTSTRAP
+  std::map<std::string, cm::optional<cmCMakePresetsFile::CacheVariable>>
+    UnprocessedPresetVariables;
+  std::map<std::string, cm::optional<std::string>>
+    UnprocessedPresetEnvironment;
+#endif
 
 #if !defined(CMAKE_BOOTSTRAP)
   std::unique_ptr<cmVariableWatch> VariableWatch;
@@ -664,6 +704,8 @@ private:
 #define CMAKE_STANDARD_OPTIONS_TABLE                                          \
   { "-S <path-to-source>", "Explicitly specify a source directory." },        \
     { "-B <path-to-build>", "Explicitly specify a build directory." },        \
+    { "--preset=<preset-name>", "Explicitly specify a preset." },             \
+    { "--list-presets", "List available presets." },                          \
     { "-C <initial-cache>", "Pre-load a script to populate the cache." },     \
     { "-D <var>[:<type>]=<value>", "Create or update a cmake cache entry." }, \
     { "-U <globbing_expr>", "Remove matching entries from CMake cache." },    \

+ 6 - 1
Source/cmakemain.cxx

@@ -49,7 +49,8 @@ const char* cmDocumentationUsage[][2] = {
   { nullptr,
     "  cmake [options] <path-to-source>\n"
     "  cmake [options] <path-to-existing-build>\n"
-    "  cmake [options] -S <path-to-source> -B <path-to-build>" },
+    "  cmake [options] -S <path-to-source> -B <path-to-build>\n"
+    "  cmake [options] -S <path-to-source> --preset=<preset-name>" },
   { nullptr,
     "Specify a source directory to (re-)generate a build system for "
     "it in the current working directory.  Specify an existing build "
@@ -253,6 +254,9 @@ int do_cmake(int ac, char const* const* av)
     } else if (cmHasLiteralPrefix(av[i], "--find-package")) {
       workingMode = cmake::FIND_PACKAGE_MODE;
       args.emplace_back(av[i]);
+    } else if (strcmp(av[i], "--list-presets") == 0) {
+      workingMode = cmake::HELP_MODE;
+      args.emplace_back(av[i]);
     } else {
       args.emplace_back(av[i]);
     }
@@ -269,6 +273,7 @@ int do_cmake(int ac, char const* const* av)
   cmState::Mode mode = cmState::Unknown;
   switch (workingMode) {
     case cmake::NORMAL_MODE:
+    case cmake::HELP_MODE:
       mode = cmState::Project;
       break;
     case cmake::SCRIPT_MODE:

+ 38 - 0
Tests/CMakeGUI/CMakeGUITest.cmake

@@ -27,6 +27,10 @@ function(run_cmake_gui_test name)
   if(EXISTS "${_cmakelists_in}")
     configure_file("${_cmakelists_in}" "${_workdir}/src/CMakeLists.txt" @ONLY)
   endif()
+  set(_cmakepresets_in "${_srcdir}/CMakePresets.json.in")
+  if(EXISTS "${_cmakepresets_in}")
+    configure_file("${_cmakepresets_in}" "${_workdir}/src/CMakePresets.json" @ONLY)
+  endif()
   if(_rcgt_DO_CONFIGURE)
     if(NOT _rcgt_GENERATOR)
       set(_rcgt_GENERATOR "${CMakeGUITest_GENERATOR}")
@@ -118,3 +122,37 @@ set(ENV{KEPT_VARIABLE} "Kept variable")
 set(ENV{CHANGED_VARIABLE} "This variable will be changed")
 set(ENV{REMOVED_VARIABLE} "Removed variable")
 run_cmake_gui_test(environment)
+
+run_cmake_gui_test(presetArg:preset
+  ARGS
+    -S "${CMakeGUITest_BINARY_DIR}/presetArg-preset/src"
+    "--preset=ninja"
+  )
+run_cmake_gui_test(presetArg:presetBinary
+  ARGS
+    -S "${CMakeGUITest_BINARY_DIR}/presetArg-presetBinary/src"
+    -B "${CMakeGUITest_BINARY_DIR}/presetArg-presetBinary/build"
+    "--preset=ninja"
+  )
+run_cmake_gui_test(presetArg:presetBinaryChange
+  ARGS
+    -S "${CMakeGUITest_BINARY_DIR}/presetArg-presetBinaryChange/src"
+    -B "${CMakeGUITest_BINARY_DIR}/presetArg-presetBinaryChange/build"
+    "--preset=ninja"
+  )
+run_cmake_gui_test(presetArg:noPresetBinaryChange
+  ARGS
+    -S "${CMakeGUITest_BINARY_DIR}/presetArg-noPresetBinaryChange/src"
+    -B "${CMakeGUITest_BINARY_DIR}/presetArg-noPresetBinaryChange/build"
+  )
+run_cmake_gui_test(presetArg:presetConfigExists
+  ARGS
+    -S "${CMakeGUITest_BINARY_DIR}/presetArg-presetConfigExists/src"
+    "--preset=ninja"
+  )
+run_cmake_gui_test(presetArg:noExist
+  ARGS
+    -S "${CMakeGUITest_BINARY_DIR}/presetArg-noExist/src"
+    "--preset=noExist"
+  )
+run_cmake_gui_test(changingPresets)

+ 266 - 1
Tests/CMakeGUI/CMakeGUITest.cxx

@@ -5,6 +5,10 @@
 #include "QCMake.h"
 #include <QApplication>
 #include <QEventLoop>
+#include <QFile>
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QJsonObject>
 #include <QMessageBox>
 #include <QSettings>
 #include <QString>
@@ -18,8 +22,11 @@
 #include "CatchShow.h"
 #include "FirstConfigure.h"
 
+using WindowSetupHelper = std::function<void(CMakeSetupDialog*)>;
+Q_DECLARE_METATYPE(WindowSetupHelper)
+
 namespace {
-void loopSleep(int msecs = 100)
+void loopSleep(int msecs = 500)
 {
   QEventLoop loop;
   QTimer::singleShot(msecs, &loop, &QEventLoop::quit);
@@ -172,6 +179,264 @@ void CMakeGUITest::environment()
   QCOMPARE(penv.value("REMOVED_VARIABLE"), "Removed variable");
 }
 
+void CMakeGUITest::presetArg()
+{
+  QFETCH(WindowSetupHelper, setupFunction);
+  QFETCH(QString, presetName);
+  QFETCH(QString, sourceDir);
+  QFETCH(QString, binaryDir);
+  QFETCH(QCMakePropertyList, properties);
+
+  if (setupFunction) {
+    setupFunction(this->m_window);
+  }
+
+  // Wait a bit for everything to update
+  loopSleep();
+
+  QCOMPARE(this->m_window->Preset->presetName(), presetName);
+  QCOMPARE(this->m_window->SourceDirectory->text(), sourceDir);
+  QCOMPARE(this->m_window->BinaryDirectory->currentText(), binaryDir);
+
+  auto actualProperties =
+    this->m_window->CacheValues->cacheModel()->properties();
+  QCOMPARE(actualProperties.size(), properties.size());
+  for (int i = 0; i < actualProperties.size(); ++i) {
+    // operator==() only compares Key, we need to compare Value and Type too
+    QCOMPARE(actualProperties[i].Key, properties[i].Key);
+    QCOMPARE(actualProperties[i].Value, properties[i].Value);
+    QCOMPARE(actualProperties[i].Type, properties[i].Type);
+  }
+}
+
+namespace {
+QCMakePropertyList makePresetProperties(const QString& name)
+{
+  return QCMakePropertyList{
+    QCMakeProperty{
+      /*Key=*/"FALSE_VARIABLE",
+      /*Value=*/false,
+      /*Strings=*/{},
+      /*Help=*/"",
+      /*Type=*/QCMakeProperty::BOOL,
+      /*Advanced=*/false,
+    },
+    QCMakeProperty{
+      /*Key=*/"FILEPATH_VARIABLE",
+      /*Value=*/
+      QString::fromLocal8Bit(CMakeGUITest_BINARY_DIR "/%1/src/CMakeLists.txt")
+        .arg(name),
+      /*Strings=*/{},
+      /*Help=*/"",
+      /*Type=*/QCMakeProperty::FILEPATH,
+      /*Advanced=*/false,
+    },
+    QCMakeProperty{
+      /*Key=*/"ON_VARIABLE",
+      /*Value=*/true,
+      /*Strings=*/{},
+      /*Help=*/"",
+      /*Type=*/QCMakeProperty::BOOL,
+      /*Advanced=*/false,
+    },
+    QCMakeProperty{
+      /*Key=*/"PATH_VARIABLE",
+      /*Value=*/
+      QString::fromLocal8Bit(CMakeGUITest_BINARY_DIR "/%1/src").arg(name),
+      /*Strings=*/{},
+      /*Help=*/"",
+      /*Type=*/QCMakeProperty::PATH,
+      /*Advanced=*/false,
+    },
+    QCMakeProperty{
+      /*Key=*/"STRING_VARIABLE",
+      /*Value=*/"String value",
+      /*Strings=*/{},
+      /*Help=*/"",
+      /*Type=*/QCMakeProperty::STRING,
+      /*Advanced=*/false,
+    },
+    QCMakeProperty{
+      /*Key=*/"UNINITIALIZED_VARIABLE",
+      /*Value=*/"Uninitialized value",
+      /*Strings=*/{},
+      /*Help=*/"",
+      /*Type=*/QCMakeProperty::STRING,
+      /*Advanced=*/false,
+    },
+  };
+}
+}
+
+void CMakeGUITest::presetArg_data()
+{
+  QTest::addColumn<WindowSetupHelper>("setupFunction");
+  QTest::addColumn<QString>("presetName");
+  QTest::addColumn<QString>("sourceDir");
+  QTest::addColumn<QString>("binaryDir");
+  QTest::addColumn<QCMakePropertyList>("properties");
+
+  QTest::newRow("preset") << WindowSetupHelper{} << "ninja"
+                          << CMakeGUITest_BINARY_DIR "/presetArg-preset/src"
+                          << CMakeGUITest_BINARY_DIR
+    "/presetArg-preset/src/build"
+                          << makePresetProperties("presetArg-preset");
+  QTest::newRow("presetBinary")
+    << WindowSetupHelper{} << "ninja"
+    << CMakeGUITest_BINARY_DIR "/presetArg-presetBinary/src"
+    << CMakeGUITest_BINARY_DIR "/presetArg-presetBinary/build"
+    << makePresetProperties("presetArg-presetBinary");
+  QTest::newRow("presetBinaryChange")
+    << WindowSetupHelper{ [](CMakeSetupDialog* window) {
+         loopSleep();
+         window->Preset->setPresetName("ninja2");
+       } }
+    << "ninja2" << CMakeGUITest_BINARY_DIR "/presetArg-presetBinaryChange/src"
+    << CMakeGUITest_BINARY_DIR "/presetArg-presetBinaryChange/src/build"
+    << makePresetProperties("presetArg-presetBinaryChange");
+  QTest::newRow("noPresetBinaryChange")
+    << WindowSetupHelper{ [](CMakeSetupDialog* window) {
+         loopSleep();
+         window->Preset->setPresetName("ninja");
+       } }
+    << "ninja" << CMakeGUITest_BINARY_DIR "/presetArg-noPresetBinaryChange/src"
+    << CMakeGUITest_BINARY_DIR "/presetArg-noPresetBinaryChange/src/build"
+    << makePresetProperties("presetArg-noPresetBinaryChange");
+  QTest::newRow("presetConfigExists")
+    << WindowSetupHelper{} << "ninja"
+    << CMakeGUITest_BINARY_DIR "/presetArg-presetConfigExists/src"
+    << CMakeGUITest_BINARY_DIR "/presetArg-presetConfigExists/src/build"
+    << makePresetProperties("presetArg-presetConfigExists");
+  QTest::newRow("noExist") << WindowSetupHelper{} << QString{}
+                           << CMakeGUITest_BINARY_DIR "/presetArg-noExist/src"
+                           << "" << QCMakePropertyList{};
+}
+
+namespace {
+void writePresets(const QString& buildDir, const QStringList& names)
+{
+  QJsonArray presets{
+    QJsonObject{
+      { "name", "base" },
+      { "generator", "Ninja" },
+      { "binaryDir",
+        QString::fromLocal8Bit("${sourceDir}/%1/${presetName}")
+          .arg(buildDir) },
+      { "hidden", true },
+    },
+  };
+
+  for (auto const& name : names) {
+    presets.append(QJsonObject{
+      { "name", name },
+      { "inherits", QJsonArray{ "base" } },
+    });
+  }
+
+  QJsonDocument doc{ QJsonObject{
+    { "version", 1 },
+    { "configurePresets", presets },
+  } };
+
+  QFile presetsFile(CMakeGUITest_BINARY_DIR
+                    "/changingPresets/src/CMakePresets.json");
+  bool open = presetsFile.open(QIODevice::WriteOnly);
+  Q_ASSERT(open);
+  presetsFile.write(doc.toJson());
+}
+}
+
+void CMakeGUITest::changingPresets()
+{
+  QDir::root().mkpath(CMakeGUITest_BINARY_DIR "/changingPresets/src");
+
+  this->m_window->SourceDirectory->setText(CMakeGUITest_BINARY_DIR
+                                           "/changingPresets/src");
+  loopSleep();
+  QCOMPARE(this->m_window->Preset->presetName(), QString{});
+  QCOMPARE(this->m_window->Preset->presets().size(), 0);
+  QCOMPARE(this->m_window->BinaryDirectory->currentText(), "");
+  QCOMPARE(this->m_window->Preset->isHidden(), true);
+  QCOMPARE(this->m_window->PresetLabel->isHidden(), true);
+
+  writePresets("build1", { "preset" });
+  loopSleep(1500);
+  QCOMPARE(this->m_window->Preset->presetName(), QString{});
+  QCOMPARE(this->m_window->Preset->presets().size(), 1);
+  QCOMPARE(this->m_window->BinaryDirectory->currentText(), "");
+  QCOMPARE(this->m_window->Preset->isHidden(), false);
+  QCOMPARE(this->m_window->PresetLabel->isHidden(), false);
+
+  this->m_window->Preset->setPresetName("preset");
+  loopSleep();
+  QCOMPARE(this->m_window->Preset->presetName(), "preset");
+  QCOMPARE(this->m_window->Preset->presets().size(), 1);
+  QCOMPARE(this->m_window->BinaryDirectory->currentText(),
+           CMakeGUITest_BINARY_DIR "/changingPresets/src/build1/preset");
+  QCOMPARE(this->m_window->Preset->isHidden(), false);
+  QCOMPARE(this->m_window->PresetLabel->isHidden(), false);
+
+  writePresets("build2", { "preset2", "preset" });
+  loopSleep(1500);
+  QCOMPARE(this->m_window->Preset->presetName(), "preset");
+  QCOMPARE(this->m_window->Preset->presets().size(), 2);
+  QCOMPARE(this->m_window->BinaryDirectory->currentText(),
+           CMakeGUITest_BINARY_DIR "/changingPresets/src/build1/preset");
+  QCOMPARE(this->m_window->Preset->isHidden(), false);
+  QCOMPARE(this->m_window->PresetLabel->isHidden(), false);
+
+  writePresets("build3", { "preset2" });
+  loopSleep(1500);
+  QCOMPARE(this->m_window->Preset->presetName(), QString{});
+  QCOMPARE(this->m_window->Preset->presets().size(), 1);
+  QCOMPARE(this->m_window->BinaryDirectory->currentText(),
+           CMakeGUITest_BINARY_DIR "/changingPresets/src/build1/preset");
+  QCOMPARE(this->m_window->Preset->isHidden(), false);
+  QCOMPARE(this->m_window->PresetLabel->isHidden(), false);
+
+  this->m_window->Preset->setPresetName("preset2");
+  loopSleep();
+  QCOMPARE(this->m_window->Preset->presetName(), "preset2");
+  QCOMPARE(this->m_window->Preset->presets().size(), 1);
+  QCOMPARE(this->m_window->BinaryDirectory->currentText(),
+           CMakeGUITest_BINARY_DIR "/changingPresets/src/build3/preset2");
+  QCOMPARE(this->m_window->Preset->isHidden(), false);
+  QCOMPARE(this->m_window->PresetLabel->isHidden(), false);
+
+  QDir::root().mkpath(CMakeGUITest_BINARY_DIR "/changingPresets/src2");
+  QFile::copy(CMakeGUITest_BINARY_DIR "/changingPresets/src/CMakePresets.json",
+              CMakeGUITest_BINARY_DIR
+              "/changingPresets/src2/CMakePresets.json");
+  this->m_window->SourceDirectory->setText(CMakeGUITest_BINARY_DIR
+                                           "/changingPresets/src2");
+  loopSleep();
+  QCOMPARE(this->m_window->Preset->presetName(), QString{});
+  QCOMPARE(this->m_window->Preset->presets().size(), 1);
+  QCOMPARE(this->m_window->BinaryDirectory->currentText(),
+           CMakeGUITest_BINARY_DIR "/changingPresets/src/build3/preset2");
+  QCOMPARE(this->m_window->Preset->isHidden(), false);
+  QCOMPARE(this->m_window->PresetLabel->isHidden(), false);
+
+  this->m_window->Preset->setPresetName("preset2");
+  loopSleep();
+  QCOMPARE(this->m_window->Preset->presetName(), "preset2");
+  QCOMPARE(this->m_window->Preset->presets().size(), 1);
+  QCOMPARE(this->m_window->BinaryDirectory->currentText(),
+           CMakeGUITest_BINARY_DIR "/changingPresets/src2/build3/preset2");
+  QCOMPARE(this->m_window->Preset->isHidden(), false);
+  QCOMPARE(this->m_window->PresetLabel->isHidden(), false);
+
+  QFile(CMakeGUITest_BINARY_DIR "/changingPresets/src2/CMakePresets.json")
+    .remove();
+  loopSleep(1500);
+  QCOMPARE(this->m_window->Preset->presetName(), QString{});
+  QCOMPARE(this->m_window->Preset->presets().size(), 0);
+  QCOMPARE(this->m_window->BinaryDirectory->currentText(),
+           CMakeGUITest_BINARY_DIR "/changingPresets/src2/build3/preset2");
+  QCOMPARE(this->m_window->Preset->isHidden(), true);
+  QCOMPARE(this->m_window->PresetLabel->isHidden(), true);
+}
+
 void SetupDefaultQSettings()
 {
   QSettings::setDefaultFormat(QSettings::IniFormat);

+ 3 - 0
Tests/CMakeGUI/CMakeGUITest.h

@@ -23,4 +23,7 @@ private slots:
   void simpleConfigure();
   void simpleConfigure_data();
   void environment();
+  void presetArg();
+  void presetArg_data();
+  void changingPresets();
 };

+ 21 - 0
Tests/CMakeGUI/CMakeLists.txt

@@ -72,3 +72,24 @@ add_cmake_gui_lib_test(QCMakeCacheModel
   MOC_SOURCES
     QCMakeCacheModelTest.h
   )
+add_cmake_gui_lib_test(QCMakePreset
+  SOURCES
+    QCMakePresetTest.cxx
+    QCMakePresetTest.h
+  MOC_SOURCES
+    QCMakePresetTest.h
+  )
+add_cmake_gui_lib_test(QCMakePresetItemModel
+  SOURCES
+    QCMakePresetItemModelTest.cxx
+    QCMakePresetItemModelTest.h
+  MOC_SOURCES
+    QCMakePresetItemModelTest.h
+  )
+add_cmake_gui_lib_test(QCMakePresetComboBox
+  SOURCES
+    QCMakePresetComboBoxTest.cxx
+    QCMakePresetComboBoxTest.h
+  MOC_SOURCES
+    QCMakePresetComboBoxTest.h
+  )

+ 80 - 0
Tests/CMakeGUI/QCMakePresetComboBoxTest.cxx

@@ -0,0 +1,80 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "QCMakePresetComboBoxTest.h"
+
+#include <QtTest>
+
+void QCMakePresetComboBoxTest::changePresets()
+{
+  QCMakePresetComboBox box;
+  QSignalSpy presetChanged(&box, &QCMakePresetComboBox::presetChanged);
+
+  QCOMPARE(presetChanged.size(), 0);
+
+  box.setPresets({});
+  QCOMPARE(presetChanged.size(), 0);
+
+  box.setPresetName(QString{});
+  QCOMPARE(presetChanged.size(), 0);
+
+  box.setPresets({
+    {
+      /*name=*/"preset",
+      /*description=*/"",
+      /*description=*/"",
+      /*generator=*/"Ninja",
+      /*architecture=*/"",
+      /*toolset=*/"",
+      /*setGenConfig=*/true,
+      /*enabled=*/true,
+    },
+  });
+  QCOMPARE(presetChanged.size(), 0);
+
+  box.setPresetName(QString{});
+  QCOMPARE(presetChanged.size(), 0);
+
+  box.setPresetName("noexist");
+  QCOMPARE(presetChanged.size(), 0);
+
+  box.setPresetName("preset");
+  QCOMPARE(presetChanged.size(), 1);
+  QCOMPARE(presetChanged.last(), QList<QVariant>{ "preset" });
+
+  box.setPresets({
+    {
+      /*name=*/"preset",
+      /*description=*/"",
+      /*description=*/"",
+      /*generator=*/"Ninja Multi-Config",
+      /*architecture=*/"",
+      /*toolset=*/"",
+      /*setGenConfig=*/true,
+      /*enabled=*/true,
+    },
+  });
+  QCOMPARE(presetChanged.size(), 1);
+
+  box.setPresetName("noexist");
+  QCOMPARE(presetChanged.size(), 2);
+  QCOMPARE(presetChanged.last(), QList<QVariant>{ QString{} });
+
+  box.setPresetName("preset");
+  QCOMPARE(presetChanged.size(), 3);
+  QCOMPARE(presetChanged.last(), QList<QVariant>{ "preset" });
+
+  box.blockSignals(true);
+  box.setPresetName(QString{});
+  box.blockSignals(false);
+  QCOMPARE(presetChanged.size(), 3);
+
+  box.setPresetName("preset");
+  QCOMPARE(presetChanged.size(), 4);
+  QCOMPARE(presetChanged.last(), QList<QVariant>{ "preset" });
+
+  box.setPresets({});
+  QCOMPARE(presetChanged.size(), 5);
+  QCOMPARE(presetChanged.last(), QList<QVariant>{ QString{} });
+}
+
+QTEST_MAIN(QCMakePresetComboBoxTest)

+ 13 - 0
Tests/CMakeGUI/QCMakePresetComboBoxTest.h

@@ -0,0 +1,13 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "QCMakePresetComboBox.h"
+#include <QObject>
+
+class QCMakePresetComboBoxTest : public QObject
+{
+  Q_OBJECT
+private slots:
+  void changePresets();
+};

+ 162 - 0
Tests/CMakeGUI/QCMakePresetItemModelTest.cxx

@@ -0,0 +1,162 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "QCMakePresetItemModelTest.h"
+
+#include <utility>
+
+#include "QCMakePreset.h"
+#include "QCMakePresetItemModel.h"
+#include <QHash>
+#include <QMetaType>
+#include <QSignalSpy>
+#include <QVariant>
+#include <QVector>
+#include <QtTest>
+
+using QItemDataHash = QHash<Qt::ItemDataRole, QVariant>;
+
+void QCMakePresetItemModelTest::initTestCase()
+{
+  QMetaType::registerComparators<QCMakePreset>();
+}
+
+void QCMakePresetItemModelTest::initTestCase_data()
+{
+  QTest::addColumn<QVector<QCMakePreset>>("presets");
+  QTest::addColumn<QVector<QItemDataHash>>("data");
+
+  QVector<QCMakePreset> presets{
+    QCMakePreset{
+      /*name=*/"no-description",
+      /*description=*/"",
+      /*description=*/"",
+      /*generator=*/"",
+      /*architecture=*/"",
+      /*toolset=*/"",
+      /*setGenConfig=*/true,
+      /*enabled=*/true,
+    },
+    QCMakePreset{
+      /*name=*/"short-description",
+      /*description=*/"Short Description",
+      /*description=*/"",
+      /*generator=*/"",
+      /*architecture=*/"",
+      /*toolset=*/"",
+      /*setGenConfig=*/true,
+      /*enabled=*/true,
+    },
+    QCMakePreset{
+      /*name=*/"long-description",
+      /*description=*/"",
+      /*description=*/"Long Description",
+      /*generator=*/"",
+      /*architecture=*/"",
+      /*toolset=*/"",
+      /*setGenConfig=*/true,
+      /*enabled=*/true,
+    },
+    QCMakePreset{
+      /*name=*/"disabled",
+      /*description=*/"",
+      /*description=*/"",
+      /*generator=*/"",
+      /*architecture=*/"",
+      /*toolset=*/"",
+      /*setGenConfig=*/true,
+      /*enabled=*/false,
+    },
+  };
+  QVector<QItemDataHash> data{
+    QItemDataHash{
+      { Qt::AccessibleDescriptionRole, "" },
+      { Qt::DisplayRole, "no-description" },
+      { Qt::ToolTipRole, "" },
+      { Qt::UserRole, QVariant::fromValue(presets[0]) },
+      { Qt::FontRole, QFont{} },
+    },
+    QItemDataHash{
+      { Qt::AccessibleDescriptionRole, "" },
+      { Qt::DisplayRole, "Short Description" },
+      { Qt::ToolTipRole, "" },
+      { Qt::UserRole, QVariant::fromValue(presets[1]) },
+      { Qt::FontRole, QFont{} },
+    },
+    QItemDataHash{
+      { Qt::AccessibleDescriptionRole, "" },
+      { Qt::DisplayRole, "long-description" },
+      { Qt::ToolTipRole, "Long Description" },
+      { Qt::UserRole, QVariant::fromValue(presets[2]) },
+      { Qt::FontRole, QFont{} },
+    },
+    QItemDataHash{
+      { Qt::AccessibleDescriptionRole, "" },
+      { Qt::DisplayRole, "disabled" },
+      { Qt::ToolTipRole, "" },
+      { Qt::UserRole, QVariant::fromValue(presets[3]) },
+      { Qt::FontRole, QFont{} },
+    },
+    QItemDataHash{
+      { Qt::AccessibleDescriptionRole, "separator" },
+      { Qt::DisplayRole, QVariant{} },
+      { Qt::ToolTipRole, QVariant{} },
+      { Qt::UserRole, QVariant{} },
+      { Qt::FontRole, QFont{} },
+    },
+    QItemDataHash{
+      { Qt::AccessibleDescriptionRole, "" },
+      { Qt::DisplayRole, "<custom>" },
+      { Qt::ToolTipRole, "Specify all settings manually" },
+      { Qt::UserRole, QVariant{} },
+      { Qt::FontRole,
+        []() {
+          QFont f;
+          f.setItalic(true);
+          return f;
+        }() },
+    },
+  };
+  QTest::newRow("many") << presets << data;
+  QTest::newRow("none") << QVector<QCMakePreset>{}
+                        << QVector<QItemDataHash>{ data.last() };
+}
+
+void QCMakePresetItemModelTest::data()
+{
+  QFETCH_GLOBAL(QVector<QCMakePreset>, presets);
+  QFETCH_GLOBAL(QVector<QItemDataHash>, data);
+  QFETCH(Qt::ItemDataRole, role);
+
+  QCMakePresetItemModel model;
+  QSignalSpy spy1(&model, &QCMakePresetItemModel::modelAboutToBeReset);
+  QSignalSpy spy2(&model, &QCMakePresetItemModel::modelReset);
+  model.setPresets(presets);
+  QCOMPARE(spy1.size(), 1);
+  QCOMPARE(spy2.size(), 1);
+
+  QVector<QVariant> expectedData(data.size());
+  for (int i = 0; i < data.size(); ++i) {
+    expectedData[i] = data[i][role];
+  }
+
+  auto rows = model.rowCount();
+  QVector<QVariant> actualData(rows);
+  for (int i = 0; i < rows; ++i) {
+    actualData[i] = model.data(model.index(i, 0), role);
+  }
+
+  QCOMPARE(actualData, expectedData);
+}
+
+void QCMakePresetItemModelTest::data_data()
+{
+  QTest::addColumn<Qt::ItemDataRole>("role");
+
+  QTest::newRow("accessible") << Qt::AccessibleDescriptionRole;
+  QTest::newRow("display") << Qt::DisplayRole;
+  QTest::newRow("tooltip") << Qt::ToolTipRole;
+  QTest::newRow("user") << Qt::UserRole;
+  QTest::newRow("font") << Qt::FontRole;
+}
+
+QTEST_MAIN(QCMakePresetItemModelTest)

+ 17 - 0
Tests/CMakeGUI/QCMakePresetItemModelTest.h

@@ -0,0 +1,17 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "QCMakePresetItemModel.h"
+#include <QObject>
+
+class QCMakePresetItemModelTest : public QObject
+{
+  Q_OBJECT
+private slots:
+  void initTestCase();
+  void initTestCase_data();
+
+  void data();
+  void data_data();
+};

+ 82 - 0
Tests/CMakeGUI/QCMakePresetTest.cxx

@@ -0,0 +1,82 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "QCMakePresetTest.h"
+
+#include <utility>
+
+#include "QCMakePreset.h"
+#include <QtTest>
+
+namespace {
+QCMakePreset makePreset()
+{
+  return QCMakePreset{
+    /*name=*/"name",
+    /*displayName=*/"displayName",
+    /*description=*/"description",
+    /*generator=*/"generator",
+    /*architecture=*/"architecture",
+    /*toolset=*/"toolset",
+    /*setGenConfig=*/true,
+    /*enabled=*/true,
+  };
+}
+
+template <typename T, typename U>
+QCMakePreset makePreset(T QCMakePreset::*field, U&& value)
+{
+  auto preset = makePreset();
+  preset.*field = std::forward<U>(value);
+  return preset;
+}
+}
+
+void QCMakePresetTest::equality()
+{
+  QFETCH(QCMakePreset, rhs);
+  QFETCH(bool, equal);
+  QFETCH(bool, lt);
+  QFETCH(bool, gt);
+
+  auto lhs = makePreset();
+  QVERIFY((lhs == rhs) == equal);
+  QVERIFY((lhs != rhs) == !equal);
+  QVERIFY((lhs < rhs) == lt);
+  QVERIFY((lhs >= rhs) == !lt);
+  QVERIFY((lhs > rhs) == gt);
+  QVERIFY((lhs <= rhs) == !gt);
+}
+
+void QCMakePresetTest::equality_data()
+{
+  QTest::addColumn<QCMakePreset>("rhs");
+  QTest::addColumn<bool>("equal");
+  QTest::addColumn<bool>("lt");
+  QTest::addColumn<bool>("gt");
+
+  QTest::newRow("equal") << makePreset() << true << false << false;
+  QTest::newRow("name") << makePreset(&QCMakePreset::name, "other-name")
+                        << false << true << false;
+  QTest::newRow("displayName")
+    << makePreset(&QCMakePreset::displayName, "other-displayName") << false
+    << true << false;
+  QTest::newRow("description")
+    << makePreset(&QCMakePreset::description, "other-description") << false
+    << true << false;
+  QTest::newRow("generator")
+    << makePreset(&QCMakePreset::generator, "other-generator") << false << true
+    << false;
+  QTest::newRow("architecture")
+    << makePreset(&QCMakePreset::architecture, "other-architecture") << false
+    << true << false;
+  QTest::newRow("toolset") << makePreset(&QCMakePreset::toolset,
+                                         "other-toolset")
+                           << false << false << true;
+  QTest::newRow("setGenConfig")
+    << makePreset(&QCMakePreset::setGenConfig, false) << false << false
+    << true;
+  QTest::newRow("enabled") << makePreset(&QCMakePreset::enabled, false)
+                           << false << false << true;
+}
+
+QTEST_MAIN(QCMakePresetTest)

+ 14 - 0
Tests/CMakeGUI/QCMakePresetTest.h

@@ -0,0 +1,14 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "QCMakePreset.h"
+#include <QObject>
+
+class QCMakePresetTest : public QObject
+{
+  Q_OBJECT
+private slots:
+  void equality();
+  void equality_data();
+};

+ 33 - 0
Tests/CMakeGUI/presetArg-noPresetBinaryChange/CMakePresets.json.in

@@ -0,0 +1,33 @@
+{
+  "version": 1,
+  "configurePresets": [
+    {
+      "name": "ninja",
+      "generator": "Ninja",
+      "binaryDir": "${sourceDir}/build",
+      "cacheVariables": {
+        "STRING_VARIABLE": {
+          "type": "STRING",
+          "value": "String value"
+        },
+        "PATH_VARIABLE": {
+          "type": "PATH",
+          "value": "${sourceDir}"
+        },
+        "FILEPATH_VARIABLE": {
+          "type": "FILEPATH",
+          "value": "${sourceDir}/CMakeLists.txt"
+        },
+        "ON_VARIABLE": {
+          "type": "BOOL",
+          "value": "ON"
+        },
+        "FALSE_VARIABLE": {
+          "type": "BOOL",
+          "value": "FALSE"
+        },
+        "UNINITIALIZED_VARIABLE": "Uninitialized value"
+      }
+    }
+  ]
+}

+ 33 - 0
Tests/CMakeGUI/presetArg-preset/CMakePresets.json.in

@@ -0,0 +1,33 @@
+{
+  "version": 1,
+  "configurePresets": [
+    {
+      "name": "ninja",
+      "generator": "Ninja",
+      "binaryDir": "${sourceDir}/build",
+      "cacheVariables": {
+        "STRING_VARIABLE": {
+          "type": "STRING",
+          "value": "String value"
+        },
+        "PATH_VARIABLE": {
+          "type": "PATH",
+          "value": "${sourceDir}"
+        },
+        "FILEPATH_VARIABLE": {
+          "type": "FILEPATH",
+          "value": "${sourceDir}/CMakeLists.txt"
+        },
+        "ON_VARIABLE": {
+          "type": "BOOL",
+          "value": "ON"
+        },
+        "FALSE_VARIABLE": {
+          "type": "BOOL",
+          "value": "FALSE"
+        },
+        "UNINITIALIZED_VARIABLE": "Uninitialized value"
+      }
+    }
+  ]
+}

+ 33 - 0
Tests/CMakeGUI/presetArg-presetBinary/CMakePresets.json.in

@@ -0,0 +1,33 @@
+{
+  "version": 1,
+  "configurePresets": [
+    {
+      "name": "ninja",
+      "generator": "Ninja",
+      "binaryDir": "${sourceDir}/build",
+      "cacheVariables": {
+        "STRING_VARIABLE": {
+          "type": "STRING",
+          "value": "String value"
+        },
+        "PATH_VARIABLE": {
+          "type": "PATH",
+          "value": "${sourceDir}"
+        },
+        "FILEPATH_VARIABLE": {
+          "type": "FILEPATH",
+          "value": "${sourceDir}/CMakeLists.txt"
+        },
+        "ON_VARIABLE": {
+          "type": "BOOL",
+          "value": "ON"
+        },
+        "FALSE_VARIABLE": {
+          "type": "BOOL",
+          "value": "FALSE"
+        },
+        "UNINITIALIZED_VARIABLE": "Uninitialized value"
+      }
+    }
+  ]
+}

+ 39 - 0
Tests/CMakeGUI/presetArg-presetBinaryChange/CMakePresets.json.in

@@ -0,0 +1,39 @@
+{
+  "version": 1,
+  "configurePresets": [
+    {
+      "name": "ninja",
+      "generator": "Ninja",
+      "binaryDir": "${sourceDir}/build",
+      "cacheVariables": {
+        "STRING_VARIABLE": {
+          "type": "STRING",
+          "value": "String value"
+        },
+        "PATH_VARIABLE": {
+          "type": "PATH",
+          "value": "${sourceDir}"
+        },
+        "FILEPATH_VARIABLE": {
+          "type": "FILEPATH",
+          "value": "${sourceDir}/CMakeLists.txt"
+        },
+        "ON_VARIABLE": {
+          "type": "BOOL",
+          "value": "ON"
+        },
+        "FALSE_VARIABLE": {
+          "type": "BOOL",
+          "value": "FALSE"
+        },
+        "UNINITIALIZED_VARIABLE": "Uninitialized value"
+      }
+    },
+    {
+      "name": "ninja2",
+      "inherits": [
+        "ninja"
+      ]
+    }
+  ]
+}

+ 2 - 0
Tests/CMakeGUI/presetArg-presetConfigExists/CMakeLists.txt.in

@@ -0,0 +1,2 @@
+cmake_minimum_required(VERSION 3.18)
+project(sourceBinaryArgs-sourceDir NONE)

+ 33 - 0
Tests/CMakeGUI/presetArg-presetConfigExists/CMakePresets.json.in

@@ -0,0 +1,33 @@
+{
+  "version": 1,
+  "configurePresets": [
+    {
+      "name": "ninja",
+      "generator": "Ninja",
+      "binaryDir": "${sourceDir}/build",
+      "cacheVariables": {
+        "STRING_VARIABLE": {
+          "type": "STRING",
+          "value": "String value"
+        },
+        "PATH_VARIABLE": {
+          "type": "PATH",
+          "value": "${sourceDir}"
+        },
+        "FILEPATH_VARIABLE": {
+          "type": "FILEPATH",
+          "value": "${sourceDir}/CMakeLists.txt"
+        },
+        "ON_VARIABLE": {
+          "type": "BOOL",
+          "value": "ON"
+        },
+        "FALSE_VARIABLE": {
+          "type": "BOOL",
+          "value": "FALSE"
+        },
+        "UNINITIALIZED_VARIABLE": "Uninitialized value"
+      }
+    }
+  ]
+}

+ 2 - 0
Tests/CMakeGUI/presetArg-presetConfigExists/CMakeSetup.ini.in

@@ -0,0 +1,2 @@
+[Settings]
+StartPath\WhereBuild0=@CMake_BINARY_DIR@

+ 23 - 12
Tests/CMakeLib/testJSONHelpers.cxx

@@ -37,6 +37,7 @@ enum class ErrorCode
   InvalidInt,
   InvalidBool,
   InvalidString,
+  InvalidSubObject,
   InvalidObject,
   InvalidArray,
   MissingRequired,
@@ -148,15 +149,20 @@ bool testString()
 
 bool testObject()
 {
+  auto const subhelper =
+    cmJSONObjectHelper<ObjectStruct, ErrorCode>(ErrorCode::Success,
+                                                ErrorCode::InvalidSubObject)
+      .Bind("subfield"_s, &ObjectStruct::Field2, IntHelper);
   auto const helper = cmJSONObjectHelper<ObjectStruct, ErrorCode>(
                         ErrorCode::Success, ErrorCode::InvalidObject)
                         .Bind("field1"_s, &ObjectStruct::Field1, StringHelper)
-                        .Bind("field2"_s, &ObjectStruct::Field2, IntHelper)
+                        .Bind("field2"_s, subhelper)
                         .Bind<std::string>("field3"_s, nullptr, StringHelper);
 
   Json::Value v(Json::objectValue);
   v["field1"] = "Hello";
-  v["field2"] = 2;
+  v["field2"] = Json::objectValue;
+  v["field2"]["subfield"] = 2;
   v["field3"] = "world!";
   v["extra"] = "extra";
 
@@ -165,29 +171,34 @@ bool testObject()
   ASSERT_TRUE(s1.Field1 == "Hello");
   ASSERT_TRUE(s1.Field2 == 2);
 
-  v["field2"] = "wrong";
+  v["field2"]["subfield"] = "wrong";
   ObjectStruct s2;
   ASSERT_TRUE(helper(s2, &v) == ErrorCode::InvalidInt);
 
-  v.removeMember("field2");
+  v["field2"].removeMember("subfield");
   ObjectStruct s3;
-  ASSERT_TRUE(helper(s3, &v) == ErrorCode::InvalidObject);
+  ASSERT_TRUE(helper(s3, &v) == ErrorCode::InvalidSubObject);
 
-  v["field2"] = 2;
-  v["field3"] = 3;
+  v.removeMember("field2");
   ObjectStruct s4;
-  ASSERT_TRUE(helper(s4, &v) == ErrorCode::InvalidString);
+  ASSERT_TRUE(helper(s4, &v) == ErrorCode::InvalidObject);
 
-  v.removeMember("field3");
+  v["field2"] = Json::objectValue;
+  v["field2"]["subfield"] = 2;
+  v["field3"] = 3;
   ObjectStruct s5;
-  ASSERT_TRUE(helper(s5, &v) == ErrorCode::InvalidObject);
+  ASSERT_TRUE(helper(s5, &v) == ErrorCode::InvalidString);
 
-  v = "Hello";
+  v.removeMember("field3");
   ObjectStruct s6;
   ASSERT_TRUE(helper(s6, &v) == ErrorCode::InvalidObject);
 
+  v = "Hello";
   ObjectStruct s7;
-  ASSERT_TRUE(helper(s7, nullptr) == ErrorCode::InvalidObject);
+  ASSERT_TRUE(helper(s7, &v) == ErrorCode::InvalidObject);
+
+  ObjectStruct s8;
+  ASSERT_TRUE(helper(s8, nullptr) == ErrorCode::InvalidObject);
 
   return true;
 }

+ 1 - 0
Tests/RunCMake/CMakeLists.txt

@@ -758,6 +758,7 @@ add_RunCMake_test(PrecompileHeaders -DCMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID}
   -DCMAKE_C_COMPILER_VERSION=${CMAKE_C_COMPILER_VERSION})
 
 add_RunCMake_test("UnityBuild")
+add_RunCMake_test(CMakePresets)
 
 if(WIN32)
   add_RunCMake_test(Win32GenEx)

+ 1 - 0
Tests/RunCMake/CMakePresets/CMakeGeneratorConfigDefault-result.txt

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

+ 11 - 0
Tests/RunCMake/CMakePresets/CMakeGeneratorConfigDefault-stderr.txt

@@ -0,0 +1,11 @@
+^CMake Error at CMakeLists\.txt:[0-9]+ \(project\):
+  Generator
+
+    [^
+]*
+
+  does not support platform specification, but platform
+
+    a
+
+  was specified\.$

+ 0 - 0
Tests/RunCMake/CMakePresets/CMakeGeneratorConfigIgnore.cmake


+ 1 - 0
Tests/RunCMake/CMakePresets/CMakeGeneratorConfigNone-result.txt

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

+ 11 - 0
Tests/RunCMake/CMakePresets/CMakeGeneratorConfigNone-stderr.txt

@@ -0,0 +1,11 @@
+^CMake Error at CMakeLists\.txt:[0-9]+ \(project\):
+  Generator
+
+    [^
+]*
+
+  does not support platform specification, but platform
+
+    a
+
+  was specified\.$

+ 4 - 0
Tests/RunCMake/CMakePresets/CMakeLists.txt.in

@@ -0,0 +1,4 @@
+cmake_minimum_required(VERSION 3.18)
+project(${RunCMake_TEST} NONE)
+set(RunCMake_SOURCE_DIR [==[@RunCMake_SOURCE_DIR@]==])
+include("${RunCMake_SOURCE_DIR}/${RunCMake_TEST}.cmake")

+ 481 - 0
Tests/RunCMake/CMakePresets/CMakePresets.json.in

@@ -0,0 +1,481 @@
+/*
+ * Block comment
+ */
+{
+  // Inline comment
+  "version": 1,
+  "cmakeMinimumRequired": {
+    "major": 3,
+    "minor": 18,
+    "patch": 0
+  },
+  "vendor": {
+    "example.com/ExampleIDE/1.0": true
+  },
+  "configurePresets": [
+    {
+      "name": "Good",
+      "displayName": "Good Preset",
+      "description": "This preset is meant to test most of the fields when set correctly.",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build",
+      "cmakeExecutable": "/path/does/not/exist/cmake",
+      "vendor": {
+        "example.com/ExampleIDE/1.0": {
+          "transmogrify": true
+        }
+      },
+      "cacheVariables": {
+        "TEST_SOURCE_DIR": {
+          "type": "PATH",
+          "value": "${sourceDir}"
+        },
+        "TEST_SOURCE_PARENT_DIR": {
+          "type": "PATH",
+          "value": "${sourceParentDir}"
+        },
+        "TEST_SOURCE_LIST": {
+          "type": "FILEPATH",
+          "value": "${sourceDir}/CMakeLists.txt"
+        },
+        "TEST_TRUE": {
+          "type": "BOOL",
+          "value": "TRUE"
+        },
+        "TEST_OFF": {
+          "type": "BOOL",
+          "value": "OFF"
+        },
+        "TEST_PRESET_NAME": {
+          "type": "STRING",
+          "value": "x${presetName}x"
+        },
+        "TEST_GENERATOR": {
+          "value": "x${generator}x"
+        },
+        "TEST_DOLLAR": {
+          "value": "${dollar}"
+        },
+        "TEST_ENV_REF": "$env{TEST_ENV_REF}",
+        "TEST_ENV": "$env{TEST_ENV}",
+        "TEST_D_ENV_REF": "$env{TEST_D_ENV_REF}",
+        "TEST_ENV_OVERRIDE": "$env{TEST_ENV_OVERRIDE}",
+        "TEST_PENV": "$env{TEST_PENV}",
+        "TEST_ENV_REF_PENV": "$env{TEST_ENV_REF_PENV}",
+        "TEST_ENV_REF_P": "$penv{TEST_ENV_REF}",
+        "TEST_ENV_P": "$penv{TEST_ENV}",
+        "TEST_D_ENV_REF_P": "$penv{TEST_D_ENV_REF}",
+        "TEST_ENV_OVERRIDE_P": "$penv{TEST_ENV_OVERRIDE}",
+        "TEST_PENV_P": "$penv{TEST_PENV}",
+        "TEST_ENV_REF_PENV_P": "$penv{TEST_ENV_REF_PENV}",
+        "TEST_MULTIPLE_MACROS": "${presetName} ${generator}",
+        "TEST_EXPANSION": "\\${presetName} ${dollar}{dollar} $unknown{namespace} $en{NOT_ENV} $enve{NOT_ENV} $ \\$ $a",
+        "TEST_TRAILING_DOLLAR": "a $",
+        "TEST_TRAILING_BACKSLASH": "a \\",
+        "TEST_TRAILING_UNKNOWN_NAMESPACE": "$unknown{namespace",
+        "TEST_OVERRIDE_1": {
+          "type": "STRING",
+          "value": "Default value"
+        },
+        "TEST_OVERRIDE_2": "Default value",
+        "TEST_OVERRIDE_3": {
+          "type": "STRING",
+          "value": "Default value"
+        },
+        "TEST_OVERRIDE_4": {
+          "type": "STRING",
+          "value": "Default value"
+        },
+        "TEST_UNDEF": "undef"
+      },
+      "environment": {
+        "TEST_ENV_REF": "$env{TEST_ENV}",
+        "TEST_ENV": "Environment variable",
+        "TEST_D_ENV_REF": "x$env{TEST_ENV_REF}x",
+        "TEST_ENV_OVERRIDE": "Overridden environment variable",
+        "TEST_ENV_REF_PENV": "prefix+$penv{TEST_ENV_REF_PENV}",
+        "TEST_PENV": null
+      }
+    },
+    {
+      "name": "GoodNoArgs",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build"
+    },
+    {
+      "name": "GoodBinaryUp",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/../GoodBinaryUp-build"
+    },
+    {
+      "name": "GoodBinaryRelative",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "build"
+    },
+    {
+      "name": "Good Spaces",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build"
+    },
+    {
+      "name": "GoodWindowsBackslash",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}\\build"
+    },
+    {
+      "name": "GoodBinaryCmdLine",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build"
+    },
+    {
+      "name": "GoodGeneratorCmdLine",
+      "generator": "Invalid Generator",
+      "binaryDir": "${sourceDir}/build"
+    },
+    {
+      "name": "InvalidGeneratorCmdLine",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build"
+    },
+    {
+      "name": "GoodNoS",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build"
+    },
+    {
+      "name": "GoodInheritanceParentBase",
+      "hidden": true,
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build",
+      "cacheVariables": {
+        "TEST_VARIABLE": {
+          "type": "STRING",
+          "value": "Some string"
+        }
+      },
+      "environment": {
+        "TEST_ENV": "Some environment variable"
+      }
+    },
+    {
+      "name": "GoodInheritanceParent",
+      "inherits": "GoodInheritanceParentBase"
+    },
+    {
+      "name": "GoodInheritanceChildBase",
+      "hidden": true
+    },
+    {
+      "name": "GoodInheritanceChild",
+      "inherits": "GoodInheritanceChildBase",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build",
+      "cacheVariables": {
+        "TEST_VARIABLE": {
+          "type": "STRING",
+          "value": "Some string"
+        }
+      },
+      "environment": {
+        "TEST_ENV": "Some environment variable"
+      }
+    },
+    {
+      "name": "GoodInheritanceOverrideBase",
+      "hidden": true,
+      "generator": "Invalid Generator",
+      "binaryDir": "${sourceDir}/../GoodInheritanceBase-build",
+      "cacheVariables": {
+        "PARENT_VARIABLE": {
+          "type": "STRING",
+          "value": "Parent variable"
+        },
+        "OVERRIDDEN_VARIABLE": {
+          "type": "BOOL",
+          "value": "ON"
+        },
+        "DELETED_VARIABLE": "This variable will be deleted"
+      },
+      "environment": {
+        "PARENT_ENV": "Parent environment variable",
+        "OVERRIDDEN_ENV": "This environment variable will be overridden",
+        "DELETED_ENV": "This environment variable will be deleted"
+      }
+    },
+    {
+      "name": "GoodInheritanceOverride",
+      "inherits": "GoodInheritanceOverrideBase",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build",
+      "cacheVariables": {
+        "OVERRIDDEN_VARIABLE": {
+          "type": "STRING",
+          "value": "Overridden variable"
+        },
+        "CHILD_VARIABLE": {
+          "type": "STRING",
+          "value": "Child variable"
+        },
+        "DELETED_VARIABLE": null
+      },
+      "environment": {
+        "OVERRIDDEN_ENV": "Overridden environment variable",
+        "CHILD_ENV": "Child environment variable",
+        "DELETED_ENV": null
+      }
+    },
+    {
+      "name": "GoodInheritanceOverrideDummy",
+      "inherits": "GoodInheritanceOverride"
+    },
+    {
+      "name": "GoodInheritanceMulti1",
+      "hidden": true,
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build",
+      "cacheVariables": {
+        "FIRST_VARIABLE": {
+          "type": "STRING",
+          "value": "First variable"
+        },
+        "OVERRIDDEN_VARIABLE": {
+          "type": "STRING",
+          "value": "Overridden variable"
+        }
+      },
+      "environment": {
+        "FIRST_ENV": "First environment variable",
+        "OVERRIDDEN_ENV": "Overridden environment variable"
+      }
+    },
+    {
+      "name": "GoodInheritanceMulti2",
+      "hidden": true,
+      "generator": "Invalid Generator",
+      "binaryDir": "${sourceDir}/../GoodInheritanceMulti2-build",
+      "cacheVariables": {
+        "SECOND_VARIABLE": {
+          "type": "STRING",
+          "value": "Second variable"
+        },
+        "OVERRIDDEN_VARIABLE": {
+          "type": "BOOL",
+          "value": "ON"
+        }
+      },
+      "environment": {
+        "SECOND_ENV": "Second environment variable",
+        "OVERRIDDEN_ENV": "This will be overridden"
+      }
+    },
+    {
+      "name": "GoodInheritanceMulti",
+      "inherits": [
+        "GoodInheritanceMulti1",
+        "GoodInheritanceMulti2"
+      ]
+    },
+    {
+      "name": "GoodInheritanceMultiSecond1",
+      "hidden": true
+    },
+    {
+      "name": "GoodInheritanceMultiSecond2",
+      "hidden": true,
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build"
+    },
+    {
+      "name": "GoodInheritanceMultiSecond",
+      "inherits": [
+        "GoodInheritanceMultiSecond1",
+        "GoodInheritanceMultiSecond2"
+      ]
+    },
+    {
+      "name": "GoodInheritanceMacroBase",
+      "hidden": true,
+      "cacheVariables": {
+        "PRESET_NAME": "${presetName}"
+      }
+    },
+    {
+      "name": "GoodInheritanceMacro",
+      "inherits": "GoodInheritanceMacroBase",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build"
+    },
+    {
+      "name": "UnclosedMacro",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir"
+    },
+    {
+      "name": "NoSuchMacro",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${noexist}"
+    },
+    {
+      "name": "VendorMacro",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "$vendor{unknown.unknownMacro}"
+    },
+    {
+      "name": "InvalidGenerator",
+      "generator": "Invalid Generator",
+      "binaryDir": "${sourceDir}/build"
+    },
+    {
+      "name": "EnvCycle",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build",
+      "environment": {
+        "ENV_1": "$env{ENV_2}",
+        "ENV_2": "$env{ENV_1}"
+      }
+    },
+    {
+      "name": "UseHiddenPreset",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build",
+      "hidden": true
+    },
+    {
+      "name": "VisualStudioGeneratorArch",
+      "generator": "@RunCMake_GENERATOR@ Win64",
+      "binaryDir": "${sourceDir}/build"
+    },
+    {
+      "name": "VisualStudioWin32",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build",
+      "architecture": "Win32"
+    },
+    {
+      "name": "VisualStudioWin64",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build",
+      "architecture": "x64"
+    },
+    {
+      "name": "VisualStudioWin32Override",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build",
+      "architecture": "Win32"
+    },
+    {
+      "name": "VisualStudioToolset",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build",
+      "toolset": "Test Toolset"
+    },
+    {
+      "name": "VisualStudioToolsetOverride",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build",
+      "toolset": "Invalid Toolset"
+    },
+    {
+      "name": "VisualStudioInheritanceParentBase",
+      "hidden": true,
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build",
+      "architecture": "Test Platform",
+      "toolset": "Test Toolset"
+    },
+    {
+      "name": "VisualStudioInheritanceParent",
+      "inherits": "VisualStudioInheritanceParentBase"
+    },
+    {
+      "name": "VisualStudioInheritanceChildBase",
+      "hidden": true
+    },
+    {
+      "name": "VisualStudioInheritanceChild",
+      "inherits": "VisualStudioInheritanceChildBase",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build",
+      "architecture": "Test Platform",
+      "toolset": "Test Toolset"
+    },
+    {
+      "name": "VisualStudioInheritanceOverrideBase",
+      "hidden": true,
+      "architecture": "Invalid Platform",
+      "toolset": "Invalid Toolset"
+    },
+    {
+      "name": "VisualStudioInheritanceOverride",
+      "inherits": "VisualStudioInheritanceOverrideBase",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build",
+      "architecture": "Test Platform",
+      "toolset": "Test Toolset"
+    },
+    {
+      "name": "VisualStudioInheritanceMulti1",
+      "hidden": true,
+      "architecture": "Test Platform",
+      "toolset": "Test Toolset"
+    },
+    {
+      "name": "VisualStudioInheritanceMulti2",
+      "hidden": true,
+      "architecture": "Invalid Platform",
+      "toolset": "Invalid Toolset"
+    },
+    {
+      "name": "VisualStudioInheritanceMulti",
+      "inherits": [
+        "VisualStudioInheritanceMulti1",
+        "VisualStudioInheritanceMulti2"
+      ],
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build"
+    },
+    {
+      "name": "VisualStudioInheritanceMultiSecond1",
+      "hidden": true
+    },
+    {
+      "name": "VisualStudioInheritanceMultiSecond2",
+      "hidden": true,
+      "architecture": "Test Platform",
+      "toolset": "Test Toolset"
+    },
+    {
+      "name": "VisualStudioInheritanceMultiSecond",
+      "inherits": [
+        "VisualStudioInheritanceMultiSecond1",
+        "VisualStudioInheritanceMultiSecond2"
+      ],
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build"
+    },
+    {
+      "name": "CMakeGeneratorConfigNone",
+      "generator": "@RunCMake_GENERATOR@",
+      "architecture": "a",
+      "toolset": "a",
+      "binaryDir": "${sourceDir}/build"
+    },
+    {
+      "name": "CMakeGeneratorConfigBase",
+      "generator": "@RunCMake_GENERATOR@",
+      "architecture": "a",
+      "toolset": "a",
+      "cmakeGeneratorConfig": "ignore",
+      "binaryDir": "${sourceDir}/build"
+    },
+    {
+      "name": "CMakeGeneratorConfigDefault",
+      "inherits": "CMakeGeneratorConfigBase",
+      "cmakeGeneratorConfig": "default"
+    },
+    {
+      "name": "CMakeGeneratorConfigIgnore",
+      "inherits": "CMakeGeneratorConfigBase"
+    }
+  ]
+}

+ 2 - 0
Tests/RunCMake/CMakePresets/CacheOverride.cmake

@@ -0,0 +1,2 @@
+set(TEST_OVERRIDE_3 "Overridden value" CACHE STRING "")
+set(TEST_OVERRIDE_4 "Overridden value" CACHE INTERNAL "")

+ 1 - 0
Tests/RunCMake/CMakePresets/CyclicInheritance0-result.txt

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

+ 2 - 0
Tests/RunCMake/CMakePresets/CyclicInheritance0-stderr.txt

@@ -0,0 +1,2 @@
+^CMake Error: Could not read presets from [^
+]*/Tests/RunCMake/CMakePresets/CyclicInheritance0: Cyclic preset inheritance$

+ 13 - 0
Tests/RunCMake/CMakePresets/CyclicInheritance0.json.in

@@ -0,0 +1,13 @@
+{
+  "version": 1,
+  "configurePresets": [
+    {
+      "name": "CyclicInheritance0",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build",
+      "inherits": [
+        "CyclicInheritance0"
+      ]
+    }
+  ]
+}

+ 1 - 0
Tests/RunCMake/CMakePresets/CyclicInheritance1-result.txt

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

+ 2 - 0
Tests/RunCMake/CMakePresets/CyclicInheritance1-stderr.txt

@@ -0,0 +1,2 @@
+^CMake Error: Could not read presets from [^
+]*/Tests/RunCMake/CMakePresets/CyclicInheritance1: Cyclic preset inheritance$

+ 21 - 0
Tests/RunCMake/CMakePresets/CyclicInheritance1.json.in

@@ -0,0 +1,21 @@
+{
+  "version": 1,
+  "configurePresets": [
+    {
+      "name": "CyclicInheritance0",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build",
+      "inherits": [
+        "CyclicInheritance1"
+      ]
+    },
+    {
+      "name": "CyclicInheritance1",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build",
+      "inherits": [
+        "CyclicInheritance0"
+      ]
+    }
+  ]
+}

+ 1 - 0
Tests/RunCMake/CMakePresets/CyclicInheritance2-result.txt

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

+ 2 - 0
Tests/RunCMake/CMakePresets/CyclicInheritance2-stderr.txt

@@ -0,0 +1,2 @@
+^CMake Error: Could not read presets from [^
+]*/Tests/RunCMake/CMakePresets/CyclicInheritance2: Cyclic preset inheritance$

+ 29 - 0
Tests/RunCMake/CMakePresets/CyclicInheritance2.json.in

@@ -0,0 +1,29 @@
+{
+  "version": 1,
+  "configurePresets": [
+    {
+      "name": "CyclicInheritance0",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build",
+      "inherits": [
+        "CyclicInheritance1"
+      ]
+    },
+    {
+      "name": "CyclicInheritance1",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build",
+      "inherits": [
+        "CyclicInheritance2"
+      ]
+    },
+    {
+      "name": "CyclicInheritance2",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build",
+      "inherits": [
+        "CyclicInheritance0"
+      ]
+    }
+  ]
+}

+ 1 - 0
Tests/RunCMake/CMakePresets/Debug-stderr.txt

@@ -0,0 +1 @@
+  find_package considered the following locations for the Config module:

+ 4 - 0
Tests/RunCMake/CMakePresets/Debug-stdout.txt

@@ -0,0 +1,4 @@
+-- Generating [^
+]*/Tests/RunCMake/CMakePresets/Debug/build
+   Called from: \[1\][^
+]*/Tests/RunCMake/CMakePresets/Debug/CMakeLists\.txt

+ 4 - 0
Tests/RunCMake/CMakePresets/Debug.cmake

@@ -0,0 +1,4 @@
+include(${CMAKE_CURRENT_LIST_DIR}/DebugBase.cmake)
+if(NOT EXISTS "${CMAKE_BINARY_DIR}/CMakeFiles/CMakeTmp/CMakeLists.txt")
+  message(SEND_ERROR "Debugging try_compile() did not work")
+endif()

+ 19 - 0
Tests/RunCMake/CMakePresets/Debug.json.in

@@ -0,0 +1,19 @@
+{
+  "version": 1,
+  "configurePresets": [
+    {
+      "name": "NoDebug",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build"
+    },
+    {
+      "name": "Debug",
+      "inherits": "NoDebug",
+      "debug": {
+        "output": true,
+        "find": true,
+        "tryCompile": true
+      }
+    }
+  ]
+}

+ 3 - 0
Tests/RunCMake/CMakePresets/DebugBase.cmake

@@ -0,0 +1,3 @@
+enable_language(C)
+try_compile(_result ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_LIST_DIR}/main.c)
+find_package(ThisPackageHopefullyDoesNotExist CONFIG)

+ 1 - 0
Tests/RunCMake/CMakePresets/DisableWarningFlags.cmake

@@ -0,0 +1 @@
+include(${CMAKE_CURRENT_LIST_DIR}/WarningsBase.cmake)

+ 1 - 0
Tests/RunCMake/CMakePresets/DuplicatePresets-result.txt

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

+ 2 - 0
Tests/RunCMake/CMakePresets/DuplicatePresets-stderr.txt

@@ -0,0 +1,2 @@
+^CMake Error: Could not read presets from [^
+]*/Tests/RunCMake/CMakePresets/DuplicatePresets: Duplicate presets$

+ 15 - 0
Tests/RunCMake/CMakePresets/DuplicatePresets.json.in

@@ -0,0 +1,15 @@
+{
+  "version": 1,
+  "configurePresets": [
+    {
+      "name": "DuplicatePresets",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build"
+    },
+    {
+      "name": "DuplicatePresets",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build"
+    }
+  ]
+}

+ 1 - 0
Tests/RunCMake/CMakePresets/EmptyPresetName-result.txt

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

+ 2 - 0
Tests/RunCMake/CMakePresets/EmptyPresetName-stderr.txt

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

+ 10 - 0
Tests/RunCMake/CMakePresets/EmptyPresetName.json.in

@@ -0,0 +1,10 @@
+{
+  "version": 1,
+  "configurePresets": [
+    {
+      "name": "",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build"
+    }
+  ]
+}

+ 1 - 0
Tests/RunCMake/CMakePresets/EnvCycle-result.txt

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

+ 1 - 0
Tests/RunCMake/CMakePresets/EnvCycle-stderr.txt

@@ -0,0 +1 @@
+^CMake Error: Could not evaluate preset "EnvCycle": Invalid macro expansion$

+ 1 - 0
Tests/RunCMake/CMakePresets/ErrorDeprecated-result.txt

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

+ 7 - 0
Tests/RunCMake/CMakePresets/ErrorDeprecated-stderr.txt

@@ -0,0 +1,7 @@
+^CMake Deprecation Error at [^
+]*/Tests/RunCMake/CMakePresets/WarningsBase\.cmake:[0-9]+ \(message\):
+  Deprecation warning
+Call Stack \(most recent call first\):
+  [^
+]*/Tests/RunCMake/CMakePresets/ErrorDeprecated\.cmake:[0-9]+ \(include\)
+  CMakeLists\.txt:[0-9]+ \(include\)$

+ 1 - 0
Tests/RunCMake/CMakePresets/ErrorDeprecated.cmake

@@ -0,0 +1 @@
+include(${CMAKE_CURRENT_LIST_DIR}/WarningsBase.cmake)

+ 1 - 0
Tests/RunCMake/CMakePresets/ErrorDev-result.txt

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

+ 8 - 0
Tests/RunCMake/CMakePresets/ErrorDev-stderr.txt

@@ -0,0 +1,8 @@
+^CMake Error \(dev\) at [^
+]*/Tests/RunCMake/CMakePresets/WarningsBase\.cmake:[0-9]+ \(message\):
+  Dev warning
+Call Stack \(most recent call first\):
+  [^
+]*/Tests/RunCMake/CMakePresets/ErrorDev\.cmake:[0-9]+ \(include\)
+  CMakeLists\.txt:[0-9]+ \(include\)
+This error is for project developers\. Use -Wno-error=dev to suppress it\.$

+ 1 - 0
Tests/RunCMake/CMakePresets/ErrorDev.cmake

@@ -0,0 +1 @@
+include(${CMAKE_CURRENT_LIST_DIR}/WarningsBase.cmake)

+ 1 - 0
Tests/RunCMake/CMakePresets/ErrorNoWarningDeprecated-result.txt

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

+ 2 - 0
Tests/RunCMake/CMakePresets/ErrorNoWarningDeprecated-stderr.txt

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

+ 16 - 0
Tests/RunCMake/CMakePresets/ErrorNoWarningDeprecated.json.in

@@ -0,0 +1,16 @@
+{
+  "version": 1,
+  "configurePresets": [
+    {
+      "name": "ErrorNoWarningDeprecated",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build",
+      "warnings": {
+        "deprecated": false
+      },
+      "errors": {
+        "deprecated": true
+      }
+    }
+  ]
+}

+ 1 - 0
Tests/RunCMake/CMakePresets/ErrorNoWarningDev-result.txt

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

+ 2 - 0
Tests/RunCMake/CMakePresets/ErrorNoWarningDev-stderr.txt

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

+ 16 - 0
Tests/RunCMake/CMakePresets/ErrorNoWarningDev.json.in

@@ -0,0 +1,16 @@
+{
+  "version": 1,
+  "configurePresets": [
+    {
+      "name": "ErrorNoWarningDev",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build",
+      "warnings": {
+        "dev": false
+      },
+      "errors": {
+        "dev": true
+      }
+    }
+  ]
+}

+ 1 - 0
Tests/RunCMake/CMakePresets/ExtraPresetField-result.txt

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

+ 2 - 0
Tests/RunCMake/CMakePresets/ExtraPresetField-stderr.txt

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

+ 11 - 0
Tests/RunCMake/CMakePresets/ExtraPresetField.json.in

@@ -0,0 +1,11 @@
+{
+  "version": 1,
+  "configurePresets": [
+    {
+      "name": "ExtraPresetField",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build",
+      "invalid": true
+    }
+  ]
+}

+ 1 - 0
Tests/RunCMake/CMakePresets/ExtraRootField-result.txt

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

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff