cmake: Enable features based on the result of multiple find_package() calls - build

I have a project that contains a visual debugger that I want to build only if a group of packages were found. I don't want to force a user that just want to build the base application to install all the debugger dependencies, so I don't mark then as REQUIRED when running find_package().
Right now my build looks like this:
./CMakeList
find_package(gazebo)
find_package(OpenGL)
find_package(GLUT)
add_subdirectory(debugger)
./debugger/CMakeList
if(NOT gazebo_FOUND OR NOT OpenGL_FOUND OR NOT GLUT_FOUND)
return()
endif()
This works just fine, but as the list of dependencies for the debugger grows, using this if to enable/disable the debugger build looks clumsy.
Is there a better alternative to enable the build of this sub-directory base on the result of find_package()?
The debugger is not the only thing in my project that I enable/disable this way, so I need a generic solution for enabling features based on the packages found.

CMake has no already-made a macro or a function to check list of variables at once.
But it is not a difficult to write a function which do that. E.g.:
# Usage: feature_check(FEATURE_VAR REQUIRED_VAR [REQUIRED_VAR2 ...])
#
# Set FEATURE_VAR to TRUE if all required vars are TRUE,
# Otherwise set FEATURE_VAR to FALSE.
function(feature_check FEATURE_VAR REQUIRED_VAR)
foreach(var ${REQUIRED_VAR} ${ARGN})
if(NOT ${var})
set(${FEATURE_VAR} "FALSE" PARENT_SCOPE)
return()
endif(NOT ${var})
endforeach(var ${REQUIRED_VAR} ${ARGN})
set(${FEATURE_VAR} "TRUE" PARENT_SCOPE)
endfunction(feature_check FEATURE_VAR REQUIRED_VAR)
So, in you case you may use
# In CMakeList.txt, after find_package()
feature_check(USE_DEBUGGER gazebo_FOUND OpenGL_FOUND GLUT_FOUND)
# In debugger/CMakeList.txt
if(NOT USE_DEBUGGER)
return()
endif(NOT USE_DEBUGGER)
...

Turning my comments into an answer
I see three alternatives:
Using FindPkgConfig module to combine the search for packages into a single call/result variable? Something like:
pkg_check_modules(DEBUGGER_DEPS gazebo gl glu)
would - on platforms with pkg-config - also make the find_package() calls obsolete by using the results of the above call:
target_compile_options(debugger PUBLIC ${DEBUGGER_DEPS_CFLAGS})
target_link_libraries(debugger PUBLIC ${DEBUGGER_DEPS_LDFLAGS})
Using CMakeDependentOption module to combine the boolean values into a single option to check with something like
CMAKE_DEPENDENT_OPTION(
BUILD_DEBUGGER "Build debugger" ON
"gazebo_FOUND;OpenGL_FOUND;GLUT_FOUND" OFF
)
Or - if the project is huge - maybe just write my own my_target_link_libraries() function which includes the necessary if (TARGET ...) dependency checks and removes the main target from all/default build if it fails:
function(my_target_link_libraries _target)
foreach(_dep IN ITEMS ${ARGN})
if (TARGET _dep)
target_link_libraries(${_target} ${_dep})
else()
message(STATUS "No target '${_dep}' found. Disabling '${_target}' build")
set_target_properties(${_target} PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD 1)
endif()
endforach()
endfunction()
my_target_link_libraries(debugger GLUT::GLUT ...)

Related

Access CMake's target/library name at compile time globally

