CMake: Jak budować zewnętrzne projekty i uwzględniać ich cele

114

Mam projekt A, który eksportuje bibliotekę statyczną jako cel:

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

Teraz chcę użyć projektu A jako projektu zewnętrznego z projektu B i uwzględnić jego wbudowane cele:

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)

Problem polega na tym, że plik dołączania jeszcze nie istnieje, gdy jest uruchamiany CMakeLists projektu B.

Czy istnieje sposób na uzależnienie dołączenia od budowanego projektu zewnętrznego?

Aktualizacja : Napisałem krótki samouczek dotyczący CMake by Example w oparciu o ten i inne typowe problemy, które napotkałem.

mirkokiefer
źródło

Odpowiedzi:

67

Myślę, że mieszasz tutaj dwa różne paradygmaty.

Jak zauważyłeś, wysoce elastyczny ExternalProjectmoduł uruchamia swoje polecenia w czasie kompilacji, więc nie możesz bezpośrednio korzystać z pliku importu projektu A, ponieważ jest on tworzony dopiero po zainstalowaniu projektu A.

Jeśli chcesz includerzutować w pliku importu, będziesz mieć do zainstalowania projekt jest ręcznie przed wywołaniem Projekt B za CMakeLists.txt - podobnie jak każda inna zależność osób trzecich dodany w ten sposób lub za pomocą find_file/ find_library/ find_package.

Jeśli chcesz skorzystać z ExternalProject_Add, musisz dodać coś takiego do swojego 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
źródło
2
Dziękuję za odpowiedź. To, co sugerujesz, jest podobne do tego, co miałem wcześniej. Miałem nadzieję, że znajdę sposób na wykorzystanie wyeksportowanych celów, ponieważ wydawało mi się, że jest to ładniejszy interfejs niż ręczne określanie ścieżek lib ...
mirkokiefer
7
Chciałem uniknąć konieczności dołączania źródeł zewnętrznych projektów do mojego drzewa źródłowego. Byłoby wspaniale, gdyby ExternalProject_Addzachowywał się jak add_subdirectoryi wystawiał wszystkie cele. Rozwiązanie, które opisałeś powyżej, jest prawdopodobnie nadal najczystsze.
mirkokiefer
2
Rozważ uczynienie ich zarówno kompilacjami ExternalProject, a następnie uzależnienie B od A, a wtedy plik CMakeLists dla projektu B będzie zawierał plik docelowy z projektu A, ale Twoje CMakeLists „Super Build” utworzyłyby tylko A, a następnie B, oba jako ExternalProjects ...
DLRdave
3
@DLRdave - Kilka razy widziałem zalecane rozwiązanie Super Build, ale myślę, że nie jestem pewien, jakie korzyści zapewnia tylko w przypadku niektórych projektów zewnętrznych za pośrednictwem ExternalProject. Czy jest to spójność, czy bardziej kanoniczna, czy coś innego? Jestem pewien, że brakuje mi tutaj czegoś podstawowego.
Fraser
6
Jednym z problemów z tym rozwiązaniem jest to, że właśnie zakodowaliśmy na stałe nazwę biblioteki (alib.lib), co sprawia, że ​​system kompilacji nie jest wieloplatformowy, ponieważ różne systemy operacyjne używają różnych schematów nazewnictwa dla bibliotek współdzielonych i dostosowują się do tych różnych nazw Schematy to jedna z cech CMake.
nsg
22

Ten post ma rozsądną odpowiedź:

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

Jednak wydaje się to dość hakerskie. Chciałbym zaproponować alternatywne rozwiązanie - użyj podmodułów 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"

Następnie MyProject/dependencies/gtest/CMakeList.txtmożesz zrobić coś takiego:

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

Nie próbowałem jeszcze tego intensywnie, ale wydaje się czystszy.

Edycja: jest wada tego podejścia: podkatalog może uruchamiać install()polecenia, których nie chcesz. Ten post ma podejście do ich wyłączania, ale był błędny i nie działał dla mnie.

Edycja 2: Jeśli używasz add_subdirectory("googletest" EXCLUDE_FROM_ALL), wydaje się, że install()polecenia w podkatalogu nie są używane domyślnie.

Timmmm
źródło
Prawdopodobnie jestem zbyt ostrożny, ponieważ to tylko przykład, a gtest jest prawdopodobnie dość stabilny, ale zdecydowanie zalecam używanie określonego GIT_TAGpodczas klonowania, możesz stracić powtarzalność kompilacji, ponieważ za 2 lata ktoś uruchamiający skrypt kompilacji otrzyma inna wersja niż ta, którą zrobiłeś. CUpewnij za docs polecić tego zbyt.
jrh
5

Edycja: CMake ma teraz wbudowaną obsługę tego. Zobacz nową odpowiedź .

Możesz także wymusić kompilację zależnego celu w dodatkowym procesie tworzenia

Zobacz moją odpowiedź na pokrewny temat.

David
źródło
1

cmake ExternalProject_Addrzeczywiście może być używany, ale nie podobało mi się to - to, że wykonuje coś podczas budowania, ciągłej ankiety itp ... Wolałbym budować projekt w fazie kompilacji, nic więcej. Próbowałem nadpisać ExternalProject_Addkilka prób, niestety bez powodzenia.

Następnie próbowałem również dodać podmoduł git, ale to przeciąga całe repozytorium git, podczas gdy w niektórych przypadkach potrzebuję tylko podzbioru całego repozytorium git. Co sprawdziłem - rzeczywiście można wykonać rzadkie wyewidencjonowanie gita, ale wymagają one oddzielnej funkcji, o której pisałem poniżej.

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

Poniżej dodałem dwa wywołania funkcji, aby zilustrować sposób korzystania z funkcji.

Ktoś może nie chcieć wykupić master / trunk, ponieważ ten może być uszkodzony - wtedy zawsze można określić konkretny tag.

Wypisanie nastąpi tylko raz, dopóki nie wyczyścisz folderu pamięci podręcznej.

TarmoPikaro
źródło
1

Szukałem podobnego rozwiązania. Odpowiedzi tutaj i samouczek na górze mają charakter informacyjny. Przestudiowałem posty / blogi, o których tutaj mowa, aby zbudować mój udany. Wysyłam kompletny plik CMakeLists.txt, który pracował dla mnie. Myślę, że byłoby to pomocne jako podstawowy szablon dla początkujących.

"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
źródło