Why is CMakePresets.json preventing --config argument for multiconfiguration CMake project?

140 views Asked by At

tl;dr: why does Visual Studio Code not include --config argument to cmake.exe when my project has a CMakePresets.json?


I'm transitioning from a primarily CLI-driven development environment to an IDE-driven development environment, and I'm having trouble understanding some interactions between the IDE and invoked CMake commands.

My original problem was that compiled binaries were being generated in unexpected locations.
E.g. for my (intended) Release build of Google Test, files like gmock.lib were being generated in a Debug/ directory instead of the expected Release/ directory.

I then traced this behavior to the presence/absence of the --config argument to cmake.exe as invoked by Visual Studio Code. From this answer, I'm aware of the difference between single- and multi-configuration generators. I'm using the "Visual Studio 17 2022" generator, which is multi-configuration, so I'm aware that the user is expected to supply a --config argument at build time (otherwise, I think/infer, that "Debug" is chosen as the default configuration).

Specifically, I discovered that when my project has a CMakePresets.json, Visual Studio Code does not supply a --config argument to cmake.exe when building, and when there is no CMakePresets.json, Visual Studio Code does supply a --config argument.

I.e., when my project has no CMakePresets.json, this is Visual Studio Code's CMake command, as captured from the "OUTPUT" window:

"C:\Program Files\CMake\bin\cmake.EXE" --build c:/dev/googletest/out/vscode/build --config Release --target ALL_BUILD -j 22 --

I also noticed that when there is no CMakePresets.json, under the "Configure" drop-down in the CMake pane, there is a "Release/Debug/etc." choice: enter image description here

Coversely, when my project does have CMakePresets.json, this is Visual Studio Code's CMake command:

"C:\Program Files\CMake\bin\cmake.EXE" --build C:/dev/googletest/out/vscode/build/x64-release --parallel 22 --target ALL_BUILD

...i.e. there is no --config argument. Also, there is no "Release/Debug" choice in the "Configure" drop-down in the CMake pane: enter image description here

This implicit presence/absence of the --config flag makes me not confident in the integrity of what I'm building: I feel like I'm creating some weird Debug/Release hybrid.

I'm newly learning CMake, and I've only just learned about the CMakePresets.json. I am probably mistaken, but I was under the impression that the CMake Presets only played a role in CMake generation, i.e. I don't understand why the presence/absence of CMakePresets.json impacts Visual Studio Code's decision whether to include --config in the build.

My question: please explain what role CMakePresets.json plays in building a project, after CMake generation, and specifically how it interacts with "intelligent" IDEs, like Visual Studio Code, and if there a way I can both have CMakePresets.json and have the IDE correctly apply the --config build argument for multiconfiguration builds.
(A reason why I'm particularly puzzled about the interaction with IDEs is if I both generate and build from the command line (i.e. not from the IDE), then I can explicitly specify the --config argument, regardless of the presence/absence of CMakePresets.json, in which case all output appears to be as expected (at least I do not observe any unexpected "Debug"s when I'm building Release")

This is the CMakePresets.json:

{
  "version": 3,
  "configurePresets": [
    {
      "name": "windows-base",
      "description": "Target Windows with the Visual Studio development environment.",
      "hidden": true,
      "generator": "Visual Studio 17 2022",
      "binaryDir": "${sourceDir}/out/vscode/build/${presetName}",
      "installDir": "${sourceDir}/out/vscode/install/${presetName}",
      "cacheVariables": {
        "gtest_force_shared_crt": {
          "type": "BOOL",
          "value":  "ON"
        }
      },
      "condition": {
        "type": "equals",
        "lhs": "${hostSystemName}",
        "rhs": "Windows"
      }
    },
    {
      "name": "x64-debug",
      "displayName": "x64 Debug",
      "description": "Target Windows (64-bit) with the Visual Studio development environment. (Debug)",
      "inherits": "windows-base",
      "architecture": {
        "value": "x64",
        "strategy": "external"
      },
      "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" }
    },
    {
      "name": "x64-release",
      "displayName": "x64 Release",
      "description": "Target Windows (64-bit) with the Visual Studio development environment. (RelWithDebInfo)",
      "inherits": "windows-base",
      "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" }
    },
    {
      "name": "x86-debug",
      "displayName": "x86 Debug",
      "description": "Target Windows (32-bit) with the Visual Studio development environment. (Debug)",
      "inherits": "windows-base",
      "architecture": {
        "value": "x86",
        "strategy": "external"
      },
      "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" }
    },
    {
      "name": "x86-release",
      "displayName": "x86 Release",
      "description": "Target Windows (32-bit) with the Visual Studio development environment. (RelWithDebInfo)",
      "inherits": "windows-base",
      "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" }
    }
  ]
}
1

There are 1 answers

3
StoneThrow On

why does Visual Studio Code not include --config argument to cmake.exe when my project has a CMakePresets.json?

Because your (my) CMakePresets.json does not include a buildPresets section when it has specified a multi-configuration generator.

There are a few good answers on Stack Overflow that discuss what a generator is (e.g. this and this). Essentially a generator is the build system for which CMake will generate build files. Examples of "build systems" are "Unix Makefiles" and "Visual Studio 17 2022." A build system has associated build files that describe how to go about doing the build: directories to descend into, source files, output files, and what compiler to use to turn source files to output files. The build files associated with the "Unix Makefiles" build system are Makefiles. The build files associated with "Visual Studio 17 2022" are .sln files (and possibly other associated files).

