132 votes

CMake : Comment construire des projets externes et inclure leurs cibles

J'ai un projet A qui exporte une bibliothèque statique comme cible :

install(TARGETS alib DESTINATION lib EXPORT project_a-targets)
install(EXPORT project_a-targets DESTINATION lib/alib)

Maintenant je veux utiliser le projet A comme un projet externe du projet B et inclure ses cibles construites :

ExternalProject_Add(project_a
  URL ...project_a.tar.gz
  PREFIX ${CMAKE_CURRENT_BINARY_DIR}/project_a
  CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
)

include(${CMAKE_CURRENT_BINARY_DIR}/lib/project_a/project_a-targets.cmake)

Le problème est que le fichier d'inclusion n'existe pas encore lorsque CMakeLists du projet B est lancé.

Existe-t-il un moyen de rendre l'inclusion dépendante du projet externe en cours de construction ?

Mise à jour : J'ai écrit un court Tutoriel CMake par exemple sur la base de ce problème et d'autres problèmes communs que j'ai rencontrés.

76voto

Fraser Points 20579

Je pense que vous mélangez deux paradigmes différents ici.

Comme vous l'avez noté, le système très flexible ExternalProject exécute ses commandes au moment de la construction, donc vous ne pouvez pas utiliser directement le fichier d'importation du Projet A puisqu'il n'est créé qu'une fois le Projet A installé.

Si vous voulez include le fichier d'importation du projet A, vous allez ont pour installer manuellement le projet A avant d'invoquer le CMakeLists.txt du projet B - comme toute autre dépendance d'un tiers ajoutée de cette manière ou via find_file / find_library / find_package .

Si vous voulez faire usage de ExternalProject_Add vous devrez ajouter quelque chose comme ce qui suit à votre CMakeLists.txt :

ExternalProject_Add(project_a
  URL ...project_a.tar.gz
  PREFIX ${CMAKE_CURRENT_BINARY_DIR}/project_a
  CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
)

include(${CMAKE_CURRENT_BINARY_DIR}/lib/project_a/project_a-targets.cmake)

ExternalProject_Get_Property(project_a install_dir)
include_directories(${install_dir}/include)

add_dependencies(project_b_exe project_a)
target_link_libraries(project_b_exe ${install_dir}/lib/alib.lib)

26voto

Timmmm Points 9909

Ce poste a une réponse raisonnable :

CMakeLists.txt.in :

cmake_minimum_required(VERSION 2.8.2)

project(googletest-download NONE)

include(ExternalProject)
ExternalProject_Add(googletest
  GIT_REPOSITORY    https://github.com/google/googletest.git
  GIT_TAG           master
  SOURCE_DIR        "${CMAKE_BINARY_DIR}/googletest-src"
  BINARY_DIR        "${CMAKE_BINARY_DIR}/googletest-build"
  CONFIGURE_COMMAND ""
  BUILD_COMMAND     ""
  INSTALL_COMMAND   ""
  TEST_COMMAND      ""
)

CMakeLists.txt :

# Download and unpack googletest at configure time
configure_file(CMakeLists.txt.in
               googletest-download/CMakeLists.txt)
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
  WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download )
execute_process(COMMAND ${CMAKE_COMMAND} --build .
  WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download )

# Prevent GoogleTest from overriding our compiler/linker options
# when building with Visual Studio
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)

# Add googletest directly to our build. This adds
# the following targets: gtest, gtest_main, gmock
# and gmock_main
add_subdirectory(${CMAKE_BINARY_DIR}/googletest-src
                 ${CMAKE_BINARY_DIR}/googletest-build)

# The gtest/gmock targets carry header search path
# dependencies automatically when using CMake 2.8.11 or
# later. Otherwise we have to add them here ourselves.
if (CMAKE_VERSION VERSION_LESS 2.8.11)
  include_directories("${gtest_SOURCE_DIR}/include"
                      "${gmock_SOURCE_DIR}/include")
