CMake: come costruire progetti esterni e includere i loro obiettivi


114

Ho un progetto A che esporta una libreria statica come destinazione:

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

Ora voglio utilizzare il progetto A come progetto esterno dal progetto B e includere i suoi obiettivi costruiti:

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)

Il problema è che il file di inclusione non esiste ancora quando si esegue CMakeLists del progetto B.

C'è un modo per rendere l'inclusione dipendente dal progetto esterno in costruzione?

Aggiornamento : ho scritto un breve tutorial su CMake by Example basato su questo e altri problemi comuni che ho riscontrato.

Risposte:


67

Penso che tu stia mescolando due diversi paradigmi qui.

Come hai notato, il ExternalProjectmodulo altamente flessibile esegue i suoi comandi in fase di compilazione, quindi non puoi fare un uso diretto del file di importazione del progetto A poiché viene creato solo una volta installato il progetto A.

Se si desidera includefile di importazione di Progetto A, sarà necessario installare Progetto A manualmente prima di richiamare Progetto B CMakeLists.txt - proprio come qualsiasi altra dipendenza di terze parti aggiunto in questo modo o tramite find_file/ find_library/ find_package.

Se vuoi utilizzarlo ExternalProject_Add, dovrai aggiungere qualcosa di simile al seguente al tuo 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)

2
Grazie per la tua risposta. Quello che suggerisci è simile a quello che avevo prima. Speravo di trovare un modo per utilizzare gli obiettivi esportati in quanto sembrava un'interfaccia più carina rispetto alla specifica manuale dei percorsi della lib ...
mirkokiefer

7
Volevo evitare di dover includere il sorgente di progetti esterni nel mio albero dei sorgenti. Sarebbe fantastico se ExternalProject_Addsolo si comportasse come add_subdirectoryed esponesse tutti gli obiettivi. La soluzione che hai descritto sopra è probabilmente ancora la più pulita.
mirkokiefer

2
Considera l'idea di crearli entrambi build ExternalProject, quindi fare in modo che B dipenda da A, quindi il file CMakeLists per il progetto B includerebbe il file target dal progetto A, ma il tuo CMakeLists "Super Build" creerebbe solo A e poi B, entrambi come ExternalProjects ...
DLRdave

3
@DLRdave - Ho visto la soluzione Super Build consigliata un paio di volte, ma immagino di non essere sicuro dei vantaggi che offre rispetto all'inclusione solo di alcuni progetti esterni tramite ExternalProject. È coerenza, o più canonica, o qualcos'altro? Sono sicuro che mi manca qualcosa di fondamentale qui.
Fraser

6
Uno dei problemi con questa soluzione è che abbiamo appena codificato il nome della libreria (alib.lib), il che rende il sistema di compilazione non multipiattaforma, poiché diversi sistemi operativi utilizzano schemi di denominazione diversi per le librerie condivise e si adattano a questi diversi nomi schemi è una delle caratteristiche di CMake.
nsg

22

Questo post ha una risposta ragionevole:

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

Tuttavia sembra piuttosto hacky. Vorrei proporre una soluzione alternativa: utilizzare i sottomoduli 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"

Quindi MyProject/dependencies/gtest/CMakeList.txtpuoi fare qualcosa come:

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

Non l'ho ancora provato ampiamente, ma sembra più pulito.

Modifica: questo approccio presenta uno svantaggio: la sottodirectory potrebbe eseguire install()comandi che non desideri. Questo post ha un approccio per disabilitarli ma era bacato e non ha funzionato per me.

Modifica 2: se lo usi add_subdirectory("googletest" EXCLUDE_FROM_ALL), sembra che i install()comandi nella sottodirectory non vengano utilizzati per impostazione predefinita.


Probabilmente sono solo io che sono eccessivamente cauto perché questo è solo un esempio e gtest è probabilmente abbastanza stabile, ma consiglio vivamente di usare sempre uno specifico GIT_TAGdurante il clone, potresti perdere la ripetibilità della build perché tra 2 anni qualcuno che esegue lo script di build riceverà un versione diversa da quella che hai fatto. I documenti di CMake consigliano anche questo.
jrh

5

Modifica: CMake ora ha il supporto integrato per questo. Vedi nuova risposta .

È inoltre possibile forzare la creazione della destinazione dipendente in un processo di creazione secondario

Vedi la mia risposta su un argomento correlato.


1

Cmake ExternalProject_Addpuò effettivamente essere utilizzato, ma quello che non mi è piaciuto è che esegue qualcosa durante la compilazione, il sondaggio continuo, ecc ... Preferirei costruire il progetto durante la fase di compilazione, nient'altro. Ho provato a eseguire l'override ExternalProject_Addin diversi tentativi, purtroppo senza successo.

Poi ho provato anche ad aggiungere il sottomodulo git, ma questo trascina l'intero repository git, mentre in alcuni casi ho bisogno solo di un sottoinsieme dell'intero repository git. Quello che ho controllato - è effettivamente possibile eseguire un checkout git sparse, ma ciò richiede una funzione separata, che ho scritto di seguito.

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

Ho aggiunto due chiamate di funzione di seguito solo per illustrare come utilizzare la funzione.

Qualcuno potrebbe non voler effettuare il checkout master / trunk, in quanto potrebbe essere rotto, quindi è sempre possibile specificare un tag specifico.

Il checkout verrà eseguito solo una volta, finché non svuoti la cartella della cache.


1

Stavo cercando una soluzione simile. Le risposte qui e il tutorial in alto sono informative. Ho studiato post / blog riferiti qui per costruire il mio successo. Sto pubblicando CMakeLists.txt completo che ha funzionato per me. Immagino che questo sarebbe utile come modello di base per i principianti.

"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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.