54 votes

CMake + GoogleTest

J'ai juste téléchargé googletest, généré son makefile avec CMake et compilé. Maintenant, j'ai besoin de l'utiliser dans mon projet de test.

Avec CMake, on m'a conseillé de ne pas pointer directement vers les bibliothèques gtest (en utilisant include_directories ou link_directories) mais d'utiliser plutôt find_package().

Le problème, c'est qu'il n'y a pas de cible d'installation pour le makefile gtest généré. Je ne comprends pas comment find_package(GTest REQUIRED) pourrait fonctionner sans une sorte d'installation. De plus, mettre le dossier gtest en tant que sous-dossier dans mon projet n'est pas possible.

Merci pour toute aide.

66voto

Fraser Points 20579

Ceci est un cas inhabituel ; la plupart des projets spécifient des règles d'installation.

Le module ExternalProject_Add de CMake est peut-être le meilleur outil pour ce travail. Cela vous permet de télécharger, configurer et construire gtest depuis votre projet, puis de lier les bibliothèques gtest.

J'ai testé le CMakeLists.txt suivant sur Windows avec Visual Studio 10 et 11, et sur Ubuntu en utilisant GCC 4.8 et Clang 3.2 - il pourrait nécessiter des ajustements pour d'autres plates-formes/compilateurs :

cmake_minimum_required(VERSION 2.8.7 FATAL_ERROR)
project(Test)

# Créez main.cpp qui utilise 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")

# Créez le fichier de correctif pour gtest avec 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 "       # La surcharge résolue a été trouvée par la recherche dépendante des arguments.\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()

# Activer le module CMake ExternalProject
include(ExternalProject)

# Définir le type de construction s'il n'est pas déjà défini
if(NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE Release)
endif()

# Définir le répertoire racine par défaut d'ExternalProject
set_directory_properties(PROPERTIES EP_PREFIX ${CMAKE_BINARY_DIR}/ThirdParty)

# Ajouter 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
    # Forcer des chemins de sortie séparés pour les compilations debug et release pour permettre une identification facile de la bonne lib dans les commandes TARGET_LINK_LIBRARIES suivantes
    CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
               -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG:CHEMIN=DebugLibs
               -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE:CHEMIN=ReleaseLibs
               -Dgtest_force_shared_crt=ON
    # Désactiver l'étape d'installation
    INSTALL_COMMAND ""
    # Envelopper les étapes de téléchargement, configuration et construction dans un script pour enregistrer la sortie
    LOG_DOWNLOAD ON
    LOG_CONFIGURE ON
    LOG_BUILD ON)

# Spécifier le répertoire d'inclusion
ExternalProject_Get_Property(googletest source_dir)
include_directories(${source_dir}/include)

# Ajouter un drapeau de compilateur pour MSVC 2012
if(MSVC_VERSION EQUAL 1700)
  add_definitions(-D_VARIADIC_MAX=10)
endif()

# Ajouter la cible exécutable de test
add_executable(MainTest ${PROJECT_SOURCE_DIR}/src/main.cpp)

# Créer la dépendance de MainTest à googletest
add_dependencies(MainTest googletest)

# Spécifier les bibliothèques de liaison de MainTest
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})

Si vous créez ceci en tant que CMakeLists.txt dans un répertoire vide (disons MyTest), alors :

cd MyTest
mkdir build
cd build
cmake ..

Cela devrait créer un main.cpp de base dans MyTest/src et créer un fichier de projet (MyTest/build/Test.sln sur Windows)

Lorsque vous construisez le projet, il devrait télécharger les sources gtest dans MyTest/build/ThirdParty/src/googletest, et les construire dans MyTest/build/ThirdParty/src/googletest-build. Vous devriez alors pouvoir exécuter avec succès la cible MainTest.

4 votes

Il me semble complexe. N'y a-t-il pas moyen de référencer directement le fichier CMakeLists.txt de google test? Ou dois-je ajouter la bibliothèque googletest en tant que sous-dossier à la place?

3 votes

