Is it possible in CMake to have inline conditonals within argument lists somehow?
Example of what I want (the whole IF line is not parsed but handled like a string, but I want it parsed):
LIST(APPEND myList
foo
bar
IF(MINGW) hello ENDIF()
cool
)
instead of what I have now
LIST(APPEND myList
foo
bar
)
IF(MINGW)
LIST(APPEND myList hello)
ENDIF(MINGW)
LIST(APPEND myList cool)
Something similar to the example would make my CMakeLists.txt files way easier to read at many places! Especially if there's a specific order that needs be kept the CMake code gets very big without inline conditionals sometimes, because one needs to repeat the same call everytime.
Note: I took LIST as an example here, the question should be seen as general for other functions, too!
There is currently no such feature in CMake, although I agree it would be quite useful in certain situations.
I usually rely on the fact that CMake has no problem with ignoring empty values in most contexts:
if(MINGW)
set(ADDITIONAL_ITEMS hello)
endif()
list(APPEND mylist
foo
bar
${ADDITIONAL_ITEMS}
cool
)
It's not perfect, but IMHO at least cleaner than appending to the same list twice. The same technique also works for conditionally passing function parameters.
Note that depending on the context where this is needed, CMake generator expressions might be an option:
target_link_libraries(t foo bar $<$<BOOL:${SOME_CONDITION}>:hello> cool)
You can use the PLATFORM_ID generator expression, depending on what you're doing with the list you're creating:
http://www.cmake.org/cmake/help/v3.0/manual/cmake-generator-expressions.7.html
target_link_libraries(t foo bar $<$<PLATFORM_ID:MINGW>:hello> cool)
Apart from target_link_libraries, such expressions work with target_include_directories, target_compile_definitions, target_compile_options, target_compile_features (CMake 3.1), target_sources (CMake 3.1), file(GENERATE), install(FILES), add_custom_target etc. You get the idea :).
Unfortunately, no.
The only way to conditionally build lists without repetition is with list APPEND (BTW unless ordering matters, you can simplify it by adding cool in the list definition).
CMake syntax is quite verbose and limited and it is often hard not to repeat yourself. That is why I have sometimes ended up generating parts of the CMake code in another language.
Related
Not sure how to perfectly word this from the title, but I am new to CMake and slowly progressing through the online tutorial.
I am up to Step 4 and sometimes find it confusing when mixing passed values that in my eyes are strings, and thus in all programming languages I expect them to have quotation marks or some sort around them. However sometimes I create new targets with the same names. I will elaborate with an example. I reworded some things from the tutorial to make it a bit more clear for me to see what they actually do.
In the root CMakeLists.txt I have this file,
cmake_minimum_required(VERSION 3.10)
project(My_Project VERSION 1.0)
add_library(tutorial_compiler_flags INTERFACE)
target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)
option(USE_MYMATH "Use tutorial provided math implementation" TRUE)
configure_file(src/sqrt.h.in src/sqrt.h)
if(USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions)
endif()
add_executable(compute_square_root src/sqrt.cxx)
target_link_libraries(compute_square_root PUBLIC ${EXTRA_LIBS} tutorial_compiler_flags)
target_include_directories(compute_square_root PUBLIC "${PROJECT_BINARY_DIR}/src")
Inside of MathFunctions I have
add_library(MathFunctions mysqrt.cxx)
target_include_directories(MathFunctions INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
Here is where the confusion can come from. Notice that in
add_subdirectory(MathFunctions)
MathFunctions is kind of treated as a string in my eyes, because it is now looking for the directory current_location/MathFunctions. However inside of of the MathFunctions CMakeLists.txt it now creates a target with the exact same spelling from the line "add_library(MathFunctions mysqrt.cxx)", this is then immediately referenced afterwards from the "target_include_directories(MathFunctions, ...".
Here, target_include_directories is referring to the target MathFunctions we just created. Now, when we leave that CMakeLists.txt we now have another line "list(APPEND EXTRA_LIBS MathFunctions)". Now I some confusion, like, is this MathFunctions referring to the target we just made? Is it a string called "MathFunctions"? In the documentation for target_link_libraries it says that it has to be a target created by add_library so I assume it is referring to the previous add_library(MathFunctions ...) call. I find the scoping weird here too, since we are referring to something that was made from a child, inside a different call.
Do we have certain rules in CMake for this kind of behaviour? THanks
All command parameters are treated as strings in cmake. Parameters are separated by whitespace unless quoted. The exact effect of a parameter depends on the command.
The following commands have the same effect:
add_subdirectory(MathFunctions)
add_subdirectory("MathFunctions")
In the case of add_library the first parameter is treated as the target name. CMake internally keeps track of targets and stores several pieces of information for them. The target name MathFunctions is entirely unrelated to the name of the subdirectory added via add_subdirectory; you could rename the directory to FooBar and use add_subdirectory(FooBar) and nothing would change.
There are several commands you pass the target name to to modify the properties of the cmake target as well as commands that treat the name of cmake targets specially e.g.:
target_include_directories: the target to modify the [INTERFACE_]INCLUDE_DIRECTORIES property for is passed
target_link_directories: the target to modify the [INTERFACE_]LINK_DIRECTORIES property for is passed
set_target_properties: One or more targets to set properties for are passed
target_link_libraries: The cmake target linked to is passed. Furthermore cmake library targets may be specified as libraries to be linked to the target.
add_test: If you use the name of a cmake target in the COMMAND part, the test logic uses the path to the target instead.
...
As for scope:
Variable values you write are actually set for the current scope only, but reading variables cmake looks into ancestor scopes, unless a variable is found in the current scope. CMake targets are visible in the whole cmake project though from the point of the parsing of the command creating the target(add_library, add_executable, add_custom_target): cmake targets are "global objects". (Exceptions exist for imported libs and alias targets, but that's probably nothing relevant to you right now.)
I've got the following generator expression working, which sets the /GS flag if the compiler is MSVC and it sets it for the build configurations RelWithDebInfo and Release:
target_compile_options(mytarget PRIVATE
"$<$<CONFIG:Release>: $<$<CXX_COMPILER_ID:MSVC>:/GS>>
$<$<CONFIG:RelWithDebInfo>:$<$<CXX_COMPILER_ID:MSVC>:/GS>>")
Now I also want to let the user configure this, and I've added an option:
option(MYTARGET_ENABLE_GS "Enable /GS" OFF)
So now, I (obviously) want to enable the /GS flag if the user enabled this option, and if they did, I want to add it if the compiler is MSVC, and it should be added to the Release and RelWithDebInfo configurations.
This is pretty nested, and I can't seem to get it right. This is as far as I got:
target_compile_options(mytarget PRIVATE
"$<$<BOOL:MYTARGET_ENABLE_GS>:
$<$<CONFIG:Release>: $<$<CXX_COMPILER_ID:MSVC>:/GS>>
$<$<CONFIG:RelWithDebInfo>:$<$<CXX_COMPILER_ID:MSVC>:/GS>>>")
Edit: Fixed, see below.
I've had to use the $<$<BOOL:...>> because that "translates" the option (which can be on/off or true/false, to 0 or 1, which the generator expression needs.
However above line doesn't work: It doesn't add (or not add) /GS.
I'd like to know:
1) Where is my mistake? How to do this? And
Stuff like this results in pretty convoluted nested expressions, that are really hard to read - imagine 6 months down the line reading that line of code again, even if it is documented. And it's so easy to misplace a > or something like that.
I could probably use "manual" ifs to make this more readable, but imagine having 5-10 of such options - writing an if/end with a target_compile_options inside results in like 15-30 lines of if/end code, which is also not very pretty to look at. What's the best way to do this?
Edit: I was nearly there. Variables have to be enclosed with ${...} in generator expressions. So it's for example:
target_compile_options(mytarget PRIVATE
"$<$<BOOL:${MYTARGET_ENABLE_GS}>:
$<$<CONFIG:Release>: $<$<CXX_COMPILER_ID:MSVC>:/GS>>
$<$<CONFIG:RelWithDebInfo>:$<$<CXX_COMPILER_ID:MSVC>:/GS>>>")
and that works great.
Which still leaves point "2)", which I would be keen to getting insights on.
When you generate a MSVC solution, the build type (Release, Debug, etc.) is unknown until you actually run the build. Thus, using generator expression for that is correct.
But for the 2 other variables (does user set MYTARGET_ENABLE_GS to OFF and is generation performed for MSVC), they are resolved at configuration time. So, you don't have to check them in a generator expression. You could simply write:
if(MSVC AND MYTARGET_ENABLE_GS)
target_compile_options(mytarget PRIVATE "$<$<OR:$<CONFIG:Release>,$<CONFIG:RelWithDebInfo>>:/GS>")
endif()
This solution also uses $<OR:?[,?]...> generator expression to regroup under the same expression both cases you build in Release OR RelWithDebInfo
Whenever you may use if command, use it. Generator expressions are not replacement for if command.
Generator expressions allow to use conditions dependent on build type. Because on multi-configuration build systems, like Visual Studio, build type isn't known at configuration stage, you cannot use such condition in if command.
But generator expressions are ugly, so do not use them when it is not needed.
There is nothing bad in
if(MYTARGET_ENABLE_GS)
target_compile_options(mytarget PRIVATE "$<$<CONFIG:Release>:$<$<CXX_COMPILER_ID:MSVC>:/GS>>$<$<CONFIG:RelWithDebInfo>:$<$<CXX_COMPILER_ID:MSVC>:/GS>>")
endif()
If you want to check an option at the beginning, but create a target later, you may store generator expression in the variable, and use this variable later:
set(additional_options)
# Depending on parameters, add options to 'additional_options' list.
if(MYTARGET_ENABLE_GS)
list(APPEND additional_options "$<$<CONFIG:Release>:$<$<CXX_COMPILER_ID:MSVC>:/GS>>$<$<CONFIG:RelWithDebInfo>:$<$<CXX_COMPILER_ID:MSVC>:/GS>>")
endif()
if(<other option>)
list(APPEND additional_options <...>)
endif()
# ...
target_compile_options(mytarget PRIVATE ${additional_options})
CMake's documentation a generator expressions works if your config matches any configuration listed after CONFIG:, but I couldn't find the syntax documented anywhere. Trial and error show the following works correctly:
if(MSVC AND MYTARGET_ENABLE_GS)
target_compile_options(mytarget PRIVATE "$<$<CONFIG:Release,RelWithDebInfo>:/GS>")
endif()
I need to use a property from an external project. So far I'm successfully doing that this way:
ExternalProject_Add(
some_ep_name
...
)
# Get source dir
ExternalProject_Get_Property(some_ep_name SOURCE_DIR)
# Set source dir value into less generically named variable
set(SOME_EP_NAME_SOURCE_DIR "${SOURCE_DIR}")
This works, but it seems unnecessarily verbose, and it annoys me a little. I was hoping I could use a generator expression, like so:
"$<TARGET_PROPERTY:some_ep_name,SOURCE_DIR>"
But it seems like this doesn't work. Before I give up, I wanted to check if I was doing something wrong or if anyone knows a better way.
All "properties" of ExternalProject are known at configuration time. So they don't require support of generator expressions, which main intention is usage for values not known at configuration time (but known at build time).
If you found "unnecessarily verbose" having several lines of code for save external project' property into the variable, you may create a macro/function for incorporate all these lines. Then calling the macro/function will use only single line of code:
function(ExternalProject_Property_to_Var VAR eproject prop)
ExternalProject_Get_Property(${eproject} ${eprop})
set(${VAR} ${${eprop}} PARENT_SCOPE)
endfunction()
...
ExternalProject_Property_to_Var(SOME_EP_NAME_SOURCE_DIR some_ep_name SOURCE_DIR)
I want to check whether a lists contains a specific entry like in the following code snipplet:
macro(foo)
if ($(ARGN} contains "bar")
...
endif
endmacro()
CMake does not offer a contains. What is best / easiest way to get the desired result?
In CMake's wiki I found a LIST_CONTAINS macro, but the wiki page is outdated. Is this still the best way to go or has CMake gained new capabilities?
With CMake 3.3 or later, the if command supports an IN_LIST operator, e.g.:
if ("bar" IN_LIST _list)
...
endif()
For older versions of CMake, you can use the built-in list(FIND) function:
list (FIND _list "bar" _index)
if (${_index} GREATER -1)
...
endif()
Fewer lines:
if (";${ARGN};" MATCHES ";bar;")
# ...
endif()
But see the IN_LIST syntax from #sakra for a more-modern syntax.
I have been using one liner like if ("${PLATFORM}" MATCHES "^(os|ios|android|linux|win32)$") to check if PLATFORM is in the list
If the intention here is to add a value to a list but only if it's not already in the list, then an alternative approach is to just add it to the list and immediately remove possible duplicates again:
list(APPEND SOME_LIST "value")
list(REMOVE_DUPLICATES SOME_LIST)
I feel a little stupid right now. After recently converting a few smaller projects to use CMake, I decided to also get rid of a few "Platform_Config.h" files. These files contain a few preprocessing directives like #define USE_NEW_CACHE and control compilation.
How would I 'convert' these defines to be controlled with CMake? Ideally by using these "cache" variables the user can easily edit.
There are two options. You can use the add_definitions method to pass defines as compiler flags: E.g. somewhere in your projects cmakelists.txt:
add_definitions( -DUSE_NEW_CACHE )
CMake will make sure the -D prefix is converted to the right flag for your compiler (/D for msvc and -D for gcc).
Alternatively, check out configure_file. It is more complex, but may be better suited to your original approach with a Platform_Config file.
You can create an input-file, similar to your original Platform_Config.h and add "#cmakedefine" lines to it.
Let's call in Platform_Config.h.in:
// In Platform_Config.h.in
#cmakedefine USE_NEW_CACHE
// end of Platform_Config.h.in
When then running
configure_file( ${CMAKE_SOURCE_DIR}/Platform_Config.h.in ${CMAKE_BINARY_DIR}/common/Platform_Config.h )
it will generate a new Platform_Config file in your build-dir. Those variables in cmake which are also a cmakedefine will be present in the generated file, the other ones will be commented out or undefed.
Of course, you should make sure the actual, generated file is then correctly found when including it in your source files.
option command might provide what you are looking for.
use it with the COMPILE DEFINITIONS property on the target and i think you are done.
To set the property on the target, use the command set target properties
option(DEBUGPRINTS "Prints a lot of debug prints")
target(myProgram ...)
if(DEBUGPRINTS)
set_target_properties(myProgram PROPERTIES COMPILE_DEFINITIONS "DEBUGPRINTS=1")
endif()
edit:
The option i wrote in the example shows up as a checkbox in the CMake GUI.
In case you want to set defines per target: Since 2.8.11 you can use target_compile_definitions.
In earlier versions you probably don't want to use set_target_properties as is, since it overwrites any defines you set previously. Call get_target_property first instead, then merge with previous values. See add_target_definitions here.
Use target_compile_options. Do not quote your define or it not be detected as a define. CMake parses off the /define and adds the actual define to the DefineConstants section of the csproj, if there are quotes it will put the entire quoted string in the AdditionalOptions section of the csproj.
An example from one of my projects that uses generator expressions:
target_compile_options( ${LIBRARY_NAME} PRIVATE
$<${IS_ART_ITERATION_BUILD}:/define:ART_ITERATION_BUILD>
)
An example without generator expressions:
target_compile_options( ${LIBRARY_NAME} PRIVATE
/define:GRAPHICS_VULKAN
)