CMake
CMake is an open-source, cross-platform build system generator that enables developers to manage the building, testing, and packaging of software projects in a platform- and compiler-independent manner.[1] Originally developed in 2000 by Bill Hoffman as part of the Insight Toolkit (ITK) project at the National Library of Medicine, CMake was created to support portable C++ development for medical image segmentation and registration across Unix, Windows, and Mac platforms.[2] It generates native build files for various tools, including Makefiles, Ninja, and IDE project files for Visual Studio and Xcode, simplifying the configuration of complex build environments.[1]
Over the years, CMake has evolved significantly, with key milestones including its adoption by the Visualization Toolkit (VTK) and ParaView in 2002, funding from the National Alliance for Medical Image Computing (NA-MIC) in 2004, and the KDE project's switch to CMake in 2006, which helped establish it as a standard for large-scale open-source software.[2] By 2016, LLVM had adopted CMake, and in 2023, it added support for C++20 module dependency information, followed by the release of version 4.0 in March 2025, reflecting its ongoing adaptation to modern programming needs.[2][3] Today, maintained by Kitware—a company co-founded by Hoffman—CMake is the de facto standard build system, boasting over 2 million monthly downloads and widespread use in generating libraries, executables, and wrappers for C, C++, and other languages across diverse ecosystems.[4] Its extensible design allows for multiple builds from a single source tree, handling static and dynamic libraries, complex dependencies, and even graphical configuration via a generated cache file.[4]
Origins
History
CMake was initially developed in 2000 by Kitware Inc. for the Insight Software Consortium, sponsored by the U.S. National Library of Medicine, to address the challenges of building complex Makefiles for the Insight Toolkit (ITK), a medical imaging software project requiring cross-platform portability.[2] The tool emerged from the success of the Visualization Toolkit (VTK), aiming to simplify the build process for C++ applications across multiple operating systems and compilers.[5] Development began on August 31, 2000, with Kitware creating a prototype in a few weeks to meet the consortium's needs.[5]
The first stable release, CMake 1.0, arrived in November 2001, marking the tool's public availability under the permissive BSD-3-Clause license, which has remained its licensing model since inception to encourage widespread adoption.[6] Early adoption followed quickly, with integration into VTK and ParaView by 2002, supported by funding from national laboratories such as Los Alamos, Sandia, and Lawrence Livermore, which expanded its use in scientific computing projects.[2] In 2004, CMake received funding from the National Alliance for Medical Image Computing (NA-MIC), increasing its use in open-source projects.[2] CMake 2.0, released on June 11, 2004, introduced significant enhancements in cross-platform support, including better handling of Windows and macOS builds, solidifying its role as a versatile alternative to platform-specific tools.[7]
A pivotal milestone occurred in 2006 when the KDE project transitioned from autotools to CMake, championed by developer Alexander Neundorf, enabling faster porting of KDE applications to Windows and macOS while adding features like shared library versioning.[2] This integration boosted CMake's visibility in the open-source community and influenced subsequent adoptions, such as LLVM's initial use in 2008 and full switch in 2016.[2] The 3.x series began with CMake 3.0 on June 10, 2014, shifting to a modern target-based API that emphasized declarative build descriptions through commands like add_library with INTERFACE types, improving modularity and dependency handling.[8]
The evolution continued into the 4.x series, launched with CMake 4.0 on March 29, 2025, which deprecated policies from versions prior to 3.5 to streamline compatibility.[3] Subsequent releases built on this foundation; CMake 4.1.0 arrived in August 2025, followed by 4.1.2 as the latest stable version on September 30, 2025.[9] As of November 2025, the 4.2.0 release candidate series, including 4.2.0-rc2 on October 31, 2025, added new generators such as Visual Studio 18 2026 and FASTBuild for distributed caching.[9] These updates reflect CMake's ongoing commitment to modern language features and performance optimizations, maintaining its status as a cornerstone for cross-platform software development.[10]
Name
The name "CMake" is a portmanteau of "C"—referring to its initial focus on building C and C++ software—and "make", selected to convey a simple, effective build tool in contrast to more complex systems like Imake used in X11 development.[2] It stands for "Cross-platform Make". This naming choice deliberately avoids direct use of "Make" to differentiate from the Unix Make utility, underscoring CMake's function as a meta-build system generator that produces native build files for various platforms and tools rather than executing builds itself.[11]
Branding for CMake began with simple text-based representations in its early releases around 2000, evolving to integrate Kitware's corporate visual identity, including a stylized logo featuring the tool's name in a modern sans-serif font aligned with the company's blue and white color scheme.
Features
Generators and IDE support
CMake generators are responsible for producing native build system files from CMake configurations, enabling the creation of Makefiles, Ninja build files, IDE project files, and other formats tailored to specific platforms and tools.[12] This abstraction allows developers to write portable build scripts while leveraging optimized native tools for compilation and linking.[12]
Major generators include Unix Makefiles for traditional Unix-like systems, which generate standard Makefile syntax for command-line builds; Ninja, a high-performance small build system that produces faster incremental builds; Visual Studio generators supporting versions from 9 2008 up to 17 2022, creating solution (.sln) and project files for Microsoft's IDE; Xcode for Apple ecosystems, outputting project files (.xcodeproj) compatible with Xcode; and CodeLite, which generates project files (.cbp) for the CodeLite IDE.[12] These generators are selected based on the target environment, with platform-specific defaults such as Unix Makefiles on Linux and macOS, or Visual Studio on Windows.[12]
CMake provides native integration with several IDEs through its generators and auxiliary mechanisms. Visual Studio offers direct support via its generators, allowing users to open and build .sln files seamlessly within the IDE.[13] CLion and Qt Creator leverage CMakePresets.json for configuration and the CMake File API to access build artifacts and project details, enabling full editing, building, and debugging workflows.[13] For lighter editors, VS Code integrates via the CMake Tools extension, which handles generation and build processes using Ninja or other backends, while Vim relies on community plugins like cmake-tools.vim for similar functionality.[14]
Introduced in the CMake 4.2 release candidates (as of November 2025), new generators enhance support for advanced and emerging workflows: the FASTBuild generator enables distributed compilation across machines for faster builds in large projects, and the Visual Studio 18 2026 generator provides experimental compatibility with the unreleased Visual Studio 2026, based on Insider previews.[15]
Generators are selected during configuration using the cmake -G command-line option, such as cmake -G [Ninja](/page/Ninja) .. to specify Ninja files, or interactively via cmake-gui; the full list of available generators is shown with cmake --help.[11] This selection influences the entire build tree and can be overridden for cross-compilation scenarios.[11]
Build targets
In CMake, build targets form the core organizational units of a build system, representing the logical components such as executables, libraries, or custom tasks that the build process generates or executes.[16] Each target encapsulates the necessary rules for compilation, linking, and other actions, allowing developers to define project outputs declaratively in CMakeLists.txt files. Targets are created using dedicated commands and can have properties configured to specify sources, dependencies, and build behaviors, promoting modularity and reusability across projects.[16]
CMake supports several primary target types. Executable targets are defined with the add_executable() command, which specifies a target name and one or more source files to compile and link into a runnable binary; for example, add_executable(hello hello.cpp) creates an executable named "hello" from the given C++ source.[17] Library targets are created via add_library(), supporting variants such as static libraries (default or specified with STATIC), shared libraries (SHARED), loadable modules (MODULE), object libraries (OBJECT) for intermediate compilation without archiving, and framework bundles on Apple platforms (FRAMEWORK). For instance, add_library(mylib STATIC source1.cpp source2.cpp) produces a static archive.[18] Custom targets, added with add_custom_target(), represent non-binary outputs or tasks like generating documentation or running scripts, without producing compilable artifacts; an example is add_custom_target(gen_docs COMMAND doxygen) to invoke an external tool.
Target properties control various aspects of a target's build and usage, set using commands like set_target_properties() since CMake 2.8, which allows assigning values to properties such as SOURCES for listing input files, INCLUDE_DIRECTORIES for header paths, and COMPILE_DEFINITIONS for preprocessor macros. Starting with CMake 2.8.12, finer-grained control became available through dedicated target_* commands, such as target_sources(target PRIVATE file.cpp) to add sources scoped to the target. These properties enable customization of build artifacts, like setting OUTPUT_NAME to rename the generated file or POSITION_INDEPENDENT_CODE for PIC compilation in libraries.[19]
The modern target-based API, emphasized since CMake 3.0, shifts away from global variables toward target-centric commands for better encapsulation and portability.[20] Key introductions include target_include_directories() and target_compile_definitions() in CMake 3.0, allowing per-target configuration of includes and defines without affecting the entire project. Linking is handled via target_link_libraries(), which connects targets and propagates properties; for example, target_link_libraries(myexe mylib) links the executable to the library.[21] This command supports scoping keywords—[PRIVATE](/page/Private) for internal use only, [PUBLIC](/page/Public) for both the target and its consumers, and [INTERFACE](/page/Interface) for consumers alone—introduced in CMake 2.8.12 and refined in 3.0 to enable transitive usage requirements, such as automatically passing include directories to dependent targets. This approach fosters hierarchical project designs where targets expose interfaces without exposing implementation details.[16]
Hierarchical configuration
CMake's hierarchical configuration enables the organization of large projects into modular components by processing multiple CMakeLists.txt files across directories. The add_subdirectory() command is central to this mechanism, as it instructs CMake to load and execute the CMakeLists.txt file from a specified subdirectory, thereby incorporating its contents into the overall build process. This allows developers to structure projects as trees of subprojects, where each subdirectory can define its own targets, dependencies, and settings while maintaining a unified build. For instance, in a project with a root directory containing the main CMakeLists.txt and subfolders for libraries or executables, invoking add_subdirectory(src/lib) from the root would process the corresponding file in the src/lib directory, enabling a scalable and maintainable project layout.[22]
In a typical hierarchical setup, the root CMakeLists.txt file establishes global project policies, such as the minimum required CMake version via cmake_minimum_required(), the project name with project(), and top-level options like enabling languages or setting build types. These settings propagate to subdirectories unless overridden. In contrast, leaf-level CMakeLists.txt files—those in the deepest subdirectories—focus on defining local targets, such as libraries or executables using add_library() or add_executable(), and specifying their immediate dependencies without altering broader project configurations. This division promotes separation of concerns, where the root handles overarching setup and leaves manage specific build artifacts, facilitating collaboration in multi-developer environments.[23]
Variable scoping in CMake hierarchies is managed through directory and function scopes, ensuring isolation while allowing controlled propagation. Each invocation of add_subdirectory() or the function() command creates a new scope, where variables set with set(VAR VALUE) remain local by default and do not affect parent scopes. To propagate values upward, the PARENT_SCOPE option can be used, as in set(MY_VAR value PARENT_SCOPE), which sets the variable in the immediately enclosing scope, such as the parent directory. The function() command, in particular, enforces strict scoping to prevent unintended side effects, differing from macros which share the caller's scope. This design supports clean hierarchies by minimizing global pollution.[24][25]
Best practices for hierarchical CMake projects emphasize avoiding global variables in favor of target-based approaches to enhance modularity and reduce coupling. Instead of relying on directory-wide variables like INCLUDE_DIRECTORIES(), developers should use target-specific commands such as target_include_directories(target PRIVATE path) to attach properties directly to targets, with visibility keywords (PRIVATE, PUBLIC, INTERFACE) controlling propagation to dependents. This target-centric method ensures that subdirectory changes do not inadvertently affect unrelated parts of the project, improving scalability and debuggability in complex builds. For propagation needs, explicit use of PARENT_SCOPE or target properties is recommended over cache variables, aligning with modern CMake principles.[26][27]
Separate build tree
CMake supports out-of-source builds, which involve running the CMake configuration process from a directory separate from the source code tree, thereby generating all build artifacts in the designated build directory while leaving the source directory pristine.[11] This approach has been a core capability since CMake's early versions and is strongly recommended to avoid mixing generated files with source code. Traditionally, users create a new build directory adjacent to the source directory, navigate into it, and invoke CMake with a path to the source directory, such as mkdir build && cd build && cmake ...[11]
Since CMake 3.13, explicit specification of source and build directories has been facilitated by the -S and -B command-line options, allowing invocation from any location without changing directories, for example: cmake -S <path-to-[source](/page/source)> -B <path-to-build>.[11] The CMAKE_BINARY_DIR variable is automatically set to the full path of the top-level build tree during configuration, providing a reliable reference for build system scripts and CMakeLists.txt files to locate generated files relative to the build directory rather than the source.[28] This variable remains distinct from CMAKE_SOURCE_DIR, which points to the source tree, enabling clear separation in out-of-source setups.[28]
Out-of-source builds enable the creation of multiple parallel build trees from the same source, such as separate directories for Debug and Release configurations, without duplicating the source code or risking conflicts.[29] This supports efficient management of variant builds, as each tree can be configured independently with different options while sharing the underlying source.[11] Key advantages include preventing source pollution by build outputs like object files and executables, simplifying cleanup through deletion of the entire build directory, and improving integration with version control systems by excluding generated files from the repository.[11] These benefits promote cleaner workflows, especially in large or multi-platform projects.[29]
Dependency management
CMake manages dependencies between build targets through a combination of explicit commands and automated mechanisms, ensuring correct build order, linking, and propagation of compile-time requirements. Internal dependencies within a project are primarily handled via the target_link_libraries() command, which establishes both link-time and compile-time relationships between targets. For external dependencies, CMake provides tools like find_package() to locate pre-installed libraries and FetchContent to fetch and integrate source-based dependencies. Transitive dependencies are propagated using INTERFACE libraries and usage requirements, allowing header-only libraries and other non-compiled components to influence downstream targets seamlessly. Recent enhancements, such as broader integration with pkg-config files, further streamline dependency discovery.
Internal Dependencies
The target_link_libraries() command is central to defining internal dependencies in CMake projects. It specifies libraries or targets that a given executable or library depends on, creating link-time edges by adding the necessary linker flags and object files during the build process. For compile-time edges, it propagates usage requirements such as include directories, compile definitions, and compiler flags to dependent targets, ensuring that compilation occurs with the correct settings. The command supports visibility scopes—PRIVATE for internal use only, PUBLIC for both the target and its dependents, and INTERFACE for dependents only—which control how dependencies are inherited. For example, to link an executable myexe to a library target mylib while propagating its include paths:
add_executable(myexe main.cpp)
target_link_libraries(myexe PUBLIC mylib)
add_executable(myexe main.cpp)
target_link_libraries(myexe PUBLIC mylib)
This setup ensures myexe links against mylib and any targets linking to myexe also inherit mylib's public usage requirements.[21]
CMake also automates header dependency scanning for incremental builds, leveraging compiler-specific tools to detect include relationships without manual specification. Introduced in CMake 3.10 for supported generators like Ninja and Makefiles, this feature uses compiler flags (e.g., -MD for GCC/Clang) to generate dependency information at compile time, rebuilding affected sources only when headers change. This reduces build times and errors from missed dependencies, particularly in large projects with complex include graphs.
External Dependencies
For system-installed or third-party libraries, find_package() searches for and configures external dependencies, supporting two modes: Module mode, which uses heuristic Find<PackageName>.cmake scripts provided by CMake, and Config mode, which relies on package-provided <PackageName>Config.cmake files for precise location and version handling. Config mode is preferred for modern packages as it allows exact version matching and component selection, such as requiring specific sub-libraries. An example invocation for the Google Test framework:
find_package(GTest REQUIRED)
target_link_libraries(my_test_exe GTest::gtest_main)
find_package(GTest REQUIRED)
target_link_libraries(my_test_exe GTest::gtest_main)
If the package is not found locally, CMake can fail the configuration with REQUIRED or provide fallbacks. Module mode, while legacy, remains useful for older or non-CMake packages via paths in CMAKE_MODULE_PATH.[30]
Introduced in CMake 3.11, the FetchContent module addresses cases where dependencies are not pre-installed by downloading and building them from source during the configure step. It declares content via Git, SVN, or file URLs and integrates the resulting targets seamlessly. For instance:
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG 703bd9caab50b139428cea1aaff9974ebee5742e
)
FetchContent_MakeAvailable(googletest)
add_executable(my_test test.cpp)
target_link_libraries(my_test gtest)
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG 703bd9caab50b139428cea1aaff9974ebee5742e
)
FetchContent_MakeAvailable(googletest)
add_executable(my_test test.cpp)
target_link_libraries(my_test gtest)
This approach avoids external installation requirements and supports overrides for sub-dependencies, making it ideal for reproducible builds. Enhancements in later versions, such as CMake 3.24, allow find_package() to redirect to FetchContent for unified handling.[31]
Transitive Propagation
Transitive dependencies in CMake are managed through usage requirements and INTERFACE libraries, ensuring that dependencies of dependencies are correctly resolved without explicit repetition. When a target links to another via target_link_libraries(), PUBLIC and INTERFACE items propagate their properties—such as include directories (target_include_directories()) or compile options—to all consuming targets, forming a dependency graph that the build system traverses for ordering and configuration. PRIVATE items remain isolated to the direct target. This mechanism prevents over-linking and supports modular designs.
For header-only libraries, which lack compiled sources, INTERFACE libraries provide a lightweight way to encapsulate and propagate requirements. Created with add_library(name [INTERFACE](/page/Interface)), they define properties like includes and definitions that are inherited by linking targets. An example for a header-only utility library:
add_library(myutils [INTERFACE](/page/Interface))
target_include_directories(myutils [INTERFACE](/page/Interface) ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(myutils [INTERFACE](/page/Interface) some_external_lib) # If it depends on others
target_link_libraries(myexe [PUBLIC](/page/Public) myutils)
add_library(myutils [INTERFACE](/page/Interface))
target_include_directories(myutils [INTERFACE](/page/Interface) ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(myutils [INTERFACE](/page/Interface) some_external_lib) # If it depends on others
target_link_libraries(myexe [PUBLIC](/page/Public) myutils)
Here, myexe and its dependents automatically receive the include paths and any transitive links from myutils, enabling clean separation of interface concerns. This is particularly valuable for template-heavy or inline code, where no linking artifacts are produced but compile-time visibility is essential.
New Features in CMake 4.0
CMake 4.0 introduces broader pkg-config integration through the cmake_pkg_config() command, allowing native processing of .pc files to generate CMake variables and imported targets without requiring the pkg-config executable. This enhances dependency discovery for Unix-like systems, where many libraries provide pkg-config metadata for flags and paths. The command extracts details like CFLAGS and LIBS, supporting version constraints and strict parsing. For example:
cmake_pkg_config(EXTRACT mypackage 1.0 REQUIRED)
target_link_libraries(my_target mypackage)
cmake_pkg_config(EXTRACT mypackage 1.0 REQUIRED)
target_link_libraries(my_target mypackage)
This generates targets like mypackage::mypackage with propagated properties, bridging traditional pkg-config workflows into CMake's target-based system and improving portability across environments.[32]
Flexible project structure
CMake supports both in-source builds, where the build artifacts are generated within the source directory, and out-of-source builds, where a separate build tree is used to keep the source tree pristine and enable multiple configurations without interference.[11] Out-of-source builds are the recommended approach for larger projects, as they facilitate cleaner management of generated files and easier switching between build types like Debug and Release.[11]
For complex dependencies, CMake enables superbuilds through the ExternalProject module, which uses the ExternalProject_Add() command to orchestrate the download, configuration, building, and installation of external projects as part of the main build process.[33] This approach allows a top-level CMake project to manage and integrate third-party libraries or components in external trees, ensuring they are built and linked appropriately without embedding their sources directly.[33]
CMake's language-agnostic design permits handling multiple programming languages within the same project via the enable_language() command, which activates support for languages such as C, C++ (CXX), Fortran, Java, CUDA, and others depending on the platform and compiler availability.[34] This flexibility allows developers to mix languages seamlessly, for instance, compiling Fortran routines alongside C++ code in a single build configuration.[34]
To accommodate custom workflows, CMake integrates with other build systems like Meson or Bazel using custom commands and targets, such as add_custom_command() or add_custom_target(), which execute external scripts or tools during the build phase. For example, a CMake project can invoke Meson's build process for specific subcomponents via these mechanisms, enabling hybrid setups where CMake oversees the overall structure while delegating parts to alternative tools.[35]
Best practices for scalable projects emphasize a minimal root-level CMakeLists.txt file that sets essential options like cmake_minimum_required() and project(), then delegates implementation details to subdirectory CMakeLists.txt files using add_subdirectory().[23] This structure promotes modularity, where the root handles global settings and subdirectories manage local targets, reducing complexity in large codebases and improving maintainability.[36]
Compiler feature detection
CMake provides several mechanisms to detect and require specific compiler features, ensuring that build configurations align with the capabilities of the selected compiler. This is particularly important for C++ projects where language standards and extensions vary across compilers like GCC, Clang, and MSVC. The primary tools include functions for testing compiler flags and properties for specifying required features, which automatically adjust compile options such as -std=[c++20](/page/C++20) when support is confirmed.[37]
One fundamental way to test compiler support is through the check_cxx_compiler_flag function, which verifies if a given flag is accepted by the C++ compiler without producing diagnostics. This function, part of the CheckCXXCompilerFlag module, uses an internal try_compile test to attempt compilation with the specified flag and caches the result in a variable for reuse. For instance, to check for C++20 support, the following can be used:
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag(-std=c++20 HAVE_STD_CXX20)
if(HAVE_STD_CXX20)
target_compile_options(my_target PRIVATE -std=c++20)
endif()
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag(-std=c++20 HAVE_STD_CXX20)
if(HAVE_STD_CXX20)
target_compile_options(my_target PRIVATE -std=c++20)
endif()
This approach allows conditional application of flags based on detected support, avoiding errors on older compilers. The function has been available since early CMake versions and supports additional variables like CMAKE_REQUIRED_QUIET (added in 3.1) to suppress output during testing.[38]
For more structured feature requirements, the target_compile_features command specifies expected compiler features for a target, such as cxx_std_20 for C++20 compliance. Introduced in CMake 3.1, this command checks the feature against the compiler's known capabilities stored in variables like CMAKE_CXX_COMPILE_FEATURES and raises a fatal error if unsupported. If the feature is available, it automatically infers and adds necessary flags, such as -std=gnu++20 for GNU compilers. An example is:
add_library(my_library source.cpp)
target_compile_features(my_library PUBLIC cxx_std_20)
add_library(my_library source.cpp)
target_compile_features(my_library PUBLIC cxx_std_20)
Features can be scoped as PRIVATE, PUBLIC, or INTERFACE to control propagation to dependent targets. This mechanism supports both required and optional features, with conditional compilation handled via generator expressions.[39][37]
CMake maintains a list of all known C++ features in the global property CMAKE_CXX_KNOWN_FEATURES, which includes meta-features like cxx_std_20 (added in 3.12) representing full standard support. This property, introduced in 3.1, catalogs over 100 features ranging from basic types (cxx_auto_type) to advanced constructs, independent of the current compiler. In contrast, CMAKE_CXX_COMPILE_FEATURES lists only those supported by the detected compiler, enabling precise requirements. For example, cxx_std_20 ensures the compiler handles C++20 elements like concepts and coroutines, with support tracked for compilers such as GCC 10+ and Clang 10+.[40][41]
Automatic detection of language standards is facilitated by the CMAKE_CXX_STANDARD variable, which sets a default C++ standard level (e.g., 20) for all targets in the project unless overridden by target properties. Added in 3.1, this variable influences compile flags globally; for example, setting set(CMAKE_CXX_STANDARD 20) prompts CMake to append -std=[c++20](/page/C++20) (or equivalent) during configuration if the compiler supports it. If the specified standard is unavailable, CMake issues a warning unless CMAKE_CXX_STANDARD_REQUIRED is set to ON, in which case it errors out. This provides a straightforward way to enforce modern standards without manual flag testing in simple cases.[42]
In CMake 4.0, enhancements to C++20 module feature checks were introduced, building on prior module support from 3.28 to include better integration for standard library modules like std with GCC compilers (version 15+). These improvements allow more reliable detection of module-related flags and dependencies, such as those needed for import std;, expanding usability for modular C++ builds across generators like Ninja.[43][44]
Compiler support
CMake supports a range of programming languages as primary targets for building projects, including C, C++, and Fortran, which form the core of its usage in software development.[34] Secondary languages such as CUDA (introduced in version 3.8), HIP (version 3.21), Objective-C (version 3.16), and Swift (version 3.15) are also supported, enabling CMake to handle specialized workloads like GPU programming and Apple ecosystem development.[34]
The tool supports various compilers across these languages, with minimum version requirements varying by compiler and feature set. For C and C++, key supported compilers include GCC starting from version 3.4 for C and 4.4 for C++, Clang from 2.9, Microsoft Visual C++ (MSVC) from 2010 (Visual Studio 2010), Intel C++ Compiler from version 12.1, and NVIDIA's NVCC for CUDA from version 7.5.[37] As of 2025, support extends to the MSVC 2026 preview toolset within Visual Studio previews, ensuring compatibility with emerging Microsoft compiler updates. Additional compilers like Cray (8.1+), Fujitsu HPC (4.0+), and PGI/NVHPC (11.0+) are supported for high-performance computing environments.[37]
| Compiler | Minimum Version | Languages Supported | Source |
|---|
| GCC | C: 3.4+; C++/Fortran: 4.4+ | C, C++, Fortran | [37] |
| Clang | 2.9+ | C, C++, Objective-C, Swift, CUDA | [37] |
| MSVC | 2010+ (VS 2010) | C, C++, Fortran | [37] |
| Intel | 12.1+ | C, C++, Fortran | [37] |
| NVCC | 7.5+ | CUDA | [37] |
CMake is compatible with major platforms including Windows, Linux, macOS, BSD variants (such as FreeBSD and OpenBSD), Android, and iOS, with cross-compilation facilitated through toolchain files that allow building for different architectures and operating systems from a single host.[45] This platform support enables developers to maintain portable build configurations across diverse environments.[46]
In CMake 4.0, support for policies from versions prior to 3.5 was removed, which impacts projects relying on deprecated behaviors and may affect compatibility with very old compilers that do not align with modern policy standards.[47] CMake provides mechanisms to detect and verify compiler features, as detailed in the Compiler feature detection section.[37]
Packaging
CMake provides packaging capabilities through CPack, an integrated tool that generates binary installers and source packages from CMake projects. By including the CPack module in a project's CMakeLists.txt file via include(CPack), developers can configure and create packages using the cpack executable, which supports a variety of formats suitable for different platforms and distribution methods.[48]
CPack supports multiple generators to produce packages such as DEB for Debian-based Linux distributions, RPM for Red Hat-based systems, NSIS for Windows installers, and archive formats like ZIP and TGZ for cross-platform bundles. These generators leverage the installation rules defined in the project's CMake configuration to assemble the final package, allowing for platform-specific customization without altering the core build logic.[49][50]
The install() command is central to specifying files and targets for packaging, directing executables, libraries, headers, and other artifacts to installation directories relative to CMAKE_INSTALL_PREFIX. For modular installations, the COMPONENT option groups items into logical sets, such as "runtime" or "development," enabling selective packaging and user choices during installation. CPack integrates these components via the CPackComponent module, which allows defining dependencies between components and generating installers that prompt users for selections.[51][52]
Configuration occurs through CPACK_* variables, such as CPACK_PACKAGE_NAME for the package identifier, CPACK_GENERATOR to select output formats, and CPACK_PACKAGE_VERSION for versioning. These variables can be set in CMakeLists.txt or overridden at runtime, providing flexibility for tailored packages. The packaging workflow supports a streamlined one-step process: after building the project, running cpack (or cmake --build . --target package) installs files to a staging directory and bundles them into the final package.[48][50]
Introduced in CMake 4.0, native support for the cmake_pkg_config command enables processing of pkg-config files directly within CMake, generating imported targets and variables without requiring an external pkg-config tool, which enhances packaging of dependencies across ecosystems. Additionally, experimental support for the Common Package Specification (CPS) improves dependency packaging by allowing export and import of transitive dependencies in a standardized format, facilitating better resolution in multi-package scenarios when enabled via CMAKE_EXPERIMENTAL_FIND_CPS_PACKAGES.[53][54]
GUI
CMake provides two primary graphical user interfaces for interactive configuration of projects: ccmake, a terminal-based curses interface, and cmake-gui, a cross-platform Qt-based application. These tools allow users to edit CMake cache variables, select build generators, and generate build systems without relying on command-line invocation, facilitating easier setup for complex projects.[11]
ccmake serves as a lightweight, text-based GUI utilizing the ncurses library to enable interactive editing of CMake variables and management of the CMake cache file (CMakeCache.txt). Users navigate the interface using keyboard controls, such as arrow keys to select variables, Enter to edit values, and 'c' to configure the project or 'g' to generate build files. It displays variable descriptions and supports toggling the view of advanced cache entries by pressing 't', hiding less commonly used options by default to streamline the configuration process. This tool is particularly suited for environments without graphical desktops, such as remote servers or embedded systems, and integrates seamlessly with terminal workflows for iterative cache adjustments.[55][56]
In contrast, cmake-gui offers a full graphical interface built with Qt, providing a more visual and user-friendly experience across Windows, macOS, and Linux platforms. It features fields for specifying source and build directories, a dropdown for selecting build generators (e.g., Ninja, Visual Studio), and a table for editing cache variables with inline help text. Users can enable a "Search" box to filter variables by name, toggle "Advanced" view via a checkbox to reveal hidden options, and group variables by prefix for better organization. The interface updates in real-time during configuration, warning of errors, and supports presets from CMakePresets.json files for reproducible setups.[57][56]
Both tools can be launched directly from integrated development environments (IDEs) that support CMake, such as by invoking them via build menus or scripts to handle initial project configuration before IDE-specific project generation. This integration allows developers to leverage the GUIs for fine-tuning options like compiler flags or dependency paths without leaving their primary workflow.[11]
Despite their utility, these GUIs have limitations, including a lack of support for scripting or automation, making them unsuitable for continuous integration pipelines or batch processing; they are designed primarily for one-time or occasional interactive setup rather than repeated executions. Additionally, changing the build generator after initial configuration requires clearing the build directory, as the cache becomes tied to the selected generator.[56]
Precompiled headers and modules
CMake provides support for precompiled headers (PCH) through the target_precompile_headers() command, introduced in version 3.16, which allows specifying a list of header files to precompile for a target to accelerate subsequent compilations by reusing partially processed versions of those headers.[58] This command supports visibility options such as INTERFACE, PUBLIC, and PRIVATE to control how the precompiled headers are propagated to dependent targets, and it generates a PCH file (e.g., cmake_pch.h or cmake_pch.hxx) that is automatically force-included in source files via compiler flags like -include for GCC or /FI for MSVC.[58] Automatic PCH generation is handled by populating target properties like PRECOMPILE_HEADERS, enabling reuse across sources without manual intervention, though care must be taken to ensure consistent compiler settings to avoid mismatches.[59]
Unity builds, also known as jumbo builds, are supported via the UNITY_BUILD target property, added in CMake 3.16, which combines multiple source files into fewer "unity" source files to reduce compilation overhead and speed up the build process by minimizing repeated header processing.[60] Developers enable this by setting set_target_properties(target PROPERTIES UNITY_BUILD ON), with the global CMAKE_UNITY_BUILD variable providing default control; the property uses algorithms like BATCH (default, grouping files automatically) or GROUP (explicit via source properties) to form unity files, limited by UNITY_BUILD_BATCH_SIZE to prevent excessively large files.[60] This feature is available for C, C++, CUDA (since 3.31), and Objective-C languages, but requires compatible compilers and may introduce one-definition-rule issues if not managed carefully.[60]
CMake introduced experimental support for C++20 header units in version 3.28 (released in 2023), allowing basic integration of modular headers, though full header unit support remains limited as of the latest releases.[61] Full C++20 modules, including named modules, are officially supported starting in 3.28 with the Ninja and Visual Studio generators (VS 2022+), using file sets of type CXX_MODULES in target_sources() to specify module interface files like .ixx for MSVC or .cppm for others, with automatic scanning for import dependencies to enforce compilation ordering.[61] In CMake 4.0 (released in 2025), enhanced integration for the GCC standard library module (import std;) was added for GCC 15 and later, enabling seamless use of compiler-provided modules alongside custom ones via properties like CXX_MODULE_STD.[62]
For Fortran, CMake natively handles module dependencies through automatic scanning of source files for MODULE and USE statements, generating and tracking .mod files since version 3.10, with the Fortran_MODULE_DIRECTORY target property allowing specification of a dedicated output directory for these files to organize builds involving mixed-language projects.[63] This ensures proper dependency resolution without manual intervention, supporting compilers like gfortran and supporting propagation of module interfaces across targets.[63]
Language
Command syntax
CMake commands are invoked in CMakeLists.txt files using a simple, parenthesized syntax that specifies the command name followed by its arguments. The general form is command(arg1 arg2 ... ), where the command name is case-insensitive and the arguments are enclosed in parentheses, separated by whitespace or newlines.[64] Arguments are treated as strings or lists; unquoted arguments are automatically split into lists using semicolon (;) as the delimiter, unless the semicolon is escaped with a backslash (\;).[64] To preserve semicolons in paths or multi-word arguments (e.g., those containing spaces), arguments can be quoted with double quotes ("...") or enclosed in bracket argument syntax ([=[...]=]), which supports nested content without escaping.[64]
Comments in CMake scripts begin with a hash symbol (#) and extend to the end of the line, allowing inline documentation without affecting execution.[64] For multi-line or bracketed comments that might include special characters, the bracket argument syntax can be used, such as #[=[This is a multi-line comment]=]#.[64] When dealing with file paths that include spaces, proper quotation is essential to prevent unintended list splitting; for instance, set(PATH_TO_SOURCE "/path with spaces/main.cpp") ensures the entire path is treated as a single argument.[64]
Control flow structures in CMake enable conditional execution and iteration, forming the basis for dynamic script behavior. Conditional blocks use if(condition) paired with endif(), optionally including elseif() or else() for branching; the condition is evaluated as a boolean expression.[65] Loops are supported via foreach(item IN LISTS listname ITEMS item1 item2 ...) or while(condition), each terminated by endforeach() or endwhile(), respectively, allowing repetition over lists or until a condition fails.[66] These constructs process commands line-by-line within their blocks, with variables (detailed in the Variables and expressions section) often used to store and manipulate loop variables or conditions.[64]
CMake provides mechanisms for defining reusable code through functions and macros, which differ in scope and argument evaluation. A function is declared with function(name arg1 arg2 ... ) and ended by endfunction(), creating a new variable scope where arguments are evaluated lazily upon invocation, similar to procedural calls in other languages.[25] In contrast, a macro uses macro(name arg1 arg2 ... ) and endmacro(), operating in the caller's scope and substituting arguments textually before evaluation, which can lead to unintended variable capture but allows more direct text replacement. For example, a simple function might be defined as:
function(add_numbers a b result)
math(EXPR ${result} "${a} + ${b}")
endfunction(add_numbers)
function(add_numbers a b result)
math(EXPR ${result} "${a} + ${b}")
endfunction(add_numbers)
This sets ${result} in the function's local scope, invoked as add_numbers(1 2 output).[25] Macros, however, expand arguments immediately, making them suitable for simple substitutions but riskier for complex logic due to scope sharing.
Variables and expressions
In CMake, variables are fundamental data structures used to store and manipulate values during the configuration and build process. Normal variables are defined using the set() command with the syntax set(<variable> <value>... [PARENT_SCOPE]), which assigns one or more values to the variable in the current scope; multiple values automatically form a semicolon-separated list.[24] For example, set(MY_LIST item1 item2 item3) creates a list variable that can be iterated or joined later in the CMakeLists.txt file.[24] These variables are scope-specific and do not persist beyond the current directory or function unless the PARENT_SCOPE option is used to propagate them upward.[24]
Cache variables provide persistence across CMake runs and allow user interaction via the CMake GUI or command line, making them ideal for configurable options like build paths or feature flags. They are set with set(<variable> <value>... CACHE <type> <docstring> [FORCE]), where <type> specifies the data type (e.g., BOOL, [STRING](/page/String), PATH), and the optional FORCE overwrites existing cache entries.[24] For instance, set(BUILD_TESTS ON CACHE BOOL "Enable unit tests") stores a boolean value that users can toggle, ensuring reproducibility in multi-run builds.[24] Unlike normal variables, cache entries are written to the CMakeCache.txt file and can be queried or modified externally.[24]
Properties in CMake extend variables by associating metadata with specific entities like targets, directories, or the global scope, enabling fine-grained control over build behaviors. They are accessed and manipulated using commands such as get_target_property(<variable> <target> <property>) for target-specific properties or get_directory_property(<variable> [<directory>] <property>) for directory-wide ones.[19] A common example is the INCLUDE_DIRECTORIES property, which can be retrieved with get_target_property(INCLUDES my_target INTERFACE_INCLUDE_DIRECTORIES) to obtain a list of header search paths for a given target, allowing dynamic adjustments based on project needs.[19] Properties support various categories, including those for sources, tests, and cache entries, and their values can be used interchangeably with regular variables in expressions.[19]
Generator expressions, introduced in CMake 3.0, provide a powerful mechanism for conditional logic evaluated at build time rather than configuration time, using the syntax $<expression> within commands like target_include_directories() or properties.[67] The $<CONFIG> expression returns the active build configuration (e.g., "Debug" or "Release"), enabling config-specific settings, as in target_compile_definitions(my_target PRIVATE $<$<CONFIG:Debug>:DEBUG_BUILD>), which defines DEBUG_BUILD only for Debug builds.[67] Similarly, $<TARGET_PROPERTY:tgt,prop> retrieves a property value from a target, such as $<TARGET_PROPERTY:my_target,INCLUDE_DIRECTORIES>, allowing runtime queries that adapt to the build environment without re-running CMake.[67] These expressions support nesting and logical operators for complex conditions, like version checks on compilers.[67]
Since CMake 3.19, the string() command includes support for JSON string manipulation, facilitating the extraction and processing of structured data like package metadata into variables.[68] Using subcommands like JSON GET, values can be queried from a JSON string, as in:
set(package_json "{\"name\": \"example\", \"version\": \"2.1.0\"}")
string(JSON version GET "${package_json}" version)
set(package_json "{\"name\": \"example\", \"version\": \"2.1.0\"}")
string(JSON version GET "${package_json}" version)
This assigns "2.1.0" to the version variable, converting JSON elements to CMake-compatible types (e.g., strings, booleans as ON/OFF, or numbers).[68] Other subcommands, such as JSON LENGTH for array/object sizes or JSON SET for modifications, integrate seamlessly with variables, enabling dynamic handling of external data sources like manifest files in modern package ecosystems.[68]
Internals
Implementation
CMake's core engine is implemented in C++ using an object-oriented design that emphasizes inheritance and encapsulation to manage the build configuration process.[69] This modular architecture divides responsibilities into distinct components, including reader modules for input processing, evaluator modules for script execution, and writer modules for output generation, enabling extensible handling of commands and configurations.[69] The engine operates in a two-phase manner: first, the configure phase reads and evaluates input files to build an in-memory representation of the project; second, the generate phase produces the necessary build files or IDE projects based on that representation.[69]
The parser interprets CMakeLists.txt files as a scripting language, employing a lex/yacc-based lexical analyzer and parser to tokenize and syntactically analyze the input.[69] During this process, it generates dependency graphs by automatically scanning source files for inclusions and other relationships, particularly for languages like C, C++, and Fortran, to determine build order and incremental compilation needs.[69] These graphs are represented internally through objects such as cmMakefile for directory-level targets and cmTarget for individual build artifacts, ensuring efficient traversal and resolution of interdependencies.[69]
The cache system maintains project configuration in a file named CMakeCache.txt, which stores persistent variables and settings generated during the initial configure run.[11] This file allows CMake to avoid re-evaluating unchanged aspects across invocations, but it is regenerated or updated if source changes or explicit reconfiguration occurs, preserving user-specified options while adapting to modifications.[11] By design, the cache decouples configuration from environment variables, promoting reproducibility in cross-platform builds.[69]
For enhanced IDE integration, CMake introduced a file-based API in version 3.14, documented as cmake-file-api(7), which exposes semantic details of the generated buildsystem through JSON-formatted query and reply files in the .cmake/api directory.[70] Clients, such as IDEs, initiate queries for objects like codemodel versions to retrieve information on targets, directories, and configurations without needing a persistent server connection.[70] The codemodel uses major version 2 since the API's introduction in 3.14, with minor version updates over time (e.g., to 2.8 in CMake 4.0) providing more detailed structural insights into the buildsystem for improved project navigation and editing support.[70]
Build process
The CMake build process involves a multi-stage workflow that separates configuration, generation of build files, and actual compilation, enabling cross-platform consistency and efficient incremental builds. This out-of-source approach recommends creating a separate build directory to keep source files clean and allow multiple build configurations without duplication.[11]
The first stage is configuration, where CMake reads the CMakeLists.txt files in the source tree, detects the compiler and system features, and populates a cache of variables for reuse. This step uses the command cmake [<options>] -B <build-dir> [-S <source-dir>], with options like -G <generator> to select the build system (e.g., Unix Makefiles or Ninja) and -D <var>=<value> to set cache entries such as build type or paths. During configuration, CMake performs platform detection, resolves dependencies, and stores results in CMakeCache.txt to avoid redundant work on subsequent runs; changes to source files or CMakeLists.txt trigger partial reconfiguration to update only affected parts.[11][11]
Following configuration, the generation stage produces native build files tailored to the chosen generator, such as Makefiles or Visual Studio project files, based on the processed CMake instructions. This is invoked implicitly during the initial cmake run or explicitly with cmake --build <dir> --preset=<name> for preset-based workflows. The resulting files enable the build tool to handle compilation, linking, and dependency resolution.[11]
The final building stage compiles and links the project using the generated files, typically via cmake --build <dir> [--target <tgt>] [-- <build-tool-args>], which unifies invocation across generators without needing the native tool directly (e.g., make or ninja). Options include --parallel or -j <jobs> for concurrent builds to speed up large projects, --target <tgt> to build specific targets like executables or libraries, and --clean-first for cleaning outputs before rebuilding to ensure a fresh state. Reconfiguration integrates seamlessly if source changes are detected, minimizing full rebuilds through dependency tracking.[11][11]
In CMake 4.1, enhancements to the Makefile and Ninja generators introduced support for linker launchers in Fortran, CUDA, and HIP languages via the CMAKE_<LANG>_LINKER_LAUNCHER variable, allowing custom tools to wrap linking steps for better integration with specialized environments.[71]
CMake provides several companion tools that extend its functionality for testing, packaging, and diagnostic reporting. Among these, CTest serves as the primary test driver, enabling automated unit testing and integration within CMake projects. By invoking the enable_testing() and add_test() commands in a CMakeLists.txt file, developers can define tests that CTest executes and reports on, supporting parallel runs with the -j option and verbose output via -V. A key feature for selective test execution is the -R <regex> command-line option, which runs only tests whose names match the specified regular expression, available since CMake 2.4.[72][73]
CTest also facilitates continuous integration by integrating with CDash, an open-source dashboard application for aggregating and visualizing build and test results across distributed systems. Projects can submit results to a CDash server using CTest's dashboard modes—such as Experimental, Nightly, or Continuous—through a sequence of steps including Start, Update, Configure, Build, Test, and Submit, executed via the ctest -S script mode or directly from the command line. This setup allows teams to monitor build health, track regressions, and generate reports without manual intervention.[74][75]
CPack complements CMake by generating installers and packages from the project's installation rules, invoked separately after the CMake configuration and build phases. The cpack command reads a generated CPackConfig.cmake file—produced when the CPack module is included in the CMakeLists.txt—and creates output in formats specified by the -G option, such as ZIP, DEB, or NSIS installers (e.g., cpack -G ZIP). This tool supports a wide range of generators for different platforms, ensuring portable distribution of built artifacts.[50][48]
For handling runtime libraries, particularly on Windows with MSVC, CMake includes the rare but useful InstallRequiredSystemLibraries module, which detects and installs necessary compiler-provided runtime libraries (such as MSVCRT.dll) to avoid dependency issues in distributed binaries. In recent developments, CMake 4.0 introduced support for the SARIF (Static Analysis Results Interchange Format) standard, enabling export of build diagnostics—including static analysis warnings—in a standardized JSON format via the CMAKE_EXPORT_SARIF variable, improving integration with analysis tools and IDEs as of October 2025.[76][77][78]
Adoption
CMake has seen widespread adoption across major open-source projects, particularly in C++ ecosystems. The KDE project, one of the earliest large-scale adopters, transitioned to CMake around 2006 to manage its complex, cross-platform framework development.[79] LLVM and Clang, core components of modern compiler infrastructure, have relied on CMake as their primary build system since version 3.8 (2016), enabling reliable target exports for downstream projects.[80] MySQL uses CMake for source configuration and building across platforms, providing extensive options for customization.[81] Boost, a foundational C++ library collection, integrates with CMake through dedicated support modules, facilitating its use in countless CMake-based builds despite its primary b2 tool.[82] Unreal Engine supports CMake natively for Linux builds and external library integration, streamlining cross-platform game development workflows.[83] In a 2024 survey of modern C++ developers, 83% reported using CMake, reflecting its dominance in top GitHub repositories for the language.[84]
In industry applications, CMake powers diverse sectors beyond general software development. In game development, it enables plugin and external C++ library integration for engines like Unreal, supporting rapid iteration in high-performance environments. For scientific computing, the Insight Toolkit (ITK) and Visualization Toolkit (VTK) are built exclusively with CMake, ensuring reproducible builds for medical imaging and data visualization pipelines.[85][86] In embedded systems, CMake's toolchain files facilitate cross-compilation to various architectures, making it a staple for resource-constrained device firmware.
CMake's growth trajectory traces from a niche tool in the early 2000s, initially developed for ITK and VTK, to the de facto standard for C++ builds by the 2010s, driven by its cross-platform reliability and extensibility.[2] By 2025, it achieves over 2 million downloads per month, underscoring its entrenched role in professional and open-source workflows.[46]
Despite its prevalence, adoption faces hurdles, including a learning curve for developers accustomed to simpler scripting, which can complicate initial setup for complex configurations.[87] Migrating from legacy systems like Autotools requires manual reconfiguration without automated tools, often demanding significant effort to replicate conditional logic and platform checks.[88]
Examples
Hello world
To create a basic "Hello, World!" program using CMake, begin with a minimal project structure consisting of a single source file and a CMakeLists.txt configuration file.[36]
The CMakeLists.txt file specifies the project's requirements and targets. A minimal version includes the following commands:
cmake
cmake_minimum_required(VERSION 3.10)
project(Hello)
add_executable(hello main.cpp)
cmake_minimum_required(VERSION 3.10)
project(Hello)
add_executable(hello main.cpp)
The cmake_minimum_required command enforces a minimum CMake version to ensure compatibility with required features.[47] The project command declares the project name, which influences variable scoping and default build settings. The add_executable command defines an executable target named hello built from main.cpp.[17]
The source file main.cpp contains a standard C++ "Hello, World!" implementation:
cpp
#include <iostream>
int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}
#include <iostream>
int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}
This code includes the iostream header for output and prints the greeting in the main function.
To build the project, follow these steps from the project root directory (assuming a Unix-like system; adjust for Windows as needed):
- Create a build directory:
mkdir build
- Enter the build directory:
[cd](/page/.cd) build
- Configure the build:
cmake ..
- Compile the project:
cmake --build .
These steps generate build files in the build directory using an out-of-source build approach, which keeps source files clean. The cmake .. command reads CMakeLists.txt from the parent directory and configures the project based on the detected compiler and platform. The cmake --build . command invokes the native build tool (e.g., Make on Linux or MSBuild on Windows) to produce the executable.
Upon successful compilation, an executable named hello (or hello.exe on Windows) is created in the build directory. Running it with ./hello (or hello.exe on Windows) outputs:
Hello, World!
Hello, World!
This demonstrates CMake's ability to automate the compilation of a simple C++ program across platforms without manual makefile editing.[36]
Multi-file project
In CMake, multi-file projects are organized using a hierarchical directory structure, where the root CMakeLists.txt file invokes subdirectories containing their own build configurations via the add_subdirectory() command. This approach enables modular development, allowing separate CMakeLists.txt files to define libraries, executables, and dependencies within subfolders, while the top-level file orchestrates the overall build.[36]
Consider a project with source code divided into a library and an application. The directory structure might include a src/lib subdirectory for the library sources and a src/app subdirectory for the executable. In the root CMakeLists.txt, the following commands integrate these components:
cmake
cmake_minimum_required(VERSION 3.10)
project(MyProject)
add_subdirectory(src/lib)
add_subdirectory(src/app)
cmake_minimum_required(VERSION 3.10)
project(MyProject)
add_subdirectory(src/lib)
add_subdirectory(src/app)
The src/lib/CMakeLists.txt file defines a static library target, such as a simple math utility:
cmake
add_library(math math.cpp)
add_library(math math.cpp)
Here, math.cpp contains the library's implementation, and add_library() creates a target named math, typically producing a static archive like libmath.a during the build.
In the src/app/CMakeLists.txt file, an executable target is created that depends on the library:
cmake
add_executable(calc calc.cpp)
target_link_libraries(calc math)
add_executable(calc calc.cpp)
target_link_libraries(calc math)
The add_executable() command builds the calc executable from calc.cpp, while target_link_libraries() specifies that calc links against the math library, ensuring the executable can access the library's functions at runtime. To handle header includes, modern CMake recommends using target_include_directories() on the library target for public interfaces:
cmake
target_include_directories(math PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_include_directories(math PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
This propagates include paths to dependent targets like calc automatically.
For integrating external libraries, such as the Eigen linear algebra library, the find_package() command locates and configures the dependency. In the root or relevant CMakeLists.txt, add:
cmake
find_package(Eigen3 REQUIRED NO_MODULE)
target_link_libraries(calc Eigen3::Eigen)
find_package(Eigen3 REQUIRED NO_MODULE)
target_link_libraries(calc Eigen3::Eigen)
This imports Eigen's headers and links its target, assuming Eigen is installed system-wide or via a package manager; CMake version 3.5 or later is required for this imported target support.[89]
The build process remains consistent with single-file projects: run cmake . (or cmake -S . -B build for out-of-source builds) in the project root to generate build files, followed by make (or the appropriate generator command) to compile. This produces the libmath.a static library in the build directory and the calc executable, which can then be run directly. Target linking ensures dependencies are resolved correctly, with the library archived before the executable is linked.[36]