How do I add a linker or compile flag in a CMake file?
Categories:
Mastering CMake: Adding Linker and Compiler Flags

Learn how to effectively add custom linker and compiler flags in your CMake projects to control build behavior, optimize code, and link necessary libraries.
CMake is a powerful, cross-platform build system generator that helps manage the compilation process of software projects. While it provides sensible defaults, you often need to add custom compiler or linker flags to achieve specific build requirements, such as enabling optimizations, adding warnings, or linking against particular libraries. This article will guide you through the various methods to incorporate these flags into your CMakeLists.txt
files.
Understanding Compiler and Linker Flags
Compiler flags are options passed to the compiler (e.g., g++
, clang
) during the compilation phase of source files. They control aspects like optimization levels (-O2
, -O3
), warning levels (-Wall
, -Wextra
), language standards (-std=c++17
), and preprocessor definitions (-DNDEBUG
).
Linker flags, on the other hand, are options passed to the linker during the final linking phase. They specify which libraries to link against (-lmy_library
), where to find those libraries (-L/path/to/libs
), and other linking-specific behaviors. Understanding the distinction is crucial for applying the correct flags at the right stage of your build.
flowchart TD A[Source Files (.cpp, .c)] --> B{Compiler} B --> C[Object Files (.o)] C --> D{Linker} D --> E[Executable/Library] subgraph Compiler Flags B -- "-O3" --> B B -- "-Wall" --> B B -- "-std=c++17" --> B end subgraph Linker Flags D -- "-lmy_library" --> D D -- "-L/path/to/libs" --> D end
Flowchart illustrating the compilation and linking process with flag application points.
Adding Compiler Flags
CMake offers several ways to add compiler flags, depending on whether you want them to apply globally, to specific targets, or conditionally based on build type or compiler. The most common methods involve add_compile_options
, target_compile_options
, and setting properties.
# Global compiler flags (applies to all targets in current and sub-directories)
add_compile_options(-Wall -Wextra -Werror)
# Target-specific compiler flags
add_executable(my_app main.cpp)
target_compile_options(my_app PRIVATE -std=c++17 -O3)
# Conditional flags based on build type
if (CMAKE_BUILD_TYPE MATCHES "Debug")
target_compile_options(my_app PRIVATE -g -DDEBUG_MODE)
endif()
# Using properties (less common for direct flags, but useful for more complex scenarios)
set_property(TARGET my_app APPEND_STRING PROPERTY COMPILE_OPTIONS " -fPIC")
Examples of adding compiler flags globally, per target, and conditionally.
target_compile_options
over add_compile_options
for better encapsulation and to avoid unintended side effects on other targets. Use PRIVATE
for flags only affecting the target's own compilation, INTERFACE
for flags needed by consumers of a library, and PUBLIC
for both.Adding Linker Flags
Similar to compiler flags, linker flags can be added globally or per target. The primary commands for this are link_libraries
(older, less recommended), target_link_libraries
, and add_link_options
.
# Global linker flags (applies to all targets in current and sub-directories)
add_link_options(-Wl,--no-undefined)
# Target-specific linker flags
add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE my_library)
# Adding specific linker options (e.g., for RPATH or version scripts)
target_link_options(my_app PRIVATE "-Wl,--rpath,$ORIGIN/../lib")
# Conditional linker flags
if (UNIX)
target_link_options(my_app PRIVATE -pthread)
endif()
Examples of adding linker flags globally and per target.
add_compile_options
and add_link_options
as they can affect all targets in the current directory and its subdirectories, potentially leading to unexpected build failures or warnings in unrelated parts of your project. Target-specific commands are generally safer and more maintainable.Best Practices and Advanced Scenarios
When working with flags, consider the following best practices:
- Use Generator Expressions: For highly conditional flags (e.g., different flags for different compilers or platforms), generator expressions provide a powerful and flexible way to specify them. They are evaluated at build-time rather than configure-time.
- Encapsulate in Functions/Macros: For complex sets of flags or common patterns, define CMake functions or macros to keep your
CMakeLists.txt
clean and reusable. - Respect
INTERFACE
,PUBLIC
,PRIVATE
: When defining libraries, correctly specify the scope of your flags.PRIVATE
flags are only for the library's own compilation/linking.INTERFACE
flags are for targets that link against the library.PUBLIC
flags are for both. - Avoid Hardcoding Paths: Use CMake variables like
${CMAKE_SOURCE_DIR}
or${CMAKE_BINARY_DIR}
instead of absolute paths for includes or libraries.
# Example using generator expressions for build type specific flags
add_library(my_lib STATIC my_lib.cpp)
target_compile_options(my_lib PRIVATE
$<$<CONFIG:Debug>:-g -DDEBUG>
$<$<CONFIG:Release>:-O3 -DNDEBUG>
)
# Example using generator expressions for platform specific flags
target_link_options(my_lib PRIVATE
$<$<PLATFORM_ID:Linux>:-Wl,--no-as-needed>
$<$<PLATFORM_ID:Windows>:/SAFESEH>
)
Using generator expressions for conditional compiler and linker flags.
By following these guidelines and understanding the various commands, you can effectively manage compiler and linker flags in your CMake projects, ensuring your code builds correctly and efficiently across different environments and configurations.