125

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?

12 Answers 12

61

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/

Sign up to request clarification or add additional context in comments.

5 Comments

Here is the link to official CMake documentation: cmake.org/cmake/help/latest/command/…
There's a post from MSVC team on figuring out which headers to include in PCH: devblogs.microsoft.com/cppblog/…
For Obj-C and/or Obj-C++ languages, use syntax like target_precompile_headers(MyTarget PUBLIC "$<$<COMPILE_LANGUAGE:OBJC,OBJCXX>:${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).
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?
@JoergS the CMake documentation states: "The named <target> must have been created by a command such as add_executable() or add_library() and must not be an ALIAS target". cmake.org/cmake/help/latest/command/… Just add your 'target_precompile_headers(xxx ...) AFTER your 'add_library(xxx ...)' or ' add_executable(xxx ...)'
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 :)

5 Comments

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: pastebin.com/84dm5rXZ
@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.
@Iarsam Please correct the /Yu and /FI arguments, they should be ${PrecompiledHeader} and not ${PrecompiledBinary}.
can you explain why we need "/Fp" and "/FI" flags? According to 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.
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 <C:/foo/bar.h> 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 <bar.h> will not work and emit error C2857.
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)

2 Comments

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 ).
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.
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})

Comments

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)

6 Comments

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.
Nice to have the flexibility of the other solution in my back pocket, but this is the one I was looking for, thanks!
Nice answer. Beware of the missing parenthesis for set_source_files_properties.
It can be selectively turned off for individual files with /Y- using set_source_files_properties
What is abc in your example?
|
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 )

Comments

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"
)

Comments

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.

Comments

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..

Comments

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.

Comments

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.

Comments

-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.

3 Comments

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.
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?
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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.