Building multiple configurations of a project at the same time with Ninja

Introduction

I’ve been interested for a while on how to optimize builds, and one thing that I thought about is to have a central ninja build.ninja that can drive the build for everything. It seems like the topic has been discussed before, both on the Ninja’s GitHub Issues page and on the maillist here, and the conclusion was that it’s not possible.

However, I still wanted to try it. This is the top ninja file that I was playing it:

subninja release_32/build.ninja
subninja release_64/build.ninja

build all: phony release_32/all release_64/all

default all

The problems

The first big problem I faced was that the files are relative to the top ninja file. This was solved by setting CMAKE_NINJA_OUTPUT_PATH_PREFIX. The next problem problem was that I had duplicated rule names (like cmake_object_order_depends_target_libninja-re2c).

At this point I tried different combinations of regex and simple replace scripts to try and patch the ninja files, but I couldn’t get anything working. So as any person with too much enthusiasm would do, I figured it’s time to make a project that could parse and edit in place ninja files.

Here it is for anyone interested (disclaimer: very early and untested, made the minimum necessary work to get something working). In the end, I managed to make something that looks like it works, but I wouldn’t trust it to use it on a real build.

What I’m hoping is that CMake could be changed so it would generate files that would solve (some of) the following problems that I found:

1. Duplicated rules names

Like I’ve mentioned before, CMake generated rules named like cmake_... What I did is to add a prefix to each rule, much more like CMAKE_NINJA_OUTPUT_PATH_PREFIX. I think this could be solved pretty easily with something like CMAKE_NINJA_OUTPUT_RULE_PREFIX with the same logic.

2. Rule that builds CMakeLists.txt

Right now there’s a rule that looks like this

build /home/tachyon/repos/ninja/build-cmake/src_32/CMakeLists.txt /usr/share/cmake-3.27/Modules/BasicConfigVersion-AnyNewerVersion.cmake.in ..... : phony

This is a problem because more than one configuration will try to “generate” those files. To be honest I don’t understand why this exists in the first place, but it’s probably important.

One thing I did to “solve” a part of the problem is to make symlinks of the source directory (ln -s $PWD/.. src_32) and pass it to CMake (-S src_32) for each configuration, but I can’t tell if this is actually an okay thing to do. This still didn’t solve it building files in /usr/share/cmake.., so I just mangled the path to those with a prefix that “soft” broke it.

3. Files that are generated in source

This will result in multiple rules generating the same files, which is fair enough. I have no solution for this other than not doing that. While not ideal, I could absolutely live with this in my projects. Generating the file in the build directory sounds already like a good idea for me.

Conclusion

The final questions would be:

  1. Do you see better solutions for these problems?
  2. Do you see any other problem that might arise?
  3. Could CMake implement changes that would make this easier and less error prone?

Edit: I used “rule” when I actually meant edge.

Have you looked at the Ninja Multi-Config generator?

I have, but as far as I can tell, it can only do different build types. I’m also interested in building different architectures (i686, amd64, arm64, etc.) with different toolchain files and so on at the same time.

You’ll need separate build trees for that kind of thing. I would suggest something like a “superbuild” that uses ExternalProject to build each configuration you want.

Is there information somewhere on how to setup such a superbuild? Or I should try to figure it out from the docs.

You might try to extract it from the (slightly abstract) common-superbuild.

The core of it is:

  • use ExternalProject for all builds within the project
  • use DEPENDS to make sure things build in the right order
  • use install trees rather than build trees of dependencies

Of course, one may stretch things a bit for each line, but I’d recommend not straying too far off the path until it is understood why each rule might be bent (it’s “gut feeling” for me; I don’t think I can explain it).