A Compact and Fast Build System, Fit for a Ninja
In its essence, compiling C and C++ is straightforward. The compiler translates each source file into object files containing machine code; then it links all the object files into one large executable. Libraries are built as separate files, which the compiler then dynamically links to the executable at run-time. As a rule, you only recompile changed parts of the source code, as well as all code depending on it.
BUILD AUTOMATION TOOLS
No efficient development is complete without build automation tools. There exist many build automation tools, such as GNU make, CMake, Apache Maven, MSBuild or Apache Ant. Some of them are language agnostic; some are tailored to a specific language, while others support most of the popular languages.
These tools determine dependencies between parts of your source code and initiate prompt compilations of only the modified portions. Most development teams with complex projects do not build directly any more but use build script generators that produce files to be used by the native build automation tool.
Nevertheless, the periods of forced waiting during C++ compilation sessions can grow tiresome rapidly. There are many resources on how to accelerate C++ compiling and project-building that you can google, but I would like to show you an elegant and simple solution that worked for us.
In Qt Creator under Projects enable previously added ninja kit. Be careful to set Build directory to the existing folder.
A story of a customer’s codebase
Our story began when a customer gave us a medium-size C++/Qt code base (1M LOC). As usual, the customer was in a hurry, and so, consequently, were we. The full build of the codebase took about one hour. Nevertheless, in a read–eval–print loop (REPL), you rarely do a complete build, so this was not a problem. After a couple of days studying the code, we started programming.
But we found out quickly that even with just a one-line change, the compilation performed by our usual build script generator, CMake, took a long time, between 1 to 2 minutes. And no, there was no change in a header file included everywhere or even heavily templated code.
It seemed to me that something was wrong, so I fired up the Task Manager to get some insight. I quickly realised that when I started building the project, cmake.exe kept the CPU busy at 100% for quite a long time. The latter was very strange, as I did not edit any CMakeLists.txt files. It was only after CMake finished and the compiler started, followed by the linker, that things speeded up and finished in a reasonable time of about 10s seconds.
Ninja to the rescue
After doing some research, I found that this could be a fairly common problem with larger projects. Of course, since this problem was much more noticeable in larger projects, some smart people had already tackled the situation and, fortunately, created an open-source tool called Ninja. The latter is a small build system, focused on speed and processes build scripts generated by higher-level build systems. Developers use Ninja for building Google Chrome, parts of Android, and LLVM, and you can efficiently utilise it in many other projects due to CMake’s Ninja backend.
Ninja differs from its brethren in two significant respects: its input comes from higher-level build systems, and it runs builds faster than many other similar tools.
Fast it is. Replacing the build system from jom to Ninja can reduce compilation by up to seven times. In our example, we did not see the previous spike in CMake CPU usage and compilation started immediately. Because our project used CMake this was (almost) a drop-in replacement*. All we had to do was to specify the Ninja generator (e.g. cmake -Gninja …) in addition to installing Ninja on the system, which consists of putting ninja.exe into any PATH folder.
That’s another advantage of Ninja: it is just a small execution file without any dependencies! In the process of switching to the new build system, you will have to do a full rebuild. But this is done only once – the first time.
The project’s CMake files were written in an old (pre 2.8) version of CMake, so they used INCLUDE(…) instead of the modern TARGET_INCLUDE(…) function, and because the project had a long list of includes, we initially got some errors as
you can see above.
Conclusion
I did, however, almost give up on the idea of using Ninja on this project. Namely, when investigating this error, I figured out that Ninja uses the Win32API function CreateProcessA on Windows, which can only accept strings up to a maximum length of 32,768 characters. Some of the project modules had so many includes that the whole string was longer than 32K characters. We were also not permitted to change any CMakeLists.txt files, and to replace all uses of INCLUDE() with TARGET_INCLUDE() would have taken too long, as time was of the essence.
Luckily for us, we could suppress the 32K include string limitation by setting the CMake variable CMAKE_NINJA_FORCE_RESPONSE_FILE to ON.
Example in QT Creator setting CMAKE_NINJA_FORCE_RESPONSE_FILE to ON.
So, if you thought that ninjas are just fictional characters in movies, you were wrong!
They are real and help us to battle slow compile times in C++. In the end, Ninja did not just speed up the REPL process, but it also helped to improve our software development productivity overall. Its quick turnaround gave our project work a feeling of lightness that left us grinning.
Note:
If you, too, are struggling with long compilation times, I would also recommend the following resources:
- How to Make Your C++ Qt Project Build 10x Faster with 4 Optimisations
<https://developex.com/blog/how-to-make-your-c-qt-project-build-10x-faster-with-4-optimizations/> - Tobias Hieta: Compiling C++ is slow – let’s go faster
<https://www.youtube.com/watch?v=X4pyOtawqjg>
About the author
Peter Karlovšek is a Software Engineer at Cosylab, where he primarily works on a real-time framework, developed in C++. For most of his professional programming career, he has been developing console and graphical programs written in C++ and Qt. Peter is very passionate about the C++ language and enjoys fathoming its depths and learning new tools. The latter helps him build cleaner, more expressive and secure code. He also loves to tweak and optimise build tools to accelerate the development process. In his free time, Peter is an organiser of Meetup’s Ljubljana C++ User Group. He also enjoys cycling, walks, reading books, and most of all, spending time with his family.