2012-03-13 18 views
43

Ho appena scaricato googletest, generato il suo makefile con CMake e lo ho creato. Ora, ho bisogno di usarlo nel mio progetto di test.CMake + GoogleTest

Con CMake, mi è stato consigliato di non puntare direttamente alle librerie gtest (utilizzando include _directories o link_directories) ma utilizzare find_package().

Il problema è che non esiste una destinazione di installazione per il makefile gtest generato. Non riesco a capire come find_package(GTest REQUIRED) potrebbe funzionare senza alcun tipo di installazione. Inoltre, non è possibile inserire la cartella gtest come sottocartella nel mio progetto.

Grazie per qualsiasi aiuto.

risposta

62

Questo è un caso insolito; la maggior parte dei progetti specifica le regole di installazione.

Il modulo di CMake ExternalProject_Add è forse lo strumento migliore per questo lavoro. Questo ti permette di scaricare, configurare e compilare gtest dal tuo progetto, e quindi collegarti alle librerie gtest.

Ho testato il seguente CMakeLists.txt su Windows con Visual Studio 10 e 11, e su Ubuntu usando GCC 4.8 e 3.2 Clang - si potrebbe avere bisogno rettificato per altre piattaforme/compilatori:

cmake_minimum_required(VERSION 2.8.7 FATAL_ERROR) 
project(Test) 

# Create main.cpp which uses gtest 
file(WRITE src/main.cpp "#include \"gtest/gtest.h\"\n\n") 
file(APPEND src/main.cpp "TEST(A, B) { SUCCEED(); }\n") 
file(APPEND src/main.cpp "int main(int argc, char **argv) {\n") 
file(APPEND src/main.cpp " testing::InitGoogleTest(&argc, argv);\n") 
file(APPEND src/main.cpp " return RUN_ALL_TESTS();\n") 
file(APPEND src/main.cpp "}\n") 

# Create patch file for gtest with MSVC 2012 
if(MSVC_VERSION EQUAL 1700) 
    file(WRITE gtest.patch "Index: cmake/internal_utils.cmake\n") 
    file(APPEND gtest.patch "===================================================================\n") 
    file(APPEND gtest.patch "--- cmake/internal_utils.cmake (revision 660)\n") 
    file(APPEND gtest.patch "+++ cmake/internal_utils.cmake (working copy)\n") 
    file(APPEND gtest.patch "@@ -66,6 +66,9 @@\n") 
    file(APPEND gtest.patch "  # Resolved overload was found by argument-dependent lookup.\n") 
    file(APPEND gtest.patch "  set(cxx_base_flags \"\${cxx_base_flags} -wd4675\")\n") 
    file(APPEND gtest.patch "  endif()\n") 
    file(APPEND gtest.patch "+ if (MSVC_VERSION EQUAL 1700)\n") 
    file(APPEND gtest.patch "+  set(cxx_base_flags \"\${cxx_base_flags} -D_VARIADIC_MAX=10\")\n") 
    file(APPEND gtest.patch "+ endif()\n") 
    file(APPEND gtest.patch "  set(cxx_base_flags \"\${cxx_base_flags} -D_UNICODE -DUNICODE -DWIN32 -D_WIN32\")\n") 
    file(APPEND gtest.patch "  set(cxx_base_flags \"\${cxx_base_flags} -DSTRICT -DWIN32_LEAN_AND_MEAN\")\n") 
    file(APPEND gtest.patch "  set(cxx_exception_flags \"-EHsc -D_HAS_EXCEPTIONS=1\")\n") 
else() 
    file(WRITE gtest.patch "") 
endif() 

# Enable ExternalProject CMake module 
include(ExternalProject) 

# Set the build type if it isn't already 
if(NOT CMAKE_BUILD_TYPE) 
    set(CMAKE_BUILD_TYPE Release) 
endif() 

# Set default ExternalProject root directory 
set_directory_properties(PROPERTIES EP_PREFIX ${CMAKE_BINARY_DIR}/ThirdParty) 

# Add gtest 
ExternalProject_Add(
    googletest 
    SVN_REPOSITORY http://googletest.googlecode.com/svn/trunk/ 
    SVN_REVISION -r 660 
    TIMEOUT 10 
    PATCH_COMMAND svn patch ${CMAKE_SOURCE_DIR}/gtest.patch ${CMAKE_BINARY_DIR}/ThirdParty/src/googletest 
    # Force separate output paths for debug and release builds to allow easy 
    # identification of correct lib in subsequent TARGET_LINK_LIBRARIES commands 
    CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} 
       -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG:PATH=DebugLibs 
       -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE:PATH=ReleaseLibs 
       -Dgtest_force_shared_crt=ON 
    # Disable install step 
    INSTALL_COMMAND "" 
    # Wrap download, configure and build steps in a script to log output 
    LOG_DOWNLOAD ON 
    LOG_CONFIGURE ON 
    LOG_BUILD ON) 

