Project build configuration in CMake - cmake

My question is very similar to CMake : Changing name of Visual Studio and Xcode exectuables depending on configuration in a project generated by CMake. In that post the output file name will change according to the project configuration (Debug, Release and so on). I want to go further. When I know the configuration of the project, I want to tell the executable program to link different library names depending on project configurations. I was wondering whether there is a variable in CMake that can tell the project configuration. If there exists such a variable, my task will become easier:
if (Project_Configure_Name STREQUAL "Debug")
#do some thing
elseif (Project_Configure_Name STREQUAL "Release")
#do some thing
endif()

According to http://cmake.org/cmake/help/v2.8.8/cmake.html#command:target_link_libraries, you can specify libraries according to the configurations, for example:
target_link_libraries(mytarget
debug mydebuglibrary
optimized myreleaselibrary
)
Be careful that the optimized mode means every configuration that is not debug.
Following is a more complicated but more controllable solution:
Assuming you are linking to an imported library (not compiled in your cmake project), you can add it using:
add_library(foo STATIC IMPORTED)
set_property(TARGET foo PROPERTY IMPORTED_LOCATION_RELEASE c:/path/to/foo.lib)
set_property(TARGET foo PROPERTY IMPORTED_LOCATION_DEBUG c:/path/to/foo_d.lib)
add_executable(myexe src1.c src2.c)
target_link_libraries(myexe foo)
See http://www.cmake.org/Wiki/CMake/Tutorials/Exporting_and_Importing_Targets for more details.

There is always another way:
if(CMAKE_BUILD_TYPE MATCHES "release")
SET(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE})
else(CMAKE_BUILD_TYPE MATCHES "debug")
SET(CMAKE_BUILD_TYPE "debug")
endif(CMAKE_BUILD_TYPE MATCHES "release")
We can use the variable CMAKE_BUILD_TYPE. We can also change this variable at the beginning of invoking CMAKE:
cmake .. -DCMAKE_BUILD_TYPE:STRING=debug
Then we can use this variable as an indicator of build configuration.

Related

CMake conditional for Release,Debug and Etc

