120

I have seen a few (old) posts on the 'net about hacking together some support for pre-compiled headers in CMake. They all seem a bit all-over the place and everyone has their own way of doing it. What is the best way of doing it currently?

Mehrdad Afshari
  • 414,610
  • 91
  • 852
  • 789
Glutinous
  • 1,201
  • 2
  • 9
  • 3

12 Answers12

55

CMake has just gained support for PCHs (pre-compiled headers), it is available from 3.16 (released October 2019) onwards:

https://gitlab.kitware.com/cmake/cmake/merge_requests/3553

  target_precompile_headers(<target>
    <INTERFACE|PUBLIC|PRIVATE> [header1...]
    [<INTERFACE|PUBLIC|PRIVATE> [header2...] ...])

Sharing PCHs between targets is supported via the REUSE_FROM keyword such as here.

There is some additional context (motivation, numbers) available at https://blog.qt.io/blog/2019/08/01/precompiled-headers-and-unity-jumbo-builds-in-upcoming-cmake/

Felix Dombek
  • 13,664
  • 17
  • 79
  • 131
janisozaur
  • 842
  • 7
  • 10
  • 4
    Here is the link to official CMake documentation: https://cmake.org/cmake/help/latest/command/target_precompile_headers.html – Alex Che May 08 '20 at 15:38
  • 2
    There's a post from MSVC team on figuring out which headers to include in PCH: https://devblogs.microsoft.com/cppblog/faster-builds-with-pch-suggestions-from-c-build-insights/ – janisozaur May 09 '20 at 18:04
  • **For *Obj-C* and/or *Obj-C++* languages**, use syntax like `target_precompile_headers(MyTarget PUBLIC "$<$:${CMAKE_CURRENT_LIST_DIR}/src/MyPrefixHeader.pch>")` - if your header does not support C/C++, or simply add support (by wrapping any `#import` in `#ifdef __OBJC__` checks). – Top-Master Jun 29 '21 at 19:20
  • 1
    Tried to use it. Somehow I cannot make it work. Getting error messages like `Cannot specify precompile headers for target "xxx," which is not built by this project`. Most probably because of the CMakeLists.txt being included elsewhere. Maybe someone has an example how to use this in a multi-project environment? – Joerg S Oct 20 '21 at 11:18
  • 2
    @JoergS the CMake documentation states: "The named must have been created by a command such as add_executable() or add_library() and must not be an ALIAS target". https://cmake.org/cmake/help/latest/command/target_precompile_headers.html Just add your 'target_precompile_headers(xxx ...) AFTER your 'add_library(xxx ...)' or ' add_executable(xxx ...)' – Gediminas Jul 17 '22 at 11:11
34

Im using the following macro to generate and use precompiled headers:

MACRO(ADD_MSVC_PRECOMPILED_HEADER PrecompiledHeader PrecompiledSource SourcesVar)
  IF(MSVC)
    GET_FILENAME_COMPONENT(PrecompiledBasename ${PrecompiledHeader} NAME_WE)
    SET(PrecompiledBinary "${CMAKE_CURRENT_BINARY_DIR}/${PrecompiledBasename}.pch")
    SET(Sources ${${SourcesVar}})

    SET_SOURCE_FILES_PROPERTIES(${PrecompiledSource}
                                PROPERTIES COMPILE_FLAGS "/Yc\"${PrecompiledHeader}\" /Fp\"${PrecompiledBinary}\""
                                           OBJECT_OUTPUTS "${PrecompiledBinary}")
    SET_SOURCE_FILES_PROPERTIES(${Sources}
                                PROPERTIES COMPILE_FLAGS "/Yu\"${PrecompiledHeader}\" /FI\"${PrecompiledHeader}\" /Fp\"${PrecompiledBinary}\""
                                           OBJECT_DEPENDS "${PrecompiledBinary}")  
    # Add precompiled header to SourcesVar
    LIST(APPEND ${SourcesVar} ${PrecompiledSource})
  ENDIF(MSVC)
ENDMACRO(ADD_MSVC_PRECOMPILED_HEADER)

Lets say you have a variable ${MySources} with all your sourcefiles, the code you would want to use would be simply be

ADD_MSVC_PRECOMPILED_HEADER("precompiled.h" "precompiled.cpp" MySources)
ADD_LIBRARY(MyLibrary ${MySources})

The code would still function just fine on non-MSVC platforms too. Pretty neat :)

user75810
  • 829
  • 7
  • 14
