Cmake: How to Set Up Source, Library and Cmakelists.Txt Dependencies

CMake: How to set up source, library and CMakeLists.txt dependencies?

Adding the same subdirectory multiple times is out of question, it's not how CMake is intended to work. There are two main alternatives to do it in a clean way:

  1. Build your libraries in the same project as your app. Prefer this option for libraries you're actively working on (while you're working on the app) so they are likely to be frequently edited and rebuilt. They will also show up in the same IDE project.

  2. Build your libraries in an external project (and I don't mean ExternalProject). Prefer this option for libraries that are just used by your app but you're not working on them. This is the case for most third-party libraries. They will not clutter your IDE workspace, either.

Method #1

  • your app's CMakeLists.txt adds the subdirectories of the libraries (and your libs' CMakeLists.txt's don't)
  • your app's CMakeLists.txt is responsible to add all immediate and transitive dependencies and to add them in the proper order
  • it assumes that adding the subdirectory for libx will create some target (say libx) that can be readily used with target_link_libraries

As a sidenote: for the libraries it's a good practice to create a full-featured library target, that is, one that contains all the information needed to use the library:

add_library(LibB Src/b.cc Inc/b.h)
target_include_directories(LibB PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/Inc>)

So the location of include directories of the library can remain an internal affair of the lib. You will only have to do this;

target_link_libraries(LibC LibB)

then the include dirs of LibB will also be added to the compilation of LibC. Use the PRIVATE modifier if LibB is not used by the public headers of LibC:

target_link_libraries(LibC PRIVATE LibB)

Method #2

Build and install your libraries in seperate CMake projects. Your libraries will install a so-called config-module which describes the locations of the headers and library files and also compile flags. Your app's CMakeList.txt assumes the libraries has already been built and installed and the config-modules can be found by the find_package command. This is a whole another story so I won't go into details here.

A few notes:

  • You can mix #1 and #2 as in most cases you will have both unchanging, third-party libs and your own libraries under development.
  • A compromise between #1 and #2 is using the ExternalProject module, preferred by many. It's like including the external projects of your libraries (built in their own build tree) into your app's project. In way it combines the disadvantages of both approaches: you can't use your libraries as targets (because they're in a different project) and you can't call find_package (because the libs are not installed the time your app's CMakeLists is configuring).
  • A variant of #2 is to build the library in an external project but instead of installing the artifacts use them from their source/build locations. For more about this see the export() command.

How to install external library from source for cmake to find it?

Mostly, developers add SDKs folder to let others use the required SDKs. It may be because they did minor/major changes inside the library and it became a custom build version and special for them. So, it means, they have to pack it inside the project since others can't find the modified version of that library. And I think, this is what you're looking for:

find_package(Library ${LIB_VERSION} EXACT REQUIRED PATHS "${LIB_DIR}")

By declaring PATHS you can have CMake look for the libraries specific directories.

How to include library from sibling directory in CMakeLists.txt for compilation

Binary directory has to be a subdirectory of current dir, it can't be above ../bin. Use:

add_subdirectory(../private/corelib some_unique_name)

Overall, let's fix some issues. A more advanced CMake might look like this:

# system/CmakeLists.txt
add_subdirectory(private EXCLUDE_FROM_ALL)
add_subdirectory(collections)

# system/collections/CMakeLists.txt
cmake_minimum_required(VERSION 3.11)
project(collections VERSION 0.1.0 LANGUAGES C)
file(GLOB_RECURSE srcs *.c *.h)
add_library(collections ${srcs})
# Use only target_* intefaces
target_include_directories(collections PUBLIC
./includes
)
target_link_libraries(collections PRIVATE
corelib
)

# system/private/CMakeLists.txt
add_subdirectory(corelib)

# system/private/corelib/CMakeLists.txt
cmake_minimum_required(VERSION 3.11)
project(corelib VERSION 0.1.0 LANGUAGES C)
file(GLOB_RECURSE srcs *.c *.h)
add_library(corelib ${srcs})
target_include_directorieS(corelib PUBLIC ./includes)

# system/CMakePresets.json
{
... see documentation ...
"configurePresets": [
{
...
"cacheVariables": {
"BUILD_SHARED_LIBS": "1",
"ARCHIVE_OUTPUT_DIRECTORY": "${binaryDir}/bin",
"LIBRARY_OUTPUT_DIRECTORY": "${binaryDir}/bin",
"RUNTIME_OUTPUT_DIRECTORY": "${binaryDir}/bin"
}
}

I.e. overall, I do not think every project inside system wants to compile his own separate instance of corelib, rather one corelib should be shared. Just add corelib once, from anywhere. Note that it doesn't have to be in order - you can target_link_libraries on targets before they are defined.

CMake library dependencies

You can add to your CMakeLists.txt for libB, another include directory:

include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../libA/include")

CMake and finding other projects and their dependencies

Easy. Here is the example from the top of my head:

The top level 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.



Related Topics



Leave a reply



Submit