第 22.2 節

Top-level CMake and Public Compilation Options

0瀏覽次數0訪問次數--跳出率--平均停留

This section covers two files:

  1. Top Level CMakeLists.txt
  2. 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:

  1. Specify the minimum CMake version required for the project.
  2. Instruct CMake to use the policy behavior of the corresponding version.
  3. 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 methodExplanation
3.10It's quite old, and many modern CMake writing practices are inconvenient.
3.16Ubuntu 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.22Common versions of Ubuntu 22.04
3.25This 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:

  1. Set the project name.
  2. Set the project version.
  3. Declare which languages the project uses.
  4. Initialize a batch of CMake variables.

Project Name

project(cmake_template)

The project name affects these variables:

variablesvalue
PROJECT_NAMEcmake_template
CMAKE_PROJECT_NAMEThe 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:

variablesSample value
PROJECT_VERSION1.0.0
PROJECT_VERSION_MAJOR1
PROJECT_VERSION_MINOR0
PROJECT_VERSION_PATCH0

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:

valueExplanation
CC language
CXXC++
CUDACUDA
ASMassembly
FortranFortran

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:

variablesCommon valueUsage
CMAKE_INSTALL_BINDIRbinexecutable file
CMAKE_INSTALL_LIBDIRlib or lib64Dynamic library, static library
CMAKE_INSTALL_INCLUDEDIRincludeHeader file
CMAKE_INSTALL_DATADIRshareData file
CMAKE_INSTALL_DOCDIRshare/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 methodExplanation
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:

valueStandard
11C++11
14C++14
17C++17
20C++20
23C++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:

valueExplanation
ONMust use the specified standard; report an error if unsupported.
OFFmay 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:

valuecompiler option preferencesExplanation
ONgnu++17Allow GNU Extensions
OFFc++17more 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:

valueStandard
99C99
11C11
17C17
23C23

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.

valueExplanation
ONGenerate
OFFDo 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:

typeIs there source code available?Whether to generate outputUsage
STATICThere is.aStatic library
SHAREDThere is.sodynamic library
MODULEThere isPlug-in Dynamic Module
OBJECTThere is.o setReuse object files
INTERFACENoneNonePass 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 methodExplanation
cxx_std_11At least C++11
cxx_std_14At least C++14
cxx_std_17At least C++17
cxx_std_20At least C++20
cxx_std_23At 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:

optioneffect
-WallEnable a set of common warnings
-WextraEnable more warnings
-Wpedanticremind about non-standard writing practices

Common additional options:

optionExplanation
-WconversionWarning: Casting may result in loss of information
-WshadowWarning on variable name shadowing
-WerrorTreat 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."

KeywordsFor my own usePass to others.
PRIVATEYesNo
PUBLICYesYes
INTERFACENoYes

For example:

target_link_libraries(lib1_src_lib
  PUBLIC
    project_options
  PRIVATE
    project_warnings
)

Meaning:

  1. I need lib1_src_lib and project_options.
  2. The link to lib1_src_lib also gets project_options.
  3. project_warnings is only used for compiling lib1_src_lib itself, not forcing downstream to enable the same warnings.
音乐页