The process of building a CMake project is comprised of two steps: 1) the CMake configuration/generation, and 2) building the project.
CMake configuration/generation is itself comprised of two steps: configuration, and generation, but it is often discussed as though it were one stage, and different documentation will refer to this interchangeably as "configuration" or "generation." Configuration refers to the creation of the CMake cache, typically encapsulated in a file named CMakeCache.txt that effectively captures the state of CMake's analysis of the project's CMakeList.txt files, and various aspects of the build environment. Generation refers to the creation of the build system files based on the content of the CMake cache. I.e. if the generator is "Unix Makefiles," then generation will create Makefile files, and if the generator is "Visual Studio 17 2022," then generation will create .sln files (and possibly other associated files).
Building the project consists of running a build tool on the build files that were generated by the CMake generation. The build tool for "Unix Makefiles" is make, and the build tool for "Visual Studio 17 2022" is the Visual Studio IDE and the msbuild CLI-invokable command.

Build files may support multiple configurations or a single configuration.
Single configuration means that a given set of build files can only be used to build a specific configuration. I.e. the Makefile files created by one run of cmake can only be used to build the debug version of your hello-world application. Another run of cmake, that generates another discrete set of Makefile files, would be needed to build a release version of your application. Multiple runs of cmake would overwrite the Makefile files from prior runs, unless you organized different directories for them, etc. In short: one build configuration per (set of) build file(s).
Multiple configuration means that one set of build files can be used to build multiple configurations. I.e. the .sln file created by one run of cmake could be used to build both debug and release versions of your application. In short: multiple build configurations per (set of) build file(s).

CMake only supports single configuration Makefiles. Visual Studio solutions support multiple configurations. CMake once treated Ninja as a single configuration generator, but was apparently updated to treat as a multiple configuration generator. (Confusing this issue during initial research is that lots of online documentation exists that still describes Ninja as a single-configuration generator) CMake once only offered the single-configuration "Ninja" generator, but was later updated to offer "Ninja Multi-Config", a multiple configuration generator that still targets the Ninja build tool.

I was under the impression that the CMake Presets only played a role in CMake generation

This is incorrect. CMake Presets play a role in building the project for multiple configuration generators. Your (my) CMakePresets.json specified "generator": "Visual Studio 17 2022", which is a multiple configuration generator. Your (my) CMakePresets.json only has a configurePresets section, which only feeds the CMake configuration/generation stage. You (I), similarly, need a section that feeds the build tool since the project, by virtue of choosing a multiple configuration generator, allows "multiple build configurations per (set of) build file(s)." That section, appropriately named, is "buildPresets".
Importantly, if you do not specify a buildPresets section when you have specified a multiple configuration generator, when you build, the default build configuration will be specified; this default build configuration is usually Debug, which aligns with your observation.

The default Build Preset is equivalent to passing cmake --build with no other arguments from the command line.
https://learn.microsoft.com/en-us/cpp/build/cmake-presets-vs?view=msvc-170

Here is an example CMakePresets.txt, inspired by https://github.com/esweet431/box2d-lite/blob/vs-launch/CMakePresets.json, that illustrates a buildPresets section:

{
  "version": 3,
  "configurePresets": [
    {
      "name": "windows-base",
      "description": "Target Windows with the Visual Studio development environment.",
      "hidden": true,
      "generator": "Visual Studio 17 2022",
      "binaryDir": "${sourceDir}/out/vs/build/${presetName}",
      "installDir": "${sourceDir}/out/vs/install/${presetName}",
      "cacheVariables": {
        "CMAKE_C_COMPILER": "cl.exe",
        "CMAKE_CXX_COMPILER": "cl.exe"
      },
      "condition": {
        "type": "equals",
        "lhs": "${hostSystemName}",
        "rhs": "Windows"
      }
    },
    {
      "name": "x64-debug",
      "displayName": "x64 Debug",
      "description": "Target Windows (64-bit) with the Visual Studio development environment. (Debug)",
      "inherits": "windows-base",
      "architecture": {
        "value": "x64",
        "strategy": "external"
      },
      "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" }
    },
    {
      "name": "x64-release",
      "displayName": "x64 Release",
      "description": "Target Windows (64-bit) with the Visual Studio development environment. (RelWithDebInfo)",
      "inherits": "x64-debug",
      "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" }
    },
    {
      "name": "x86-debug",
      "displayName": "x86 Debug",
      "description": "Target Windows (32-bit) with the Visual Studio development environment. (Debug)",
      "inherits": "windows-base",
      "architecture": {
        "value": "x86",
        "strategy": "external"
      },
      "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" }
    },
    {
      "name": "x86-release",
      "displayName": "x86 Release",
      "description": "Target Windows (32-bit) with the Visual Studio development environment. (RelWithDebInfo)",
      "inherits": "x86-debug",
      "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" }
    }
  ],
  "buildPresets": [
    {
      "name": "Debug",
      "displayName": "Debug",
      "configurePreset": "x64-debug",
      "configuration": "debug",
      "description": "Target Windows (64-bit) with the Visual Studio development environment. (Debug)"
    },
    {
      "name": "Release",
      "displayName": "Release",
      "configurePreset": "x64-release",
      "configuration": "release",
      "description": "Target Windows (64-bit) with the Visual Studio development environment. (RelWithDebInfo)"
    }
  ]
}