Używanie wstępnie skompilowanych nagłówków z CMake

104

Widziałem kilka (starych) postów w sieci na temat zhakowania wsparcia dla wstępnie skompilowanych nagłówków w CMake. Wszyscy wydają się być trochę wszędzie i każdy ma na to swój własny sposób. Jak najlepiej to zrobić obecnie?

Kleisty
źródło

Odpowiedzi:

79

Istnieje zewnętrzny moduł CMake o nazwie „Cotire”, który automatyzuje użycie prekompilowanych nagłówków dla systemów kompilacji opartych na CMake, a także obsługuje kompilacje unity.

sakra
źródło
1
Stworzyłem zestaw makr, które zawijają funkcjonalność cotire (prekompilowane nagłówki i kompilacje unity) tutaj dla łatwiejszego użycia
onqtam
2
@onqtam jak to jest łatwiejsze, jest tak gigantyczne, że nawet nie widzę, jak po prostu uzyskać prekompilację, pracując z cmake i gcc
Pavel P
5
CMake 3.16 wprowadził wbudowaną obsługę prekompilowanych nagłówków. Zobacz moją odpowiedź stackoverflow.com/a/59514029/2799037 Nie potrzebujesz już modułów innych firm!
usr1234567
34

Używam następującego makra do generowania i używania prekompilowanych nagłówków:

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)

Powiedzmy, że masz zmienną $ {Moje źródła} ze wszystkimi plikami źródłowymi, kod, którego chciałbyś użyć, wyglądałby po prostu

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

Kod nadal działałby dobrze na platformach innych niż MSVC. Całkiem schludnie :)

larsmoa
źródło
2
To makro ma 1 wadę. Jeśli generator nie jest oparty na MSVC, prekompilowane źródło nie zostanie dodane do listy źródeł. Więc moja modyfikacja po prostu przesuwa na list( APPEND ... )zewnątrz zamknięcie endif(). Zobacz pełny kod tutaj: pastebin.com/84dm5rXZ
void.pointer
1
@RobertDailey: To właściwie celowe - nie chcę kompilować prekompilowanego pliku źródłowego, gdy nie używam prekompilowanych nagłówków - i tak nie powinien definiować żadnych symboli.
larsmoa
1
@Iarsam Popraw argumenty /Yui /FI, powinny być, ${PrecompiledHeader}a nie ${PrecompiledBinary}.
Mourad
czy możesz wyjaśnić, dlaczego potrzebujemy flag „/ Fp” i „/ FI”? Według msdn.microsoft.com/en-us/library/z0atkd6c.aspx użycie „/ Fp” nie jest obowiązkowe. Jednakże, jeśli wycinam te flagi z twojego makra, nie ma ustawionego pch.
Vram Vardanian
2
Należy jednak pamiętać, że argument do / Yu jest traktowany bardzo dosłownie. Np. Zmusi /YuC:/foo/bar.hcię do przekazania /FpC:/foo/bar.hflagi lub umieszczenia #include <C:/foo/bar.h>na górze wszystkich twoich plików .cpp jako pierwsza instrukcja dołączania. MSVC porównuje #includeargumenty za pomocą ciągów , nie sprawdza, czy wskazuje na ten sam plik, do którego został podany /Yu. Ergo, #include <bar.h>nie zadziała i wyemituje błąd C2857.
Manuzor
21

CMake właśnie zyskał wsparcie dla PCH, powinno być dostępne w nadchodzącej wersji 3.16, która ma się pojawić 01.10.2019:

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

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

Trwa dyskusja na temat wspierania udostępniania PCH między celami: https://gitlab.kitware.com/cmake/cmake/issues/19659

Istnieje dodatkowy kontekst (motywacja, liczby) dostępny pod adresem https://blog.qt.io/blog/2019/08/01/precompiled-headers-and-unity-jumbo-builds-in-upcoming-cmake/

janisozaur
źródło
2
Oto link do oficjalnej dokumentacji CMake: cmake.org/cmake/help/latest/command/ ...
Alex Che
2
Jest post od zespołu MSVC na temat ustalania, które nagłówki umieścić w PCH: devblogs.microsoft.com/cppblog/…
janisozaur
19

