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:

 1# Requires CMake v. 3.21, else will issue an error
 2cmake_minimum_required(VERSION 3.21 FATAL_ERROR)
 3
 4# Top project name
 5project(CMake_Example VERSION 1.0)
 6
 7# Setup the language to C++ 20
 8set(CMAKE_CXX_STANDARD 20)
 9set(CMAKE_CXX_STANDARD_REQUIRED ON)
10
11# Discover projects
12add_subdirectory(LibA)
13add_subdirectory(Prog1)
14add_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:

 1# Project name
 2set(PROJECT_NAME LibA)
 3# Declare the project
 4project(${PROJECT_NAME} VERSION 1.0)
 5# Define where the includes are
 6set(LIBA_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/inc") # PARENT_SCOPE does not define the variable to this level, so do it twice
 7set(LIBA_INCLUDE_DIR ${LIBA_INCLUDE_DIR} PARENT_SCOPE)
 8set(PROJ_EXP_NAME ${PROJECT_NAME}_EXPORT)
 9set(PROJ_EXP_F_NAME ${PROJECT_NAME}_export.h)
10set(PROJ_STAT_DEF ${PROJECT_NAME}_STATIC)
11# Transform the .h.in (using some CMake vars) to a .h with fixed values
12# Any value @MY_VAR@ is replaced with the current value
13configure_file("${CMAKE_CURRENT_SOURCE_DIR}/inc/LibA.h.in" "${CMAKE_CURRENT_SOURCE_DIR}/inc/LibA.h" @ONLY)
14configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/LibA.cpp.in" "${CMAKE_CURRENT_SOURCE_DIR}/src/LibA.cpp" @ONLY)
15# Include the .h files here, so any file can use simple includes instead of relative paths
16include_directories(${LIBA_INCLUDE_DIR})
17# Create the library
18add_library(${PROJECT_NAME} SHARED inc/LibA.h inc/${PROJ_EXP_F_NAME} src/LibA.cpp)
19# Generate the export
20include(GenerateExportHeader)
21GENERATE_EXPORT_HEADER (${PROJECT_NAME}
22    BASE_NAME ${PROJECT_NAME}
23    EXPORT_MACRO_NAME ${PROJ_EXP_NAME}
24    EXPORT_FILE_NAME ${PROJ_EXP_F_NAME}
25    STATIC_DEFINE ${PROJ_STAT_DEF}
26)
27# Copy the generated file to the current source directory (else won't compile)
28execute_process(
29    COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/${PROJ_EXP_F_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/inc/${PROJ_EXP_F_NAME}
30)

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:

1# Project name
2set(PROJECT_NAME Prog1)
3# Declare the project
4project(${PROJECT_NAME} VERSION 1.0)
5# Create the library
6add_executable(${PROJECT_NAME} src/main.cpp)
7# Link to previsouly created library
8include_directories(${LIBA_INCLUDE_DIR})
9target_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:

 1# Project name
 2set(PROJECT_NAME Prog2)
 3# Declare the project
 4project(${PROJECT_NAME} VERSION 1.0)
 5# Create the exec
 6add_executable(${PROJECT_NAME} src/main.cpp)
 7# Link to previsouly created library
 8include_directories(${LIBA_INCLUDE_DIR})
 9target_link_libraries(${PROJECT_NAME} LibA)
10# Include sub library and link to
11add_subdirectory(LibB)
12include_directories(${LIBB_INCLUDE_DIR})
13target_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:

 1# Project name
 2set(PROJECT_NAME LibB)
 3# Declare the project
 4project(${PROJECT_NAME} VERSION 1.0)
 5# Define where the includes are
 6set(LIBB_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/inc") # PARENT_SCOPE does not define the variable to this level, so do it twice
 7set(LIBB_INCLUDE_DIR ${LIBB_INCLUDE_DIR} PARENT_SCOPE)
 8# Include the .h files here, so any file can use simple includes instead of relative paths
 9include_directories(${LIBB_INCLUDE_DIR})
10# Create the library
11add_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