CMake: Как создавать внешние проекты и включать их цели

114

У меня есть проект 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)

Проблема в том, что включаемый файл еще не существует при запуске CMakeLists проекта B.

Есть ли способ сделать включение зависимым от создаваемого внешнего проекта?

Обновление : я написал короткое руководство по CMake by Example, основанное на этой и других распространенных проблемах, с которыми я столкнулся.

Mirkokiefer
источник

Ответы:

67

Я думаю, вы смешиваете здесь две разные парадигмы.

Как вы отметили, очень гибкий ExternalProjectмодуль выполняет свои команды во время сборки, поэтому вы не можете напрямую использовать файл импорта проекта A, поскольку он создается только после установки Project 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)
Fraser
источник
2
Спасибо за Ваш ответ. То, что вы предлагаете, похоже на то, что я предлагал раньше. Я надеялся найти способ использовать экспортированные цели, поскольку это казалось более приятным интерфейсом, чем указание путей к
библиотекам
7
Я хотел избежать включения источника внешних проектов в свое дерево исходных текстов. Было бы здорово, если бы ExternalProject_Addпросто вели себя как add_subdirectoryи все цели выставлялись. Решение, которое вы описали выше, вероятно, остается самым чистым.
mirkokiefer
2
Подумайте о том, чтобы сделать их обе сборки ExternalProject, а затем сделать так, чтобы B зависел от A, и тогда файл CMakeLists для проекта B будет включать целевой файл из проекта A, но ваши CMakeLists "Super Build" просто построят A, а затем B, оба как ExternalProjects ...
DLRdave 07
3
@DLRdave - я видел, как решение Super Build рекомендовалось несколько раз, но я не уверен, какие преимущества оно дает по сравнению с включением только некоторых внешних проектов через ExternalProject. Это последовательность, или более каноничность, или что-то еще? Я уверен, что мне здесь не хватает чего-то фундаментального.
Fraser
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 тоже рекомендуют это.
младший
5

Изменить: CMake теперь имеет встроенную поддержку для этого. Смотрите новый ответ .

Вы также можете принудительно построить зависимую цель во вторичном процессе make.

См. Мой ответ по связанной теме.

Дэвид
источник
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)
Гопи
источник