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 .