Oto fragment kodu, który umożliwia użycie wstępnie skompilowanego nagłówka w projekcie. Dodaj następujące elementy do zastępowania CMakeLists.txt myprecompiledheadersi myproject_SOURCE_FILESodpowiednio:

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
źródło
Dzięki; Użyję tego jako przewodnika do zbudowania jednego dla GCC (jeśli mogę). Opublikuję odpowiedź, gdy tylko skończę. =]
strager
@Jayen, nie; W końcu porzuciłem projekt i nigdy więcej nie wdałem się w kłopoty z C ++.
strager
Czy można ustawić PCH na cały projekt? Ponieważ nie jest możliwe pobranie listy automatycznie generowanych plików w cmake with set( CMAKE_AUTOMOC ON ).
Dmitry Sazonov
Korzystałem z twojego rozwiązania, ale niestety czas kompilacji wzrósł z 2'10 "do 2'40", dla ~ 130 plików. Czy muszę się upewnić, że myprecompiledheader.cppjest skompilowany jako pierwszy? Z tego fragmentu wygląda na to, że zostanie skompilowany jako ostatni, więc może to może być przyczyną opóźnienia. myprecompiledheader.hzawiera tylko najpopularniejsze nagłówki STL, których używa mój kod.
Grim Fandango,
13

Skończyło się na tym, że użyłem zaadaptowanej wersji makra larsm. Użycie $ (IntDir) dla ścieżki pch powoduje oddzielenie prekompilowanych nagłówków do debugowania i kompilacji wydania.

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})
jari
źródło
2
Abstrakcja $ {IntDir} jest pomocna. Dzięki.
Gopalakrishna Palem
12

Zaadaptowane od Dave, ale bardziej wydajne (ustawia właściwości docelowe, nie dla każdego pliku):

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
źródło
2
Kiedyś korzystałem z tego rozwiązania, ale działa tylko wtedy, gdy projekt zawiera tylko pliki C ++. Ponieważ COMPILE_FLAGS jest stosowany do wszystkich plików źródłowych, będzie również stosowany do plików c (np. Tych generowanych przez MIDL), które nie będą lubić c ++ PCH. Korzystając z rozwiązania Dave'a, możesz użyć get_source_file_property (_language $ {src_file} LANGUAGE) i ustawić flagi kompilatora tylko wtedy, gdy jest to naprawdę plik CXX.
Andreas Haferburg
Miło mieć w tylnej kieszeni elastyczność drugiego rozwiązania, ale właśnie tego szukałem, dzięki!
kylewm
Niezła odpowiedź. Uważaj na brakujący nawias dla set_source_files_properties.
Arnaud
2
Można ją selektywnie wyłączyć dla poszczególnych plików za pomocą / Y- za pomocą set_source_files_properties
mlt
Co jest abcw twoim przykładzie?
Sandburg
7

jeśli nie chce wyważać otwartych drzwi, wystarczy użyć Cotire jako górna odpowiedź sugeruje lub prostsze - cmake-prekompilowana-header tutaj . Aby z niego skorzystać, wystarczy dołączyć moduł i zadzwonić:

include( cmake-precompiled-header/PrecompiledHeader.cmake )
add_precompiled_header( targetName StdAfx.h FORCEINCLUDE SOURCE_CXX StdAfx.cpp )
Roman Kruglov
źródło
5

CMake 3.16 wprowadził obsługę prekompilowanych nagłówków. Jest nowe polecenie CMake, target_precompile_headersktóre robi wszystko, czego potrzebujesz pod maską. Więcej informacji można znaleźć w dokumentacji: https://cmake.org/cmake/help/latest/command/target_precompile_headers.html

usr1234567
źródło
2
Ta odpowiedź nie dodaje nic nowego do odpowiedzi janisozaura, opublikowanej pół roku wcześniej, poza linkiem do ostatecznej oficjalnej dokumentacji, który prawdopodobnie należałoby dodać jako komentarz do tej odpowiedzi.
Alex Che
4

Przykład użycia prekompilowanego nagłówka z cmake i Visual Studio 2015

„stdafx.h”, „stdafx.cpp” - nazwa prekompilowanego nagłówka.

Umieść poniższy plik w głównym pliku 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()

Umieść poniższy kod w pliku cmake projektu.

„src” - folder z plikami źródłowymi.

set_source_files_properties(src/stdafx.cpp
    PROPERTIES
    COMPILE_FLAGS "/Ycstdafx.h"
)
Maks
źródło
3

IMHO najlepszym sposobem jest ustawienie PCH dla całego projektu, jak sugerował martjno, w połączeniu z możliwością ignorowania PCH dla niektórych źródeł w razie potrzeby (np. Wygenerowane źródła):

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

