CMake: how to specify different CMakeFileList? - c++

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()

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}")

CMake post-build custom command on multiple targets?

I'm learning CMake through a C++ hobby project using Visual Studio 2017, and the way I have it set up, I have one folder for source code and one folder for test code. I build the source code as a static library, and build the test code as an executable (using Catch2). The problem I have now is that these are two separate targets, and whenever one or both of these targets are rebuilt I want to run the testing executable. Now I can find out how to run a post-build event using ADD_CUSTOM_COMMAND, but that only works for a single target. Putting multiple targets after "TARGET" leads to only the last target being used (I tested this), and duplicating the custom command can lead to the tests being run twice, and also it seems like poor code style. Is there any way to do this elegantly? My CMake file looks like this:
# CMakeList.txt : Top-level CMake project file, do global configuration
# and include sub-projects here.
#
cmake_minimum_required (VERSION 3.8)
project ("SheepyEngine")
set (CMAKE_CXX_STANDARD 17)
set (HEADER_FILES 3rdParty/CImg/CImg.h)
set (SOURCE_DIRECTORY Source)
set (TEST_DIRECTORY Test)
# Include sub-projects.
add_subdirectory ("Source")
add_subdirectory ("Test")
# Include libraries
include_directories (
"${CMAKE_CURRENT_LIST_DIR}/3rdParty/CImg"
"${CMAKE_CURRENT_LIST_DIR}/3rdParty/Catch2/single_include"
)
add_library (SheepyEngine STATIC
"${SOURCE_DIRECTORY}/Game.cpp"
"${SOURCE_DIRECTORY}/Game.h"
"${SOURCE_DIRECTORY}/GameObject.h"
${HEADER_FILES})
target_include_directories(SheepyEngine PRIVATE ${CMAKE_CURRENT_LIST_DIR}/3rdParty/CImg/)
add_executable(SheepyEngineTest "${TEST_DIRECTORY}/test.cpp" "3rdParty/Catch2/single_include/catch.hpp")
target_include_directories(SheepyEngineTest PRIVATE ${CMAKE_CURRENT_LIST_DIR}/3rdParty/Catch2/)
# TODO: Add tests and install targets if needed.
if(${RUN_TESTS})
ADD_CUSTOM_COMMAND(
TARGET SheepyEngineTest SheepyEngine
POST_BUILD
COMMAND ${CMAKE_CURRENT_LIST_DIR}/Build/Debug/SheepyEngineTest.exe
)
endif()
The SheepyTestProgram target needs to be dependent on SheepyEngine:
target_link_libraries(SheepyEngineTest SheepyEngine)
Then the target of add_custom_command will be just SheepyEngineTest (add_custom_command accepts only a single target).

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

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 ...)

CMake CMAKE_AUTOMOC in cross compilation

I've following issue. I'm tring to use native mechanism build in CMake for cross compilation. I prepared following toolchain.cmake file:
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_SYSROOT /tmp/filesystem)
set(tools /opt/gcc-linaro-arm-linux-gnueabihf-4.8-2014.04_linux/bin/arm-linux-gnueabihf)
set(CMAKE_C_COMPILER ${tools}-gcc)
set(CMAKE_CXX_COMPILER ${tools}-g++)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
and in target CMakeList.txt is use:
set(CMAKE_AUTOMOC ON)
I expect that when I use CMAKE_FIND_ROOT_PATH_MODE_PROGRAM set to NEVER the CMake, according to documentation, will use moc from HOST:
If set to NEVER, then the roots in CMAKE_FIND_ROOT_PATH will be
ignored and only the host system root will be used.
However it still try to use moc from TARGET arm image rootfs.
I try to refind the moc executable like in first answer from this post: How to include a certain Qt installation using CMake? but with no luck.
I also try to set the QT_MOC_EXECUTABLE variable to proper path from HOST rootfs instead of TARGET one but also with no luck there. I event think that this variable isn't use by CMake when CMAKE_AUTOMOC is set to ON since after forcing change this cached variable cmake still use moc from TARGET rootfs.
Any ideas how to resolve this issue?
# EDIT 1
I found that the automoc generates such file in build folder:
CMakeFiles/*target_name*_automoc.dir/AutogenInfo.cmake
And in my case such variable is set to wrong path:
set(AM_QT_MOC_EXECUTABLE "/tmp/filesystem/usr/lib/arm-linux-gnueabihf/qt5/bin/moc")
should be:
set(AM_QT_MOC_EXECUTABLE "/usr/bin/moc")
I set AM_QT_MOC_EXECUTABLE to correct value in main CMakeList.txt but still after mentioned file is generated with wrong path from TARGET rootfs.
I finally found the solution thanks to this post: How can I use CMake's AUTOMOC feature with a custom Qt package?. As I assumed the QT_MOC_EXECUTABLE isn't use directly by AUTOMOC.
Before first qt find_package following lines must be added:
set(QT_MOC_EXECUTABLE /usr/bin/moc)
add_executable(Qt5::moc IMPORTED)
set_property(TARGET Qt5::moc PROPERTY IMPORTED_LOCATION ${QT_MOC_EXECUTABLE})
The issue here was that not only the variable QT_MOC_EXECUTABLE has to be set to proper value but finally the automoc uses just Qt5:moc target which must be declared before any qt package will be included in CMakeList.txt file.
This same issue is with other qt tools so more generic option will be:
file(GLOB Qt_bin /usr/bin)
find_program(QT_MOC_EXECUTABLE qt_moc moc PATHS ${Qt_bin})
add_executable(Qt5::moc IMPORTED)
set_property(TARGET Qt5::moc PROPERTY IMPORTED_LOCATION ${QT_MOC_EXECUTABLE})

CMakeLists.txt and two .cmake files

I have two *.cmake files, lets say nacl.cmake and pnacl.cmake in my ./CMake/ folder. How do I cmake using the specific one using my CMakeLists.txt?
Make an option to let user switch between them:
option(USE_PNACL "Use PNaCl instead fo NaCl" TRUE)
if(USE_PNACL)
include(CMake/PNacl.cmake)
else()
include(CMake/Nacl.cmake)
endif()
They sound like two alternative toolchains. You can chose between different toolchains during configuration with the CMAKE_TOOLCHAIN_FILE option:
cmake -DCMAKE_TOOLCHAIN_FILE=<project-folder>/CMake/PNacl.cmake <project-folder>
If you don't specify a toolchain file, the build system will target the build machine.