We implement a logger which automatically prints out the project name of a log entry among other infos.
We recently change our build system from using native Microsoft visual c++ to cmake generated.
With native Microsoft c++ build files, we were just defining a macro "PROJECT_NAME" in a global ".props" as such:
<PreprocessorDefinitions>%(PreprocessorDefinitions);PROJECT_NAME=R"($(ProjectName))"</PreprocessorDefinitions>
However, now that we use CMake, I struggle to find a good way to access the project name at compile time. I can use configure_file or target_compile_definitions to access a CMake variable at compile time but I do not know any variable holding a string with the target name. Is there such a variable or can it be defined?
Also, can it be defined only in one place? I don't want to copy paste in every CMakeList.txt a line of the such:
target_compile_definitions(MYTARGET PRIVATE PROJECT_NAME="$mytarget_name")
If you've got multiple options to apply to all targets, you could link a INTERFACE library which allows you to inherit multiple properties of this cmake target via a single target_link_libraries use.
For using the name of the linking target, generator expressions that do not require specifying the target can be used:
add_library(OptionsForAll INTERFACE)
# note: using the target name not the cmake project name here
target_compile_definitions(OptionsForAll INTERFACE PROJECT_NAME=\"$<TARGET_PROPERTY:NAME>\")
target_link_libraries(MYTARGET PRIVATE OptionsForAll)
Another option mentioned in #Tsyvarevs comment would be to use add_compile_definitions to apply the definition for all targets defined in the current directory and subdirectory, but this makes it harder to remove it from some targets...
fabian's answer is IMO the good approach.
However, i wanted to offer an alternative based on BUILDSYSTEM_TARGETS inspired from this answer.
Thus, there is no need to modify all target's call to target_link_libraries.
function(get_all_targets var)
set(targets)
get_all_targets_recursive(targets ${CMAKE_CURRENT_SOURCE_DIR})
set(${var} ${targets} PARENT_SCOPE)
endfunction()
macro(get_all_targets_recursive targets dir)
get_property(subdirectories DIRECTORY ${dir} PROPERTY SUBDIRECTORIES)
foreach(subdir ${subdirectories})
get_all_targets_recursive(${targets} ${subdir})
endforeach()
get_property(current_targets DIRECTORY ${dir} PROPERTY BUILDSYSTEM_TARGETS)
list(APPEND ${targets} ${current_targets})
endmacro()
function(define_targetname all_targets)
foreach(target ${all_targets})
get_target_property(type ${target} TYPE)
if (NOT ${type} STREQUAL "INTERFACE_LIBRARY" AND NOT ${target} STREQUAL "PCHTarget") # INTERFACE library cannot target_compile_definitions && precompiled headers will redefine the MACRO
target_compile_definitions(${target} PRIVATE PROJECT_NAME="${target}")
endif()
endforeach()
endfunction()
get_all_targets(all_targets)
define_targetname("${all_targets}")

Do I need a Shared library in a CMAKE project with several subdirectories? How do I do it?

I need to implement a StatisticsLogger library on to a C++/C solution built with cmake and compiled with VS#2010. The project uses the ADTF API, since is built for the ADTF framework.
The solution is composed by different modules/projects, which include their own CMakeLists.txt.
My doubt/problem is regarding the library and Cmake, I need it to be accesible to every single module, but it can't be copied, them all should access the same StatisticsLogger library, that I have implemented with a Singleton.
When I run the framework, the concurrent execution accesses StatsLogger constructor once on each module, like if I had created one StatsLogger on each module, making it unable to trace together all the data I want to log and difficulting file handling.
This is how I added the library in CMakeLists.txt:
add_library(loggerModule
${DSTD_DIR}/dstdfloat.h
${DSTD_DIR}/dstdint.h
${DSTD_DIR}/dstdbool.h
${SUPT_DIR}/logg/statslogger.h
${SUPT_DIR}/logg/statslogger_c_connector.h
${SUPT_DIR}/logg/statslogger_c_connector.cpp
${SUPT_DIR}/logg/statslogger.cpp
)
#set_target_properties(loggerModule PROPERTIES VERSION ${PROJECT_VERSION})
#set_target_properties(loggerModule PROPERTIES PUBLIC_HEADER include/mylib.h)
link_libraries(loggerModule)
It would seem that adding SHARED property to the command add_library would do the job, but Im not capable of getting it working. It returns several link problems.
So, regarding my doubt, Is this the way to get the desired funcionality, to make the library SHARED? What am I doing wrong?
Main CMakeLists.txt:
# CMAKE for test filter build
cmake_minimum_required(VERSION 2.8.4 FATAL_ERROR)
# Set default install prefix
set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR})
# Set project name
project(${PROJECT_NAME})
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
# Environment checks
find_package(ADTF 2.14.2 REQUIRED)
IF (NOT DEFINED ADTF_FOUND)
MESSAGE( FATAL_ERROR "ADTF was NOT found!!!")
ENDIF()
find_package(ADTF_DISPLAY_TOOLBOX REQUIRED)
IF (NOT DEFINED ADTF_DISPLAY_TOOLBOX_FOUND)
MESSAGE( FATAL_ERROR "ADTF_DISPLAY_TOOLBOX was NOT found!!!")
ENDIF()
set(COMPLETE_PROJECT_BINARY_OUTPUT_DIR ${CMAKE_SOURCE_DIR}/${PROJECT_BINARY_OUTPUT_DIRECTORY})
# Set path to sw module dependencies
set(COMMON_DIR ${CMAKE_SOURCE_DIR}/common)
set(DSTD_DIR ${CMAKE_SOURCE_DIR}/common/dstd)
set(INTF_DIR ${CMAKE_SOURCE_DIR}/common/intf)
set(SUPT_DIR ${CMAKE_SOURCE_DIR}/common/supt)
#set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
#option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
include_directories(${DSTD_DIR})
include_directories(${SUPT_DIR}/logg)
add_library(loggerModule SHARED
${DSTD_DIR}/dstdfloat.h
${DSTD_DIR}/dstdint.h
${DSTD_DIR}/dstdbool.h
${SUPT_DIR}/logg/statslogger.h
${SUPT_DIR}/logg/statslogger_c_connector.h
${SUPT_DIR}/logg/statslogger_c_connector.cpp
${SUPT_DIR}/logg/statslogger.cpp
)
#set_target_properties(loggerModule PROPERTIES VERSION ${PROJECT_VERSION})
#set_target_properties(loggerModule PROPERTIES PUBLIC_HEADER include/mylib.h)
link_libraries(loggerModule)
# Set commands for BB nodes generation
# ...and dependencies for generation
# Go into sub-directory with filter sources
add_subdirectory(${CMAKE_SOURCE_DIR}/tool/adtf/af_acca)
#[33 other add_subdirectories commands]
Example subdirectory CMakeLists.txt:
# External required components have to be provided with path variables
# Internal required components have to be connected to the source file list
if(NOT DEFINED DSTD_DIR)
message( FATAL_ERROR "AF_CDAS requires DSTD_DIR" )
endif()
if(NOT DEFINED INTF_DIR)
message( FATAL_ERROR "AF_CDAS requires INTF_DIR" )
endif()
if(NOT DEFINED SUPT_DIR)
message( FATAL_ERROR "AF_CDAS requires SUPT_DIR" )
endif()
set(CDAS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../)
include_directories(${COMMON_DIR})
[some more includes]
#
# CDAS FILES
#
set(CDAS_FILES
${CDAS_DIR}/src/cdas/cdas.c
${CDAS_DIR}/src/cdas/cdas.h)
source_group("cdas\\files" FILES ${CDAS_FILES})
#
# CDAS ADTF WRAPPER
#
set(CDAS_ADTF_WRAPPER
${CDAS_DIR}/tool/adtf/af_cdas/af_cdas_conf.h
${CDAS_DIR}/tool/adtf/af_cdas/af_cdas.h
${CDAS_DIR}/tool/adtf/af_cdas/af_cdas.cpp)
source_group("cdas\\adtf" FILES ${CDAS_ADTF_WRAPPER})
adtf_add_filter(CDAS
# List source and header files of your filter and its required components
# ${MAIN_FILES}
${CDAS_FILES}
${CDAS_ADTF_WRAPPER}
)
# stdafx.h workaround
if (MSVC)
set_target_properties(CDAS PROPERTIES COMPILE_FLAGS "/Y-")
endif(MSVC)
# MANDATORY PATH SETTING !!!
install (TARGETS CDAS DESTINATION ${COMPLETE_PROJECT_BINARY_OUTPUT_DIR}/debug CONFIGURATIONS Debug)
install (TARGETS CDAS DESTINATION ${COMPLETE_PROJECT_BINARY_OUTPUT_DIR}/release CONFIGURATIONS Release)
adtf_set_folder(CDAS filter)
When using this files, I get the following error:
LINK : fatal error LNK1181: cannot open input file '......\Release\loggerModule.lib' [C:\Users\inno\Desktop\rad
ar_processing\test\adtf\build\win64_vc100\tool\adtf\af_aoca\AOCA.vcxproj]
There isn't any loggerModule.lib in the Release folder, but a loggerModule.dll. In the Debug folder there is a .lib, but copying it to Release won't solve the problem.
I never got too familiar with Cmake, and I am trying to learn it, so I don't know what is going on here, if I am doing something wrong, or my approach wasn't good from the beggining.

