【问题标题】:Using pre-compiled headers with CMake在 CMake 中使用预编译的头文件
【发布时间】:2010-09-13 23:55:15
【问题描述】:

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

【问题讨论】:

    标签: c++ visual-studio gcc cmake precompiled-headers


    【解决方案1】:

    有一个third party CMake module named 'Cotire' 可以自动为基于 CMake 的构建系统使用预编译头文件,并且还支持统一构建。

    【讨论】:

    • 我创建了一组宏来包装 cotire 功能(预编译头文件和统一构建)here 以便于使用
    • @onqtam 怎么这么简单,它太大了,我什至看不到如何简单地使用 cmake 和 gcc 进行预编译
    • CMake 3.16 引入了对预编译头文件的内置支持。看我的回答stackoverflow.com/a/59514029/2799037 不再需要第三方模块了!
    【解决方案2】:

    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/ 上提供了一些额外的背景信息(动机、数字)

    【讨论】:

    • 这里是官方 CMake 文档的链接:cmake.org/cmake/help/latest/command/…
    • MSVC 团队发布了一篇关于确定哪些标头包含在 PCH 中的帖子:devblogs.microsoft.com/cppblog/…
    • 对于 Obj-C 和/或 Obj-C++ 语言,请使用 target_precompile_headers(MyTarget PUBLIC "$&lt;$&lt;COMPILE_LANGUAGE:OBJC,OBJCXX&gt;:${CMAKE_CURRENT_LIST_DIR}/src/MyPrefixHeader.pch&gt;") 之类的语法 - 如果您的标头不支持 C/C++,或者只是添加支持(通过将任何 #import 包装在 #ifdef __OBJC__ 检查中)。
    • 尝试使用它。不知怎的,我无法让它工作。收到类似Cannot specify precompile headers for target "xxx," which is not built by this project 的错误消息。很可能是因为 CMakeLists.txt 包含在其他地方。也许有人有一个如何在多项目环境中使用它的例子?
    【解决方案3】:

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

    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 平台上仍然可以正常运行。很整洁:)

    【讨论】:

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

    这里有一个代码 sn-p 允许您为您的项目使用预编译头文件。 将以下内容添加到您的 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)
    

    【讨论】:

    • @Jayen,不;我最终放弃了这个项目,基本上再也没有遇到过 C++ 的麻烦。
    • 是否可以将 PCH 设置为整个项目?因为无法在 cmake with set( CMAKE_AUTOMOC ON ) 中获取自动生成的文件列表。
    • 我使用了你的解决方案,但不幸的是编译时间从 2'10" 到 2'40",大约 130 个文件。我是否必须确保首先编译 myprecompiledheader.cpp?从这个 sn-p 看起来它将最后编译,所以也许这可能是导致延迟的原因。 myprecompiledheader.h 仅包含我的代码使用的最常见的 STL 标头。
    【解决方案5】:

    我最终使用了 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})
    

    【讨论】:

      【解决方案6】:

      改编自 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)
      

      【讨论】:

      • 我曾经使用过这个解决方案,但它只有在项目只包含 c++ 文件时才有效。由于 COMPILE_FLAGS 应用于所有源文件,因此它也将应用于 c 文件(例如 MIDL 生成的文件),这与 c++ PCH 不同。使用 Dave 的解决方案时,您可以使用 get_source_file_property(_language ${src_file} LANGUAGE),并且仅在它确实是 CXX 文件时设置编译器标志。
      • 很高兴在我的后兜里拥有其他解决方案的灵活性,但这是我一直在寻找的,谢谢!
      • 不错的答案。注意 set_source_files_properties 缺少括号。
      • 可以通过/Y-使用set_source_files_properties对单个文件选择性关闭
      • 在您的示例中abc 是什么?
      【解决方案7】:

      如果您不想重新发明轮子,只需按照最佳答案的建议使用 Cotire 或更简单的 - cmake-precompiled-header here。要使用它,只需包含模块并调用:

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

      【讨论】:

        【解决方案8】:

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

        【讨论】:

          【解决方案9】:

          恕我直言,最好的方法是按照 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)
          

          这种方法已经过测试并且效果很好。

          【讨论】:

            【解决方案10】:

            好吧,当您每次更改任何项目文件中的一行时,在四核机器上构建需要 10 多分钟,它会告诉您是时候为 Windows 添加预编译头文件了。在 *nux 上,我只会使用 ccache 而不必担心。

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

            【讨论】:

              【解决方案11】:

              最简洁的方法是将预编译选项添加为全局选项。在 vcxproj 文件中,这将显示为 &lt;PrecompiledHeader&gt;Use&lt;/PrecompiledHeader&gt;,而不是对每个单独的文件都执行此操作。

              然后您需要将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 文件,我不介意使用什么文件名,所以我没有努力指定它。

              【讨论】:

                【解决方案12】:

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

                【讨论】:

                  【解决方案13】:

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

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

                  【讨论】:

                  • 预编译的标头在引用更改的标头时最有用... STL、Boost、其他第三方的东西。如果您将 PCH 用于您自己的项目头文件,那么您就浪费了大部分好处。
                  • 即使您将 PCH 用于您自己项目的标头,像 CMake 这样的构建系统的全部意义在于确保依赖关系受到的尊重。如果我更改我的 .h 文件(或其依赖项之一),我想要重新生成 .pch。如果我不更改我的 .h 文件,我希望重新生成 .pch。我认为 OP 的整个问题是:如何使用 CMake 实现这一目标?
                  • 预编译头文件是减少编译时间的最佳工具,直到所有主流编译器都支持 C++ 模块。他们解决了一个随着模板和仅标头库的使用越来越多而变得更糟的问题。如果使用得当,没有替代品。无论如何,这并不构成对所提出问题的回答,而只是表达意见。被否决和删除投票。
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2014-02-23
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2020-08-16
                  • 2012-07-09
                  • 1970-01-01
                  相关资源
                  最近更新 更多