Understanding CMake: Core Principles and Basic Syntax

1. Core Principles of CMake

CMake is not a build tool (like <span>make</span> or <span>ninja</span>), but rather a build system generator, such as generating Makefiles.

Its core working principle can be summarized in the following three steps:

  1. 1. Configuration:
  • • You write a script file named <span>CMakeLists.txt</span> that describes your project structure, source files, dependencies, etc.
  • • Execute the <span>cmake /path/to/source</span> command.
  • • CMake reads the <span>CMakeLists.txt</span> file and analyzes and configures based on the current platform (Windows, Linux, macOS) and generator (Visual Studio, Makefiles, Ninja, etc.).
  • • It checks the availability of compilers, linkers, third-party libraries, etc. This stage generates <span>CMakeCache.txt</span> and some other files.
  • 2. Generation:
    • • After a successful configuration phase, CMake will generate the necessary native build files for the corresponding platform and generator based on the analysis results from the previous step.
    • • On Linux/macOS, it defaults to generating a <span>Makefile</span>.
    • • On Windows, it can generate a <span>Visual Studio</span> <span>.sln</span> solution file.
    • • You can also specify to generate files for faster build systems like <span>Ninja</span>.
  • 3. Build:
    • • Now, you no longer use CMake, but rather the corresponding native build tool for your platform to compile and link your project.
    • • For example, in the directory where the <span>Makefile</span> was generated, you just need to enter the <span>make</span> command to start compiling.
    • • In the directory where the <span>Visual Studio</span> solution was generated, you can use the <span>MSBuild</span> command or directly open it in the VS IDE to compile.

    Simple Analogy: CMake is like a translator and a blueprint designer. You write your project requirements in <span>CMakeLists.txt</span> (a universal language), and CMake “translates” it into the “construction drawings” that the “foremen” (<span>make</span>, <span>ninja</span>, <span>MSBuild</span>) on different “construction sites” (operating systems) can understand (<span>Makefile</span>, <span>.ninja</span>, <span>.sln</span>). Then the “foremen” are responsible for the actual “construction” (compilation, linking).

    Core Advantage: Cross-platform. Developers only need to maintain one universal <span>CMakeLists.txt</span> file to generate the corresponding build files on various platforms, greatly simplifying the build management of cross-platform projects.

    2. Basic Syntax of CMake

    The syntax of CMake is very intuitive, mainly consisting of Commands, Variables, and Comments.

    1. 1. Commands:
    • • The format is similar to function calls:<span>command_name(parameter1 parameter2 ...)</span>.
    • Case insensitive, but it is generally recommended to use all lowercase, for example, <span>project()</span>, <span>add_executable()</span>.
    • • Command parameters are separated by spaces or semicolons <span>;</span>.
  • 2. Variables:
    • • Set using the <span>set()</span> command:<span>set(MY_VARIABLE "some value")</span>.
    • • Referenced using <span>${}</span> syntax:<span>message(${MY_VARIABLE})</span>.
    • • Variable types are usually strings.
  • 3. Comments:
    • • Use the <span>#</span> symbol for single-line comments.
    • <span># This is a comment</span>
  • 4. Control Flow:
    • • Supports conditional statements (<span>if()</span>, <span>else()</span>, <span>endif()</span>) and loops (<span>foreach()</span>, <span>endforeach()</span>, <span>while()</span>, <span>endwhile()</span>).

    3. Common Commands and Variables

    Below is a simple project <span>CMakeLists.txt</span> example that includes the most core and commonly used commands:

    # 1. Specify the minimum required version of CMake
    cmake_minimum_required(VERSION 3.10)
    
    # 2. Define the project name, version, and programming languages used (C/CXX, etc.)
    project(MyProject VERSION 1.0 DESCRIPTION "A simple project" LANGUAGES C CXX)
    
    # 3. Set the C++ standard (very common!)
    set(CMAKE_CXX_STANDARD 11) # or 14, 17, 20...
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
    
    # 4. Add all .cpp files in the current directory to the SOURCES variable
    file(GLOB SOURCES "src/*.cpp")
    
    # 5. Add an executable target named hello_world, built from the source files in the SOURCES variable
    add_executable(hello_world ${SOURCES})
    
    # 6. Add header file search paths for the target
    #    PUBLIC means that not only hello_world will use it, but also other targets linking to hello_world will use it
    target_include_directories(hello_world
        PUBLIC
            ${PROJECT_SOURCE_DIR}/include
    )
    
    # 7. If you need to link a library
    # 7.1 First find the library (e.g., find the Threads library on the system)
    find_package(Threads REQUIRED)
    
    # 7.2 Then link the library to your target
    target_link_libraries(hello_world
        PRIVATE
            Threads::Threads # Link the Threads library
    )
    
    # 8. Add a subdirectory, which must also have a CMakeLists.txt
    # add_subdirectory(mylib)

    Detailed Explanation of Core Commands:

    • <span>cmake_minimum_required(VERSION x.x)</span>: Must be placed at the top, specifying the required CMake version.
    • <span>project(<PROJECT-NAME>)</span>: Defines the project name, which sets some key variables, such as <span>PROJECT_SOURCE_DIR</span> (current project source path) and <span>PROJECT_BINARY_DIR</span> (build output path).
    • <span>add_executable(<name> <sources>)</span>: Defines an executable build target.
    • <span>add_library(<name> [STATIC|SHARED] <sources>)</span>: Defines a library build target (static library <span>.a/.lib</span> or dynamic library <span>.so/.dll</span>).
    • <span>target_include_directories(<target> [PUBLIC|PRIVATE|INTERFACE] <dirs>)</span>: Modern CMake best practice, specifies header file include directories for specific targets. Avoids the old global command <span>include_directories()</span>.
    • <span>target_link_libraries(<target> [PUBLIC|PRIVATE|INTERFACE] <libraries>)</span>: Modern CMake best practice, specifies libraries to link for specific targets. Avoids the old global command <span>link_libraries()</span>.
    • <span>find_package(<PackageName> REQUIRED)</span>: Finds external dependency packages in the system (like OpenCV, Boost, etc.).<span>REQUIRED</span> means it will error out if not found.
    • <span>set(<variable> <value>)</span>: Sets a variable.
    • <span>message(<mode> "message to display")</span>: Prints a message for debugging (<span>STATUS</span>) or error (<span>FATAL_ERROR</span>).

    Common Variables:

    • <span>CMAKE_CXX_STANDARD</span>: Sets the C++ standard.
    • <span>CMAKE_BUILD_TYPE</span>: Sets the build type, such as <span>Debug</span>, <span>Release</span>, <span>RelWithDebInfo</span>, <span>MinSizeRel</span>.
    • <span>CMAKE_CXX_FLAGS</span>: Sets the flags for the C++ compiler.
    • <span>PROJECT_SOURCE_DIR</span>: Path to the project root directory.
    • <span>PROJECT_BINARY_DIR</span>: Path to the build output directory (the directory where the <span>cmake</span> command is run).
    • <span>CMAKE_PREFIX_PATH</span>: Sets the path for <span>find_package</span> to search for third-party libraries, for example, <span>set(CMAKE_PREFIX_PATH "/path/to/your/lib")</span>.

    4. Summary and Usage Process

    1. 1. Write <span>CMakeLists.txt</span>: Create this file in your project root directory, using the commands above to describe your project.
    2. 2. Create a build directory and configure:
      mkdir build &amp;&amp; cd build       # Strongly recommended! Out-of-source build to keep the source directory clean
      cmake ..                     # .. indicates that CMakeLists.txt is in the parent directory
    3. 3. Generate the build system and compile:
      cmake --build .              # Use CMake to call the underlying build tool (generic)
      # or
      make                         # If a Makefile was generated
    4. 4. Run the program:
      ./hello_world                # Find the generated executable in the build directory

    I hope this brief overview helps you quickly understand the core concepts and workings of CMake!

    Leave a Comment