endif()

# Now simply link your own targets against gtest, gmock,
# etc. as appropriate

Cependant, il semble que ce soit un peu bidon. J'aimerais proposer une solution alternative - utiliser les submodules Git.

cd MyProject/dependencies/gtest
git submodule add https://github.com/google/googletest.git
cd googletest
git checkout release-1.8.0
cd ../../..
git add *
git commit -m "Add googletest"

Ensuite, dans MyProject/dependencies/gtest/CMakeList.txt vous pouvez faire quelque chose comme :

cmake_minimum_required(VERSION 3.3)

if(TARGET gtest) # To avoid diamond dependencies; may not be necessary depending on you project.
    return()
endif()

add_subdirectory("googletest")

Je n'ai pas encore essayé de manière approfondie mais cela semble plus propre.

Edit : Il y a un inconvénient à cette approche : Le sous-répertoire pourrait exécuter install() des commandes que vous ne voulez pas. Ce post propose une approche pour les désactiver mais il était bogué et ne fonctionnait pas pour moi.

Edit 2 : Si vous utilisez add_subdirectory("googletest" EXCLUDE_FROM_ALL) il semble que cela signifie que le install() dans le sous-répertoire ne sont pas utilisées par défaut.

9voto

David Points 3254

Edit : CMake a maintenant un support intégré pour cela. Voir nouvelle réponse qui utilise FetchContent .

Vous pouvez également forcer la construction de la cible dépendante dans un processus make secondaire.

Ver ma réponse sur un sujet connexe.

3voto

Gopi Points 318

Je cherchais une solution similaire. Les réponses ici et le tutoriel en haut de page sont instructifs. J'ai étudié les posts/blogs référencés ici pour construire le mien avec succès. Je poste le fichier CMakeLists.txt complet qui a fonctionné pour moi. Je pense que cela pourrait être utile comme modèle de base pour les débutants.

"CMakeLists.txt"

cmake_minimum_required(VERSION 3.10.2)

# Target Project
project (ClientProgram)

# Begin: Including Sources and Headers
include_directories(include)
file (GLOB SOURCES "src/*.c")
# End: Including Sources and Headers

# Begin: Generate executables
add_executable (ClientProgram ${SOURCES})
# End: Generate executables

# This Project Depends on External Project(s) 
include (ExternalProject)

# Begin: External Third Party Library
set (libTLS ThirdPartyTlsLibrary)
ExternalProject_Add (${libTLS}
PREFIX          ${CMAKE_CURRENT_BINARY_DIR}/${libTLS}
# Begin: Download Archive from Web Server
URL             http://myproject.com/MyLibrary.tgz
URL_HASH        SHA1=<expected_sha1sum_of_above_tgz_file>
DOWNLOAD_NO_PROGRESS ON
# End: Download Archive from Web Server

# Begin: Download Source from GIT Repository
#    GIT_REPOSITORY  https://github.com/<project>.git
#    GIT_TAG         <Refer github.com releases -> Tags>
#    GIT_SHALLOW     ON
# End: Download Source from GIT Repository

# Begin: CMAKE Comamnd Argiments
CMAKE_ARGS      -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_CURRENT_BINARY_DIR}/${libTLS}
CMAKE_ARGS      -DUSE_SHARED_LIBRARY:BOOL=ON
# End: CMAKE Comamnd Argiments    
)

# The above ExternalProject_Add(...) construct wil take care of \
# 1. Downloading sources
# 2. Building Object files
# 3. Install under DCMAKE_INSTALL_PREFIX Directory

# Acquire Installation Directory of 
ExternalProject_Get_Property (${libTLS} install_dir)

# Begin: Importing Headers & Library of Third Party built using ExternalProject_Add(...)
# Include PATH that has headers required by Target Project
include_directories (${install_dir}/include)

