Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Ninja Multi-Config generator #3

Closed
ktetzlaff opened this issue Mar 23, 2022 · 11 comments · Fixed by #4
Closed

Add support for Ninja Multi-Config generator #3

ktetzlaff opened this issue Mar 23, 2022 · 11 comments · Fixed by #4

Comments

@ktetzlaff
Copy link

ktetzlaff commented Mar 23, 2022

CMake version 3.17 added the Ninja Multi-Config generator (https://cmake.org/cmake/help/latest/generator/Ninja%20Multi-Config.html?highlight=ninja%20multi#generator:Ninja%20Multi-Config) which supports multiple build types in a single binary (build) directory (currently: Debug, Release, ReleaseWithDebInfo).

When using cmake-integration with a Ninja Multi-Config build directory, it should offer targets for all combinations of target name and build type/config.

@darcamo
Copy link
Owner

darcamo commented Mar 28, 2022

When reading the configurations for a given configure preset we get a vector of configurations. I don't use Ninja Multi-Config myself and thus this vector of configurations always has only one element. Therefore, currently I just get the first element in the vector of configurations.

In the case of multi-config, this vector has three elements. The current implementation just gets the first one, which is likely the debug build.

I took a look at which changes would be required to support this. One limitation at the moment is that when configuring the user selects the desired preset from a list of configure presets. Then, when building, I just assume that there is a build preset with the same name of the configure preset and use that.

One way to solve this issue is to have a build preset for each build type (Debug, Release, etc), all referencing the same configure preset using the Ninja Multi-Config generator. Then the code in cmake-integration would need to change to read build preset names, instead of configure preset names. With this, the usage would be similar to what we have currently. But I don't know how it would work when presets are not employed.

Could you elaborate how you usually configure and build with different configurations from the command line using the Ninja Multi-Config generator.

@ktetzlaff
Copy link
Author

Using a build dir generated by the Ninja Multi-Config generator is (by default) no different than using the normal Ninja generator. You can just say:

cmake --build build # with build dir: 'build'

or

cd build
ninja

This will, by default, build the Debug target (you can change this by adding e.g. -DCMAKE_DEFAULT_BUILD_TYPE=Release to the configure step).

To build the other configurations, cmake supports the --config <buildtype> option. E.g.:

cmake --build build --config Release # or: RelWithDebInfo

or:

cd build
ninja -f build-Release.ninja

JFYI: There are some additional variables to control the Multi Config build. The more interesting ones are:

  • CMAKE_CONFIGURATION_TYPES :: Specifies the total set of configurations to
    build (default: Debug;Release;RelWithDebInfo).

  • CMAKE_DEFAULT_CONFIGS :: Specifies a semicolon-separated list of
    configurations to build for a target in build.ninja if no : suffix is
    specified.

@ktetzlaff
Copy link
Author

Looking at /.cmake/api/v1/reply/codemodel-v2-*.json, the multi config variant just has muliple elements in the configurations list:

I.e. for a project with targets foo and bar the normal Ninja generator has (json converted to yaml for better readability):

configurations:
- name: "Debug"
  directories: [...]
  projects: [...]
  targets:
  - name: "foo"
    ...:
  - name: "bar"
    ...:

whereas the Multi Config case has:

configurations:
- name: "Debug"
  directories: [...]
  projects: [...]
  targets:
  - name: "foo"
    ...:
  - name: "bar"
    ...:
- name: "Release"
  directories: [...]
  projects: [...]
  targets:
  - name: "foo"
    ...:
  - name: "bar"
    ...:
- name: "RelWithDebInfo"
  directories: [...]
  projects: [...]
  targets:
  - name: "foo"
    ...:
  - name: "bar"
    ...:

So what would make sense is to construct a Multi Config target list containing elements like:

  • all/Debug
  • all/Release
  • all/RelWithDebInfo
  • foo/Debug
  • foo/Release
  • foo/RelWithDebInfo
  • bar/Debug
  • bar/Release
  • bar/RelWithDebInfo

(or put the config at the beginning - this is a personal preference).

@ktetzlaff
Copy link
Author

ktetzlaff commented Mar 29, 2022

If you haven't spent time on this yet: I'm currently looking at the sources and will create a pull request.

@ktetzlaff
Copy link
Author

ktetzlaff commented Mar 29, 2022

In the case of multi-config, this vector has three elements. The current implementation just gets the first one, which is likely the debug build.

The three elements are the default (s. CMAKE_CONFIGURATION_TYPES). There could be less (or more).

I took a look at which changes would be required to support this. One limitation at the moment is that when configuring the user selects the desired preset from a list of configure presets. Then, when building, I just assume that there is a build preset with the same name of the configure preset and use that.

Even the presets could reference Multi-Config generators. So preset type builds should also support multiple configs.

One way to solve this issue is to have a build preset for each build type (Debug, Release, etc), all referencing the same configure preset using the Ninja Multi-Config generator. Then the code in cmake-integration would need to change to read build preset names, instead of configure preset names. With this, the usage would be similar to what we have currently. But I don't know how it would work when presets are not employed.

I wasn't able to follow you there. Maybe because I have not used CMake presets. But I think it is less complicated since presets and (multi-)configs are orthogonal (as mentioned above, a preset could use a multi-config generator - Microsoft has example for that: https://docs.microsoft.com/de-de/cpp/build/cmake-presets-vs?view=msvc-170#linux-example). The solution I implemented in #4 should work for preset and non preset builds.

Basically, for preset + multi-config builds you now get a compile command of:

cd <prjroot> && cmake --build --preset <preset> --config <config> --target <target>

whereas for non-preset builds you get:

cd <prjroot> && cmake --build <bindir> --config <config> --target <target>

For non multi-config builds, the --config <config> part is skipped (so that the result is the same, or similar to what it was before the change).

ktetzlaff pushed a commit to ktetzlaff/cmake-integration that referenced this issue Mar 29, 2022

Verified

This commit was signed with the committer’s verified signature.
chjj Christopher Jeffrey (JJ)
An example for such a generator is 'Ninja Multi-Config'.

The commit changes the 'target-name' variables/functions to 'target'
which is now a combination of the actual 'target name' and the
'configuration name' separated by
`cmake-integration--multi-config-separator' (in case of a non
multi-config generator, the configuration name is nil and 'target' is
identical to 'target name').

The handling of the 'all' target has changed, too. It is now included in the
list you get from `check-if-build-folder-exists-and-throws-if-not' (since it
also needs to have the multi-config variants).