I am working on a CMake project that need to set specific paths for each configuration type (e.g., RELEASE, DEBUG, MINSIZEREL and RELWITHDEBINFO) for a static library in linking process. Because I have different versions of my static libraries that is used on Debug and Release versions.
So far, I have done this below in order to set the folder "release" or "debug" for my static library that I am try to link:
# Here we need to determine our build type and set the each one specific 3rd library for our binary
if( CMAKE_BUILD_TYPE STREQUAL "Debug")
#set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/Install)
#message( STATUS "CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}" )
set(THIRDLIBS_BUILD_TYPE "debug")
elseif( CMAKE_BUILD_TYPE STREQUAL "Release")
#SET(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/Install.deb)
#message( STATUS "CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}" )
set(THIRDLIBS_BUILD_TYPE "release")
elseif( CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
#SET(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/Install.deb)
#message( STATUS "CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}" )
set(THIRDLIBS_BUILD_TYPE "debug")
elseif( CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
#SET(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/Install.deb)
#message( STATUS "CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}" )
set(THIRDLIBS_BUILD_TYPE "release")
else()
MESSAGE( STATUS "CMAKE_BUILD_TYPE not set yet ${CMAKE_BUILD_TYPE}" )
endif()
if (WIN32)
set(THIRD_LIBS
../external_libs/mylibalpha/win64/${THIRDLIBS_BUILD_TYPE}/libalpha
../external_libs/mylibbeta/win64/${THIRDLIBS_BUILD_TYPE}/libbeta
)
endif(WIN32)
However, in my Visual Studio, I noticed that on each project build type (RELEASE, DEBUG, MINSIZEREL and RELWITHDEBINFO) the ${THIRDLIBS_BUILD_TYPE} variable is empty and not getting properly set. I am basically getting an error on linking showing that cannot find the external_libs/mylibalpha/win64//libalpha.lib and I should be getting something like this external_libs/mylibalpha/win64/release/libalpha.lib
What is wrong with my CMakeLists.txt script?
Variable CMAKE_BUILD_TYPE has no sense with multiconfiguration generators, and Visual Studio is one of such generators.
Canonical way for define in CMake a pre-built library which have different locations for different configuration is to create IMPORTED library target with several properties IMPORTED_LOCATION_<CONFIG> to be set:
add_library(thirdPartyLib IMPORTED UNKNOWN)
set_target_properties(thirdPartyLib PROPERTIES
IMPORTED_LOCATION_RELEASE
${CMAKE_CURRENT_SOURCE_DIR}/external_libs/mylibalpha/win64/release/libalpha.a
IMPORTED_LOCATION_DEBUG
${CMAKE_CURRENT_SOURCE_DIR}/external_libs/mylibalpha/win64/debug/libalpha.a
)
Then you need to create a mapping between build types of the main project and build types of the IMPORTED target.
Such mapping could be created either on per-target basis, by setting properties MAP_IMPORTED_CONFIG_<CONFIG>:
set_target_properties(thirdPartyLib PROPERTIES
# For Debug version of the project use DEBUG-suffixed library location
MAP_IMPORTED_CONFIG_DEBUG DEBUG
# For Release version of the project use RELEASE-suffixed library location
MAP_IMPORTED_CONFIG_RELEASE RELEASE
# For ReleaseWithDebInfo version of the project use DEBUG-suffixed library location
MAP_IMPORTED_CONFIG_RELWITHDEBINFO DEBUG
# For MinSizeRel version of the project use RELEASE-suffixed library location
MAP_IMPORTED_CONFIG_MINSIZEREL RELEASE
)
Alternatively, the mapping could be created for all IMPORTED targets by setting variables CMAKE_MAP_IMPORTED_CONFIG_<CONFIG>. Note, that these variables should be set before creation of IMPORTED target(s).
# For Debug version of the project use DEBUG-suffixed locations of IMPORTED targets
set(CMAKE_MAP_IMPORTED_CONFIG_DEBUG DEBUG)
# For Release version of the project use RELEASE-suffixed locations of IMPORTED targets
set(CMAKE_MAP_IMPORTED_CONFIG_RELEASE RELEASE)
# For ReleaseWithDebInfo version of the project use DEBUG-suffixed locations of IMPORTED targets
set(CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO DEBUG)
# For MinSizeRel version of the project use RELEASE-suffixed locations of IMPORTED targets
set(CMAKE_MAP_IMPORTED_CONFIG_MINSIZEREL RELEASE)
Having IMPORTED libraries and configurations mapping, using these libraries is quite straightforward:
target_link_libraries(my_exe PRIVATE thirdPartyLib)
Depending on the build type of the project, CMake will automatically select proper library for linking.

Getting a list of tests in CMake

My CMake project is structured with a submodule that is controlled by a third party. They have a test that is broken, and I'd like to disable it.
I thought this would be something like
add_subdirectory(thirdparty)
set_tests_properties(their_broken_test PROPERTIES DISABLED true)
But not so simple, CMake tells me this test doesn't exist.
So I tried to step back and get a list of all of the tests that CMake does think exists. But this is even more baffling:
project(cmaketest)
cmake_minimum_required(VERSION 3.5.0 FATAL_ERROR)
enable_testing()
add_test(ls /bin/ls)
get_property(LIST_OF_TESTS DIRECTORY . PROPERTY TESTS)
message(STATUS "Here are the tests: ${LIST_OF_TESTS}")
On CMake 3.10.2, this shows no tests. But on CMake 3.12.1, it shows the ls test.
Is this a bug? Or is there a way to do it with the older CMake? (3.10.2 is what is in the Ubuntu Bionic repos.)
The TESTS property was added in CMake 3.12 (release notes):
A TESTS directory property was added to hold the list of tests defined by the add_test() command.
You can do something like this to collect a list of test names as they're added.
if(${CMAKE_VERSION} VERSION_LESS "3.12.0")
function(add_test)
_add_test(${ARGN})
# There are two signatures to the add_test function to handle
if(ARGV0 STREQUAL "NAME")
set(TEST_NAME "${ARGV1}")
else()
set(TEST_NAME "${ARGV0}")
endif()
list(APPEND ALL_TESTS "${TEST_NAME}")
endfunction()
endif()
However, it does not appear you can set properties on a test defined in another directory.

Set CXX_INCLUDE_WHAT_YOU_USE property in CMake for every target

I have multiple CmakeLists.txt in my project and I'd like to enable iwyu.
Adding
set_property(
TARGETS MY-TARGET
PROPERTY CXX_INCLUDE_WHAT_YOU_USE ${iwyu_path}
)
would enable it for one target. I'd like to enable it for every target to avoid redundancy in another file.cmake I include in the CMakeLists.txt which already exists.
cmake_minimum_required(VERSION 3.3 FATAL_ERROR)
find_program(IWYU_PATH NAMES include-what-you-use iwyu)
if(NOT IWYU_PATH)
message(FATAL_ERROR "Could not find the program include-what-you-use")
endif()
set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE ${IWYU_PATH})
set(CMAKE_C_INCLUDE_WHAT_YOU_USE ${IWYU_PATH})
The "documentation" can be found here: CMAKE_<LANG>_INCLUDE_WHAT_YOU_USE

Using CMake with libraries with diamond depedencies

Lets say I have four separate projects. Three are libraries, Common, Foo, and Bar, and one of them is an executable, App. Both Foo and Bar depend on the Common library, and App depends on Foo and Bar. Furthermore, some of these projects have some scripts that need to run to generate some header and source files.
Right now I have a mess of calls like this:
if (NOT TARGET common)
add_subdirectory(${common_lib_directory})
endif()
But this doesn't feel like the right solution. If I don't wrap it in that if guard, then there are errors because it tries to build Common more than once. Putting if guards in the CMakeLists for each project doesn't seem right either. Should I be writing Find<Library> scripts for each library? I've tried looking for examples or best practices on how to set up my CMakeLists files, but the only examples I can find are either too trivial or cover completely different use cases.
It was requested in the comments that I post some code. This is more or less what my CMakeLists.txt file looks like:
cmake_minimum_required(VERSION 2.8.12)
project(app)
# Temporary files (like object files) created while compiling projects.
set(tmp_dir ${CMAKE_BINARY_DIR}/obj)
# Directory which contains the source for 3rd party libraries.
if(NOT DEFINED dependencies_root)
get_filename_component(
dependencies_root "${CMAKE_CURRENT_SOURCE_DIR}/../../../../external"
REALPATH)
endif()
set(dependencies_foo_dir "${dependencies_root}/foo"
CACHE PATH "Directory containing the foo library.")
set(dependencies_bar_dir "${dependencies_root}/bar"
CACHE PATH "Directory containing the bar library.")
if(NOT TARGET foo)
add_subdirectory("${dependencies_foo_dir}" ${tmp_dir}/foo)
endif()
if(NOT TARGET bar)
add_subdirectory("${dependencies_bar_dir}" ${tmp_dir}/bar)
endif()
include_directories(${dependencies_foo_dir}/include)
include_directories(${foo_generated_include_dir})
include_directories(${dependencies_bar_dir}/include)
include_directories(${bar_generated_include_dir})
set(app_srcs ...)
add_executable(app ${app_SRCS})
target_link_libraries(app foo bar common)
As previously mentioned, the reason I have the if (NOT TARGET blah) guards is because if I don't then I get errors like these:
CMake Error at /path/to/my/project/CMakeLists.txt:267 (add_library):
add_library cannot create target "blah" because another target with the
same name already exists. The existing target is a static library created
in source directory
"/path/to/blah".
See documentation for policy CMP0002 for more details.
If you have such closely-linked projects, the best decision seems to use guard from re-including at the beginning of each project. Such guard can be easily implemented using return command, which returns from currently executed add_subdirectory() call:
Foo/CMakeLists.txt:
if(DEFINED Foo_GUARD)
if(NOT Foo_GUARD STREQUAL ${CMAKE_CURRENT_BINARY_DIR})
return() # Project has been already included by someone else
endif()
else()
set(Foo_GUARD ${CMAKE_CURRENT_BINARY_DIR} CACHE INTERNAL "Foo guard")
endif()
project(Foo)
...
So any project can use unprotected add_subdirectory() call for include given one:
App/CMakeLists.txt:
...
# Need *Foo* functionality? Simply include it!
add_subdirectory("${dependencies_foo_dir}" ${tmp_dir}/foo)
It is possible to implement guard as a macro, put it into the library and use it in every your project:
cmake/utils.cmake:
macro(project_guarded name)
if(DEFINED ${name}_GUARD)
if(NOT ${name}_GUARD STREQUAL ${CMAKE_CURRENT_BINARY_DIR})
return() # return() *doesn't* terminate a macro!
endif()
else()
set(${name}_GUARD ${CMAKE_CURRENT_BINARY_DIR} CACHE INTERNAL "${name} guard")
endif()
project(${name})
endmacro()
Macro usage is straightforward:
project_guarded(Foo)

Where to set CMAKE_CONFIGURATION_TYPES in a project with subprojects

Lets say I have a project with two independent subprojects. If I understood cmake correctly, the idea would be to have one root CMakeLists.txt defining a project(...) and then using add_subdirectory(...) to include the subprojects. Each subproject would have its own CMakeLists.txt defining its own project. This way projects can be build either together (using the root cmake file) or individually (using the subprojects cmake file).
I now would like to change the CMAKE_CONFIGURATION_TYPES. Should I do this in the root CMakeLists.txt or in each subproject, or both?
Changing it in the root would mean that building a subproject individually would offer the wrong configuration types; the other options would duplicate the cmake code. I think I'm missing something here.
Factorize out the code that sets up configuration-dependent settings. Create a file, say, SetUpConfigurations.cmake with this content:
if(NOT SET_UP_CONFIGURATIONS_DONE)
set(SET_UP_CONFIGURATIONS_DONE TRUE)
# No reason to set CMAKE_CONFIGURATION_TYPES if it's not a multiconfig generator
# Also no reason mess with CMAKE_BUILD_TYPE if it's a multiconfig generator.
get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(isMultiConfig)
set(CMAKE_CONFIGURATION_TYPES "Debug;Release;Profile" CACHE STRING "" FORCE)
else()
if(NOT CMAKE_BUILD_TYPE)
message("Defaulting to release build.")
set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE)
endif()
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY HELPSTRING "Choose the type of build")
# set the valid options for cmake-gui drop-down list
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug;Release;Profile")
endif()
# now set up the Profile configuration
set(CMAKE_C_FLAGS_PROFILE "...")
set(CMAKE_CXX_FLAGS_PROFILE "...")
set(CMAKE_EXE_LINKER_FLAGS_PROFILE "...")
endif()
Then include(..) this file at the beginning of the CMakeLists.txt's.
You have two choices about where to put SetUpConfigurations.cmake, it depends on how you organize your projects, repositories:
The quick'n'dirty way: Copy and commit this script into each project that needs it. Its location will be fixed, relative to the CMakeLists.txt of the project. So you can include it, for example, with include(${CMAKE_CURRENT_SOURCE_DIR}/<...>/SetUpConfigurations.cmake)
The disciplined way: Maintain a repository with your custom CMake scripts, like this one. Each time you generate a project with the cmake command, you pass the path to this repository in the CMAKE_MODULE_PATH variable:
cmake -DCMAKE_MODULE_PATH=<dir-of-cmake-script-repo> ...
In this case include the script with include(SetUpConfigurations) (no .cmake extension).
A note about what a multiconfig generator is:
Xcode and Visual Studio are multiconfig generators. They respect the value of CMAKE_CONFIGURATION_TYPES but CMAKE_BUILD_TYPE has no effect since no concrete configuration is defined when the CMakeLists.txt is processed. It will be selected on the IDE's user interface later.
On the other hand, the makefile-style generators are not interested in CMAKE_CONFIGURATION_TYPES. CMAKE_BUILD_TYPE defines the configuration. It is a concrete value when the CMakeLists.txt file is processed but still: never make any decisions based on the value of CMAKE_BUILD_TYPE:
if(CMAKE_BUILD_TYPE STREQUAL "Release") # WRONG!
....
endif()
You project won't work as intended in multiconfig generators.
When use add_subdirectory into subproject dir, you propagate almost all variables into that subproject, which contradicts to "subproject independency".
Instead, it is better to build and install subproject using nested cmake call inside execute_process(). If you want to make some subproject's definitions available for top-level project, you need to "export" this definitions when subproject is installed. This question/answer post describes, how to do that.