CMake: как создавать внешние проекты и включать их цели
У меня есть проект A, который экспортирует статическую библиотеку в качестве цели:
install(TARGETS alib DESTINATION lib EXPORT project_a-targets)
install(EXPORT project_a-targets DESTINATION lib/alib)
Теперь я хочу использовать Проект A как внешний проект из Проекта 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, основанное на этой и других распространенных проблемах, с которыми я столкнулся.
Ответы
Ответ 1
Думаю, вы смешиваете две разные парадигмы.
Как вы отметили, гибкий модуль ExternalProject
запускает свои команды во время сборки, поэтому вы не можете напрямую использовать проект Файл импорта, поскольку он был создан только после того, как был установлен проект A.
Если вы хотите include
Файл проекта Project A, вам нужно будет установить Project A вручную перед вызовом Project B CMakeLists.txt - как и любая другая сторонняя зависимость, добавленная таким образом, или через 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
Этот пост имеет разумный ответ:
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()
в подкаталоге по умолчанию не используются.
Ответ 3
Вы также можете принудительно построить зависимую цель во вторичном make-процессе
См. мой ответ по теме.
Ответ 4
То, что вы можете попробовать сделать, это использовать команду cmake export внутри вашего project_a
. Он работает немного иначе, чем команда install
с EXPORT option
в том project_a-targets.cmake
при запуске cmake он создает ваш файл project_a-targets.cmake
target.cmake. Сгенерированные цели импорта в файле project_a-targets.cmake
изначально указывают на несуществующие библиотечные файлы в двоичном каталоге вашего проекта, которые будут сгенерированы только после выполнения команды build.
Чтобы лучше понять, о чем я говорю, просто создайте небольшой проект cmake, который создает простую библиотеку, а затем команду экспорта (код ниже не был протестирован):
add_library (project_a lib.cpp)
export (
TARGETS
project_a
FILE
project_a-targets.cmake
)
После запуска команды cmake на вашем простом примере вы сможете найти project_a-targets.cmake
внутри вашего двоичного каталога (или в одной из его дочерних папок). Осматривая файл, вы можете заметить, что в данный момент он указывает на несуществующий файл библиотеки. Только после запуска команды сборки будет библиотека.
Итак, возвращаясь к вашей проблеме, вам нужно обновить project-a
CMakeLists.txt
включив в него команду export
. Затем вам нужно убедиться, что после обработки ExternalProject_Add
он вызывает шаг настройки, который создаст project_a-targets.cmake
, затем вы можете вызвать include(.../project_a-targets.cmake)
который должен работать. Наконец, вам нужно будет добавить зависимость между project_b
и project_a
чтобы она project_a
сборку project_b
перед попыткой сборки project_b
.
Ответ 5
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)
Я добавил два вызова функций ниже, чтобы проиллюстрировать, как использовать функцию.
Кто-то может не захотеть извлекать мастер/транк, так как он может быть сломан - тогда всегда можно указать конкретный тег.
Оформить заказ можно будет только один раз, пока вы не очистите папку кеша.