In addition to the above, I also added a call to
`check-if-build-folder-exists-and-throws-if-not' in
`cmake-integration-run-last-target'.

Closes darcamo#3.
ktetzlaff pushed a commit to ktetzlaff/cmake-integration that referenced this issue Mar 29, 2022

Verified

This commit was signed with the committer’s verified signature.
chjj Christopher Jeffrey (JJ)
An example for such a generator is `Ninja Multi-Config` (introduced in
CMake 3.17).

The commit changes the semantic (and name) of `target-name`
variables/functions to `target` which is now a combination of the
actual _target name_ and the _configuration name_ separated by
`cmake-integration--multi-config-separator` (in the - now - special
case of a _non multi-config_ generator, the _configuration name_ is
nil and _target_ is identical to _target name_).

The handling of the `all` target has changed, too. It is now included
in the list you get from
`cmake-integration-get-cmake-targets-from-codemodel-json-file` (since
it also needs to have the _multi-config_ variants).

In addition to the above, I also added a call to
`check-if-build-folder-exists-and-throws-if-not` in
`cmake-integration-run-last-target`.

Closes darcamo#3.
ktetzlaff pushed a commit to ktetzlaff/cmake-integration that referenced this issue Mar 29, 2022

Verified

This commit was signed with the committer’s verified signature.
chjj Christopher Jeffrey (JJ)
An example for such a generator is `Ninja Multi-Config` (introduced in
CMake 3.17).

The commit changes the semantic (and name) of `target-name`
variables/functions to `target` which is now a combination of the
actual _target name_ and the _configuration name_ separated by
`cmake-integration--multi-config-separator` (in the - now - special
case of a _non multi-config_ generator, the _configuration name_ is
nil and _target_ is identical to _target name_).

The handling of the `all` target has changed, too. It is now included
in the list you get from
`cmake-integration-get-cmake-targets-from-codemodel-json-file` (since
it also needs to have the _multi-config_ variants).

In addition to the above, I also added a call to
`check-if-build-folder-exists-and-throws-if-not` in
`cmake-integration-run-last-target`.

Closes darcamo#3.
@darcamo
Copy link
Owner

darcamo commented Mar 29, 2022

I wasn't able to follow you there.

There are different types of presets. There is a "configure preset", a "build preset", and a "test preset".
When you run

cmake --preset=somepreset

it's using a configure preset because you are configuring. Also, notice that the command is run from the folder with the CMakeLists.txt file and does not specify the build folder, which is obtained from the preset.

If you add --build to the command then the meaning of --preset is changed to "use the following build preset".
At the comment the user selects the name of the configure preset. When building, the build command specifies a preset with the same name, which means that a build preset with the same name must also exist.

There are two approaches here.

  1. Forget about build presets for now and use only the configure presets
    • With this the build command would not specify any preset, but the build folder needs to be provided along with the target.
      That is, it can be just like the one you provided
      cd <prjroot> && cmake --build <bindir> --config <config> --target <target>
      
  2. Specify only a build preset
    • Every build preset needs to specify a configure preset, thus we can get the corresponding "configure preset" name from the "build preset" when we need to configure the project.
    • The disadvantage is that you need a build preset, but this limitation already exists with the current code that assumes one exists.

I think using approach 1 works better and build presets can be reintroduced later in a better way.

ktetzlaff pushed a commit to ktetzlaff/cmake-integration that referenced this issue Mar 30, 2022

Verified

