Imagine the following scenario: Project A is a shared library which has several dependencies (LibA, LibB, and LibC). Project B is an executable that has a dependency on project A, and therefore requires all of Project A's dependencies also in order to build.
Additionally, both projects are built using CMake, and Project A should not need to be installed (via the 'install' target) in order for Project B to use it, as this can become a nuisance to developers.
What is the best way to solve these dependencies using CMake? The ideal solution would be as simple as possible (though no simpler) and require minimal maintenance.
CMake files provided with a software package contain instructions for finding each build dependency. Some build dependencies are optional in that the build may succeed with a different feature set if the dependency is missing, and some dependencies are required.
CMake itself does not allow to install dependencies automatically.
Add a subdirectory to the build. Adds a subdirectory to the build. The source_dir specifies the directory in which the source CMakeLists.
Easy. Here is the example from the top of my head:
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
in 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
in 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
in 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
in 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With