larsmoa
  • 12,604
  • 8
  • 62
  • 85
  • 2
    This macro has 1 flaw. If the generator isn't MSVC-based, the precompiled source will not be added to the list of sources. So my modification simply moves the `list( APPEND ... )` outside the closing `endif()`. See complete code here: http://pastebin.com/84dm5rXZ – void.pointer Oct 30 '11 at 19:05
  • 1
    @RobertDailey: This is actually deliberate - I do not want to compile the precompiled source file when not using precompiled headers - it should not define any symbols anyways. – larsmoa Oct 31 '11 at 06:54
  • 1
    @Iarsam Please correct the `/Yu` and `/FI` arguments, they should be `${PrecompiledHeader}` and not `${PrecompiledBinary}`. – Mourad Jun 28 '13 at 13:34
  • can you explain why we need "/Fp" and "/FI" flags? According to https://msdn.microsoft.com/en-us/library/z0atkd6c.aspx use of "/Fp" is not mandatory. However, if I cut out those flags from your macro no pch is set. – Vram Vardanian Apr 14 '15 at 15:56
  • 2
    Be aware though that the argument to /Yu is taken very literally. E.g. `/YuC:/foo/bar.h` will force you to either pass the `/FpC:/foo/bar.h` flag or put `#include ` at the top of all of your .cpp files as the first include statement. MSVC does a string compare of the `#include` arguments, it does not check whether it points to the same file as what was given to `/Yu`. Ergo, `#include ` will not work and emit error C2857. – Manuzor Sep 19 '15 at 12:05
20

Here is a code snippet to allow you to use precompiled header for your project. Add the following to your CMakeLists.txt replacing myprecompiledheaders and myproject_SOURCE_FILES as appropriate:

if (MSVC)

    set_source_files_properties(myprecompiledheaders.cpp
        PROPERTIES
        COMPILE_FLAGS "/Ycmyprecompiledheaders.h"
        )
    foreach( src_file ${myproject_SOURCE_FILES} )
        set_source_files_properties(
            ${src_file}
            PROPERTIES
            COMPILE_FLAGS "/Yumyprecompiledheaders.h"
            )
    endforeach( src_file ${myproject_SOURCE_FILES} )
    list(APPEND myproject_SOURCE_FILES myprecompiledheaders.cpp)
endif (MSVC)
Dave Hillier
  • 18,105
  • 9
  • 43
  • 87
  • @Jayen, Nope; I eventually dropped the project and never got into the hassles of C++ again, basically. – strager Nov 21 '10 at 07:11
  • Is it possible to set PCH to whole project? Because it is not possible to get list of autogenerated files in cmake `with set( CMAKE_AUTOMOC ON )`. – Dmitry Sazonov Mar 19 '14 at 13:54
  • I've used your solution, but unfortunately compilation time got from 2'10" to 2'40", for ~130 files. Do I have to make sure that `myprecompiledheader.cpp` is compiled first? From this snippet it looks like it'll be compiled last, so perhaps that's what might be causing the delay. `myprecompiledheader.h` contains only the most common STL headers that my code uses. – Grim Fandango Aug 05 '14 at 21:02
13

I ended up using an adapted version of larsm macro. Using $(IntDir) for pch path keeps precompiled headers for debug and release builds separate.

MACRO(ADD_MSVC_PRECOMPILED_HEADER PrecompiledHeader PrecompiledSource SourcesVar)
  IF(MSVC)
    GET_FILENAME_COMPONENT(PrecompiledBasename ${PrecompiledHeader} NAME_WE)
    SET(PrecompiledBinary "$(IntDir)/${PrecompiledBasename}.pch")
    SET(Sources ${${SourcesVar}})

    SET_SOURCE_FILES_PROPERTIES(${PrecompiledSource}
                                PROPERTIES COMPILE_FLAGS "/Yc\"${PrecompiledHeader}\" /Fp\"${PrecompiledBinary}\""
                                           OBJECT_OUTPUTS "${PrecompiledBinary}")
    SET_SOURCE_FILES_PROPERTIES(${Sources}
                                PROPERTIES COMPILE_FLAGS "/Yu\"${PrecompiledHeader}\" /FI\"${PrecompiledHeader}\" /Fp\"${PrecompiledBinary}\""
                                           OBJECT_DEPENDS "${PrecompiledBinary}")  
    # Add precompiled header to SourcesVar
    LIST(APPEND ${SourcesVar} ${PrecompiledSource})
  ENDIF(MSVC)
ENDMACRO(ADD_MSVC_PRECOMPILED_HEADER)

ADD_MSVC_PRECOMPILED_HEADER("stdafx.h" "stdafx.cpp" MY_SRCS)
ADD_EXECUTABLE(MyApp ${MY_SRCS})
jari
  • 139
  • 1
  • 2
12

Adapted from Dave, but more efficient (sets target properties, not for each file):

