CMake и поиск других проектов и их зависимостей
Представьте себе следующий сценарий: Проект A является общей библиотекой, которая имеет несколько зависимостей (LibA, LibB, LibC). Проект B - это исполняемый файл, который зависит от проекта A, поэтому для сборки требуются все зависимости проекта A.
Кроме того, оба проекта построены с использованием CMake, и не нужно устанавливать Project A (через цель 'install'), чтобы Project B мог его использовать, поскольку это может создавать неудобства для разработчиков.
Итак, вопрос в том, как лучше всего решить эти зависимости с помощью CMake? Идеальное решение было бы настолько простым, насколько это возможно (хотя и не проще), и требовало бы минимального обслуживания.
3 ответа
Легко. Вот пример из макушки моей головы:
Верхний уровень CMakeLists.txt
:
cmake_minimum_required(VERSION 2.8.10)
# You can tweak some common (for all subprojects) stuff here. For example:
set(CMAKE_DISABLE_IN_SOURCE_BUILD ON)
set(CMAKE_DISABLE_SOURCE_CHANGES ON)
if ("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}")
message(SEND_ERROR "In-source builds are not allowed.")
endif ()
set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_COLOR_MAKEFILE ON)
# Remove 'lib' prefix for shared libraries on Windows
if (WIN32)
set(CMAKE_SHARED_LIBRARY_PREFIX "")
endif ()
# When done tweaking common stuff, configure the components (subprojects).
# NOTE: The order matters! The most independent ones should go first.
add_subdirectory(components/B) # B is a static library (depends on Boost)
add_subdirectory(components/C) # C is a shared library (depends on B and external XXX)
add_subdirectory(components/A) # A is a shared library (depends on C and B)
add_subdirectory(components/Executable) # Executable (depends on A and C)
CMakeLists.txt
в components/B
:
cmake_minimum_required(VERSION 2.8.10)
project(B C CXX)
find_package(Boost
1.50.0
REQUIRED)
file(GLOB CPP_FILES source/*.cpp)
include_directories(${Boost_INCLUDE_DIRS})
add_library(${PROJECT_NAME} STATIC ${CPP_FILES})
# Required on Unix OS family to be able to be linked into shared libraries.
set_target_properties(${PROJECT_NAME}
PROPERTIES POSITION_INDEPENDENT_CODE ON)
target_link_libraries(${PROJECT_NAME})
# Expose B's public includes (including Boost transitively) to other
# subprojects through cache variable.
set(${PROJECT_NAME}_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/include
${Boost_INCLUDE_DIRS}
CACHE INTERNAL "${PROJECT_NAME}: Include Directories" FORCE)
CMakeLists.txt
в components/C
:
cmake_minimum_required(VERSION 2.8.10)
project(C C CXX)
find_package(XXX REQUIRED)
file(GLOB CPP_FILES source/*.cpp)
add_definitions(${XXX_DEFINITIONS})
# NOTE: Boost's includes are transitively added through B_INCLUDE_DIRS.
include_directories(${B_INCLUDE_DIRS}
${XXX_INCLUDE_DIRS})
add_library(${PROJECT_NAME} SHARED ${CPP_FILES})
target_link_libraries(${PROJECT_NAME} B
${XXX_LIBRARIES})
# Expose C's definitions (in this case only the ones of XXX transitively)
# to other subprojects through cache variable.
set(${PROJECT_NAME}_DEFINITIONS ${XXX_DEFINITIONS}
CACHE INTERNAL "${PROJECT_NAME}: Definitions" FORCE)
# Expose C's public includes (including the ones of C's dependencies transitively)
# to other subprojects through cache variable.
set(${PROJECT_NAME}_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/include
${B_INCLUDE_DIRS}
${XXX_INCLUDE_DIRS}
CACHE INTERNAL "${PROJECT_NAME}: Include Directories" FORCE)
CMakeLists.txt
в components/A
:
cmake_minimum_required(VERSION 2.8.10)
project(A C CXX)
file(GLOB CPP_FILES source/*.cpp)
# XXX's definitions are transitively added through C_DEFINITIONS.
add_definitions(${C_DEFINITIONS})
# NOTE: B's and Boost's includes are transitively added through C_INCLUDE_DIRS.
include_directories(${C_INCLUDE_DIRS})
add_library(${PROJECT_NAME} SHARED ${CPP_FILES})
# You could need `${XXX_LIBRARIES}` here too, in case if the dependency
# of A on C is not purely transitive in terms of XXX, but A explicitly requires
# some additional symbols from XXX. However, in this example, I assumed that
# this is not the case, therefore A is only linked against B and C.
target_link_libraries(${PROJECT_NAME} B
C)
# Expose A's definitions (in this case only the ones of C transitively)
# to other subprojects through cache variable.
set(${PROJECT_NAME}_DEFINITIONS ${C_DEFINITIONS}
CACHE INTERNAL "${PROJECT_NAME}: Definitions" FORCE)
# Expose A's public includes (including the ones of A's dependencies
# transitively) to other subprojects through cache variable.
set(${PROJECT_NAME}_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/include
${C_INCLUDE_DIRS}
CACHE INTERNAL "${PROJECT_NAME}: Include Directories" FORCE)
CMakeLists.txt
в components/Executable
:
cmake_minimum_required(VERSION 2.8.10)
project(Executable C CXX)
file(GLOB CPP_FILES source/*.cpp)
add_definitions(${A_DEFINITIONS})
include_directories(${A_INCLUDE_DIRS})
add_executable(${PROJECT_NAME} ${CPP_FILES})
target_link_libraries(${PROJECT_NAME} A C)
Чтобы прояснить это, вот соответствующая древовидная структура источника:
Root of the project
├───components
│ ├───Executable
│ │ ├───resource
│ │ │ └───icons
│ │ ├───source
| | └───CMakeLists.txt
│ ├───A
│ │ ├───include
│ │ │ └───A
│ │ ├───source
| | └───CMakeLists.txt
│ ├───B
│ │ ├───include
│ │ │ └───B
│ │ ├───source
| | └───CMakeLists.txt
│ └───C
│ ├───include
│ │ └───C
│ ├───source
| └───CMakeLists.txt
└───CMakeLists.txt
Есть много моментов, в которых это можно настроить / изменить или изменить, чтобы удовлетворить определенные потребности, но это должно, по крайней мере, помочь вам начать.
ПРИМЕЧАНИЕ: я успешно использовал эту структуру в нескольких средних и крупных проектах.
У Александра Шукаева хорошее начало, но есть ряд вещей, которые можно сделать лучше:
- Не используйте include_directories. По крайней мере, используйте
target_include_directories
, Однако вам, вероятно, даже не нужно этого делать, если вы используете импортированные цели. Используйте импортированные цели. Пример для Boost:
find_package(Boost 1.56 REQUIRED COMPONENTS date_time filesystem iostreams) add_executable(foo foo.cc) target_link_libraries(foo PRIVATE Boost::date_time Boost::filesystem Boost::iostreams )
Это заботится о каталогах include, библиотеках и т. Д. Если вы использовали Boost в своих заголовках в B, то вместо PRIVATE используйте PUBLIC, и эти зависимости будут транзитивно добавляться к тому, что зависит от B.
Не используйте подстановку файлов (если вы не используете 3.12). До недавнего времени глобализация файлов работала только во время настройки, поэтому, если вы добавляете файлы и собираете их, он не сможет обнаружить изменения, пока вы не создадите явную регенерацию проекта. Однако, если вы перечислите файлы напрямую и попытаетесь их собрать, он должен признать, что конфигурация устарела, и автоматически обновится на этапе сборки.
Здесь хороший разговор (YouTube): C++ Now 2017: Даниэль Пфайфер "Эффективный CMake"
Который охватывает идею менеджера пакетов, которая позволяет вашему корневому уровню CMake работать с find_package
ИЛИ ЖЕ subdirectory
Тем не менее, я пытался принять идеологию этого и у меня большие проблемы с использованием find_package
для всего и иметь структуру каталогов, как у вас.
Это также можно сделать с помощью CMake. Cache
Механизм для достижения того же (т.е. совместного использования переменных, специфичных для проекта):
набор (VAR "значение"
CACHE INTERNAL "")
Обратитесь к вопросу переполнения стека, как обмениваться переменными между различными файлами CMake.