将预编译的头与CMake一起使用

Using pre-compiled headers with CMake

本文关键字:CMake 一起 编译      更新时间:2023-10-16

我在网上看到了一些(旧的)帖子,关于在CMake中破解对预编译头的一些支持。他们似乎到处都是,每个人都有自己的方式。目前最好的方式是什么?

CMake刚刚获得了对PCH(预编译头)的支持,从3.16(2019年10月发布)起提供:

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

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

通过REUSE_FROM关键字(例如此处)支持在目标之间共享PCH。

在https://blog.qt.io/blog/2019/08/01/precompiled-headers-and-unity-jumbo-builds-in-upcoming-cmake/

Im使用以下宏生成并使用预编译头:

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)

假设你有一个变量${MySources}和你所有的源文件,你想使用的代码只是

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

该代码在非MSVC平台上仍然可以正常运行。非常整洁:)

这里有一个代码片段,允许您在项目中使用预编译头。将以下内容添加到您的CMakeLists.txt中,适当替换myprecompiledheadersmyproject_SOURCE_FILES

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)

我最终使用了一个经过调整的larsm宏版本。使用$(IntDir)作为pch路径可以使调试和发布版本的预编译头保持分离。

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

改编自Dave,但更高效(设置目标属性,而不是针对每个文件):

if (MSVC)
   set_target_properties(abc PROPERTIES COMPILE_FLAGS "/Yustd.h")
   set_source_files_properties(std.cpp PROPERTIES COMPILE_FLAGS "/Ycstd.h")
endif(MSVC)

如果你不想重新发明轮子,只需使用Cotire作为最重要的答案,或者使用更简单的答案-cmake预编译头。要使用它,只需包含模块并调用:

include( cmake-precompiled-header/PrecompiledHeader.cmake )
add_precompiled_header( targetName StdAfx.h FORCEINCLUDE SOURCE_CXX StdAfx.cpp )

cmake和Visual Studio 2015使用预编译头的示例

"stdafx.h","stdafx.cpp"-预编译头名称。

将以下内容放入根cmake文件中。

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

将以下内容放入项目cmake文件中。

"src"-包含源文件的文件夹。

set_source_files_properties(src/stdafx.cpp
    PROPERTIES
    COMPILE_FLAGS "/Ycstdafx.h"
)

IMHO最好的方法是为整个项目设置PCH,正如martjno所建议的那样,结合在需要时忽略某些源(例如生成的源)的PCH的能力:

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

因此,如果您有一些目标MY_target和生成的源列表IGNORE_PCH_SRC_list,您只需执行以下操作:

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

这个方法经过测试,效果很好。

当构建在四核机器上花费10分钟以上时,每次您更改任何项目文件中的一行,它都会告诉您为窗口添加预编译头的时间。在*nux上,我只会使用ccache,而不用担心。

我已经在我的主应用程序和它使用的一些库中实现了。在这一点上效果很好。还需要做的一件事是,您必须创建pch源文件和头文件,并且在源文件中包括所有要预编译的头。我在MFC做了12年,但我花了几分钟才回忆起来。。

最干净的方法是将预编译选项添加为全局选项。在vcxproj文件中,它将显示为<PrecompiledHeader>Use</PrecompiledHeader>,而不是对每个单独的文件都这样做。

然后您需要将Create选项添加到StdAfx.cpp中

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

这是经过测试的,适用于MSVC 2010,并将创建一个MyDll.pch文件,我不介意使用什么文件名,所以我没有做任何努力来指定它。

由于预编译头选项不适用于rc文件,我需要调整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)

编辑:使用这个预编译的头文件将我的主要项目的总体构建时间从4分钟30秒减少到1分钟40秒。这对我来说是一件非常好的事情。预编译头中只有boost/stl/Windows/mfc这样的头。

甚至不要去那里。预编译的标头意味着,只要其中一个标头发生更改,就必须重新生成所有。如果你有一个实现这一点的构建系统,那你就很幸运了。通常情况下,您的构建会失败,直到您意识到您更改了正在预编译的内容,因此需要进行完全的重建。你可以避免这种情况,主要是通过预编译你绝对肯定不会改变的头,但你也会放弃很大一部分速度增益。

另一个问题是,在使用预编译头的许多地方,你的命名空间会被各种你不知道或不关心的符号污染。