使用CMake与预编译头文件

121

我在网上看到了一些有关在CMake中合并预编译头文件的方法的旧帖子。它们看起来都比较混乱,每个人都有自己的做法。目前最好的方法是什么?

13个回答

82

1
我创建了一组宏,包装了cotire功能(预编译头文件和统一构建)在此处,以便更轻松地使用。 - onqtam
2
@onqtam,这怎么更容易了呢?它太庞大了,我甚至不知道如何使用cmake和gcc简单地获取预编译工作。 - Pavel P
7
CMake 3.16 引入了对预编译头的内置支持。请查看我的回答 https://stackoverflow.com/a/59514029/2799037,不再需要第三方模块了! - usr1234567

57

CMake现已支持PCH(预编译头文件),从2019年10月发布的3.16版本开始可用:

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/中找到。


4
这是官方CMake文档的链接:https://cmake.org/cmake/help/latest/command/target_precompile_headers.html。 - Alex Che
2
MSVC团队发布了一篇关于如何确定PCH中应包含哪些头文件的文章:https://devblogs.microsoft.com/cppblog/faster-builds-with-pch-suggestions-from-c-build-insights/ - janisozaur
对于 Obj-C 和/或 Obj-C++ 语言,使用以下语法:target_precompile_headers(MyTarget PUBLIC "$<$<COMPILE_LANGUAGE:OBJC,OBJCXX>:${CMAKE_CURRENT_LIST_DIR}/src/MyPrefixHeader.pch>") - 如果您的头文件不支持 C/C++,或者只需添加支持(通过在任何 #import 中包含 #ifdef __OBJC__ 检查)。 - Top-Master
1
尝试过使用它,但不知何故无法使其工作。收到错误消息,如Cannot specify precompile headers for target "xxx," which is not built by this project。很可能是由于CMakeLists.txt被包含在其他地方。也许有人有一个在多项目环境下使用它的例子? - Joerg S
2
@JoergS,CMake文档指出:“命名的<target>必须是通过add_executable()或add_library()等命令创建的,不能是ALIAS目标。”https://cmake.org/cmake/help/latest/command/target_precompile_headers.html。只需在“add_library(xxx ...)”或“add_executable(xxx ...)”之后添加“target_precompile_headers(xxx...)”。 - Gediminas

34

我正在使用以下宏来生成和使用预编译头文件:

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平台上,该代码仍会正常运行。相当不错 :)


2
这个宏有一个缺陷。如果生成器不是基于MSVC的,则预编译源代码将不会添加到源代码列表中。因此,我的修改只是将 list( APPEND ... ) 移到了关闭的 endif() 外面。在这里查看完整代码:http://pastebin.com/84dm5rXZ - void.pointer
1
@RobertDailey:这实际上是有意为之的——当不使用预编译头文件时,我不想编译预编译的源文件——它不应该定义任何符号。 - larsmoa
1
@Iarsam,请修正/Yu/FI参数,它们应该是${PrecompiledHeader}而不是${PrecompiledBinary} - Mourad
你能解释一下为什么我们需要“/Fp”和“/FI”标志吗?根据https://msdn.microsoft.com/en-us/library/z0atkd6c.aspx,使用“/Fp”不是强制性的。然而,如果我从你的宏中删除这些标志,就不会设置pch。 - Vram Vardanian
2
请注意,/Yu参数的含义非常字面。例如,/YuC:/foo/bar.h将强制您传递/FpC:/foo/bar.h标志或在所有.cpp文件的顶部作为第一个包含语句放置#include <C:/foo/bar.h>。MSVC对#include参数进行字符串比较,而不是检查它是否指向与/Yu给出的相同文件。因此,#include <bar.h>将无法工作并发出错误C2857。 - Manuzor

20

以下是一段代码片段,可以让你为你的项目使用预编译头文件。 将以下内容添加到你的CMakeLists.txt文件中,根据需要替换myprecompiledheadersmyproject_SOURCE_FILES

add_library(myprecompiledheaders OBJECT myheader1.h myheader2.h)
set_source_files_properties(${myproject_SOURCE_FILES} PROPERTIES COMPILE_FLAGS "/Yu"myheader1.h"")
target_compile_options(myprecompiledheaders PRIVATE /Yc"myheader1.h")
target_link_libraries(myproject PUBLIC myprecompiledheaders)
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)

@Jayen,不,我最终放弃了这个项目,再也没有涉及C++的麻烦了。 - strager
是否可以将PCH设置为整个项目?因为在使用set(CMAKE_AUTOMOC ON)时无法获取自动生成文件的列表。 - Dmitry Sazonov
我使用了你的解决方案,但不幸的是编译时间从2'10"增加到了2'40",大约有130个文件。我需要确保myprecompiledheader.cpp首先被编译吗?从这段代码片段来看,它似乎会最后被编译,所以这可能是导致延迟的原因。myprecompiledheader.h仅包含我的代码使用的最常见的STL头文件。 - Grim Fandango

13

我最终使用了经过改编的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})

12

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

2
我曾经使用过这个解决方案,但它只适用于项目只包含c++文件的情况。由于COMPILE_FLAGS应用于所有源文件,它也将应用于c文件(例如MIDL生成的文件),这些文件不支持c++ PCH。当使用Dave的解决方案时,您可以使用get_source_file_property(_language ${src_file} LANGUAGE)来获取语言类型,并且仅在确实是CXX文件时设置编译器标志。 - Andreas Haferburg
很高兴拥有其他解决方案的灵活性作为备选,但这正是我寻找的那一个,谢谢! - kylewm
不错的回答。注意 set_source_files_properties 中可能缺少括号。 - Arnaud
2
可以使用[/Y-](http://msdn.microsoft.com/en-us/library/1hy7a92h.aspx)和[set_source_files_properties](http://www.cmake.org/cmake/help/v2.8.12/cmake.html#command:set_source_files_properties)选择性地关闭单个文件。 - mlt
在你的例子中,abc是什么? - Sandburg
abc 是目标项目,需要设置标志。 - martjno

7
如果你不想重复造轮子,可以像前面的回答建议的那样使用Cotire或者更简单的一个叫做 cmake-precompiled-header 的工具,它在这里:https://github.com/larsch/cmake-precompiled-header。只需包含该模块并调用以下命令即可使用:
include( cmake-precompiled-header/PrecompiledHeader.cmake )
add_precompiled_header( targetName StdAfx.h FORCEINCLUDE SOURCE_CXX StdAfx.cpp )

4

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

3

我认为最好的方法是为整个项目设置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)

这种方法经过测试,完美运作。

0

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接