Usando cabeçalhos pré-compilados com CMake

104

Eu vi alguns posts (antigos) na 'net sobre hackear algum suporte para cabeçalhos pré-compilados no CMake. Todos parecem um pouco extravagantes e cada um tem sua própria maneira de fazer isso. Qual é a melhor maneira de fazer isso atualmente?

Glutinoso
fonte

Respostas:

79

Há um módulo CMake de terceiros chamado 'Cotire' que automatiza o uso de cabeçalhos pré-compilados para sistemas de compilação baseados em CMake e também oferece suporte a compilações de unidade.

sakra
fonte
1
Eu criei um conjunto de macros que envolvem a funcionalidade de cotire (cabeçalhos pré-compilados e builds de unidade) aqui para facilitar o uso
onqtam
2
@onqtam como é mais fácil, é tão gigante que eu nem vejo como simplesmente ser pré-compilado trabalhando com cmake e gcc
Pavel P
5
O CMake 3.16 introduziu suporte integrado para cabeçalhos pré-compilados. Veja minha resposta stackoverflow.com/a/59514029/2799037 Não há mais necessidade de módulos de terceiros!
usr1234567
34

Estou usando a seguinte macro para gerar e usar cabeçalhos pré-compilados:

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)

Digamos que você tenha uma variável $ {MySources} com todos os seus arquivos de origem, o código que deseja usar seria simplesmente

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

O código ainda funcionaria bem em plataformas não MSVC também. Muito arrumado :)

Larsmoa
fonte
2
Esta macro tem 1 falha. Se o gerador não for baseado em MSVC, a fonte pré-compilada não será adicionada à lista de fontes. Portanto, minha modificação simplesmente move a parte list( APPEND ... )externa do fechamento endif(). Veja o código completo aqui: pastebin.com/84dm5rXZ
void.pointer
1
@RobertDailey: Isso é realmente deliberado - eu não quero compilar o arquivo fonte pré-compilado quando não estiver usando cabeçalhos pré-compilados - ele não deve definir nenhum símbolo de qualquer maneira.
larsmoa,
1
@Iarsam Por favor, corrija os argumentos /Yue /FI, eles deveriam ser ${PrecompiledHeader}ou não ${PrecompiledBinary}.
Mourad
você pode explicar por que precisamos dos sinalizadores "/ Fp" e "/ FI"? De acordo com msdn.microsoft.com/en-us/library/z0atkd6c.aspx, o uso de "/ Fp" não é obrigatório. No entanto, se eu cortar esses sinalizadores de sua macro, nenhum pch será definido.
Vram Vardanian
2
Esteja ciente de que o argumento para / Yu é interpretado literalmente. Por exemplo, /YuC:/foo/bar.hirá forçá-lo a passar o /FpC:/foo/bar.hsinalizador ou colocar #include <C:/foo/bar.h>no topo de todos os seus arquivos .cpp como a primeira instrução de inclusão. O MSVC faz uma comparação de string dos #includeargumentos, não verifica se aponta para o mesmo arquivo que foi fornecido /Yu. Ergo, #include <bar.h>não funcionará e emitirá erro C2857.
Manuzor
21

O CMake acaba de ganhar suporte para PCHs, ele deve estar disponível na próxima versão 3.16, com vencimento em 01/10/2019:

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

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

Há uma discussão em andamento sobre o suporte de compartilhamento de PCHs entre destinos: https://gitlab.kitware.com/cmake/cmake/issues/19659

Há algum contexto adicional (motivação, números) disponível em https://blog.qt.io/blog/2019/08/01/precompiled-headers-and-unity-jumbo-builds-in-upcoming-cmake/

janisozaur
fonte
2
Aqui está o link para a documentação oficial do CMake: cmake.org/cmake/help/latest/command/…
Alex Che
2
Há uma postagem da equipe MSVC sobre como descobrir quais cabeçalhos incluir no PCH: devblogs.microsoft.com/cppblog/…
janisozaur
19