Je ne pense pas que ce soit beaucoup plus complexe que d'ajouter googletest en tant que sous-répertoire, mais si vous avez le choix, alors oui - ajoutez googletest en tant que sous-répertoire de votre arborescence source et intégrez-le via ADD_SUBDIRECTORY. (Vous avez spécifié que ce n'était pas une option dans votre question initiale).

0 votes

Oui, en effet. J'aimerais utiliser une meilleure solution. Mais un sous-répertoire semble être une meilleure et une solution plus propre que celle que tu as fournie... Mais je commence seulement dans le monde de CMake... ;)

35voto

Craig Scott Points 3981

Il est bien longtemps que la question originale a été posée, mais pour le bénéfice des autres, il est possible d'utiliser ExternalProject pour télécharger la source de gtest et ensuite utiliser add_subdirectory() pour l'ajouter à votre build. Cela présente les avantages suivants :

  • gtest est construit dans le cadre de votre build principal, il utilise donc les mêmes drapeaux du compilateur, etc. et n'a pas besoin d'être installé quelque part.
  • Il n'est pas nécessaire d'ajouter les sources de gtest à votre propre arborescence de fichiers sources.

Utilisé de manière normale, ExternalProject ne fera pas le téléchargement et le déballage au moment de la configuration (c'est-à-dire lorsque CMake est lancé), mais vous pouvez lui demander de le faire. J'ai écrit un article de blog sur la façon de le faire, qui inclut également une implémentation généralisée qui fonctionne pour n'importe quel projet externe utilisant CMake comme système de build, pas seulement gtest. Vous pouvez le trouver ici :

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

Mise à jour : L'approche décrite ci-dessus fait maintenant également partie de la documentation de googletest.

0 votes

BRAVO! - Pourquoi n'est-ce pas intégré à CMake? Pouvez-vous le soumettre?

9voto

Phil Points 11

Ma réponse est basée sur la réponse de firegurafiku. Je l'ai modifiée de la manière suivante :

  1. ajouté CMAKE_ARGS à l'appel de ExternalProject_Add pour que cela fonctionne avec msvc.
  2. obtient la source gtest à partir d'un emplacement de fichier plutôt que de la télécharger
  3. ajouté une définition et une utilisation portable (pour MSVC et non-MSVC) de IMPORTED_LOCATION
  4. Résolu le problème avec l'appel à set_target_properties qui ne fonctionne pas au moment de la configuration lorsque les INTERFACE_INCLUDE_DIRECTORIES n'existent pas encore car le projet externe n'a pas encore été construit.

Je préfère garder gtest en tant que projet externe plutôt que d'ajouter sa source directement à mon projet. Une raison en est que je n'aime pas avoir le code source gtest inclus lorsque je recherche dans mon code. Tous les drapeaux de construction spéciaux nécessaires par mon code qui doivent également être utilisés lors de la construction de gtest peuvent être ajoutés à la définition de CMAKE_ARGS dans l'appel à ExternalProject_Add

Voici mon approche modifiée :

include(ExternalProject)

# variables pour suivre les chemins de gtest
set(GTEST_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/gtest")
set(GTEST_LOCATION "${GTEST_PREFIX}/src/GTestExternal-build")
set(GTEST_INCLUDES "${GTEST_PREFIX}/src/GTestExternal/include")

# téléchargement et construction du projet externe (pas d'installation pour gtest)
ExternalProject_Add(GTestExternal
    URL ${CMAKE_CURRENT_SOURCE_DIR}/../googletest
    PREFIX "${GTEST_PREFIX}"

    # arguments cmake
    CMAKE_ARGS -Dgtest_force_shared_crt=ON

    # Désactiver l'étape d'installation
    INSTALL_COMMAND ""

    # Envelopper les étapes de téléchargement, de configuration et de construction dans un script pour enregistrer la sortie
    LOG_DOWNLOAD ON
    LOG_CONFIGURE ON
    LOG_BUILD ON
    )

# variables définissant les propriétés de l'emplacement d'importation pour les bibliothèques gtest et gtestmain générées
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()

# le répertoire d'inclusion gtest n'existe qu'après sa construction, mais il est utilisé/nécessaire
# pour l'appel à set_target_properties ci-dessous, donc le créer pour éviter une erreur
file(MAKE_DIRECTORY ${GTEST_INCLUDES})

# définition de la bibliothèque importée 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}
    )

# définition de la bibliothèque importée GTestMain
add_library(GTestMain IMPORTED STATIC GLOBAL)
set_target_properties(GTestMain PROPERTIES
    IMPORTED_LINK_INTERFACE_LIBRARIES GTest
    ${GTESTMAIN_IMPORTED_LOCATION}
    )

# rendre GTest dépendant de GTestExternal
add_dependencies(GTest GTestExternal)

#
# Mes cibles
#

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 votes

Merci pour les grandes corrections apportées aux réponses précédentes. Au fait, lorsque je suis dans Visual Studio et que je presse sur "Rebuild" pour l'un de mes projets qui est lié à gtest, il télécharge à nouveau et reconstruit également gtest. Est-ce un comportement normal? Pourquoi reconstruire un "projet externe"?

1 votes

Détails sur le fonctionnement de CMake ExternalProject me sont encore un peu mystérieux. La manière dont je l'ai décrit, gtest est déjà local, donc au moins il ne sera pas téléchargé à nouveau. En général dans Visual Studio, Rebuild reconstruira non seulement le projet sélectionné mais aussi tous les autres projets sur lesquels il dépend. Il doit y avoir quelque chose dans la solution/projets générés par CMake qui fait en sorte qu'un target ExternalProject Add soit une dépendance. En conséquence, gtest est reconstruit. Pour éviter cela, vous pouvez faire une "Reconstruction du projet uniquement" ou vous pouvez aller dans Build -> Batch Build et nettoyer les projets que vous souhaitez reconstruire.

0 votes

Merci pour la réponse! De plus, à qui cela concerne, si vous faites simplement ce qui précède, votre application ne se construira pas dans Visual Studio avec les paramètres par défaut (vous obtiendrez plusieurs erreurs de liens de symboles), car la bibliothèque gtest utilise le runtime /MT au lieu de /MD par défaut. Voici une entrée de FAQ à ce sujet: github.com/google/googletest/blob/master/googletest/docs/…

7voto

firegurafiku Points 81

Il existe une solution un peu moins complexe en utilisant le module ExternalProject et la fonctionnalité de bibliothèques importées de cmake. Il vérifie le code du dépôt, le construit et crée une cible à partir des bibliothèques statiques construites (elles sont libgtest.a et libgtest_main.a sur mon système).

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)

