Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CMake: How to set a generator expression based on an option (and compiler/build configuration)

Tags:

cmake

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

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

like image 603
Ela782 Avatar asked May 19 '18 22:05

Ela782


People also ask

How do I specify CMake generator?

CMake Generators are platform-specific so each may be available only on certain platforms. The cmake(1) command-line tool --help output lists available generators on the current platform. Use its -G option to specify the generator for a new build tree.

What is generator expression in CMake?

Generator expressions are evaluated during build system generation to produce information specific to each build configuration. Generator expressions are allowed in the context of many target properties, such as LINK_LIBRARIES , INCLUDE_DIRECTORIES , COMPILE_DEFINITIONS and others.

What is Cmake_build_type?

Specifies the build type on single-configuration generators (e.g. Makefile Generators or Ninja ). Typical values include Debug , Release , RelWithDebInfo and MinSizeRel , but custom build types can also be defined.

What is generator expression in Python?

Python generators are a simple way of creating iterators. All the work we mentioned above are automatically handled by generators in Python. Simply speaking, a generator is a function that returns an object (iterator) which we can iterate over (one value at a time).


2 Answers

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

like image 162
Antwane Avatar answered Sep 21 '22 10:09

Antwane


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})
like image 34
Tsyvarev Avatar answered Sep 21 '22 10:09

Tsyvarev