# Specify include dir 
ExternalProject_Get_Property(googletest source_dir) 
include_directories(${source_dir}/include) 

# Add compiler flag for MSVC 2012 
if(MSVC_VERSION EQUAL 1700) 
    add_definitions(-D_VARIADIC_MAX=10) 
endif() 

# Add test executable target 
add_executable(MainTest ${PROJECT_SOURCE_DIR}/src/main.cpp) 

# Create dependency of MainTest on googletest 
add_dependencies(MainTest googletest) 

# Specify MainTest's link libraries 
ExternalProject_Get_Property(googletest binary_dir) 
if(MSVC) 
    set(Suffix ".lib") 
else() 
    set(Suffix ".a") 
    set(Pthread "-pthread") 
endif() 
target_link_libraries(
    MainTest 
    debug ${binary_dir}/DebugLibs/${CMAKE_FIND_LIBRARY_PREFIXES}gtest${Suffix} 
    optimized ${binary_dir}/ReleaseLibs/${CMAKE_FIND_LIBRARY_PREFIXES}gtest${Suffix} 
    ${Pthread}) 

Se si crea questo come CMakeLists.txt in una directory vuota (diciamo MyTest), quindi:

cd MyTest 
mkdir build 
cd build 
cmake .. 

questo dovrebbe creare una base in main.cpp MyTest/src e creare un file di progetto (MyTest/build/Test.sln su Windows)

Quando si crea il progetto, è necessario scaricare le origini gtest a MyTest/build/ThirdParty/src/googletest e crearle in MyTest/build/ThirdParty/src/googletest-build. Dovresti quindi essere in grado di eseguire correttamente l'obiettivo MainTest.

+3

Sembra complesso per me. Non c'è modo di referenziare direttamente il file CMakeLists.txt di google test? O dovrei invece aggiungere la libreria googletest come sottodirectory? – Korchkidu

+3

Non penso che sia molto più complesso dell'aggiunta di googletest come sottodirectory, ma se si ha la scelta allora sicuro - aggiungi googletest come sottodirectory del tuo albero dei sorgenti e inseriscilo tramite "ADD_SUBDIRECTORY". (Hai specificato che questa non era un'opzione nella tua domanda originale). – Fraser

+0

Sì, davvero. Mi piacerebbe usare una soluzione migliore. Ma una sottodirectory sembra migliore e più pulita della soluzione che hai fornito ... Ma sto solo iniziando dal mondo di CMake ...;) – Korchkidu

8

C'è una soluzione un po 'meno complessa utilizzando il modulo ExternalProject e le librerie importate di cmake. Esegue il checkout del codice dal repository, lo crea e crea il target dalle librerie statiche create (sono libgtest.a e libgtest_main.a sul mio sistema).

find_package(Threads REQUIRED) 
include(ExternalProject) 