Aqui está um trecho de código para permitir que você use o cabeçalho pré-compilado para seu projeto. Adicione o seguinte ao seu CMakeLists.txt substituindo myprecompiledheaderse myproject_SOURCE_FILESconforme apropriado:

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)
Dave Hillier
fonte
Obrigado; Vou usar isso como um guia para construir um para o GCC (se puder). Postarei minha resposta assim que terminar. =]
strager
@Jayen, Nope; Acabei desistindo do projeto e nunca mais me envolvi com as complicações do C ++ novamente, basicamente.
strager
É possível definir o PCH para todo o projeto? Porque não é possível obter a lista de arquivos gerados automaticamente no cmake with set( CMAKE_AUTOMOC ON ).
Dmitry Sazonov
Eu usei sua solução, mas infelizmente o tempo de compilação foi de 2'10 "a 2'40", para aproximadamente 130 arquivos. Preciso ter certeza de que myprecompiledheader.cppfoi compilado primeiro? A partir desse snippet, parece que ele será compilado por último, então talvez seja isso que esteja causando o atraso. myprecompiledheader.hcontém apenas os cabeçalhos STL mais comuns que meu código usa.
Grim Fandango
13

Acabei usando uma versão adaptada do macro larsm. Usar $ (IntDir) para o caminho pch mantém os cabeçalhos pré-compilados para depuração e versões de lançamento separados.

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})
jarra
fonte
2
A abstração $ {IntDir} é útil. Obrigado.
Gopalakrishna Palem
12

Adaptado de Dave, mas mais eficiente (define propriedades de destino, não para cada arquivo):

if (MSVC)
   set_target_properties(abc PROPERTIES COMPILE_FLAGS "/Yustd.h")
   set_source_files_properties(std.cpp PROPERTIES COMPILE_FLAGS "/Ycstd.h")
endif(MSVC)
Martjno
fonte
2
Eu costumava usar essa solução, mas ela só funciona se o projeto contiver apenas arquivos c ++. Visto que COMPILE_FLAGS é aplicado a todos os arquivos fonte, ele também será aplicado a arquivos c (por exemplo, aqueles gerados por MIDL), que não irão gostar do c ++ PCH. Ao usar a solução de Dave, você pode usar get_source_file_property (_language $ {src_file} LANGUAGE) e apenas definir os sinalizadores do compilador se for realmente um arquivo CXX.
Andreas Haferburg
É bom ter a flexibilidade da outra solução no bolso de trás, mas é essa que eu estava procurando, obrigado!
kylewm
Boa resposta. Cuidado com a falta de parênteses para set_source_files_properties.
Arnaud
2
Ele pode ser seletivamente desligado para arquivos individuais com / Y- usando set_source_files_properties
mlt de
O que está abcem seu exemplo?
Sandburg
7

se você não quiser reinventar a roda, use apenas Cotire como a resposta principal sugere ou uma mais simples - cmake-pré-compilado-header aqui . Para usá-lo basta incluir o módulo e chamar:

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

O CMake 3.16 introduziu suporte para cabeçalhos pré-compilados. Há um novo comando CMake target_precompile_headersque faz tudo o que você precisa nos bastidores. Consulte sua documentação para obter mais detalhes: https://cmake.org/cmake/help/latest/command/target_precompile_headers.html

usr1234567
fonte
2
Esta resposta não acrescenta nada de novo à resposta de janisozaur, postada meio ano antes, exceto o link para a documentação oficial final, que provavelmente deveria ser melhor adicionado como um comentário a essa resposta.
Alex Che
4

Um exemplo de cabeçalho pré-compilado de uso com cmake e Visual Studio 2015

"stdafx.h", "stdafx.cpp" - nome do cabeçalho pré-compilado.

Coloque o seguinte abaixo no arquivo cmake root.

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

Coloque o seguinte abaixo no arquivo cmake do projeto.

"src" - uma pasta com arquivos de origem.

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

IMHO, a melhor maneira é definir PCH para todo o projeto, como sugeriu Martjno, combinado com a capacidade de ignorar PCH para algumas fontes, se necessário (por exemplo, fontes geradas):

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