This commit was signed with the committer’s verified signature.
chjj Christopher Jeffrey (JJ)
An example for such a generator is `Ninja Multi-Config` (introduced in
CMake 3.17).

The commit changes the semantic (and name) of `target-name`
variables/functions to `target` which is now a combination of the
actual _target name_ and the _configuration name_ separated by
`cmake-integration--multi-config-separator` (in the - now - special
case of a _non multi-config_ generator, the _configuration name_ is
nil and _target_ is identical to _target name_).

The handling of the `all` target has changed, too. It is now included
in the list you get from
`cmake-integration-get-cmake-targets-from-codemodel-json-file` (since
it also needs to have the _multi-config_ variants).

In addition to the above, I also added a call to
`check-if-build-folder-exists-and-throws-if-not` in
`cmake-integration-run-last-target`.

Closes darcamo#3.
ktetzlaff pushed a commit to ktetzlaff/cmake-integration that referenced this issue Mar 30, 2022

Verified

This commit was signed with the committer’s verified signature.
chjj Christopher Jeffrey (JJ)
An example for such a generator is `Ninja Multi-Config` (introduced in
CMake 3.17).

The commit changes the semantic (and name) of `target-name`
variables/functions to `target` which is now a combination of the
actual _target name_ and the _configuration name_ separated by
`cmake-integration--multi-config-separator` (in the - now - special
case of a _non multi-config_ generator, the _configuration name_ is
nil and _target_ is identical to _target name_).

The handling of the `all` target has changed, too. It is now included
in the list you get from
`cmake-integration-get-cmake-targets-from-codemodel-json-file` (since
it also needs to have the _multi-config_ variants).

In addition to the above, I also added a call to
`check-if-build-folder-exists-and-throws-if-not` in
`cmake-integration-run-last-target`.

Closes darcamo#3.
@ktetzlaff
Copy link
Author

There are different types of presets. There is a "configure preset", a "build preset", and a "test preset". When you run

Could you send me a preset file which contains these different types?

@darcamo
Copy link
Owner

darcamo commented Mar 30, 2022

Here is a presets file containing a "default" build and configure presets, as well as a preset using "Ninja Multi-Config".

Save this as CMakePresets.json

{
    "version": 3,
    "cmakeMinimumRequired": {
        "major": 3,
        "minor": 21,
        "patch": 0
    },
    "configurePresets": [
        {
            "name": "default",
            "displayName": "Default build using Ninja generator",
            "generator": "Ninja",
            "binaryDir": "build",
        },
        {
            "name": "ninjamulticonfig",
            "displayName": "Multi-Config configure preset",
            "description": "Default build using Ninja Multi-Config generator",
            "generator": "Ninja Multi-Config",
            "binaryDir": "${sourceDir}/build-multi/",
            "inherits": "default"
        }
    ],
    "buildPresets": [
        {
            "name": "default",
            "displayName": "Default build preset",
            "configurePreset": "default"
        },
        {
            "name": "ninjamulticonfig",
            "displayName": "Build preset using ninja multi-config",
            "configurePreset": "ninjamulticonfig",
            "configuration": "Release"
        }
    ]
}

@ktetzlaff
Copy link
Author

ktetzlaff commented Mar 30, 2022

Thanks! (BTW, the default configurePreset has an extra comma in the binaryDir line which irritates the json parser.)

Ok, I think I have a better understanding now. But I still wonder what the buildPresets really do. In your example, they basically serve the purpose of selecting a binaryDir (created by the linked configurePreset). In a way, they just create an alias name for binaryDir:

I.e. instead of:

cmake --build build-multi ...

you can now say:

cmake --build --preset ninjamulticonfig ...

Is that all buildPresets are used for? Or do they provide some additional benefits? I probably need to have a closer look at the CMake presets documentation...

Interestingly, the project to which I added the presets file already had a binaryDir named build which I had created without the presets using the Ninja Multi-Config generator. So even though it now corresponds to the /default/ preset (which just uses the Ninja generator), CMake happily accepts (and builds) the following:

cmake --build --preset default --config RelWithDebInfo --target all

So CMake doesn't check if the linked configurePreset actually supports the requested configuration.

However, after I removed the non-preset /build/ dir and recreated it using the default confgurePreset, I can add a configuration=RelWithDebInfo to the (non multi-config) default buildPreset, and CMake just ignores that (so there is some consistency checking going on).

@ktetzlaff
Copy link
Author

ktetzlaff commented Mar 30, 2022

Some further tests have shown that, In case of a multi-config binaryDir, a buildPreset also allows to select the default config. I.e.:

cmake --build --preset ninjamulticonfig --target all

behaves as if a --config <configuration> (where <configuration> is taken from the buildPreset) was added to the cmake command.

And, according to the documentation (https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html), there are some additional attributes which can be configured via buildPresets. So they do have their purpose.

@ktetzlaff
Copy link
Author

Since this issue is already closed, I opened a new issue #7 to discuss the buildPreset handling.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants