Managing Multiple Modules in CMake

Background

CMake

CMake is a cross-platform open-source build tool used to manage and automatically generate the build process of software projects.

Managing Multiple Modules in CMake

CMake automatically generates build system files suitable for different compilers and operating systems, such as Makefile and Visual Studio solutions, based on the descriptions in the CMakeLists.txt file.

Multiple Modules

Typically, a project will have multiple modules or sub-projects. In CMake, you can use add_subdirectory to add a subdirectory to the current project. This allows the project to be decomposed into multiple sub-projects, each with its own CMakeLists.txt file to manage the build process.

Managing Multiple Modules in CMake

Managing Multiple Modules

Variable Passing

Overview

In CMake, variables defined in a directory are effective for that directory and its subdirectories, but not the other way around.

Submodules Accessing Parent Module Variables

Variables defined in the parent module are passed to the submodules.

Defining Variables in the Parent Module

Define a variable in the parent module’s CMakeLists.txt:

Managing Multiple Modules in CMake

Reading Variables in the Submodule

Read the variable in the submodule’s CMakeLists.txt:

Managing Multiple Modules in CMake

Variable Reading

Execute the cmake command, the result is as follows:

Managing Multiple Modules in CMake

Submodules Exposing Variables to Parent Modules

Variables defined in the submodule are not passed to the parent module by default, but can be exposed to the parent module using the PARENT_SCOPE option.

Defining Variables in the Submodule

Define a variable in the submodule’s CMakeLists.txt:

Managing Multiple Modules in CMake

Reading Variables in the Parent Module

Read the variable in the parent module’s CMakeLists.txt:

Managing Multiple Modules in CMake

Variable Reading

Execute the cmake command, the result is as follows:

Managing Multiple Modules in CMake

Exposing Variables to Parent Modules

Use the PARENT_SCOPE option to expose variables to the parent module:

Managing Multiple Modules in CMake

Execute the cmake command again, the result is as follows:

Managing Multiple Modules in CMake

Application Example

Overview

Based on the variable passing rules in CMake, some common configuration methods can be placed in the parent module:

# Common Configuration
cmake_minimum_required(VERSION 3.4)
...

# Submodules
add_subdirectory(A)
add_subdirectory(B)

Output Path Settings

Output path related parameters in CMake:

  • CMAKE_ARCHIVE_OUTPUT_DIRECTORY: Default folder for storing static libraries
  • CMAKE_LIBRARY_OUTPUT_DIRECTORY: Default folder for storing dynamic libraries
  • LIBRARY_OUTPUT_PATH: Default folder for storing library files
  • CMAKE_RUNTIME_OUTPUT_DIRECTORY: Directory for storing executable software

Taking the program generation path settings as an example, you can uniformly set the output path in the parent module:

cmake_minimum_required(VERSION 3.0)
project(mainProject)

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/output/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/output/bin)

add_subdirectory(loglib)
add_subdirectory(test)

The execution result is as follows:

Managing Multiple Modules in CMake

Inter-module Dependencies

Overview

In CMake, use target_include_directories and target_link_libraries to add search directories, which can be marked with modifiers:

  • PUBLIC: This module needs it, other modules depending on this module also need it
  • INTERFACE: This module does not need it, other modules depending on this module need it
  • PRIVATE: This module needs it, other modules depending on this module do not need it

Application Example

In the loglib library file, use PUBLIC to mark the header file path, while in test, only linking the library file is required.

Adding Usage Dependencies

Use PUBLIC to mark the path of the dependent header files in the CMakeLists.txt under the loglib directory:

cmake_minimum_required(VERSION 3.0)
set(CMAKE_CXX_STANDARD 11)

project(loglib)

option(BUILD_SHARED_LIBS "Build shared libraries" ON)

file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)

add_library(loglib  ${SRC_LIST})

# PUBLIC mark header file path
target_include_directories(loglib PUBLIC ${PROJECT_SOURCE_DIR}/include)

Linking Library Files

In the CMakeLists.txt under the test directory, there is no need to add the header file search directory:

cmake_minimum_required(VERSION 3.0)
set(CMAKE_CXX_STANDARD 11)
project(test)

file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
add_executable(test ${SRC_LIST})

# Only need to add the linked library
target_link_libraries(test loglib)

The execution build command result is as follows:

Managing Multiple Modules in CMake

Specifying Dependency Relationships

In CMake, the add_dependencies command is used to specify that one target depends on other targets:

# target_B depends on target_A
add_dependencies(target_B target_A)

Using add_dependencies allows you to explicitly specify the build order between targets during the build process, ensuring that dependencies are correctly satisfied.

Interface Libraries

Overview

In CMake, interface libraries do not generate actual binary files and are mainly used to share compilation and linking settings between different projects or targets.

Interface Library Example

Create an interface library and place relevant common configurations in the interface library.

Creating Library Directory

Create an interfacelib folder under the directory as shown below:

Managing Multiple Modules in CMake

Adding an Interface Library

Use INTERFACE to add an interface library:

project(interfacelib)

# Add interface library interfacelib
add_library(interfacelib INTERFACE)

Specifying C++ Standard

target_compile_features is the command used in CMake to specify compiler features.

project(interfacelib)

# Add interface library interfacelib
add_library(interfacelib INTERFACE)
# Specify C++ 11
target_compile_features(interfacelib INTERFACE cxx_std_11)

Adding Dependent Header File Paths

project(interfacelib)

# Add interface library interfacelib
add_library(interfacelib INTERFACE)
# Specify C++ 11
target_compile_features(interfacelib INTERFACE cxx_std_11)
# Dependent header file paths
target_include_directories(interfacelib INTERFACE ${PROJECT_SOURCE_DIR}/../loglib/include)

Adding Subdirectories

Add the interface library subdirectory in the main module’s CMakeLists.txt:

Managing Multiple Modules in CMake

Linking Interface Libraries

Link the interface library in the loglib and test modules:

Managing Multiple Modules in CMake

Building the Entire Project

Execute the cmake command to see all projects generated successfully:

Managing Multiple Modules in CMake

Leave a Comment