Notes of Effective CMake
目录
- Notes of Effective CMake
- 1. The Philosophy of Modern CMake
- Why "Effective CMake"?
- CMake is Code
- 2. The CMake Language: A Quick Tour
- Organization
- Commands
- Variables
- Comments
- Generator Expressions: The $ Syntax
- Common Use Cases and Examples
- Custom Commands: function() vs. macro()
- 3. The Core of Modern CMake: Targets and Properties
- Thinking in Targets
- Build Specifications vs. Usage Requirements
- Header-Only Libraries
- 4. Working with Dependencies
- Finding Packages with find_package()
- Fetching Dependencies at Configure Time with FetchContent
- Exporting Your Project as a Package
- 5. Testing with CTest
- Basic Setup
- Running Tests
- Advanced: Filtering Tests
- Advanced: Testing for Compile Failure
- Advanced: Driving CTest with a Script
- 6. Cross-Compiling with Toolchain Files
- The Role of the Toolchain File
- Example Toolchain File
- Running Cross-Compiled Tests
- 7. Packaging with CPack
- 8. Static Analysis Integration
- The Philosophy of Handling Warnings
- A Better Approach: Treat New Warnings as Errors
- Pull Out All the Stops: Powerful Analysis Tools
- Modern CMake Integration via Target Properties
- Best Practice: Analyzing Header Files
- Reference
1. The Philosophy of Modern CMake
Why "Effective CMake"?
Just like with C++, the way you write CMake code significantly impacts your project's maintainability, ease of use for others, and scalability. Adopting modern practices is key.
CMake is Code
Treat your CMakeLists.txt files with the same care as your source code. Apply principles like Don't Repeat Yourself (DRY), keep it clean, and write comments where necessary.
2. The CMake Language: A Quick Tour
Organization
CMake code can be organized in three ways:
- Directories (CMakeLists.txt): The entry point for a project or sub-project. add_subdirectory() adds another directory (which must contain a CMakeLists.txt) to the build.
- Scripts (.cmake): Executed with cmake -P <script>.cmake. They are useful for automation but cannot define build targets like executables or libraries. In other words, not all commands are supported.
- Modules (.cmake): Reusable code included in your projects via include(). They are located in the CMAKE_MODULE_PATH.
Commands
CMake commands are case-insensitive, but their arguments (including variable names) are case-sensitive.- # command_name(ARGUMENT1 ARGUMENT2 ...)
- project(MyProject VERSION 1.0)
复制代码 Variables
Variables are the backbone of CMake scripting.- # Set a variable
- set(MY_VARIABLE "Hello")
- # Reference a variable (dereference)
- message(STATUS "My variable is: ${MY_VARIABLE}")
- # Unset a variable
- unset(MY_VARIABLE)
复制代码❗ IMPORTANT
- In CMake, everything is a string. Lists are just strings separated by semicolons ; (e.g., "item1;item2;item3").
- An unset or undefined variable expands to an empty string. This can be a common source of bugs! Use if(DEFINED VAR_NAME) to check if a variable is set.
Comments
- # This is a single-line comment.
- #[=[
- This is a multi-line comment.
- It can contain other symbols and even # characters.
- #]=]
复制代码 Generator Expressions: The $ Syntax
Generator expressions, often called "genex," are a powerful CMake feature that uses the $ syntax. They are not evaluated when CMake first reads your CMakeLists.txt. Instead, they are written into the native build files (like Makefiles or Visual Studio projects) and are evaluated during the build process.
This delayed evaluation is crucial because it allows you to create build configurations that are aware of things that are only known at build time, such as the specific build type (Debug, Release), the compiler being used, or the language of a source file.
❗ IMPORTANT
Think of generator expressions as placeholders that the final build tool (like Make, Ninja, or MSBuild) will fill in with the correct value at the right time. This is much more flexible than using if() statements in CMake, which are only evaluated once when you run cmake.
Common Use Cases and Examples
- Conditional Compilation Definitions ($)
This is the most common use case. You want to define a preprocessor macro differently for Debug and Release builds.- # In Debug mode, VERBOSITY will be 2. In all other modes (e.g., Release), it will be 0.
- target_compile_definitions(my_app PRIVATE
- "VERBOSITY=$<IF:$<CONFIG:Debug>,2,0>"
- )
复制代码 The $ expression is evaluated at build time. If the configuration is Debug, it resolves to VERBOSITY=2; otherwise, it becomes VERBOSITY=0.
- Conditional Include Directories ($ and $)
A library often has different include paths when being built inside a project versus when it's installed on a system.- target_include_directories(my_lib PUBLIC
- # When my_lib is built as part of this project, use the source directory.
- $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
- # When an external project uses an installed version of my_lib, the path is just 'include'.
- $<INSTALL_INTERFACE:include>
- )
复制代码 This ensures your library is usable both during development and after deployment without any changes.
- Compiler-Specific Flags ($)
You can pass specific flags depending on the compiler being used (e.g., GCC, Clang, MSVC).- # Enable strong warnings, but use the correct flag for each compiler.
- set(GCC_CLANG_WARNINGS "-Wall -Wextra -Wpedantic")
- set(MSVC_WARNINGS "/W4")
- target_compile_options(my_app PRIVATE
- "$<IF:$<CXX_COMPILER_ID:MSVC>,${MSVC_WARNINGS},${GCC_CLANG_WARNINGS}>"
- )
复制代码
- Language-Specific Standards ($)
If your target mixes C and C++ code, you can set standards for each language.- target_compile_features(my_app PRIVATE
- # Set C++ standard to 17 for all C++ files.
- $<COMPILE_LANGUAGE:CXX>:cxx_std_17
- # Set C standard to 11 for all C files.
- $<COMPILE_LANGUAGE:C>:c_std_11
- )
复制代码 Generator expressions are a cornerstone of modern, robust, and portable CMake scripts. Mastering them allows you to write cleaner and more powerful CMakeLists.txt files.
Custom Commands: function() vs. macro()
You can create your own commands to reduce code duplication.
- function(): Creates a new variable scope. To pass results back to the caller, you must use set(... PARENT_SCOPE).
- macro(): Does not create a new scope. It performs simple text replacement, much like a C preprocessor macro.
<blockquote>
来源:豆瓜网用户自行投稿发布,如果侵权,请联系站长删除 |