CMake: Wie man externe Projekte erstellt und deren Ziele einbezieht

113

Ich habe ein Projekt A, das eine statische Bibliothek als Ziel exportiert:

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

Jetzt möchte ich Projekt A als externes Projekt aus Projekt B verwenden und die erstellten Ziele einbeziehen:

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)

Das Problem ist, dass die Include-Datei noch nicht vorhanden ist, wenn CMakeLists von Projekt B ausgeführt wird.

Gibt es eine Möglichkeit, das Include vom zu erstellenden externen Projekt abhängig zu machen?

Update : Ich habe ein kurzes CMake by Example-Tutorial geschrieben, das auf diesem und anderen häufig auftretenden Problemen basiert.

mirkokiefer
quelle

Antworten:

66

Ich denke, Sie verwechseln hier zwei verschiedene Paradigmen.

Wie Sie bereits bemerkt haben, führt das hochflexible ExternalProjectModul seine Befehle zur Erstellungszeit aus, sodass Sie die Importdatei von Projekt A nicht direkt verwenden können, da sie erst nach der Installation von Projekt A erstellt wird.

Wenn Sie wollen includeProjekt A der Importdatei, Sie haben Projekt A installieren manuell vor Projekt B CMakeLists.txt Aufruf - genau wie jede andere Fremd Abhängigkeit auf diese Weise hinzugefügt oder über find_file/ find_library/ find_package.

Wenn Sie davon Gebrauch machen möchten ExternalProject_Add, müssen Sie Ihrer CMakeLists.txt Folgendes hinzufügen:

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
quelle
2
Danke für deine Antwort. Was Sie vorschlagen, ähnelt dem, was ich vorher hatte. Ich hoffte, einen Weg zu finden, die exportierten Ziele zu nutzen, da es eine schönere Oberfläche zu sein schien, als die lib-Pfade manuell anzugeben ...
mirkokiefer
7
Ich wollte vermeiden, dass die Quelle externer Projekte in meinen Quellbaum aufgenommen werden muss. Es wäre großartig, wenn Sie sich ExternalProject_Addnur so verhalten add_subdirectoryund alle Ziele bloßstellen würden. Die oben beschriebene Lösung ist wahrscheinlich immer noch die sauberste.
Mirkokiefer
2
Erwägen Sie, beide ExternalProject-Builds zu erstellen und dann B von A abhängig zu machen. Die CMakeLists-Datei für Projekt B enthält dann die Zieldatei aus Projekt A, aber Ihre CMakeLists "Super Build" erstellen nur A und dann B, beide als ExternalProjects ...
DLRdave
3
@DLRdave - Ich habe die empfohlene Super Build-Lösung einige Male gesehen, bin mir aber nicht sicher, welche Vorteile sie bietet, wenn nur einige externe Projekte über einbezogen werden ExternalProject. Ist es Konsistenz oder kanonischer oder etwas anderes? Ich bin sicher, ich vermisse hier etwas Grundlegendes.
Fraser
6
Eines der Probleme bei dieser Lösung besteht darin, dass wir gerade den Bibliotheksnamen (alib.lib) fest codiert haben, wodurch das Build-System nicht plattformübergreifend wird, da unterschiedliche Betriebssysteme unterschiedliche Namensschemata für gemeinsam genutzte Bibliotheken verwenden und sich an diese unterschiedlichen Namen anpassen Schemata ist eines der Merkmale von CMake.
nsg
22

Dieser Beitrag hat eine vernünftige Antwort:

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

Es scheint jedoch ziemlich hackig. Ich möchte eine alternative Lösung vorschlagen - verwenden Sie Git-Submodule.

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"

Dann MyProject/dependencies/gtest/CMakeList.txtkönnen Sie in etwas tun wie:

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

Ich habe das noch nicht ausgiebig ausprobiert, aber es scheint sauberer zu sein.

Bearbeiten: Dieser Ansatz hat einen Nachteil: Das Unterverzeichnis führt möglicherweise install()Befehle aus, die Sie nicht möchten. Dieser Beitrag hat einen Ansatz, um sie zu deaktivieren, aber er war fehlerhaft und hat bei mir nicht funktioniert.

Bearbeiten 2: Wenn Sie verwenden add_subdirectory("googletest" EXCLUDE_FROM_ALL), bedeutet dies, dass die install()Befehle im Unterverzeichnis standardmäßig nicht verwendet werden.

Timmmm
quelle
Dies ist wahrscheinlich nur, dass ich übermäßig vorsichtig bin, da dies nur ein Beispiel ist und gtest wahrscheinlich ziemlich stabil ist. Ich empfehle jedoch dringend, GIT_TAGbeim Klonen immer ein bestimmtes zu verwenden. Sie könnten die Wiederholbarkeit des Builds verlieren, da in 2 Jahren jemand, der das Build-Skript ausführt, einen erhält andere Version als das, was du getan hast. CMakes Dokumente empfehlen dies ebenfalls.
JRH
5

Bearbeiten: CMake hat jetzt eine integrierte Unterstützung dafür. Siehe neue Antwort .

Sie können den Build des abhängigen Ziels auch in einem sekundären Make-Prozess erzwingen

Siehe meine Antwort zu einem verwandten Thema.

David
quelle
1

cmake ExternalProject_Addkann zwar verwendet werden, aber was mir nicht gefallen hat - ist, dass es während des Builds, der kontinuierlichen Abfrage usw. etwas ausführt. Ich würde es vorziehen, ein Projekt während der Build-Phase zu erstellen, sonst nichts. Ich habe versucht, ExternalProject_Addin mehreren Versuchen zu überschreiben , leider ohne Erfolg.

Dann habe ich auch versucht, ein Git-Submodul hinzuzufügen, aber das zieht das gesamte Git-Repository in Mitleidenschaft, während ich in bestimmten Fällen nur eine Teilmenge des gesamten Git-Repositorys benötige. Was ich überprüft habe - es ist zwar möglich, eine spärliche Git-Prüfung durchzuführen, dies erfordert jedoch eine separate Funktion, die ich unten geschrieben habe.

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

Ich habe unten zwei Funktionsaufrufe hinzugefügt, um zu veranschaulichen, wie die Funktion verwendet wird.

Jemand möchte möglicherweise nicht Master / Trunk auschecken, da dieser möglicherweise defekt ist. Dann ist es immer möglich, ein bestimmtes Tag anzugeben.

Das Auschecken wird nur einmal durchgeführt, bis Sie den Cache-Ordner löschen.

TarmoPikaro
quelle
1

Ich suchte nach einer ähnlichen Lösung. Die Antworten hier und das Tutorial oben sind informativ. Ich habe die hier genannten Beiträge / Blogs studiert, um meine erfolgreich aufzubauen. Ich poste eine vollständige CMakeLists.txt, die für mich funktioniert hat. Ich denke, dies wäre als grundlegende Vorlage für Anfänger hilfreich.

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