Vous voudrez peut-être remplacer SVN_REVISION ou ajouter les options LOG_CONFIGURE et LOG_BUILD ici. Après avoir créé les cibles GTest et GTestMain, elles peuvent être utilisées de cette manière:

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

ou, si vous avez votre propre fonction main():

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

2 votes

Comment ajoutez-vous le répertoire include?

1 votes

@Jeff : réponse mise à jour, désolé pour le retard de six mois. Voir la propriété INTERFACE_INCLUDE_DIRECTORIES de la cible.

0 votes

J'ai dû supprimer la ligne INTERFACE_INCLUDE_DIRECTORIES "${GTEST_INCLUDES}" afin de faire fonctionner cela avec ma propre fonction main()

5voto

Ip Esez Points 18

Le sujet est un peu vieux, mais il est apparu une nouvelle façon d'inclure des bibliothèques externes dans CMake.

#Nécessite CMake 3.16+
include(FetchContent)

FetchContent_Declare(
  googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG        release-1.8.0
)

FetchContent_MakeAvailable(googletest)

Si vous voulez supporter les anciennes versions de CMake:

# Require CMake 3.11+
include(FetchContent)

FetchContent_Declare(
  googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG        release-1.8.0
)

FetchContent_GetProperties(googletest)
if(NOT googletest_POPULATED)
  FetchContent_Populate(googletest)
  add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR})
endif()

Ensuite, il suffit d'ajouter

enable_testing()

add_executable(test ${SOURCES} )

target_link_libraries(test gtest_main ${VOS_BIBLIOTHÈQUES})

add_test(NAME tests COMMAND test)

Lecture complémentaire: https://cmake.org/cmake/help/latest/module/FetchContent.html

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X