在CMake中使用预编译的头文件


104

我在网上看到一些(旧的)帖子,内容涉及一起破解CMake中对预编译头的支持。他们似乎遍地都是,每个人都有自己的做事方式。目前最好的方法是什么?

Answers:


79

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


1
我创建了一组宏,这些宏包装了cotire功能(预编译的标头和统一构建)以便于使用
onqtam,2016年

2
@onqtam如此容易,它是如此庞大,以至于我什至都没有看到如何简单地使用cmake和gcc进行预编译
Pavel P

5
CMake 3.16引入了对预编译头文件的内置支持。请参阅我的答案stackoverflow.com/a/59514029/2799037不再需要第三方模块!
usr1234567 '19

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
该宏有1个缺陷。如果生成器不是基于MSVC的,则预编译的源将不会添加到源列表中。因此,我所做的修改只是将list( APPEND ... )外部移至结束位置endif()。在此处查看完整的代码:pastebin.com/84dm5rXZ
void.pointer 2011年

1
@RobertDailey:这实际上是有意的-当我不使用预编译头文件时,我不想编译预编译源文件-无论如何,它都不应定义任何符号。
larsmoa 2011年

1
@Iarsam请更正/Yu/FI参数,它们应该是,${PrecompiledHeader}而不是${PrecompiledBinary}
Mourad

您能解释为什么我们需要“ / Fp”和“ / FI”标志吗?根据msdn.microsoft.com/zh-cn/library/z0atkd6c.aspx的使用,“ / Fp”不是强制性的。但是,如果我从您的宏中删除了这些标志,则不会设置pch。
Vram Vardanian 2015年

2
请注意,尽管/ Yu的参数是字面意义上的。例如,/YuC:/foo/bar.h将迫使您通过/FpC:/foo/bar.h标志或将#include <C:/foo/bar.h>所有.cpp文件的顶部作为第一个include语句。MSVC对#include参数进行字符串比较,不检查它是否指向与指定文件相同的文件/Yu。Ergo,#include <bar.h>将无法正常工作并发出错误C2857。
Manuzor

21

CMake刚刚获得了对PCH的支持,它将在即将于2019年10月1日发布的3.16版本中提供:

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

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

关于支持在目标之间共享PCH的讨论正在进行中:https ://gitlab.kitware.com/cmake/cmake/issues/19659

https://blog.qt.io/blog/2019/08/01/precompiled-headers-and-unity-jumbo-builds-in-upcoming-cmake/中提供了一些其他上下文(动机,数字)


2
这里是链接到官方的CMake文件:cmake.org/cmake/help/latest/command/...
亚历澈

2
有从MSVC队后上搞清楚在PCH中包含哪些头:devblogs.microsoft.com/cppblog/...
janisozaur

19

这是一个代码段,可让您为项目使用预编译的标头。将以下内容添加到您的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)

谢谢; 我将以此为指导为GCC构建一个(如果可以的话)。完成后,我会尽快发布答案。=]
斯特拉格

@Jayen,不,最终,我最终放弃了该项目,基本上再也没有陷入C ++的麻烦。
斯特拉格

是否可以将PCH设置为整个项目?因为不可能在cmake中获取自动生成的文件列表with set( CMAKE_AUTOMOC ON )
德米特里·索佐诺夫

我已经使用了您的解决方案,但是很遗憾,编译时间从2'10“到2'40”,大约需要130个文件。我必须确保myprecompiledheader.cpp先编译吗?从该代码段来看,它似乎将在最后编译,所以也许这就是造成延迟的原因。myprecompiledheader.h仅包含我的代码使用的最常见的STL标头。
Grim Fandango 2014年

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

2
$ {IntDir}抽象很有帮助。谢谢。
Gopalakrishna Palem 2014年

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文件时才设置编译器标志。
安德烈亚斯·哈弗堡

很高兴在我的后兜有其他解决方案的灵活性,但这是我一直在寻找的解决方案,谢谢!
kylewm

好答案。当心缺少set_source_files_properties的括号。
Arnaud 2014年

2
可以使用set_source_files_properties使用/ Y-有选择地关闭单个文件
mlt

什么是abc你的榜样?
桑德堡

7

如果您不想重新发明轮子,只需使用Cotire作为最佳答案,或者在此处使用一个更简单的cmake预编译标题 。要使用它,只需包含模块并调用:

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

好了,每当您在任何项目文件中更改一行时,在四核计算机上进行构建都需要10分钟以上的时间,它会告诉您添加Windows预编译头文件的时间。在* nux上,我只使用ccache而不用担心。

我已经在我的主应用程序及其使用的一些库中实现了。到目前为止,它的工作原理非常好。还需要做的一件事是,您必须创建pch源文件和头文件,并且在源文件中包括要预编译的所有头文件。我在MFC上做了12年,但是花了我几分钟的时间来回忆。


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文件。我不介意使用什么文件名,因此我没有做出任何努力来指定它。


0

由于预编译的头文件选项不适用于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)

编辑:使用此预编译的头文件将我的主项目的总体构建时间从4min 30s减少到1min 40s。对我来说,这确实是一件好事。在预编译头中,只有诸如boost / stl / Windows / mfc之类的头。


-15

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

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


29
当他们引用标头预编译头是最有用改变... STL,升压,其他第三方的东西。如果您将PCH用于自己的项目头文件,那么您将浪费大部分收益。
汤姆(Tom)2009年

3
即使您正在使用PCH为自己的项目的头文件,构建系统CMake的一样的整点是要确保依赖关系尊重。如果更改.h文件(或其依赖项之一),则希望重新生成.pch。如果我不更改.h文件,则不希望重新生成.pch。我认为OP的整个问题是:如何使用CMake做到这一点?
Quuxplusone

1
在所有主流编译器都支持C ++模块之前,预编译头文件是减少编译时间的最佳工具。他们解决了随着模板和仅标头库的使用不断增加而变得更加糟糕的问题。正确使用时,没有替代品。无论如何,这并不构成对所提问题的答案,仅表示意见。向下投票并删除投票。
IInspectable '18年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.