if (MSVC)
   set_target_properties(abc PROPERTIES COMPILE_FLAGS "/Yustd.h")
   set_source_files_properties(std.cpp PROPERTIES COMPILE_FLAGS "/Ycstd.h")
endif(MSVC)
martjno
  • 4,589
  • 5
  • 34
  • 31
  • 2
    I used to use this solution, but it only works if the project only contains c++ files. Since COMPILE_FLAGS is applied to all source files, it will also be applied to c files (e.g. the ones generated by MIDL), which won't like the c++ PCH. When using Dave's solution, you can use get_source_file_property(_language ${src_file} LANGUAGE), and only set the compiler flags if it's really a CXX file. – Andreas Haferburg Feb 16 '12 at 09:36
  • Nice to have the flexibility of the other solution in my back pocket, but this is the one I was looking for, thanks! – kylewm Feb 28 '12 at 21:58
  • Nice answer. Beware of the missing parenthesis for set_source_files_properties. – Arnaud Jan 12 '14 at 21:42
  • 2
    It can be selectively turned off for individual files with [/Y-](http://msdn.microsoft.com/en-us/library/1hy7a92h.aspx) using [set_source_files_properties](http://www.cmake.org/cmake/help/v2.8.12/cmake.html#command:set_source_files_properties) – mlt Feb 15 '14 at 07:04
  • What is `abc` in your example? – Sandburg Mar 05 '19 at 14:31
  • abc is the target project where to set the flags – martjno Mar 06 '19 at 03:51
7

if you don't wanna reinvent the wheel, just use either Cotire as the top answer suggests or a simpler one - cmake-precompiled-header here. To use it just include the module and call:

include( cmake-precompiled-header/PrecompiledHeader.cmake )
add_precompiled_header( targetName StdAfx.h FORCEINCLUDE SOURCE_CXX StdAfx.cpp )
Roman Kruglov
  • 3,375
  • 2
  • 40
  • 46
4

An example of usage precompiled header with cmake and Visual Studio 2015

"stdafx.h", "stdafx.cpp" - precompiled header name.

Put the following below in the root cmake file.

if (MSVC)
    # For precompiled header.
    # Set 
    # "Precompiled Header" to "Use (/Yu)"
    # "Precompiled Header File" to "stdafx.h"
    set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Yustdafx.h /FIstdafx.h")
endif()

Put the following below in the project cmake file.

"src" - a folder with source files.

set_source_files_properties(src/stdafx.cpp
    PROPERTIES
    COMPILE_FLAGS "/Ycstdafx.h"
)
Maks
  • 2,808
  • 4
  • 30
  • 31
3

IMHO the best way is to set PCH for whole project, as martjno suggested, combined with ability of ignoring PCH for some sources if needed (e.g. generated sources):

# set PCH for VS project
function(SET_TARGET_PRECOMPILED_HEADER Target PrecompiledHeader PrecompiledSource)
  if(MSVC)
     SET_TARGET_PROPERTIES(${Target} PROPERTIES COMPILE_FLAGS "/Yu${PrecompiledHeader}")
     set_source_files_properties(${PrecompiledSource} PROPERTIES COMPILE_FLAGS "/Yc${PrecompiledHeader}")
  endif(MSVC)
endfunction(SET_TARGET_PRECOMPILED_HEADER)

# ignore PCH for a specified list of files
function(IGNORE_PRECOMPILED_HEADER SourcesVar)
  if(MSVC)  
    set_source_files_properties(${${SourcesVar}} PROPERTIES COMPILE_FLAGS "/Y-")
  endif(MSVC)
endfunction(IGNORE_PRECOMPILED_HEADER)

So, if you have some target MY_TARGET, and list of generated sources IGNORE_PCH_SRC_LIST you'll simply do:

SET_TARGET_PRECOMPILED_HEADER(MY_TARGET stdafx.h stdafx.cpp)
IGNORE_PRECOMPILED_HEADER(IGNORE_PCH_SRC_LIST)

This aproach is tested and works perfectly.

Vram Vardanian
  • 539
  • 1
  • 5
  • 15
0

The cleanest way is to add the precompiled option as a global option. In the vcxproj file this will show up as <PrecompiledHeader>Use</PrecompiledHeader> and not do this for every individual file.

Then you need to add the Create option to the StdAfx.cpp. The following is how I use it:

MACRO(ADD_MSVC_PRECOMPILED_HEADER SourcesVar)
    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /YuStdAfx.h")
    set_source_files_properties(StdAfx.cpp
        PROPERTIES
        COMPILE_FLAGS "/YcStdAfx.h"
        )
    list(APPEND ${${SourcesVar}} StdAfx.cpp)
ENDMACRO(ADD_MSVC_PRECOMPILED_HEADER)

file(GLOB_RECURSE MYDLL_SRC
    "*.h"
    "*.cpp"
    "*.rc")

ADD_MSVC_PRECOMPILED_HEADER(MYDLL_SRC)
add_library(MyDll SHARED ${MYDLL_SRC})

This is tested and works for MSVC 2010 and will create a MyDll.pch file, I am not bothered what file name is used so I didn't make any effort to specify it.

uncletall
  • 6,609
  • 1
  • 27
  • 52
0

As the precompiled header option doesnt work for rc files, i needed to adjust the macro supplied by jari.

#######################################################################
# Makro for precompiled header
#######################################################################
MACRO(ADD_MSVC_PRECOMPILED_HEADER PrecompiledHeader PrecompiledSource SourcesVar)
  IF(MSVC)
    GET_FILENAME_COMPONENT(PrecompiledBasename ${PrecompiledHeader} NAME_WE)
    SET(PrecompiledBinary "$(IntDir)/${PrecompiledBasename}.pch")
    SET(Sources ${${SourcesVar}})

    # generate the precompiled header
    SET_SOURCE_FILES_PROPERTIES(${PrecompiledSource}
                                PROPERTIES COMPILE_FLAGS "/Zm500 /Yc\"${PrecompiledHeader}\" /Fp\"${PrecompiledBinary}\""
                                            OBJECT_OUTPUTS "${PrecompiledBinary}")

    # set the usage of this header only to the other files than rc
    FOREACH(fname ${Sources})
        IF ( NOT ${fname} MATCHES ".*rc$" )
            SET_SOURCE_FILES_PROPERTIES(${fname}
                                        PROPERTIES COMPILE_FLAGS "/Zm500 /Yu\"${PrecompiledHeader}\" /FI\"${PrecompiledHeader}\" /Fp\"${PrecompiledBinary}\""
                                                    OBJECT_DEPENDS "${PrecompiledBinary}")
        ENDIF( NOT ${fname} MATCHES ".*rc$" )
    ENDFOREACH(fname)

    # Add precompiled header to SourcesVar
    LIST(APPEND ${SourcesVar} ${PrecompiledSource})
  ENDIF(MSVC)
ENDMACRO(ADD_MSVC_PRECOMPILED_HEADER)

Edit: The usage of this precompiled headers reduced the Overall build time of my main Project from 4min 30s down to 1min 40s. This is for me a really good thing. In the precompile header are only headers like boost/stl/Windows/mfc.

schorsch_76
  • 794
  • 5
  • 19
0

Well when builds take 10+ minutes on a quad core machine every time you change a single line in any of the project files it tells you its time to add precompiled headers for windows. On *nux I would just use ccache and not worry about that.

I have implemented in my main application and a few of the libraries that it uses. It works great to this point. One thing that also is needed is you have to create the pch source and header file and in the source file include all the headers that you want to be precompiled. I did this for 12 years with MFC but it took me a few minutes to recall that..

-18

Don't even go there. Precompiled headers mean that whenever one of the headers changes, you have to rebuild everything. You're lucky if you have a build system that realizes this. More often than never, your build will just fail until you realize that you changed something that is being precompiled, and therefore you need to do a full rebuild. You can avoid this mostly by precompiling the headers that you are absolutely positive won't change, but then you're giving up a large part of the speed gain as well.

The other problem is that your namespace gets polluted with all kinds of symbols that you don't know or care about in many places where you'd be using the precompiled headers.

Dirk Groeneveld
  • 2,547
  • 2
  • 22
  • 23
  • 32
    Precompiled headers are most useful when they're referencing headers that *don't* change... STL, Boost, other third-party stuff. If you're using PCH for your own project header files, you're wasting most of the benefit. – Tom Mar 15 '09 at 15:47
  • 4
    Even if you're using PCH for your own project's headers, the whole point of a build system like CMake is to make sure that the dependencies *are* respected. If I change my .h file (or one of its dependencies), I *want* to have the .pch regenerated. If I don't change my .h file, I *don't* want to have the .pch regenerated. I think the OP's whole question was: How do I get this to happen, using CMake? – Quuxplusone May 29 '14 at 23:36
  • 2
    Precompiled headers are the best tool to reduce compile times until C++ modules are supported by all mainstream compilers. They solve an issue that just got worse with ever-increasing use of templates and header-only libraries. When used properly, there is no substitute. Regardless, this does not constitute an answer to the question being asked, and merely voices opinion. Down- and delete-voted. – IInspectable Jan 15 '18 at 21:02