Fast builds are critical to the C++ programmer’s productivity and happiness. One common technique for reducing build times is precompiled headers (PCH). There’s plenty of literature out there; I won’t describe PCH in detail here.
But one thing that’s always bothered me about PCH is that it affects your code.
#pragma hdrstop and
#include "StdAfx.h" everywhere. Gross.
I’m a strong believer in clean code without boilerplate, so can’t we do better? Ideally we could make a simple tweak to the build system and see build times magically improve. Enno enticed me with mentions of his fast builds, so I took a look…
Using PCH in Visual C++ requires a header (call it Precompiled.h) that includes all of the expensive dependencies:
#include <vector> #include <map> #include <iostream> #include <fstream> #include <boost/python.hpp> #include <windows.h> #include <mmsystem.h>
Additionally, we need a source file (let’s get creative and call it Precompiled.cpp), which is empty except for
OK, here’s the step that prevented me from using PCH for so long: every single source file in your project must
#include "Precompiled.h" on its first line.
That’s ridiculous! I don’t want to touch every file!
It turns out our savior is the /FI option. From the documentation:
This option has the same effect as specifying the file with double quotation marks in an #include directive on the first line of every source file specified on the command line […]
Exactly what we want!
But wait, doesn’t that mean every .cpp in our project will have access to every symbol included by the PCH? Yes. :( It’s worth the build speedup.
However, explicit physical dependencies are important, and the only way to prevent important things from breaking is by blocking commits if they fail. Since enabling and disabling PCH does not require any code changes, it’s easy enough to add a “disable PCH” option to your build system and run it on your continuous integration server:
If somebody uses
std::string but forgets to
#include <string>, the build will fail and block commits.
In the end, here’s the bit of SCons magic that lets me quickly drop PCH into a project:
def enable_pch(env, source_file, header): if PCH_ENABLED: PCH, PCH_OBJ = env.PCH(source_file) env['PCH'] = PCH env['PCHSTOP'] = header env.Append(CPPFLAGS=['/FI' + header]) return [PCH_OBJ] else: return [source_file]
Now you can benefit from fast builds with minimal effort and no change to your existing code!