- Using <_d> suffix for Debug config
- Linking targets based on configurations
- Using
git submodulesfor open-source package management - _WIN32 vs _MSC_VER Predefined Macros
- CMAKE_MODULE_PATH argument should be passed as an absolute path
- RPATH Handling
- TARGET_FILE Generator expression
- Copy files&directory after build before install
- Linking Windows DLL during CMake after build before install
find_package()usage- Installation of public & private headers
- Custom CMake functions
- CMake install for presets
- Target architecture definition
In order to append "_d" suffix for the targets based on configurations, use the following command:
set(CMAKE_DEBUG_POSTFIX "_d")Define this command within root level CMakeLists.txt to make this setting global.
Configuration based suffix does not work for executables. Some tricks have to be made.
Based on the active configuration, you can link your project to targets based on configuration.
debug and optimized keywords should be used before the target name
# Sample 1
target_link_libraries(
${PROJECT_NAME}
PRIVATE debug debug_lib1 debug_lib2 debug_lib3
PRIVATE optimized rel_lib1 rel_lib2 rel_lib3
)
# Sample 2
target_link_libraries ( ${PROJECT_NAME}
debug ${Boost_FILESYSTEM_LIBRARY_DEBUG}
optimized ${Boost_FILESYSTEM_LIBRARY_RELEASE} )
target_link_libraries ( ${PROJECT_NAME}
debug ${Boost_LOG_LIBRARY_DEBUG}
optimized ${Boost_LOG_LIBRARY_RELEASE} )
target_link_libraries ( ${PROJECT_NAME}
debug ${Boost_PROGRAM_OPTIONS_LIBRARY_DEBUG}
optimized ${Boost_PROGRAM_OPTIONS_LIBRARY_RELEASE} )You can use git submodules to bring external dependencies to your current project. If the repo has cmake support, you only need to add the target (link) to your projects i.e. GLFW. However, if the external repo has NO cmake setup available i.e. imgui, some additional setup has to be made to be consumed by other cmake targets.
Steps:
- Generate .gitmodules in the root directory:
[submodule "src/external/glfw"]
path = src/external/glfw
url = https://github.com/glfw/glfw.git- Run
git submodulescommand.
To trigger git submodules command from cmake:
# Make sure git is installed in the system so one can use git submodules
# To make the submodules download when running the cmake
find_package(Git QUIET)
if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git")
# Update submodules as needed
option(GIT_SUBMODULE "Check submodules during build" ON)
if(GIT_SUBMODULE)
message(STATUS "Submodule update")
execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
RESULT_VARIABLE GIT_SUBMOD_RESULT)
if(NOT GIT_SUBMOD_RESULT EQUAL "0")
message(FATAL_ERROR "git submodule update --init failed with ${GIT_SUBMOD_RESULT}, please checkout submodules")
endif()
endif()
endif()If you navigate to submodule dir and run git status, you have to see the status for the submodule repo, NOT the main repo.
git submodulesby default, detached to HEAD state of the repository. If you want to detach for a specific commit or tag, you have navigate to the submodule directory and checkout the specific commit/tag. link
As stated in the reference:
Well, there is no right or wrong. Both are correct - each for their purpose.
_WIN32is for generally checking if the app is built for/on Windows._MSC_VERis specifically targeted towards the Microsoft compiler (aka Visual Studio or C++ Build Tools) and checking the version thereof as each version might have different bugs aehm features 😏
MSVC++ 4.x _MSC_VER == 1000
MSVC++ 5.0 _MSC_VER == 1100
MSVC++ 6.0 _MSC_VER == 1200
MSVC++ 7.0 _MSC_VER == 1300
MSVC++ 7.1 _MSC_VER == 1310 (Visual Studio 2003)
MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005)
MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008)
MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010)
MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012)
MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013)
MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015)
MSVC++ 14.1 _MSC_VER == 1910 (Visual Studio 2017)When passing a value for argument CMAKE_MODULE_PATH through CMake CLI, make sure it is absolute (full) path.
conan install ./src/conan/conanfile.txt --profile ./src/conan/conanprofile.txt -if conan
cmake ./src -B ./src/build -DCMAKE_MODULE_PATH=$PWD/conan- How to copy contents of a directory into build directory after make with CMake?
- Installing additional files with CMake
Unless otherwise stated, Windows OS searches for a dependent DLL in the load time and the first location it looks for is the same directory of the running executable. Keeping this fact in mind that, when you try to link a shared library to an executable within CMake, you have to explicitly adjust the search paths for the CMake post-build.
NOTE: If you try to run the executable CMake target without adjusting the search path, the run will fail. NOTE: The belowmentioned methods are NOT related to or part of CMake
installprocess. As long as, you adjust the install locations properly for executables and their dependent shared libs, it will work fine.
There are several ways to do so:
- Copying the dependent shared library binaries as a
POST_BUILDoperation usingadd_custom_commandandcopy_directoryorcopy_if_differentorcopy.
# Define dependent shared libraries
set(_SHARED_LIBS)
list(APPEND _SHARED_LIBS bc_dynamic)
target_link_libraries(
${PROJECT_NAME}
PRIVATE ${_SHARED_LIBS} bc_header_only bc_static
)
# copy the .dll file to the same folder as the executable
if(WIN32)
foreach(shared_lib ${_SHARED_LIBS})
message("Copying dependent ${shared_lib} binary for ${PROJECT_NAME}...")
add_custom_command(
TARGET ${PROJECT_NAME}
POST_BUILD
COMMAND
${CMAKE_COMMAND} -E copy_directory
$<TARGET_FILE_DIR:${shared_lib}>
$<TARGET_FILE_DIR:${PROJECT_NAME}>
VERBATIM
)
endforeach()
endif()
- References
- Define the shared library as
IMPORTEDtarget.
- References
In order to arrange dependencies between projects within the same build root, there is no need to use find_package(), find_library() etc. However, if one wants to consume an out-of-build (external) cmake project other than using package managers, several steps should be taken.
It is always advised to use
GNUInstallDirsCMake module.
-
The library (to-be consumed) CMake project has to define
exportproperties usinginstallcommand.- The .config file has to be written to a location and that location should be passed to consumer build root using
CMAKE_PREFIX_PATH.
include(GNUInstallDirs) # If no destination is provided, default locations are used. # https://cmake.org/cmake/help/v3.25/command/install.html#installing-targets install( TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}-config ) # Write config install( EXPORT ${PROJECT_NAME}-config DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} ) # OPTIONAL: IF you want to copy headers install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
- This location is basically the
CMAKE_INSTALL_PREFIXof the to-be-consumed library.
#!/bin/bash cmake -B build/debug -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=install - The .config file has to be written to a location and that location should be passed to consumer build root using
-
The consumer CMake build root should be configured by passing
-DCMAKE_PREFIX_PATHwith the value from the previous step.- Configure
cmake -B build/debug -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PREFIX_PATH=$(pwd)/../library/install- The consumer projects within the build root, now can use
find_package()
#.... find_package(calculator-static REQUIRED CONFIG) target_link_libraries(${PROJECT_NAME} PRIVATE the_lib)
-
find_package()with config mode will execute a search underCMAKE_PREFIX_PATHwith a procedure for the file<lowercasePackageName>-config.cmake. -
If one wants to use
MODULEmode forfind_package(), it will execute search underCMAKE_MODULE_PATHfor the fileFind<PackageName>.cmake.- Package managers (
conan) usually handles this by implicitly.
- Package managers (
- Using
target_sourceswithFILE_SETof typeHEADERS
# target_include_directories() definition is NOT required (except header only libs)
add_library(${PROJECT_NAME} STATIC "")
target_sources(
${_LOGGING_PROJECT}
PRIVATE
logging/logger.cpp
PUBLIC
FILE_SET ${_LOGGING_PROJECT}_header_files
TYPE HEADERS
BASE_DIRS logging/include
FILES
logging/include/core/logger.h
# NOT TESTED
PRIVATE
FILE_SET private_header_files
TYPE HEADERS
BASE_DIRS logging
FILES
logging/impl.h
)
# Make sure you pass FILE_SET to install(TARGET) command
# File sets are defined by the target_sources(FILE_SET) command.
# If the file set <set> exists and is PUBLIC or INTERFACE, any files in the set are installed under the destination
include(GNUInstallDirs)
install(
TARGETS ${_LOGGING_PROJECT}
EXPORT ${_LOGGING_PROJECT}Targets
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
FILE_SET ${_LOGGING_PROJECT}_header_files DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)- Using
install(DIRECTORY)orinstall(FILES)command
target_include_directories(
${PROJECT_NAME}
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> # or ${PROJECT_SOURCE_DIR}/include
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> # or include
)
# Only define .cpp and private header files here
target_sources(${PROJECT_NAME}
PRIVATE
mylib.cpp
impl.h
)
# Only expose public header files
include(GNUInstallDirs)
install(
TARGETS ${PROJECT_NAME}
EXPORT ${PROJECT_NAME}Targets
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
install(
DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)- Using
PUBLIC_HEADERSandPRIVATE_HEADERStarget property. (This feature is mostly used for MacOS amd iOS)
# Set the target property
set_target_properties(
${PROJECT_NAME}
PROPERTIES
PUBLIC_HEADER "include/static/mylib.h"
)
# Pass it over to install
include(GNUInstallDirs)
install(
TARGETS ${PROJECT_NAME}
EXPORT ${PROJECT_NAME}Targets
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)The problem with this approach is that
PUBLIC_HEADERoutput artifact forinstall(TARGETS)command can NOT keep the nested include directory structure so for this example,mylib.hfile will sit directly underincludedir in the installation.
CMake provides custom scripting using function and macro mechanisms. The better approach is to place common functionality in .cmake files and make them available through include() command.
- Make sure the
.cmakefile's dir location is registered throughCMAKE_MODULE_PATH.
# Update CMAKE_MODULE_PATH in the root CMakeLists.txt
# Option1:
set( CUSTOM_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake" )
set( CMAKE_MODULE_PATH ${CUSTOM_MODULE_PATH} ${CMAKE_MODULE_PATH} )
# Option2: Use list
if(NOT CMAKE_MODULE_PATH)
set(CMAKE_MODULE_PATH)
endif()
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")Do NOT directly set
CMAKE_MODULE_PATHandCMAKE_FIND_ROOT_PATHusingset(CMAKE_MODULE_PATH <some_path>)
- Define the functions/macros in
.cmakefiles
function(bc_header_only_library TARGET_NAME)
# TARGET_NAME argument is the required argument
# Arg definitions
# boolean flag arguments with no value
set(flags)
# Single-valued arguments
set(args BASE_DIR)
# Multiple-valued(list) arguments
set(listArgs DEP_TARGETS DEFINES)
cmake_parse_arguments(arg "${flags}" "${args}" "${listArgs}" ${ARGN})
# check target_definitions.cmake file for details
# ...
endfunction()- Make it available via
include(target_definitions) - Usage in any other
CMakeLists.txtfile.
set(_header_files)
list(APPEND _header_files
header_only/print_utils.h
)
bc_header_only_library_file_set(${PROJECT_NAME} #-->TARGET_NAME
BASE_DIR include
PUBLIC_HEADERS ${_header_files}
)CMake does NOT provide any preset capability for install operations as of now. If you're using CMake presets and want to carry out install command, try one of the following.
-
cmake --build --preset win_debug --target install. (Preferred)- To install w/o building:
set(CMAKE_SKIP_INSTALL_ALL_DEPENDENCY ON)
-
Defining custom target that refers to generate
cmake_install.cmakefile. (Only works for Release.)
add_custom_target(INSTALL_TARGETS
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_BINARY_DIR}/cmake_install.cmake
)
set_target_properties(INSTALL_TARGETS PROPERTIES EXCLUDE_FROM_ALL TRUE)Then, call cmake --build --preset win_release --target INSTALL_TARGETS. Again, even if you had sucessfull Debug build, this call will result in error.
- This option requires the user explicitly pass hardcoded
CMAKE_BINARY_DIRlocation to the command so not really preferred. However, it is still working.
cmake --install out/build/msvc --config Debug - presets: Add install presets for `cmake --install --preset
- CMAKE_SKIP_INSTALL_ALL_DEPENDENCY - CMake Documentation
The architecture definition for target platforms in CMake is not very well documented. There are some solutions scattered around the web but no one explains it clearly. This is my clear and concise attempt to do so:
This topic has nothing to do with cross compiling.
-
CMAKE_HOST_SYSTEM_NAME– name of the platform, on which CMake is running (host platform). On major operating systems this is set to the Linux, Windows or Darwin (MacOS) value. -
CMAKE_SYSTEM_NAME– name of the platform, for which we are building (target platform). By default, this value is the same as CMAKE_HOST_SYSTEM_NAME, which means that we are building for local platform (no cross-compilation). -
CMAKE_HOST_SYSTEM_PROCESSOR: The name of the CPU CMake is running on. -
CMAKE_SYSTEM_PROCESSOR: When not cross-compiling, this variable has the same value as the CMAKE_HOST_SYSTEM_PROCESSOR variable. In many cases, this will correspond to the target architecture for the build, but this is not guaranteed. (E.g. on Windows, the host may be AMD64 even when using a MSVC cl compiler with a 32-bit target.)
Defining the target architecture is only available when CMAKE_GENERATOR_PLATFORM is defined. I typically use Visual Studio Generators for Windows builds. Use one of the following:
- CMakePresets.json
architecturefield underconfigurePresetssection.
{
"name": "msvc",
"displayName": "msvc config",
"generator": "Visual Studio 16 2019",
"architecture": {
"value": "x64",
"strategy": "set"
},
"hidden": false,
"binaryDir": "${sourceDir}/out/build/${presetName}",
"installDir": "${sourceDir}/out/install/${presetName}",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Windows"
}
}- CMake CLI by passing target platform value to
Aargument.
cmake -G "Visual Studio 16 2019" -A Win32
cmake -G "Visual Studio 16 2019" -A x64
cmake -G "Visual Studio 16 2019" -A ARM
cmake -G "Visual Studio 16 2019" -A ARM64- Documentation - CMAKE_GENERATOR_PLATFORM
- VSCode-cmake-tools doc - Select your compilers
- How to cross-compile for embedded with CMake like a champ
- Some commands to get cpu (arch) info
uname -m
uname -p
lscpu
cat /proc/cpuinfo