CMake: Comment construire des projets externes et inclure leurs objectifs

114

J'ai un projet A qui exporte une bibliothèque statique en tant que 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 en tant que 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 exécuté.

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 didacticiel CMake by Example basé sur ceci et d'autres problèmes courants que j'ai rencontrés.

mirkokiefer
la source

Réponses:

67

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

Comme vous l'avez noté, le ExternalProjectmodule très flexible exécute ses commandes au moment de la construction, vous ne pouvez donc pas utiliser directement le fichier d'importation du projet A car il n'est créé qu'une fois que le projet A a été installé.

Si vous voulez includele fichier d'importation de projet A, vous devez installer Project A manuellement avant d' appeler CMakeLists.txt du projet B - comme tout autre dépendance tiers ajouté cette façon ou via find_file/ find_library/ find_package.

Si vous souhaitez utiliser 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)
Fraser
la source
2
Merci pour votre réponse. Ce que vous suggérez est similaire à ce que j'avais auparavant. J'espérais trouver un moyen d'utiliser les cibles exportées car cela semblait être une interface plus agréable que de spécifier manuellement les chemins de la
bibliothèque
7
Je voulais éviter d'avoir à inclure la source des projets externes dans mon arborescence des sources. Ce serait formidable de ExternalProject_Addse comporter comme add_subdirectoryet d'exposer toutes les cibles. La solution que vous avez décrite ci-dessus est probablement toujours la plus propre.
mirkokiefer
2
Envisagez de les créer à la fois pour ExternalProject, puis de faire dépendre B de A, puis le fichier CMakeLists pour le projet B inclurait le fichier cibles du projet A, mais vos CMakeLists "Super Build" créeraient simplement A puis B, tous deux en tant que ExternalProjects ...
DLRdave
3
@DLRdave - J'ai vu la solution Super Build recommandée à plusieurs reprises, mais je suppose que je ne suis pas sûr des avantages qu'elle offre en incluant uniquement certains projets externes via ExternalProject. Est-ce la cohérence, ou plus canonique, ou autre chose? Je suis sûr qu'il me manque quelque chose de fondamental ici.
Fraser
6
L'un des problèmes avec cette solution est que nous venons de coder en dur le nom de la bibliothèque (alib.lib), ce qui rend le système de construction non multiplateforme, car différents systèmes d'exploitation utilisent des schémas de dénomination différents pour les bibliothèques partagées et s'adaptent à ces différents noms. schémas est l'une des fonctionnalités de CMake.
nsg le
22

Ce message 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, cela semble assez piraté. Je voudrais proposer une solution alternative - utiliser les sous-modules 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, MyProject/dependencies/gtest/CMakeList.txtvous 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é cela de manière approfondie, mais cela semble plus propre.

Edit: Il y a un inconvénient à cette approche: le sous-répertoire peut exécuter des install()commandes que vous ne voulez pas. Cet article a une approche pour les désactiver, mais il était bogué et n'a pas fonctionné pour moi.

Edit 2: Si vous l'utilisez, add_subdirectory("googletest" EXCLUDE_FROM_ALL)il semble que les install()commandes du sous-répertoire ne sont pas utilisées par défaut.

Timmmm
la source
C'est probablement juste moi qui suis trop prudent car ce n'est qu'un exemple et gtest est probablement assez stable, mais je recommande fortement de toujours utiliser un spécifique GIT_TAGpendant le clonage, vous pourriez perdre la répétabilité de la construction car dans 2 ans, quelqu'un exécutant le script de construction obtiendra un version différente de ce que vous avez fait. La documentation de CMake le recommande également.
jrh
5

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

Vous pouvez également forcer la génération de la cible dépendante dans un processus de création secondaire

Voir ma réponse sur un sujet connexe.

David
la source
1

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

Ensuite, j'ai essayé également d'ajouter un sous-module git, mais cela fait glisser 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 entier. Ce que j'ai vérifié - il est en effet possible d'effectuer un contrôle 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 peut ne pas aimer extraire le master / trunk, car celui-ci peut être cassé - il est alors toujours possible de spécifier une balise spécifique.

L'extraction ne sera effectuée qu'une seule fois, jusqu'à ce que vous effaciez le dossier de cache.

TarmoPikaro
la source
1

Je cherchais une solution similaire. Les réponses ici et le tutoriel en haut sont informatifs. J'ai étudié les articles / blogs référencés ici pour réussir le mien. Je poste CMakeLists.txt complet a fonctionné pour moi. Je suppose que ce serait 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)
Gopi
la source