Advanced CMake Usage


In the previous post, I showed demos of CMake scripts that I am using in one of my projects and gave descriptions to what each function was doing. Though this is nice, sometimes when you are in the development process, you need more functionality from your build system. Below are just a few tricks I’ve learned using CMake that might save you a whole lot of time (But also might sacrifice some of CMake’s functionality).

Custom Configurations

Sometimes in projects, you want to have custom configurations that define different things. This is nice because you wont have to reconfigure if you want to run your code in a different way – though rebuilding is always needed when adding/removing definitions. Configurations can be changed by setting them via the CMAKE_BUILD_TYPE variable for makefiles, or by the Build Configuration drop-down menu in Visual Studio.

The function above works by first enabling C/C++ features so that the cache variables are initialized, and then by going on to create all the custom variables you need and registering everything properly with CMake. It also does some light error checking, such as proper argument count, and valid base configurations. This allows the following code:

This can be very helpful for adding configurations that you may want to switch to during development. I wouldn’t recommend creating many of these, it’s really just for defining certain modes that you want to be able to switch back-and-forth from quickly. An example of this is a LeakDetect mode which will build with leak detecting tools. An example of when this is not needed is when you will spend lots of time in a specific custom configuration. It might have just been better to make it a configuration option at that point.

Setting Folder Properties

If you are using an IDE (such as Visual Studio), you will probably get some mileage out of your build targets being put into subfolders (highly recommended). Luckily, it’s not as involved as the command above.

It’s as easy as that! You can get into much more depth with IDE customization, such as filter processing and other IDE-specific things. But this is an absolute must if you have developers who are using Visual Studio. It’s a tiny bit of work that will clean up so much of the solution file.

File Globbing

One of the things the creators of CMake don’t want you to do (and for good reason) is file globbing to form targets. However, on a project that is in rapid development, file globbing can save a lot of time. This is essentially having CMake find all files in a directory instead of you listing the files manually. Sounds great, right? It is! So why doesn’t Kitware want you to do it? Well, to put it simply; source control.

The issue with globbing is that no change has happen to the CMake file on merging or updating, so CMake will not know that it needs to regenerate. There is another way around this, fortunately. What I do is I glob, and then I have a script set to run on “update” action in my source control that will run CMake in the build directory. What this allows is for me to support globbing, and my solution/makefile to not go out-of-date.

And in my git hooks for this:

If you are unfamiliar with git hooks (or whatever the equivalent would be for the source control of your choice) I highly recommend becoming accustom to them. Hooks are used to streamline many aspects of development.

Side-note: I read once on a site that passing in the headers to add_executable was bad form? I disagree. If you do not pass the headers into the target, the generated solution will not display headers (Visual Studio in particular). So I would recommend this practice, because it doesn’t hurt anyone, and only helps Windows users.

What About Precompiled Headers?

I’m glad you asked! Precompiled headers are really a mess, as they aren’t a part of the standard, but almost every compiler supports them. Since precompiled headers (PCH) are tied very closely with the code-base, and not really the build chain, it’s usually seen as “your problem”. Some build tools will take PCH into account (I believe Premake has functionality for it), but CMake does not, and for good reason. The real fix to build times is coming – but it’s still years and years down the road. Until we have binary modules in C++, we will have to use PCH.

Unfortunately, as mentioned, since PCH is such a case-by-case thing – it’s difficult to add to a build chain. What I’ve found to be the best method is to not account at all for PCH in the code itself (no #includes at the top of every file) and instead set a compiler option to force include the header if it’s there, and set other compilation flags as needed. This is best done by overriding the add_project and add_library functions in CMake (which is totally possible, but totally dangerous, because you can only override once since functions aren’t first-class citizens in CMake.)

Unfortunately I don’t have the “simple fix” just lying around somewhere. The way I deal with PCH is I don’t commit any of my PCH stuff, I parse source files, generate a PCH header and source files, and then run this function on all targets using the PCH Header and the source files before I pass them into add_executable or add_library. What is provided below is the simplified version of the code, stripped of the generation. You should be able to take this and work with it to create your own enable_precompiled_headers function that does what you need it to do.

So maybe not the simplest answer that you were expecting, and it doesn’t work on multiple platforms (you can see how you would make it work though, add other if checks). But you don’t slow down someone who literally cannot build precompiled headers for whatever reason (because we don’t physically include in the file, instead we add a Force Include). A reason PCH might be undesired is because it’s slower if we’re doing a Unity build, which is very common for build servers. So don’t think for a minute your end-user just always wants PCH, that’s not always true.

Side-note: You should be able to build your code without PCH enabled, and no PCH file being included. If you cannot your code is poorly formed.

Side-note: I plan on someday soon sharing my fix which autogenerates PCH files by parsing the source code. So look forward to that at a later date!

More Tips and Tricks!

There are many more tricks I could teach, but I think that’s enough for now. These are just a few of the important things I’ve found to be super helpful when working on a project with CMake as my build system. I hope you are able to take some of them and try them out, I know they’ve helped me! If you have success with any of them or failure, please be sure to come back and talk about it, I’m excited to listen!

Cheers ~

Leave a comment

Your email address will not be published. Required fields are marked *