CMake и поиск других проектов и их зависимостей
представьте себе следующий сценарий: проект A-это общая библиотека, которая имеет несколько зависимостей (LibA, LibB, LibC). Проект B является исполняемым файлом, который имеет зависимость от проекта A, и поэтому требует всех зависимостей проекта A также для построения.
кроме того, оба проекта построены с использованием CMake, и проект A не должен быть установлен (через цель "установить"), чтобы проект 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. Однако вам, вероятно, даже не нужно этого делать, если вы используете импортированные цели.импортированный цели. Пример для повышения:
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 )Это заботится о том, чтобы включить каталоги, библиотеки и т. д. Если вы использовали Boost в своих заголовках в B, то вместо PRIVATE, используйте PUBLIC, и эти зависимости будут транзитивно добавлены к тому, что зависит от B.
не использовать globing файл (если вы используете 3.12). Еще совсем недавно, файл подстановка работает только во время настройки, так что если вы добавляете файлы и сборки, оно не может обнаружить изменения, пока вы явно не восстанавливаются проекта. Однако если вы перечисляете файлы напрямую и пытаетесь построить, он должен признать, что конфигурация устарела и автоматически восстанавливается на этапе сборки.
есть хороший разговор здесь (YouTube):C++Now 2017: Daniel Pfeifer "Effective CMake"
который охватывает идею менеджера пакетов, которая позволяет вашему корневому уровню CMake работать с
find_packageилиsubdirectory, хотя, я пытался принять идеологию этого и у меня большие проблемы с использованиемfind_packageдля всего и иметь структуру каталогов, как у вас.
Это также можно сделать с помощью CMake
Cacheмеханизм для достижения того же (т. е. совместное использование проектных переменных):set(VAR "value"
CACHE INTERNAL "")см. вопрос переполнения стека как обмениваться переменными между различными файлами CMake.
Comments