A large number of open-source C++ projects choose Modern CMake as a powerful tool used for managing the building process across multiple platforms. Modern CMake refers to the practices, features, and methodologies introduced in CMake 3.x (and beyond) that simplify, improve, and modernize the build system configuration for C++ and other languages. It emphasizes clarity, reusability, and portability, and aims to make CMake easier to maintain while leveraging its full potential.
For the most part, the CMAKE_<LANG>_FLAGS
CMake variable is provided for consumers to set additional compiler flags for the <LANG>
programming language. For example, CMAKE_CXX_FLAGS
impacts the current C++ compiler and CMAKE_C_FLAGS
will take effect when building a C target in the subsequent tasks.
if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
string(APPEND CMAKE_CXX_FLAGS " /EHsc")
else()
string(APPEND CMAKE_CXX_FLAGS " -fexceptions")
endif()
The CMAKE_<LANG>_FLAGS
variable will always override both Release
and Debug
targets. To set flags separately for one single build type, extra variables like CMAKE_<LANG>_FLAGS_RELEASE
and CMAKE_<LANG>_FLAGS_DEBUG
are introduced for such kind of usage.
if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
string(APPEND CMAKE_CXX_FLAGS_DEBUG " /Od")
string(APPEND CMAKE_CXX_FLAGS_RELEASE " /O2")
else()
string(APPEND CMAKE_CXX_FLAGS_DEBUG " -O0")
string(APPEND CMAKE_CXX_FLAGS_RELEASE " -O3")
endif()
On Windows, Visual Studio 2015 and later versions of Visual Studio all use one Universal Visual C Runtime Library, called the Universal CRT (UCRT). The UCRT is a Microsoft Windows operating system component. It’s included as part of the operating system in Windows 10 or later, and Windows Server 2016 or later. The Visual C++ Runtime Library is usually distributed with the corresponding MSVC tooling or installed independently with third-party software. The VC++ Runtime Library depends on the UCRT in the operating system.
The UCRT and the VC++ Runtime have static and dynamic libraries within MSVC and thereby the MSVC’s compiler cl.exe
provides several options for linkage mode control of the two runtimes simultaneously. The options are:
Option (Release) | Option (Debug) | Description |
/MD | /MDd | Linking to UCRT & VC++ Runtime statically |
/MT | /MTd | Linking to UCRT & VC++ Runtime dynamically |
For GCC on Linux or Unix platform, this compiler is designed to use options, such as -static-libstdc++
and -static-libgcc
, to enable the static libraries of libstdc++.a
, libgcc.a
, or libc++.a
. The libgcc
is known as the GCC Low-level Runtime Library, which exists on some platform and GCC generates calls to routines in this library automatically, whenever it needs to perform some operation that is too complicated to emit inline code for.
For Clang on these platforms, it contains the same options as GCC has for compatibility considerations. The options in Clang are aliases to -static-libc++
and -static-compiler-rt
, which are identical to those of GCC.
Compiler | Option | Description |
GCC | -static-libstdc++ | Linking to libstdc++ statically |
GCC | -static-libgcc | Linking to libgcc statically |
Clang | -static-libc++ -static-libstdc++ | Linking to libc++ statically |
Clang | -static-libgcc -static-compiler-rt | Linking to libcompiler-rt statically. |
In a C++ project, it is recommended to keep the same configuration of compilation for all dependencies. Because of different compilers and option names, it is a bit complex to write CMake code in a portable way and users may set the options via -DCMAKE_<LANG>_FLAGS=xxx
in the command line, that could lead to incorrect building configurations or an unexpected behavior.
A solution is to define a CMake option
in convention, e.g. WITH_STATIC_RUNTIME
, indicating that it is the official method to modify the runtime linkage mode.
option(WITH_STATIC_RUNTIME OFF "Linking to the C++ runtime statically.")
A user-defined CMake function is constructed here to generate flags for different compilers correspondingly, complying the aforementioned rules.
function(es_make_c_cxx_runtime_flags)
set(options STATIC_RUNTIME)
set(one_value_args RESULT_DEBUG_FLAGS RESULT_RELEASE_FLAGS)
set(multi_value_args "")
cmake_parse_arguments(PARSE_ARGV 0 ARG "${options}" "${one_value_args}" "${multi_value_args}")
es_ensure_parameters(es_make_c_cxx_runtime_flags ARG RESULT_DEBUG_FLAGS RESULT_RELEASE_FLAGS)
if(CMAKE_C_COMPILER_ID STREQUAL "MSVC" OR CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
if(ARG_STATIC_RUNTIME)
set(debug_flag /MTd)
set(release_flag /MT)
else()
set(debug_flag /MDd)
set(release_flag /MD)
endif()
elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
if(ARG_STATIC_RUNTIME)
set(debug_flag "-static-libstdc++ -static-libgcc")
set(release_flag ${debug_flag})
else()
set(debug_flag "")
set(release_flag "")
endif()
else()
message(FATAL_ERROR "Unsupported compiler: ${CMAKE_CXX_COMPILER_ID}.")
endif()
set(${ARG_RESULT_DEBUG_FLAGS} ${debug_flag} PARENT_SCOPE)
set(${ARG_RESULT_RELEASE_FLAGS} ${release_flag} PARENT_SCOPE)
endfunction()
The es_ensure_parameters
function raises a fatal error when the mandatory parameters do not exist. Here is an example showing how to use this function:
if(WITH_STATIC_RUNTIME)
set(runtime_switch STATIC_RUNTIME)
else()
set(runtime_switch "")
endif()
es_make_c_cxx_runtime_flags(
${runtime_switch}
debug_flags
release_flags
)
message(STATUS "debug_flags: ${debug_flags}")
message(STATUS "release_flags: ${release_flags}")
Another design goal is exception safety, that is whatever users set the flags to never corrupts the building process. A workaround is to clear the old runtime flags first and set the new flags after that. A possible implementation is to iterate all the variables to remove conflicting flags and then append the generated flags to these variables. The string(REPLACE ...)
function is capable of text replacement operations.
string(REPLACE "string-to-find" "replacement" <RESULT> "source")
set(input_str "Hello world")
string(REPLACE "Hello" "Echo" output_str "${input_str}")
message(STATUS "${output_str}")
In CMake, a list
is actually strings separated by semicolons, so an ordinary string can be transformed to a list
after inserting semicolons within it. The CMAKE_<LANG>_FLAGS
and CMAKE_<LANG>_<BUILD_TYPE>
variables use spaces as delimiters and can be passed to the foreach
statement by replacing all spaces with semicolons. It is viable to retrieve conflicting flags by reversing the WITH_STATIC_RUNTIME
option.
if(ARG_STATIC_RUNTIME)
set(runtime_switch STATIC_RUNTIME)
set(reverse_runtime_switch "")
else()
set(runtime_switch "")
set(reverse_runtime_switch STATIC_RUNTIME)
endif()
es_make_c_cxx_runtime_flags(
${reverse_runtime_switch}
RESULT_DEBUG_FLAGS reverse_debug_flags
RESULT_RELEASE_FLAGS reverse_release_flags
)
string(REPLACE " " ";" reverse_debug_flag_list "${reverse_debug_flags}")
string(REPLACE " " ";" reverse_release_flag_list "${reverse_release_flags}")
foreach(item IN LISTS reverse_debug_flag_list)
string(REPLACE ${item} "" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
string(REPLACE ${item} "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
string(REPLACE ${item} "" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
string(REPLACE ${item} "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
endforeach()
foreach(item IN LISTS reverse_release_flag_list)
string(REPLACE ${item} "" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
string(REPLACE ${item} "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
string(REPLACE ${item} "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
string(REPLACE ${item} "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
endforeach()
Now all the variables do not contain conflicting flags anymore and the new flags can be appended to them immediately.
es_make_c_cxx_runtime_flags(
${runtime_switch}
RESULT_DEBUG_FLAGS debug_flags
RESULT_RELEASE_FLAGS release_flags
)
string(APPEND CMAKE_C_FLAGS_DEBUG " ${debug_flags}")
string(APPEND CMAKE_CXX_FLAGS_DEBUG " ${debug_flags}")
string(APPEND CMAKE_C_FLAGS_RELEASE " ${release_flags}")
string(APPEND CMAKE_CXX_FLAGS_RELEASE " ${release_flags}")
If all the above code are located inside a CMake function, which has its own inner scope of variables. Extra set
statements are necessary to copy the inside variables to the outer ones.
set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} PARENT_SCOPE)
set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} PARENT_SCOPE)
set(CMAKE_C_FLAGS_DEBUG ${CMAKE_C_FLAGS_DEBUG} PARENT_SCOPE)
set(CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG} PARENT_SCOPE)
set(CMAKE_C_FLAGS_RELEASE ${CMAKE_C_FLAGS_RELEASE} PARENT_SCOPE)
set(CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE} PARENT_SCOPE)
Well done! Everything might be OK now. The full application can be checked at my personal repo named “Cpp Essence” on GitHub. I will appreciate your stars and contributions there! Big thanks.
We’re a group of volunteers and opening a new scheme in our community. Your website provided us with useful information to work on. You’ve performed an impressive process and our entire neighborhood will probably be grateful to you.
Thank you so much, Beauty Fashion, for your kind words! I’m thrilled you found my post interesting. Your support means a lot!
seo просування сайту ціна
Вибрав станцію живлення для квартири, щоб бути підготовленим до відключень електрики.
Vynikající ochranu proti povětrnostním vlivům zajišťují střešní plechové krytiny.