# Import librarues from External Project required by Target Project
add_library (lmytls SHARED IMPORTED)
set_target_properties (lmytls PROPERTIES IMPORTED_LOCATION ${install_dir}/lib/libmytls.so)
add_library (lmyxdot509 SHARED IMPORTED)
set_target_properties(lmyxdot509 PROPERTIES IMPORTED_LOCATION ${install_dir}/lib/libmyxdot509.so)

# End: Importing Headers & Library of Third Party built using ExternalProject_Add(...)
# End: External Third Party Library

# Begin: Target Project depends on Third Party Component
add_dependencies(ClientProgram ${libTLS})
# End: Target Project depends on Third Party Component

# Refer libraries added above used by Target Project
target_link_libraries (ClientProgram lmytls lmyxdot509)

1voto

TarmoPikaro Points 11

De cmake ExternalProject_Add peut en effet être utilisé, mais ce que je n'ai pas aimé à son sujet - c'est qu'il effectue quelque chose pendant la construction, le sondage continu, etc... Je préférerais que le projet soit construit pendant la phase de construction, et rien d'autre. J'ai essayé de remplacer ExternalProject_Add en plusieurs tentatives, malheureusement sans succès.

J'ai ensuite essayé d'ajouter un submodule git, mais cela entraîne tout le dépôt git, alors que dans certains cas, je n'ai besoin que d'un sous-ensemble du dépôt git. Ce que j'ai vérifié - c'est qu'il est en effet possible d'effectuer un checkout git clairsemé, mais cela nécessite une fonction séparée, que j'ai écrite ci-dessous.

#-----------------------------------------------------------------------------
#
# Performs sparse (partial) git checkout
#
#   into ${checkoutDir} from ${url} of ${branch}
#
# List of folders and files to pull can be specified after that.
#-----------------------------------------------------------------------------
function (SparseGitCheckout checkoutDir url branch)
    if(EXISTS ${checkoutDir})
        return()
    endif()

    message("-------------------------------------------------------------------")
    message("sparse git checkout to ${checkoutDir}...")
    message("-------------------------------------------------------------------")

    file(MAKE_DIRECTORY ${checkoutDir})

    set(cmds "git init")
    set(cmds ${cmds} "git remote add -f origin --no-tags -t master ${url}")
    set(cmds ${cmds} "git config core.sparseCheckout true")

    # This command is executed via file WRITE
    # echo <file or folder> >> .git/info/sparse-checkout")

    set(cmds ${cmds} "git pull --depth=1 origin ${branch}")

    # message("In directory: ${checkoutDir}")

    foreach( cmd ${cmds})
        message("- ${cmd}")
        string(REPLACE " " ";" cmdList ${cmd})

        #message("Outfile: ${outFile}")
        #message("Final command: ${cmdList}")

        if(pull IN_LIST cmdList)
            string (REPLACE ";" "\n" FILES "${ARGN}")
            file(WRITE ${checkoutDir}/.git/info/sparse-checkout ${FILES} )
        endif()

        execute_process(
            COMMAND ${cmdList}
            WORKING_DIRECTORY ${checkoutDir}
            RESULT_VARIABLE ret
        )

        if(NOT ret EQUAL "0")
            message("error: previous command failed, see explanation above")
            file(REMOVE_RECURSE ${checkoutDir})
            break()
        endif()
    endforeach()

endfunction()

SparseGitCheckout(${CMAKE_BINARY_DIR}/catch_197 https://github.com/catchorg/Catch2.git v1.9.7 single_include)
SparseGitCheckout(${CMAKE_BINARY_DIR}/catch_master https://github.com/catchorg/Catch2.git master single_include)

J'ai ajouté deux appels de fonction ci-dessous juste pour illustrer comment utiliser la fonction.

Quelqu'un pourrait ne pas vouloir vérifier master / trunk, car celui-ci pourrait être cassé - il est alors toujours possible de spécifier un tag spécifique.

La vérification ne sera effectuée qu'une seule fois, jusqu'à ce que vous effaciez le dossier cache.

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