CMake qt input library postfix for custom debug build

I'm stuck with writing a cmake file for multiconfiguration IDE (Visual Studio).
My goal is to add a custom configuration and tell Visual Studio that I want to use debug libs of Qt (qtcored.lib) as it is done when I pick Debug configuration. With code below, I have release libraries in a linker input when I pick CustomDebug configuration
Does anyone know how to achieve that?
Thanks
cmake_minimum_required(VERSION 3.12.0)
project(custom-conf)
find_package(Qt5Core CONFIG REQUIRED)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(SRC main.cpp)
set(QT_LIBS Qt5::Core)
add_executable(custom-conf WIN32 ${SRC})
target_link_libraries(custom-conf ${QT_LIBS})
#
get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(isMultiConfig)
set(CMAKE_CONFIGURATION_TYPES "CustomDebug;Debug;Release" CACHE STRING "" FORCE)
set(CMAKE_EXE_LINKER_FLAGS_CUSTOMDEBUG "/debug")
endif()
I want to use debug libs of Qt (qtcored.lib) as it is done when I pick Debug configuration.
With the IMPORTED target this can be easiliy achieved by using MAP_IMPORTED_CONFIG<CONFIG> target property:
# For CustomDebug configuration of the main project
# use Debug configuration of the IMPORTED target
set_target_properties(Qt5::Core PROPERTIES
MAP_IMPORTED_CONFIG_CUSTOMDEBUG DEBUG)
With setting CMAKE_MAP_IMPORTED_CONFIG<CONFIG> variable you may automatically set the property for all IMPORTED targets:
set(CMAKE_MAP_IMPORTED_CONFIG_CUSTOMDEBUG DEBUG)
#...
# This call will create IMPORTED target Qt5::Core which
# MAP_IMPORTED_CONFIG_CUSTOMDEBUG property is already set.
find_package(Qt5Core CONFIG REQUIRED)
(The variable assignment should come before any call like find_package which creates IMPORTED target.)