set(GTEST_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/gtest") 
ExternalProject_Add(GTestExternal 
    SVN_REPOSITORY http://googletest.googlecode.com/svn/trunk 
    SVN_REVISION -r HEAD 
    TIMEOUT 10 
    PREFIX "${GTEST_PREFIX}" 
    INSTALL_COMMAND "") 

set(LIBPREFIX "${CMAKE_STATIC_LIBRARY_PREFIX}") 
set(LIBSUFFIX "${CMAKE_STATIC_LIBRARY_SUFFIX}") 
set(GTEST_LOCATION "${GTEST_PREFIX}/src/GTestExternal-build") 
set(GTEST_INCLUDES "${GTEST_PREFIX}/src/GTestExternal/include") 
set(GTEST_LIBRARY "${GTEST_LOCATION}/${LIBPREFIX}gtest${LIBSUFFIX}") 
set(GTEST_MAINLIB "${GTEST_LOCATION}/${LIBPREFIX}gtest_main${LIBSUFFIX}") 

add_library(GTest IMPORTED STATIC GLOBAL) 
set_target_properties(GTest PROPERTIES 
    IMPORTED_LOCATION     "${GTEST_LIBRARY}" 
    INTERFACE_INCLUDE_DIRECTORIES  "${GTEST_INCLUDES}" 
    IMPORTED_LINK_INTERFACE_LIBRARIES "${CMAKE_THREAD_LIBS_INIT}") 

add_library(GTestMain IMPORTED STATIC GLOBAL) 
set_target_properties(GTestMain PROPERTIES 
    IMPORTED_LOCATION "${GTEST_MAINLIB}" 
    IMPORTED_LINK_INTERFACE_LIBRARIES 
     "${GTEST_LIBRARY};${CMAKE_THREAD_LIBS_INIT}") 

add_dependencies(GTest GTestExternal) 

Si consiglia di sostituire o aggiungere SVN_REVISIONLOG_CONFIGURE e LOG_BUILD opzioni qui. Dopo la creazione e GTestGTestMain obiettivi, possono essere utilizzati in questo modo:

add_executable(Test 
    test1.cc 
    test2.cc) 
target_link_libraries(Test GTestMain) 

oppure, se avete il vostro main() funzione:

add_executable(Test 
    main.cc 
    test1.cc 
    test2.cc) 
target_link_libraries(Test GTest) 
+2

Come si aggiunge la directory di inclusione? – Jeff

+1

@Jeff: risposta aggiornata, mi dispiace per il ritardo di un anno. Vedi la proprietà 'INTERFACE_INCLUDE_DIRECTORIES' dell'obiettivo. – firegurafiku

+0

Ho dovuto eliminare la riga 'INTERFACE_INCLUDE_DIRECTORIES" $ {GTEST_INCLUDES} "' per fare in modo che funzioni con la mia funzione 'main()' – Twonky

7

La mia risposta si basa sulla risposta da firegurafiku. Ho modificato nelle seguenti modi:

  1. aggiunto CMAKE_ARGS alla chiamata ExternalProject_Add in modo che funziona con msvc.
  2. ottiene la fonte GTEST da un percorso di file piuttosto che il download di
  3. aggiunto portatile (per MSVC e non MSVC) definizione e l'utilizzo di IMPORTED_LOCATION
  4. ha affrontato il problema con la chiamata a set_target_properties non funziona al momento della configurazione in cui il INTERFACE_INCLUDE_DIRECTORIES non esiste ancora perché il progetto esterno non è stato ancora creato.

Preferisco conservare gtest come progetto esterno piuttosto che aggiungere la sua fonte direttamente al mio progetto. Una ragione è perché non mi piace avere il codice sorgente gtest incluso quando cerco il mio codice. Eventuali flag speciali di compilazione che sono necessari per il mio codice che deve essere utilizzato anche quando si costruisce GTEST possono essere aggiunti alla definizione di CMAKE_ARGS nella chiamata a ExternalProject_Add

Ecco il mio approccio modificato:

include(ExternalProject) 

# variables to help keep track of gtest paths 
set(GTEST_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/gtest") 
set(GTEST_LOCATION "${GTEST_PREFIX}/src/GTestExternal-build") 
set(GTEST_INCLUDES "${GTEST_PREFIX}/src/GTestExternal/include") 

# external project download and build (no install for gtest) 
ExternalProject_Add(GTestExternal 
    URL ${CMAKE_CURRENT_SOURCE_DIR}/../googletest 
    PREFIX "${GTEST_PREFIX}" 

    # cmake arguments 
    CMAKE_ARGS -Dgtest_force_shared_crt=ON 

    # Disable install step 
    INSTALL_COMMAND "" 

    # Wrap download, configure and build steps in a script to log output 
    LOG_DOWNLOAD ON 
    LOG_CONFIGURE ON 
    LOG_BUILD ON 
    ) 

# variables defining the import location properties for the generated gtest and 
# gtestmain libraries 
if (MSVC) 
    set(GTEST_IMPORTED_LOCATION 
     IMPORTED_LOCATION_DEBUG   "${GTEST_LOCATION}/Debug/${CMAKE_STATIC_LIBRARY_PREFIX}gtest${CMAKE_STATIC_LIBRARY_SUFFIX}" 
     IMPORTED_LOCATION_RELEASE   "${GTEST_LOCATION}/Release/${CMAKE_STATIC_LIBRARY_PREFIX}gtest${CMAKE_STATIC_LIBRARY_SUFFIX}" 
     ) 
    set(GTESTMAIN_IMPORTED_LOCATION 
     IMPORTED_LOCATION_DEBUG   "${GTEST_LOCATION}/Debug/${CMAKE_STATIC_LIBRARY_PREFIX}gtest_main${CMAKE_STATIC_LIBRARY_SUFFIX}" 
     IMPORTED_LOCATION_RELEASE   "${GTEST_LOCATION}/Release/${CMAKE_STATIC_LIBRARY_PREFIX}gtest_main${CMAKE_STATIC_LIBRARY_SUFFIX}" 
     ) 
else() 
    set(GTEST_IMPORTED_LOCATION 
     IMPORTED_LOCATION     "${GTEST_LOCATION}/${CMAKE_STATIC_LIBRARY_PREFIX}gtest${CMAKE_STATIC_LIBRARY_SUFFIX}") 
    set(GTESTMAIN_IMPORTED_LOCATION 
     IMPORTED_LOCATION     "${GTEST_LOCATION}/${CMAKE_STATIC_LIBRARY_PREFIX}gtest_main${CMAKE_STATIC_LIBRARY_SUFFIX}") 
endif() 

# the gtest include directory exists only after it is build, but it is used/needed 
# for the set_target_properties call below, so make it to avoid an error 
file(MAKE_DIRECTORY ${GTEST_INCLUDES}) 

# define imported library GTest 
add_library(GTest IMPORTED STATIC GLOBAL) 
set_target_properties(GTest PROPERTIES 
    INTERFACE_INCLUDE_DIRECTORIES  "${GTEST_INCLUDES}" 
    IMPORTED_LINK_INTERFACE_LIBRARIES "${CMAKE_THREAD_LIBS_INIT}" 
    ${GTEST_IMPORTED_LOCATION} 
    ) 

# define imported library GTestMain 
add_library(GTestMain IMPORTED STATIC GLOBAL) 
set_target_properties(GTestMain PROPERTIES 
    IMPORTED_LINK_INTERFACE_LIBRARIES GTest 
    ${GTESTMAIN_IMPORTED_LOCATION} 
    ) 

# make GTest depend on GTestExternal 
add_dependencies(GTest GTestExternal) 

# 
# My targets 
# 

project(test_pipeline) 
add_executable(${PROJECT_NAME} test_pipeline.cpp) 
set_target_properties(${PROJECT_NAME} PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX}) 
target_link_libraries(${PROJECT_NAME} ${TBB_LIBRARIES}) 
target_link_libraries(${PROJECT_NAME} GTest) 
+0

Grazie per le ottime correzioni alle risposte precedenti. BTW, quando in Visual Studio premo "Ricostruisci" per tutti i miei progetti che si collegano con gtest, scarica nuovamente e ricostruisce anche il gtest. È un comportamento normale? Perché dovrebbe essere "ricostruito" il progetto esterno? –

+1

I dettagli di come si comporta CMake ExternalProject sono ancora un po 'un mistero per me. Il modo in cui ho descritto, gtest è già locale, quindi almeno non verrà ri-scaricato. In generale in Visual Studio, Ricostruzione ricostruisce non solo il progetto selezionato ma anche ogni altro progetto da cui dipende. Deve esserci qualcosa nella soluzione/nei progetti generati da cmake che rende un target Add di ExternalProject una dipendenza. Di conseguenza, Gtest viene ricostruito. Per evitare ciò, puoi eseguire una "Ricostruzione progetto solo" oppure puoi andare a Build -> Batch Build e pulire i progetti che vuoi ricostruire. – Phil

+0

Grazie per la risposta! Inoltre, a chi può interessare, se si fa quanto sopra, l'applicazione non compilerà in Visual Studio con le impostazioni predefinite (si otterranno più errori di collegamento dei simboli), poiché la libreria gtest utilizza/runtime MT invece di default/MD . Ecco una domanda frequente su questo argomento: https://github.com/google/googletest/blob/master/googletest/docs/FAQ.md#i-am-building-my-project-with-google-test-in- visual-studio-and-all-im-getting-is-a-mazzo-di-linker-errors-or-warnings-help –

21

E ' passato lontano quando la domanda originale è stata posta, ma a beneficio di altri, è possibile utilizzare ExternalProject per scaricare la sorgente gtest e quindi usare add_subdirectory() per aggiungerla alla build. Questo ha i seguenti vantaggi:

  • gtest è costruito come parte della build principale, quindi utilizza gli stessi flag del compilatore, ecc. E non deve essere installato da nessuna parte.
  • Non è necessario aggiungere le sorgenti gtest al proprio albero dei sorgenti.

Utilizzato nel modo normale, ExternalProject non esegue il download e il disimballaggio in fase di configurazione (ad esempio quando viene eseguito CMake), ma è possibile farlo. Ho scritto un post sul blog su come fare ciò che include anche un'implementazione generalizzata che funziona per qualsiasi progetto esterno che usa CMake come sistema di compilazione, non solo gtest. Lo si può trovare qui:

https://crascit.com/2015/07/25/cmake-gtest/

Update: L'approccio descritto sopra è ora anche part of the googletest documentation.

+2

Questa grande risposta non dovrebbe essere in fondo! – user3667089

+0

Questa è davvero una soluzione molto bella. – JonesV

+0

BRAVO! - Perché questo non è integrato in CMake? Puoi inviarlo? – Zak