Подтвердить что ты не робот

CMake и поиск других проектов и их зависимостей

Представьте себе следующий сценарий: Проект A является общей библиотекой, которая имеет несколько зависимостей (LibA, LibB и LibC). Проект B - это исполняемый файл, который зависит от проекта A, и, следовательно, для построения требуются все зависимости проекта A.

Кроме того, оба проекта построены с использованием CMake, и не нужно устанавливать Project A (через цель 'install'), чтобы Project B мог его использовать, поскольку это может создавать неудобства для разработчиков.

Каков наилучший способ решить эти зависимости с помощью CMake? Идеальное решение было бы настолько простым, насколько это возможно (хотя и не проще) и потребовало бы минимального обслуживания.

4b9b3361

Ответ 1

Легко. Вот пример из верхней части головы:

Верхний уровень 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 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 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 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 public includes (including the ones of C 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 definitions are transitively added through C_DEFINITIONS.
add_definitions(${C_DEFINITIONS})

# NOTE: B and Boost 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 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 public includes (including the ones of A 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

Есть много моментов, когда это может быть изменено или настроено или изменено для удовлетворения определенных потребностей, но это должно по крайней мере начать с вас.

ПРИМЕЧАНИЕ. Я успешно использовал эту структуру в нескольких проектах среднего и крупного масштаба.

Ответ 2

Александр Шукаев отлично стартовал, но есть ряд вещей, которые можно сделать лучше:

  1. Не используйте include_directories. По крайней мере, используйте target_include_directories. Однако вам, вероятно, даже не нужно этого делать, если вы используете импортированные цели.
  2. Используйте импортированные цели. Пример для 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. Не используйте подстановку файлов (если вы не используете 3.12). До недавнего времени глобализация файлов работала только во время конфигурирования, поэтому, если вы добавляете файлы и собираете их, он не сможет обнаружить изменения, пока вы явно не создадите проект заново. Однако, если вы перечислите файлы напрямую и попытаетесь их собрать, он должен признать, что конфигурация устарела, и автоматически обновится на этапе сборки.

Здесь хорошие разговоры (YouTube): С++ Now 2017: Даниэль Пфайфер "Effective CMake"

Который охватывает идею менеджера пакетов, которая позволяет вашему корневому уровню CMake работать с subdirectory find_package OR, хотя я пытался принять идеологию этого и у find_package большие проблемы с использованием find_package для всего и с такой структурой каталогов, как у вас.

Ответ 3

Это также может быть сделано с использованием механизма CMake Cache для достижения того же (то есть совместного использования переменных, специфичных для проекта):

set (VAR "значение" CACHE INTERNAL "")

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