Portanto, se você tiver algum alvo MY_TARGET e uma lista de fontes geradas IGNORE_PCH_SRC_LIST, você simplesmente fará:

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

Esta abordagem foi testada e funciona perfeitamente.

Vram Vardanian
fonte
0

Bem, quando as compilações levam mais de 10 minutos em uma máquina quad core, toda vez que você altera uma única linha em qualquer um dos arquivos do projeto, é hora de adicionar cabeçalhos pré-compilados para janelas. No * nux, eu apenas usaria o ccache e não me preocuparia com isso.

Eu implementei em meu aplicativo principal e algumas das bibliotecas que ele usa. Funciona muito bem neste ponto. Uma coisa que também é necessária é criar o arquivo de origem e de cabeçalho do pch e incluir no arquivo de origem todos os cabeçalhos que deseja pré-compilados. Fiz isso por 12 anos com o MFC, mas demorei alguns minutos para me lembrar disso.


fonte
0

A maneira mais limpa é adicionar a opção pré-compilada como uma opção global. No arquivo vcxproj, isso aparecerá <PrecompiledHeader>Use</PrecompiledHeader>e não fará isso para cada arquivo individual.

Em seguida, você precisa adicionar a Createopção ao StdAfx.cpp. O seguinte é como eu o uso:

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

Ele foi testado e funciona para o MSVC 2010 e criará um arquivo MyDll.pch, não estou incomodado com o nome do arquivo usado, então não fiz nenhum esforço para especificá-lo.

limpar
fonte
0

Como a opção de cabeçalho pré-compilado não funciona para arquivos rc, eu precisei ajustar a macro fornecida por 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)

Edit: O uso desses cabeçalhos pré-compilados reduziu o tempo de compilação geral do meu projeto principal de 4min 30s para 1min 40s. Isso é para mim uma coisa muito boa. No cabeçalho de pré-compilação, existem apenas cabeçalhos como boost / stl / Windows / mfc.

schorsch_76
fonte
-15

Nem vá lá. Cabeçalhos pré-compilados significam que sempre que um dos cabeçalhos muda, você deve reconstruir tudo . Você tem sorte se tiver um sistema de compilação que perceba isso. Mais frequentemente do que nunca, sua construção irá falhar até que você perceba que alterou algo que está sendo pré-compilado e, portanto, precisa fazer uma reconstrução completa. Você pode evitar isso principalmente ao pré-compilar os cabeçalhos que você tem certeza absoluta de que não vão mudar, mas também está desistindo de uma grande parte do ganho de velocidade.

O outro problema é que seu namespace fica poluído com todos os tipos de símbolos que você não conhece ou com os quais você não se importa em muitos lugares onde você usaria os cabeçalhos pré-compilados.

Dirk Groeneveld
fonte
29
Cabeçalhos pré-compilados são mais úteis quando estão referenciando cabeçalhos que não mudam ... STL, Boost, outras coisas de terceiros. Se você estiver usando o PCH para seus próprios arquivos de cabeçalho de projeto, estará perdendo a maior parte do benefício.
Tom,
3
Mesmo se você estiver usando o PCH para os cabeçalhos do seu próprio projeto, o objetivo de um sistema de compilação como o CMake é garantir que as dependências sejam respeitadas. Se eu mudar meu arquivo .h (ou uma de suas dependências), quero que o .pch seja regenerado. Se eu não alterar meu arquivo .h, não quero que o .pch seja regenerado. Acho que toda a questão do OP era: Como faço para que isso aconteça, usando o CMake?
Quuxplusone
1
Os cabeçalhos pré-compilados são a melhor ferramenta para reduzir os tempos de compilação até que os módulos C ++ sejam suportados por todos os compiladores convencionais. Eles resolvem um problema que piorou com o uso cada vez maior de modelos e bibliotecas somente de cabeçalho. Quando usado corretamente, não há substituto. Independentemente disso, isso não constitui uma resposta à pergunta que está sendo feita e apenas expressa uma opinião. Votado para baixo e excluído.
Inspecionável