Top-level CMake and Public Compilation Options
This section covers two files:
- Top Level
CMakeLists.txt cmake/ProjectOptions.cmake
These two files determine the basic metadata and common compilation rules for the entire project.
Top Level CMakeLists.txt
The top-level files of this template are as follows:
cmake_minimum_required(VERSION 3.25)
project(cmake_template VERSION 1.0.0 LANGUAGES C CXX)
include(GNUInstallDirs)
include("${CMAKE_CURRENT_LIST_DIR}/cmake/ProjectOptions.cmake")
add_subdirectory(src)
The principle of the top-level CMakeLists.txt is to only serve as overall control without containing specific business logic or third-party dependencies.
cmake_minimum_required
cmake_minimum_required(VERSION 3.25)
Purpose:
- Specify the minimum CMake version required for the project.
- Instruct CMake to use the policy behavior of the corresponding version.
- Outdated versions of CMake will fail outright and not proceed with configuration.
What can be filled in:
cmake_minimum_required(VERSION 3.20)
cmake_minimum_required(VERSION 3.25)
cmake_minimum_required(VERSION 3.28)
Selection suggestions:
| writing method | Explanation |
|---|---|
3.10 | It's quite old, and many modern CMake writing practices are inconvenient. |
3.16 | Ubuntu 20.04 is a commonly used version with excellent compatibility. It's an LTS release with 5-year support and good hardware and driver compatibility. |
3.22 | Common versions of Ubuntu 22.04 |
3.25 | This template is used in conjunction with preset version 6. |
3.28+ | Suitable for newer systems, but template universality decreases. |
It's recommended that the teaching template not endlessly chase novelty. Just meeting the current writing style is sufficient.
project
project(cmake_template VERSION 1.0.0 LANGUAGES C CXX)
Purpose:
- Set the project name.
- Set the project version.
- Declare which languages the project uses.
- Initialize a batch of CMake variables.
Project Name
project(cmake_template)
The project name affects these variables:
| variables | value |
|---|---|
PROJECT_NAME | cmake_template |
CMAKE_PROJECT_NAME | The top-level project name, which is also usually cmake_template |
This template is used in src/CMakeLists.txt:
add_executable(${PROJECT_NAME}
${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
)
So the final executable file name is:
cmake_template
If the project name is changed to:
project(robot_app VERSION 1.0.0 LANGUAGES C CXX)
The final executable file will become:
robot_app
VERSION
VERSION 1.0.0
Project version number.
What can be filled in:
VERSION 0.1.0
VERSION 1.0.0
VERSION 2.3.4
CMake will generate these variables:
| variables | Sample value |
|---|---|
PROJECT_VERSION | 1.0.0 |
PROJECT_VERSION_MAJOR | 1 |
PROJECT_VERSION_MINOR | 0 |
PROJECT_VERSION_PATCH | 0 |
In the future, when creating installation packages, exporting CMake packages, or generating version header files, these variables will be quite useful.
LANGUAGES
LANGUAGES C CXX
Declares which languages are enabled for the project.
Common values:
| value | Explanation |
|---|---|
C | C language |
CXX | C++ |
CUDA | CUDA |
ASM | assembly |
Fortran | Fortran |
If the project only includes C++, you can also write:
project(cmake_template VERSION 1.0.0 LANGUAGES CXX)
This template retains C CXX to allow both src/*.c and src/*.cpp to participate in the build.
include(GNUInstallDirs)
include(GNUInstallDirs)
Purpose: loads GNU-style installation directory variables.
Common variables:
| variables | Common value | Usage |
|---|---|---|
CMAKE_INSTALL_BINDIR | bin | executable file |
CMAKE_INSTALL_LIBDIR | lib or lib64 | Dynamic library, static library |
CMAKE_INSTALL_INCLUDEDIR | include | Header file |
CMAKE_INSTALL_DATADIR | share | Data file |
CMAKE_INSTALL_DOCDIR | share/doc/项目名 | documentation |
Why not use handwritten:
install(TARGETS app DESTINATION bin)
install(TARGETS lib DESTINATION lib)
Since different Linux distributions may use lib or lib64. Using GNUInstallDirs allows CMake to adapt based on system conventions.
Usage example in the template:
install(TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
Library Installation:
install(TARGETS ${PREFIX}_src_lib
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
Header file installation:
install(DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/inc/"
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
include("${CMAKE_CURRENT_LIST_DIR}/cmake/ProjectOptions.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/cmake/ProjectOptions.cmake")
Function: Reads another CMake file.
include(...)
include will execute the specified CMake script in the current scope.
Yes, you can write:
include(GNUInstallDirs)
include(cmake/ProjectOptions.cmake)
include("${CMAKE_CURRENT_LIST_DIR}/cmake/ProjectOptions.cmake")
Difference:
| writing method | Explanation |
|---|---|
include(GNUInstallDirs) | Loading built-in CMake modules |
include(cmake/ProjectOptions.cmake) | Relative path notation. |
include("${CMAKE_CURRENT_LIST_DIR}/cmake/ProjectOptions.cmake") | more stable, unaffected by the current working directory |
CMAKE_CURRENT_LIST_DIR
Indicates the directory of the CMake file currently being processed.
In the top-level CMakeLists.txt, it serves as the project root directory.
So:
"${CMAKE_CURRENT_LIST_DIR}/cmake/ProjectOptions.cmake"
Indicates:
项目根目录/cmake/ProjectOptions.cmake
add_subdirectory(src)
add_subdirectory(src)
Function: Enters the src subdirectory and continues reading src/CMakeLists.txt.
What can be filled in:
add_subdirectory(src)
add_subdirectory(test)
add_subdirectory(third_party/some_lib)
common practice
add_subdirectory(<源码目录>)
You can also specify the build directory:
add_subdirectory(src src_build)
But generally, it's not needed.
At the top level, only write:
add_subdirectory(src)
Specific executables, libraries, and dependencies are all managed by the CMake files under src.
cmake/ProjectOptions.cmake
The content of this template is as follows:
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_library(project_options INTERFACE)
target_compile_features(project_options INTERFACE cxx_std_17)
add_library(project_warnings INTERFACE)
target_compile_options(project_warnings INTERFACE
-Wall
-Wextra
-Wpedantic
)
This file is only for project-wide options and does not include third-party dependencies like Eigen or OpenCV.
CMAKE_CXX_STANDARD
set(CMAKE_CXX_STANDARD 17)
Set default C++ standard.
Common values:
| value | Standard |
|---|---|
11 | C++11 |
14 | C++14 |
17 | C++17 |
20 | C++20 |
23 | C++23 |
This template uses C++17:
set(CMAKE_CXX_STANDARD 17)
If you want to use C++20:
set(CMAKE_CXX_STANDARD 20)
target_compile_features(project_options INTERFACE cxx_std_20)
It's best to change both places together.
CMAKE_CXX_STANDARD_REQUIRED
set(CMAKE_CXX_STANDARD_REQUIRED ON)
Purpose: This specifies that the compiler must support the designated C++ standard.
What can be filled in:
| value | Explanation |
|---|---|
ON | Must use the specified standard; report an error if unsupported. |
OFF | may fall back to a lower standard when not supported |
The template recommends ON. Otherwise, you might think you're using C++17, but the compiler could revert to an older standard, making the errors more subtle.
CMAKE_CXX_EXTENSIONS
set(CMAKE_CXX_EXTENSIONS OFF)
Purpose: Controls whether to use the compiler extension standard.
What can be filled in:
| value | compiler option preferences | Explanation |
|---|---|---|
ON | gnu++17 | Allow GNU Extensions |
OFF | c++17 | more standardized and more portable |
For Linux-only projects, it's also recommended to write OFF first, so the code is closer to standard C++.
C standard compliance options
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
The meaning is similar to the corresponding options in C++.
Common C Standards:
| value | Standard |
|---|---|
99 | C99 |
11 | C11 |
17 | C17 |
23 | C23 |
If the project does not have the .c file, you can also enable only C++:
project(cmake_template VERSION 1.0.0 LANGUAGES CXX)
Then remove the C standard related settings.
CMAKE_EXPORT_COMPILE_COMMANDS
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
Function: Generates compile_commands.json.
I can assist with the translation, but it appears the content you'd like me to translate is missing from your message.
Please provide the complete text (Chinese Markdown fragment) you want translated into American English.
| value | Explanation |
|---|---|
ON | Generate |
OFF | Do not generate |
This option is also described in the CMakePresets.json references:
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
It's not conflicting to have both in two places. The advantage of a preset is that the configuration is clearly visible in both the IDE and command line; the benefit of ProjectOptions.cmake is that it enables the feature by default, even without using the preset.
add_library(project_options INTERFACE)
add_library(project_options INTERFACE)
Create an interface target.
INTERFACE target has no source code and will not generate .a or .so. It is only used to carry "usage requirements" such as compile features, macro definitions, include paths, and linker dependencies.
add_library Common types:
| type | Is there source code available? | Whether to generate output | Usage |
|---|---|---|---|
STATIC | There is | .a | Static library |
SHARED | There is | .so | dynamic library |
MODULE | There is | Plug-in Dynamic Module | |
OBJECT | There is | .o set | Reuse object files |
INTERFACE | None | None | Pass configuration |
This template uses:
project_options
Save C++ standard requirements.
target_compile_features
target_compile_features(project_options INTERFACE cxx_std_17)
Purpose: Declares that the target using this target requires C++17.
What can be filled in:
| writing method | Explanation |
|---|---|
cxx_std_11 | At least C++11 |
cxx_std_14 | At least C++14 |
cxx_std_17 | At least C++17 |
cxx_std_20 | At least C++20 |
cxx_std_23 | At least C++23. |
C++23 is the latest standard, building on C++20 with new features like std::expected, std::mdspan, and further library improvements.|
Why is C++17 mentioned again here?
set(CMAKE_CXX_STANDARD 17)
It's the global default value.
target_compile_features(project_options INTERFACE cxx_std_17)
It's about target-level requirements. Modern CMake favors target-level requirements more because they can propagate with target linking relationships.
add_library(project_warnings INTERFACE)
add_library(project_warnings INTERFACE)
Create an interface target specifically for storing warning options.
This way each target can choose whether to link.
target_link_libraries(my_target
PRIVATE
project_warnings
)
target_compile_options
target_compile_options(project_warnings INTERFACE
-Wall
-Wextra
-Wpedantic
)
Purpose: Add compilation options to the target.
This template option:
| option | effect |
|---|---|
-Wall | Enable a set of common warnings |
-Wextra | Enable more warnings |
-Wpedantic | remind about non-standard writing practices |
Common additional options:
| option | Explanation |
|---|---|
-Wconversion | Warning: Casting may result in loss of information |
-Wshadow | Warning on variable name shadowing |
-Werror | Treat warnings as errors. |
It is not recommended to add the template default -Werror. Because different compilers and third-party header files may generate different warnings, beginners can easily get stuck at this stage.
The intuition of PUBLIC, PRIVATE, INTERFACE
The following CMake files will appear extensively:
PUBLIC
PRIVATE
INTERFACE
They control "whether attributes are passed to users."
| Keywords | For my own use | Pass to others. |
|---|---|---|
PRIVATE | Yes | No |
PUBLIC | Yes | Yes |
INTERFACE | No | Yes |
For example:
target_link_libraries(lib1_src_lib
PUBLIC
project_options
PRIVATE
project_warnings
)
Meaning:
- I need
lib1_src_libandproject_options. - The link to
lib1_src_libalso getsproject_options. project_warningsis only used for compilinglib1_src_libitself, not forcing downstream to enable the same warnings.