CMake: Як будувати зовнішні проекти та включати їх цілі


113

У мене є проект A, який експортує статичну бібліотеку як ціль:

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

Тепер я хочу використовувати Project A як зовнішній проект від Project B і включати його вбудовані цілі:

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)

Проблема полягає в тому, що файл include ще не існує при запуску CMakeLists проекту B.

Чи є спосіб зробити включення залежним від зовнішнього проекту, який будується?

Оновлення : Я написав короткий підручник CMake by Example на основі цієї та інших поширених проблем.

Відповіді:


67

Я думаю, ви тут змішуєте дві різні парадигми.

Як ви зазначали, надзвичайно гнучкий ExternalProjectмодуль виконує свої команди під час збирання, тому ви не можете безпосередньо використовувати файл імпорту проекту A, оскільки він створений лише після встановлення проекту A.

Якщо ви хочете includeімпортувати файл Проект А, ви будете мати , щоб встановити Project вручну перед викликом CMakeLists.txt Проект B - точно як і будь-яка інша залежність третьою стороною додав цей шлях або через find_file/ find_library/ find_package.

Якщо ви хочете скористатися ExternalProject_Add, вам потрібно додати щось подібне до вашого 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
Дякую за вашу відповідь. Те, що ви пропонуєте, схоже на те, що я мав раніше. Я сподівався знайти спосіб використати експортовані цілі, оскільки це здавалося приємнішим інтерфейсом, ніж вказівка ​​шляхів lib вручну ...
mirkokiefer

7
Я хотів уникнути необхідності включати джерело зовнішніх проектів у своє джерело. Було б чудово, якби ExternalProject_Addпросто так поводився add_subdirectoryі виставляв усі цілі. Розчин, який ви описали вище, мабуть, все ще є найчистішим.
mirkokiefer

2
Подумайте про те, як зробити їх як збірками ExternalProject, а потім мати B залежать від A, і тоді файл CMakeLists для проекту B включатиме файл цілей проекту A, але ваші CMakeLists "Super Build" просто будують A, а потім B, обидва як ExternalProjects ...
DLRdave

3
@DLRdave - Я кілька разів бачив рішення Super Build, яке рекомендується, але, мабуть, я не впевнений, які переваги надає він, включаючи лише деякі зовнішні проекти через ExternalProject. Це послідовність, чи більш канонічна, чи щось інше? Я впевнений, що тут я пропускаю щось принципове.
Фрейзер

6
Однією з проблем цього рішення є те, що ми просто жорстко кодували ім’я бібліотеки (alib.lib), завдяки чому система збірки не є кросплатформою, оскільки різні ОС використовують різні схеми імен для спільних бібліотек та адаптуються до цих різних імен схеми - одна з особливостей CMake.
nsg

22

Ця публікація має обґрунтовану відповідь:

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

Однак це здається досить хитким. Я хотів би запропонувати альтернативне рішення - використовувати підмодулі 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"

Тоді MyProject/dependencies/gtest/CMakeList.txtви можете зробити щось на кшталт:

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

Я ще не намагався цього широко, але це здається чистішим.

Редагувати: У цього підходу є і недолік: підкаталог може виконувати install()команди, які вам не потрібні. У цій публікації є підхід до їх відключення, але це було помилково і не працювало для мене.

Редагувати 2: Якщо ви користуєтеся, add_subdirectory("googletest" EXCLUDE_FROM_ALL)то, мабуть, значить, install()команди в підкаталозі за замовчуванням не використовуються.


Це, мабуть, просто надто обережно, тому що це лише приклад, і gtest, ймовірно, досить стабільний, але я настійно рекомендую завжди використовувати конкретний GIT_TAGпід час клонування, ви можете втратити повторюваність збірки, оскільки через 2 роки хтось із запущеним сценарієм зборки отримає інша версія, ніж те, що ви робили. Документи CMake також рекомендують це.
jrh

5

Редагувати: CMake тепер має вбудовану підтримку для цього. Дивіться нову відповідь .

Ви також можете примусити побудувати залежну ціль у процесі вторинного створення

Дивіться мою відповідь на пов’язану тему.


1

cmake ExternalProject_Addсправді може використовуватися, але те, що мені не сподобалось у цьому - це те, що він виконує щось під час збирання, безперервного опитування тощо ... Я б вважав за краще будувати проект під час фази збірки, нічого іншого. Я намагався перемогти ExternalProject_Addв декількох спробах, на жаль, без успіху.

Тоді я спробував також додати підмодуль git, але це перетягує ціле сховище git, тоді як у певних випадках мені потрібно лише підмножину цілого сховища git. Те, що я перевірив - це дійсно можливо виконати розріджену перевірку git, але для цього потрібна окрема функція, про яку я писав нижче.

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

Нижче я додав два виклики функцій, щоб проілюструвати, як використовувати цю функцію.

Хтось може не хотіти перевірити майстер / багажник, оскільки це може бути зламано - тоді завжди можна вказати конкретний тег.

Оформлення замовлення буде здійснено лише один раз, поки ви не очистите папку кешу.


1

Я шукав подібне рішення. Відповіді тут і Посібник зверху є інформативними. Я вивчав публікації / блоги, згадані тут, щоб будувати шахту успішною. Я публікую повний CMakeLists.txt, який працював на мене. Я думаю, це було б корисно як основний шаблон для початківців.

"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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.