development

CMake 및 다른 프로젝트 및 해당 종속성 찾기

big-blog 2020. 11. 7. 10:47
반응형

CMake 및 다른 프로젝트 및 해당 종속성 찾기


다음 시나리오를 상상해보십시오. 프로젝트 A는 여러 종속성 (LibA, LibB 및 LibC)이있는 공유 라이브러리입니다. 프로젝트 B는 프로젝트 A에 대한 종속성이있는 실행 파일이므로 빌드하려면 프로젝트 A의 모든 종속성이 필요합니다.

또한 두 프로젝트는 모두 CMake를 사용하여 빌드되며 프로젝트 B가이를 사용하기 위해 '설치'대상을 통해 프로젝트 A를 설치할 필요가 없어야합니다. 이는 개발자에게 성가신 일이 될 수 있습니다.

CMake를 사용하여 이러한 종속성을 해결하는 가장 좋은 방법은 무엇입니까? 이상적인 솔루션은 가능한 한 간단하고 (더 간단하지는 않지만) 최소한의 유지 관리가 필요합니다.


쉬운. 다음은 내 머리 꼭대기의 예입니다.

최상위 수준 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)

To make it clear, here is the corresponding source tree structure:

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

There are many points where this could be tweaked/customized or changed to satisfy certain needs, but this should at least get you started.

NOTE: I've successfully employed this structure in several medium-sized and large projects.


Alexander Shukaev's got a great start, but there are a number of things that could be done better:

  1. Don't use include_directories. At the very least, use target_include_directories. However, you probably don't even need to do that if you use the imported targets.
  2. Use the imported targets. Example for 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
    )
    

    This takes care of the include directories, libraries, etc. If you used Boost in your headers in B, then instead of PRIVATE, use PUBLIC, and these dependencies would be transitively added to whatever depends on B.

  3. Don't use file globing (unless you use 3.12). Until very recently, file globbing only works during configuration time, so if you add files and build, it cannot detect the changes until you explicitly regenerate the project. However if you list the files directly, and attempt to build, it should recognize the configuration is out of date and regenerate automatically in the build step.

There's good talk here (YouTube): C++Now 2017: Daniel Pfeifer “Effective CMake"

Which covers a package manager idea that allows your root level CMake to work with find_package OR subdirectory, though, I've been trying to adopt the ideology of this and am having big problems with using find_package for everything and having a directory structure like yours.


This can also be done using the CMake Cache mechanism to achieve the same (i.e. sharing of project-specific variables):

set(VAR "value" CACHE INTERNAL "")

Refer to Stack Overflow question How to share variables between different CMake files.

참고URL : https://stackoverflow.com/questions/16398937/cmake-and-finding-other-projects-and-their-dependencies

반응형