cmake boost find_depedency Config

I ran into this issue when trying to generate a Config for my build and the consume the target upstream while using different boost COMPONENTS. It feels like I'm missing something or maybe even misunderstood how I'm supposed to be using the Config.cmake file to generate targets for an upstream package. That you have to specify find_dependency again seems...off.
In the below SomeProjectConfig.cmake file I have to check whether an upstream package did load the necessary targets for SomeProject to work, if it didn't I need to call find boost with the necessary components again.
# SomeProjectConfig.cmake.in
# Avoid repeatedly including the targets
if(NOT TARGET SomeProject::SomeProject)
# Provide path for package module scripts, CMAKE_CURRENT_LIST_DIR is the
# directory of the currently executing cmake file.
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR})
include(CMakeFindDependencyMacro)
# --------------------------------------------------------------------------
# Boost
# The way I choose to go about it is to simply check if the dependency boost
# targets are defined - if they are we don't have to do anything and just let
# everything pass through. However if they are missing we need to set
# Boost_FOUND back to FALSE so it'll resolve the dependencies...
list(APPEND SomeProject_Boost_COMPONENTS system thread)
foreach(_comp ${SomeProject_Boost_COMPONENTS})
if(NOT TARGET Boost::${_comp})
set(Boost_FOUND 0)
break()
endif()
endforeach()
# Additionally we record the BOOST_ROOT - but we try to respect the upstream
# package if specifies it.
if(NOT BOOST_ROOT)
set(BOOST_ROOT "#BOOST_ROOT#"
endif()
# We want to handle this quietly, upstream packages might want to include more
# componentes and if we ask for components first Boost will handle it as if
# we've already done all the necessary work.
find_dependency(Boost 1.55 QUIET REQUIRED COMPONENTS ${SomeProject_Boost_COMPONENTS})
# We can never leave without setting Boost_FOUND to FALSE - if the upstream
# package has find_package(Boost) after find_package(SomeProject) it'll
# break if Boost_FOUND is TRUE
set(Boost_FOUND 0)
# // Boost
# --------------------------------------------------------------------------
find_dependency(SomeOtherDependency 1.0 QUIET REQUIRED)
include("${CMAKE_CURRENT_LIST_DIR}/SomeProjectTargets.cmake")
# Clean up module path after we're done
list(REMOVE_AT CMAKE_MODULE_PATH -1)
endif()
The problem with the above as I see it is that we're leaving Boost_FOUND set to 0 and from my point of view it's really hard to control, it also feels like I'm actually not understanding the Config package process correctly.
# CMakeLists.txt upstream
find_package(Boost 1.55 REQUIRED COMPONENTS filesystem)
set(SomePackage_DIR "/path/to/Config/location")
find_package(SomePackage REQUIRED)
add_library(UpstreamTarget SHARED ${sources})
target_link_libraries(UpstreamTarget PUBLIC Boost::filesystem SomeProject::SomeProject)
The above will work, but it leaves me with a bad itch - it feels like I'm doing it wrong.
Turns out it was my use of the find_dependency macro, replacing it with find_package gave me the correct results without workarounds.
If we dive into the CMakeFindDependencyMacro.cmake it becomes quite clear what the issue is.
macro(find_dependency dep)
if (NOT ${dep}_FOUND)
...
If the package is already found it will stop the execution right there and won't append the additional components. Meaning you could have the find_package(SomePackage) call before any additional find_package(Boost) upstream, but not the other way around.
I don't think this is a problem for isolated packages but once you have components that you might need to append to an already existing target find_dependency seems to break due to the if statement. Judging from another answer in regards to find_dependency it looks like it would mostly be for diagnostic messages telling the user that the problem comes from a *Config.cmake file than anything else.
for completeness here's a working example of the config file where I removed the workarounds:
# SomeProjectConfig.cmake.in
# Avoid repeatedly including the targets
if(NOT TARGET SomeProject::SomeProject)
# Provide path for package module scripts, CMAKE_CURRENT_LIST_DIR is the
# directory of the currently executing cmake file.
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR})
include(CMakeFindDependencyMacro)
# Additionally we record the BOOST_ROOT - but we try to respect the upstream
# package if it specifies it.
if(NOT BOOST_ROOT)
set(BOOST_ROOT "#BOOST_ROOT#"
endif()
find_package(Boost 1.55 QUIET REQUIRED COMPONENTS system thread)
find_dependency(SomeOtherDependency 1.0 QUIET REQUIRED)
include("${CMAKE_CURRENT_LIST_DIR}/SomeProjectTargets.cmake")
# Clean up module path after we're done
list(REMOVE_AT CMAKE_MODULE_PATH -1)
endif()

CMake: how to specify different CMakeFileList?

We created 3 different CMakeFileList files (CMakeFileList.engine, CMakeFileList.data and CMakeFileList.fep) for different build options within the same project. Does CMake support specifying CMakeFileList file as an argument? If not, what's the best way to accomplish our task by leveraging cmake? Any suggestion is appreciated.
In general I've done such kind of things by using the option() cmake command and providing just a single CMakeLists.txt file (what to build is decided inside according to the options, i.e. you can then also build everything in a single cmake/make run).
Example:
# the defaults (can be overridden on the command line)
option(BUILD_ENGINE "Build the Engine" ON)
option(BUILD_DATA "Build the Data" ON)
option(BUILD_FEP "Build the Fep" OFF)
if (BUILD_ENGINE)
# commands to build the Engine target or include(CMakeFileList.engine)
endif()
if (BUILD_DATA)
# commands to build the Data target or include(CMakeFileList.data)
endif()
if (BUILD_FEP)
# commands to build the Fep target or include(CMakeFileList.fep)
endif()
Then you can have everything in a single CMakeLists.txt and build what is needed each time, might it be multiple packages (if different than the defaults, you can switch on/off on the cmake command line). Or include the separate cmake lists as well (and just to make sure that they will work together if everything needs to be build).
Create main CMakeLists.txt file and conditionally use command include for use component-specific parts:
set(BUILD_TARGET "" CACHE STRING "Target to build")
if(BUILD_TARGET STREQUAL "engine")
include(${CMAKE_CURRENT_SOURCE_DIR}/CMakeFileList.engine)
elseif(BUILD_TARGET STREQUAL "data")
include(${CMAKE_CURRENT_SOURCE_DIR}/CMakeFileList.data)
elseif(BUILD_TARGET STREQUAL "fep")
include(${CMAKE_CURRENT_SOURCE_DIR}/CMakeFileList.fep)
else()
message(FATAL_ERROR "Incorrect BUILD_TARGET")
endif()