Tak więc, jeśli masz jakiś cel MY_TARGET i listę wygenerowanych źródeł IGNORE_PCH_SRC_LIST, po prostu zrobisz:

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

To podejście zostało przetestowane i działa doskonale.

Vram Vardanian
źródło
0

Cóż, jeśli kompilacje trwają ponad 10 minut na czterordzeniowym komputerze, za każdym razem, gdy zmienisz jedną linię w dowolnym pliku projektu, pojawi się informacja, że ​​należy dodać wstępnie skompilowane nagłówki dla systemu Windows. Na * nux po prostu użyłbym ccache i nie martwiłem się o to.

Zaimplementowałem w swojej głównej aplikacji i kilku bibliotekach, z których ona korzysta. Do tego momentu działa świetnie. Jedną rzeczą, która jest również potrzebna, jest utworzenie źródła pch i pliku nagłówkowego, aw pliku źródłowym uwzględnij wszystkie nagłówki, które chcesz prekompilować. Robiłem to przez 12 lat z MFC, ale zajęło mi kilka minut, aby to sobie przypomnieć.


źródło
0

Najprostszym sposobem jest dodanie opcji prekompilowanej jako opcji globalnej. W pliku vcxproj pojawi się jako<PrecompiledHeader>Use</PrecompiledHeader> i nie będzie tego robić dla każdego pojedynczego pliku.

Następnie musisz dodać Createopcję do StdAfx.cpp. Oto jak go używam:

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

Jest to testowane i działa dla MSVC 2010 i utworzy plik MyDll.pch, nie przejmuję się nazwą pliku, więc nie starałem się go określić.

unletall
źródło
0

Ponieważ opcja prekompilowanego nagłówka nie działa dla plików rc, musiałem dostosować makro dostarczone przez 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)

Edycja: Użycie tych wstępnie skompilowanych nagłówków skróciło ogólny czas kompilacji mojego głównego projektu z 4 min 30 s do 1 min 40 s. To dla mnie naprawdę dobra rzecz. W nagłówku prekompilacji znajdują się tylko nagłówki, takie jak boost / stl / Windows / mfc.

schorsch_76
źródło
-15

Nawet tam nie idź. Wstępnie skompilowane nagłówki oznaczają, że za każdym razem, gdy zmieni się jeden z nagłówków, musisz wszystko odbudować . Masz szczęście, jeśli masz system kompilacji, który to rozumie. Najczęściej kompilacja kończy się niepowodzeniem, dopóki nie zdasz sobie sprawy, że zmieniłeś coś, co jest prekompilowane, i dlatego musisz przeprowadzić pełną przebudowę. Możesz tego uniknąć głównie poprzez prekompilowanie nagłówków, które nie zmienią się, co do której jesteś absolutnie pewny, ale wtedy również rezygnujesz z dużej części przyrostu szybkości.

Innym problemem jest to, że twoja przestrzeń nazw jest zanieczyszczona wszelkiego rodzaju symbolami, których nie znasz lub na których nie zależy ci w wielu miejscach, w których używałbyś prekompilowanych nagłówków.

Dirk Groeneveld
źródło
29
Wstępnie skompilowane nagłówki są najbardziej przydatne, gdy odwołują się do nagłówków, które się nie zmieniają ... STL, Boost, inne rzeczy innych firm. Jeśli używasz PCH do własnych plików nagłówkowych projektu, marnujesz większość korzyści.
Tom
3
Nawet jeśli używasz PCH do nagłówków własnego projektu, celem systemu kompilacji, takiego jak CMake, jest upewnienie się, że zależności przestrzegane. Jeśli zmienię plik .h (lub jedną z jego zależności), chcę ponownie odtworzyć plik .pch. Jeśli nie zmienię pliku .h, nie chcę, aby plik .pch był regenerowany. Myślę, że całe pytanie OP brzmiało: Jak to zrobić, używając CMake?
Quuxplusone
1
Wstępnie skompilowane nagłówki są najlepszym narzędziem do skrócenia czasu kompilacji, dopóki moduły C ++ nie będą obsługiwane przez wszystkie główne kompilatory. Rozwiązują problem, który tylko się pogarszał wraz z coraz większym wykorzystaniem szablonów i bibliotek obsługujących tylko nagłówki. Przy prawidłowym użyciu nie ma zamiennika. Niemniej jednak nie stanowi to odpowiedzi na zadane pytanie, a jedynie wyraża opinię. Głosowanie w dół i usuwanie.
Niespodziewane