Using pre-compiled headers with CMake

我在‘ net 上看到过一些(旧的)帖子,内容是关于将一些对 CMake 中预编译头文件的支持整合在一起。他们似乎都有点到处都是,每个人都有自己的做事方式。目前最好的方法是什么?

85152 次浏览

想都别想。预编译标头意味着无论何时一个标头发生更改,您都必须重新构建 一切。如果您有一个能够实现这一点的构建系统,那么您就很幸运了。通常情况下,您的构建将会失败,直到您意识到您更改了正在预编译的内容,因此您需要进行完整的重新构建。你可以通过预编译那些你绝对肯定不会改变的标题来避免这种情况,但是你也放弃了很大一部分的速度增益。

另一个问题是,在许多使用预编译头文件的地方,名称空间会受到各种符号的污染,而这些符号是您不知道或不关心的。

Here is a code snippet to allow you to use precompiled header for your project. 在 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)

当构建在一个四核心机器上需要10多分钟时,每次你在任何一个项目文件中更改一行,它都会告诉你添加预编译的窗口头文件的时间。在 * nux 上,我会直接使用 ccache,而不用担心这个问题。

我已经在我的主应用程序和它所使用的一些库中实现了。到目前为止效果很好。还需要创建 pch 源文件和头文件,并在源文件中包含所有需要预编译的头文件。我在 MFC 中做了12年,但我花了几分钟才想起来。.

我使用以下宏来生成和使用预编译头:

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)

假设您有一个包含所有源文件的变量 ${ MySource } ,那么您希望使用的代码就是

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

这些代码在非 MSVC 平台上也能正常工作

我最终使用了一个改编版的 Larsm 宏。对 pch 路径使用 $(IntDir)可以将调试版本和发布版本的预编译头分开。

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)

有一个 第三方 CMake 模块命名为“ Cotie”可以自动使用基于 CMake 的构建系统的预编译头,并且还支持统一构建。

最简单的方法是将预编译选项作为全局选项添加。在 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 文件,我不打扰使用什么文件名,所以我没有作出任何努力,指定它。

恕我直言,正如 martjno 建议的那样,最好的方法是为整个项目设置 PCH,如果需要的话,结合忽略某些来源(例如生成的来源)的 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)

这种方法经过了测试,运行良好。

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

由于预编译头选项不适用于 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秒。这对我来说是件好事。在预编译头中,只有类似于 ost/stl/Windows/mfc 的头。

如果你不想重新发明车轮,只需要使用上面的答案建议或更简单的一个-cmake-precompiled-header 给你。要使用它,只需包含模块和调用:

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

CMake 刚刚获得了对 PCHs (预编译头文件)的支持,它可以从 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...] ...])

Sharing PCHs between targets is supported via the REUSE_FROM keyword 比如这里.

https://blog.qt.io/blog/2019/08/01/precompiled-headers-and-unity-jumbo-builds-in-upcoming-cmake/中还有一些额外的上下文(动机、数字)