CMake User Guide: From Beginner to Advanced

Introduction

CMake, as a cross-platform automated build tool, has become an indispensable part of modern C++ development. Whether for simple projects or complex embedded development, CMake provides efficient and flexible build management solutions. This article will take you deep into the use of CMake, from basic commands to advanced configurations, helping you master CMake comprehensively.

1. Introduction to CMake

CMake is a cross-platform build tool that manages software project builds by generating platform-specific build files (such as Makefile or Visual Studio project files). CMake uses CMakeLists.txt files to configure the build process, which contain the rules and parameters for building.

How CMake Works

CMake generates the corresponding build files by reading the instructions in the CMakeLists.txt file. During the build process, CMake searches for source code, library files, dependencies, compiler options, etc., based on the content specified in the instructions, ultimately generating build files suitable for specific platforms. Users only need to run CMake to generate these build files and compile using other build tools (such as make, ninja, etc.).

2. Basic Usage of CMake

1. Creating a Simple CMake Project

The most basic CMakeLists.txt file includes several necessary instructions, as shown below:

cmake_minimum_required(VERSION 3.10)project(MyProject)add_executable(MyApp main.cpp)
  • cmake_minimum_required(VERSION 3.10): Specifies the minimum required version of CMake.

  • project(MyProject): Defines the project name.

  • add_executable(MyApp main.cpp): Specifies the executable file to be built and the source files.

2. Setting Compilation Options

CMake supports setting various compilation options, common instructions include:

set(CMAKE_CXX_STANDARD 11)  # Set C++ standard to C++11set(CMAKE_BUILD_TYPE Debug) # Set build type to debug mode
  • set(CMAKE_CXX_STANDARD 11): Specifies the C++ standard.

  • set(CMAKE_BUILD_TYPE Debug): Specifies the build type, such as Release, Debug, etc.

3. Adding Libraries and Header Files

If your project depends on libraries or custom header files, you can specify them as follows:

include_directories(/path/to/headers)link_directories(/path/to/libs)add_executable(MyApp main.cpp)target_link_libraries(MyApp mylib)
  • include_directories(): Adds header file paths.

  • link_directories(): Specifies library file paths.

  • target_link_libraries(): Links library files to the target executable.

3. Advanced Usage of CMake

1. Using CMake’s Modular Features

To improve project maintainability, CMake allows us to modularize the build process of the project. For example, you can use the add_subdirectory() instruction to add sub-projects to the main project:

add_subdirectory(src)add_subdirectory(tests)

This will recursively enter the src and tests directories, look for CMakeLists.txt files in the sub-projects, and execute the corresponding build operations.

2. Configuration Options and Conditional Compilation

During the build, you may selectively compile certain code based on different platforms or requirements. You can use CMake’s conditional statements to achieve this:

option(USE_FEATURE_X "Enable Feature X" ON)if(USE_FEATURE_X)    add_definitions(-DFEATURE_X_ENABLED)endif()
  • option(): Creates a boolean option.

  • if(): Decides whether to perform conditional compilation based on the option.

3. Package Finding and Dependency Management

CMake provides the find_package() instruction to find external libraries and packages, for example:

find_package(OpenCV REQUIRED)include_directories(${OpenCV_INCLUDE_DIRS})target_link_libraries(MyApp ${OpenCV_LIBS})
  • find_package(OpenCV REQUIRED): Finds the OpenCV library.

  • ${OpenCV_INCLUDE_DIRS} and ${OpenCV_LIBS}: The header file directory and library file path of OpenCV, respectively.

4. Custom CMake Modules

CMake supports user-defined modules to extend CMake functionality. You can create your own FindXXX.cmake module files to implement more complex search logic. For example, suppose you have a custom library:

# FindMyLib.cmakefind_path(MYLIB_INCLUDE_DIR NAMES mylib.h)find_library(MYLIB_LIBRARY NAMES mylib)if(MYLIB_INCLUDE_DIR AND MYLIB_LIBRARY)    set(MYLIB_FOUND TRUE)endif()

Then use it in CMakeLists.txt:

find_package(MyLib REQUIRED)

5. Multi-Platform Support

CMake recognizes the target platform through CMAKE_SYSTEM_NAME, supporting cross-platform builds. For example:

if(CMAKE_SYSTEM_NAME STREQUAL "Linux")    message("Building for Linux")elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")    message("Building for Windows")endif()

This allows CMake to automatically choose different configurations based on different operating systems or compilation environments.

4. Example of Large Project Usage

For a large project, we typically involve multiple modules, dependency management, cross-platform builds, and other complex requirements. Suppose we have a large project named SuperApp that contains several sub-modules (such as Core, Network, and UI).

  1. Project Directory Structure

SuperApp/├── CMakeLists.txt├── Core/│   └── CMakeLists.txt├── Network/│   └── CMakeLists.txt├── UI/│   └── CMakeLists.txt└── main.cpp

2. Main CMakeLists.txt

The main CMakeLists.txt file will include sub-modules and define global settings:

# Set C++ standardset(CMAKE_CXX_STANDARD 17)# Define sub-modulesadd_subdirectory(Core)add_subdirectory(Network)add_subdirectory(UI)# Create executableadd_executable(SuperApp main.cpp)# Link various sub-modulestarget_link_libraries(SuperApp Core Network UI)

3. Core Module CMakeLists.txt

The CMakeLists.txt for the Core module:

add_library(Core STATIC core.cpp)# Set compilation options for Core moduletarget_include_directories(Core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

4. Network Module CMakeLists.txt

The CMakeLists.txt for the Network module:

add_library(Network STATIC network.cpp)# Set compilation options for Network moduletarget_include_directories(Network PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

5. UI Module CMakeLists.txt

The CMakeLists.txt for the UI module:

add_library(UI STATIC ui.cpp)# Set compilation options for UI moduletarget_include_directories(UI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

6. Building and Debugging

This example demonstrates a typical large project, using CMake for modular management, cross-platform support, and dependency management. Each module has its own CMakeLists.txt file, and the main project introduces sub-modules through add_subdirectory() and links them to the final executable.

5. Common CMake Instructions Explained

Here are some common CMake instructions and their explanations, along with operational examples:

  • cmake_minimum_required(): Specifies the minimum version of CMake.

  • project(): Defines the name of the project.

  • add_executable(): Specifies the executable file.

  • add_library(): Defines a static or dynamic library.

  • set(): Sets a variable.

set(SOURCE_FILES main.cpp util.cpp)
  • include_directories(): Adds header file search paths.

include_directories(${PROJECT_SOURCE_DIR}/include)
  • find_package(): Finds external packages or libraries.

find_package(OpenCV REQUIRED)
  • target_link_libraries(): Links libraries to targets.

target_link_libraries(MyApp OpenCV::Core)
  • install(): Specifies installation rules.

install(TARGETS MyApp DESTINATION /usr/local/bin)

6. Conclusion

CMake is a powerful build tool capable of handling complex project requirements. This article gradually explains the basic usage, commands, modular support, and examples of large projects from beginner to advanced. By mastering these techniques, you can efficiently manage the project’s build and ensure smooth compilation across different platforms.

End of Article

If you encounter issues while using CMake or do not understand certain instructions, feel free to leave comments for discussion. We will continue to bring more development tutorials, so stay tuned!

Leave a Comment