Skip to content

CMake

CMake is the builder system, managing the sources and targets to build and how (libraries, executables ...).

Basis

The project is structured such that a CMakeLists.txt is present at each level of the hierarchy: ::

project
├── Prog1
│   ├── src
│   |   ├── main.cpp
│   |   └── myinclude.h
|   ├── build
|   ├── res
|   └── CMakeLists.txt
├── Prog2
│   ├── src
│   |   ├── main.cpp
│   |   └── myinclude.h
|   ├── build
|   ├── res
|   └── CMakeLists.txt
└── CMakeLists.txt

Each file will include some subfolders, create libraries and applications, gather source files, pack and install files ...

Learning by doing

Let's create a project made of two programs, each using a separate library. The second program also make use of a second library.

The files are available here

Structure

The structure is such as: ::

CMakeExample
├── Prog1
│   ├── src
│   |   └── main.cpp
|   └── CMakeLists.txt
├── Prog2
│   ├── src
│   |   └── main.cpp
|   ├── LibB
|   |   ├── src
|   |   |   └── LibB.cpp
|   |   ├── inc
|   |   |   └── LibB.h
│   |   └── CMakeLists.txt
|   └── CMakeLists.txt
├── LibA
│   ├── src
│   |   ├── LibA.cpp.in
│   |   └── LibA.cpp
│   ├── inc
│   |   ├── LibA.h.in
│   |   ├── LibA.h
│   |   └── LibA_export.h
|   └── CMakeLists.txt
├── build
└── CMakeLists.txt

The top CMakeLists.txt contains the following:

# Requires CMake v. 3.21, else will issue an error
cmake_minimum_required(VERSION 3.21 FATAL_ERROR)

# Top project name
project(CMake_Example VERSION 1.0)

# Setup the language to C++ 20
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Discover projects
add_subdirectory(LibA)
add_subdirectory(Prog1)
add_subdirectory(Prog2)

First, the minimum CMake version is defined. Some new functionalities may not be present in older revisions.

Then is defined the "node" name, the project() name. A version can be added, that can somehow be retrieved in other files.

To define a variable, the set() command is used. For example, the C++ flavor is specified here to C++20.

Finally, the three subdirectories (LibA and both programs) are added to the hierarchy.

LibA

The LibA is defined with the following:

# Project name
set(PROJECT_NAME LibA)
# Declare the project
project(${PROJECT_NAME} VERSION 1.0)
# Define where the includes are
set(LIBA_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/inc") # PARENT_SCOPE does not define the variable to this level, so do it twice
set(LIBA_INCLUDE_DIR ${LIBA_INCLUDE_DIR} PARENT_SCOPE)
set(PROJ_EXP_NAME ${PROJECT_NAME}_EXPORT)
set(PROJ_EXP_F_NAME ${PROJECT_NAME}_export.h)
set(PROJ_STAT_DEF ${PROJECT_NAME}_STATIC)
# Transform the .h.in (using some CMake vars) to a .h with fixed values
# Any value @MY_VAR@ is replaced with the current value
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/inc/LibA.h.in" "${CMAKE_CURRENT_SOURCE_DIR}/inc/LibA.h" @ONLY)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/LibA.cpp.in" "${CMAKE_CURRENT_SOURCE_DIR}/src/LibA.cpp" @ONLY)
# Include the .h files here, so any file can use simple includes instead of relative paths
include_directories(${LIBA_INCLUDE_DIR})
# Create the library
add_library(${PROJECT_NAME} SHARED inc/LibA.h inc/${PROJ_EXP_F_NAME} src/LibA.cpp)
# Generate the export
include(GenerateExportHeader)
GENERATE_EXPORT_HEADER (${PROJECT_NAME}
    BASE_NAME ${PROJECT_NAME}
    EXPORT_MACRO_NAME ${PROJ_EXP_NAME}
    EXPORT_FILE_NAME ${PROJ_EXP_F_NAME}
    STATIC_DEFINE ${PROJ_STAT_DEF}
)
# Copy the generated file to the current source directory (else won't compile)
execute_process(
    COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/${PROJ_EXP_F_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/inc/${PROJ_EXP_F_NAME}
)

The project name is defined with a variable, so it can be retrieved later and changed easily. It is then declared with its version.

The LIBA_INCLUDE_DIR is defined twice. Once at the library level, once at the PARENT_SCOPE level to be accessible by other programs. Some other values are defined for the DLL export.

The configure_file() command will transform the first file (identified by its .in extension) to a second one, replacing all @MY_VAR@ to the current CMake value.

The library inc directory is included, so that any file can have an easy access to the .h. It is then generated.

Since it is a SHARED one, it will create a .dll file that can be shared between both programs to reduce the overall size. The GENERATE_EXPORT_HEADER will create en export file for the DLL. It is finally copied to the current source directory with the execute_process() command.

Prog1

The Prog1 is defined with the following:

# Project name
set(PROJECT_NAME Prog1)
# Declare the project
project(${PROJECT_NAME} VERSION 1.0)
# Create the library
add_executable(${PROJECT_NAME} src/main.cpp)
# Link to previsouly created library
include_directories(${LIBA_INCLUDE_DIR})
target_link_libraries(${PROJECT_NAME} LibA)

After declaring the project, the add_executable() creates a compiled binary with the given sources. Since it uses the previous LibA, the target_link_libraries(${PROJECT_NAME} LibA) links it to the program, such that it will be built and released beforehand. The include_directories() will allow to easily include the library .h files.

Prog2

The Prog2 is defined with the following:

# Project name
set(PROJECT_NAME Prog2)
# Declare the project
project(${PROJECT_NAME} VERSION 1.0)
# Create the exec
add_executable(${PROJECT_NAME} src/main.cpp)
# Link to previsouly created library
include_directories(${LIBA_INCLUDE_DIR})
target_link_libraries(${PROJECT_NAME} LibA)
# Include sub library and link to
add_subdirectory(LibB)
include_directories(${LIBB_INCLUDE_DIR})
target_link_libraries(${PROJECT_NAME} LibB)

The project is defined, the executable added with its sources, the LibA linked and the LibB folder added then linked to the program.

LibB

The LibB is defined with the following:

# Project name
set(PROJECT_NAME LibB)
# Declare the project
project(${PROJECT_NAME} VERSION 1.0)
# Define where the includes are
set(LIBB_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/inc") # PARENT_SCOPE does not define the variable to this level, so do it twice
set(LIBB_INCLUDE_DIR ${LIBB_INCLUDE_DIR} PARENT_SCOPE)
# Include the .h files here, so any file can use simple includes instead of relative paths
include_directories(${LIBB_INCLUDE_DIR})
# Create the library
add_library(${PROJECT_NAME} STATIC inc/LibB.h src/LibB.cpp)

The library is declared as before, but this time will be static (.lib file) and such does not require DLL export or other specific keywords.

Project tree

To open the project, the simplest is to open the top-level CMakeLists inside an IDE (Qt, Visual Studio ...). Or you can learn to do it through command line .

CMake tree
Figure 1: CMake tree