Leveraging precompiled headers

I’ve been continuing to read Professional CMake, and there’s a section within that book about precompiled headers.

I’m trying to wrap my head around how to best utilize these precompiled headers in my project. To help visualize, I created a theoretical example for this post.

In terms of layout, you certainly would not organize your project like this, but the point is to narrow down which files should be added as precompiled headers.

This is a contrived example, so there’s no HAL or anything like that, but as you can see above, the GPIO driver is used from various other libraries/drivers. At a first glance, this would seem like a good candidate for a precompiled header…I think?

If that’s the correct assumption, then the question is, what would actually live in that precompiled header, and where would it live?

For example, would I declare it at the root level CMakeLists.txt, or would I declare it within the GPIO directory as part of its CMakeLists.txt?

Similarly, what would go in the pch.h file? Would I include only the outward-facing header files exposed by the GPIO interface? Kind of like this:

target_precompile_headers(gpio PUBLIC gpio.h gpio_registers.h gpio_driver.h)

Another option would be to create a “common” interface library containing the precompiled headers for the entire project, and then link the dependent libs to that. They actually did this in the book, which is where I got the idea,

For example, if there was a common lib, it might look like this:

# Common PCH lib for all dependent libs
add_library(common INTERFACE)

# Assume that we added gpio.h into pch.h
target_precompile_headers(common PUBLIC pch.h)

Then, in my other libs, I could do something like:

# Within each app's CMakeLists.txt
target_link_libraries(foo INTERFACE common gpio uart)
target_precompile_headers(foo REUSE_FROM common)
target_link_libraries(bar INTERFACE common spi uart)
target_precompile_headers(bar REUSE_FROM common)
target_link_libraries(baz INTERFACE common gpio)
target_precompile_headers(baz REUSE_FROM common)

# Within each respective driver's CMakeLists.txt
target_link_libraries(uart INTERFACE common gpio)
target_precompile_headers(uart REUSE_FROM common)
target_link_libraries(spi INTERFACE common gpio)
target_precompile_headers(spi REUSE_FROM common)

# for main within it's CMakeLists.txt
target_link_libraries(main INTERFACE common gpio spi uart foo bar baz)
target_precompile_headers(main REUSE_FROM common)

Is that the right idea, or am I off in the weeds on this one.

Thanks!

A good rule of thumb is to only use precompiled headers for external libraries which you know aren’t going to change as you work on your project - or, if they are, then they come to you fully debugged and dependable.

If you include your own code in a precompiled header, and then make a change to it that doesn’t compile, then finding the problem could be much harder than it would otherwise be, because it would the compilation of the precompiled header itself that would fail.

Anyway with that caveat, suppose that you wanted to precompile the headers to the various standard library headers you might use. You’d create a file called (by convention) stdafx.h alongside main.cpp containing (for example):

#pragma once

#include <algorithm>
#include <iomanip>
#include <iostream>
#include <limits>
#include <cassert>
#include <cerrno>
...

From that point on, your code is pretty close. Basically you then insert this line into your main CMakeLists.txt file (before the point where you include the various subdirectories):

target_precompile_headers(myPCH PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/stdafx.h)

Thereafter, in all of your other CMake files you’d put the line (for example):

target_precompile_headers(uart REUSE_FROM myPCH)

…and that should be pretty much it; CMake will do the rest for you.

1 Like