26

I'm migrating a makefile project to CMake. The person who wrote the makefile the first time had done a module for writing certain values in an include file.

There's a main config.h file that includes a config_in.h. The config.h file contains something like this:

#ifndef USE_FEATURE_A
#define USE_FEATURE_A 0
#endif

#ifndef USE_FEATURE_B
#define USE_FEATURE_B 0
#endif

In the makefile there's a fake target like with_feature_a that writes in config_in.h

#define USE_FEATURE_A 1

In this way someone can type

make with_feature_a
make

to get the right build.

I want to replicate something like this using this codebase but using CMake. I tried a couple of approaches suggested on the net, but I didn't get it to work.

set_target_properties(with_feature_a PROPERTIES COMPILE_DEFINITIONS 
    "WITH_FEATURE_A=1"
)

This isn't working because if I run

make with_feature_a

I don't see with_feature_a in the preprocessor command line.

The second attempt I made is to write a file directly with the content set to whatever I want, but I didn't understand how to connect the file() command to my target.

I placed this in my CMakeLists.txt

file(WRITE 
local/config_in.h 
    "#define WITH_FEATURE_A 1"
)

but this isn't executed everytime and I don't know how to set it to a single target.

Any help is appreciated. Thank you for reading all this stuff. Sorry for the long story :)

UPDATE

The solution provided here is a big enhacement on the road to solution. The problem is that is don't allow recursive definitions. I show an example:

in CMakeLists.txt I placed:

if (WITH_FEATURE_A)
MESSAGE(STATUS "WITH_FEATURE_A")
add_definitions(-DUSE_FEATURE_A=1)
    add_definitions(-DWITH_FEABURE_B=1)
endif()

if (WITH_FEABURE_B)
MESSAGE(STATUS "WITH_FEATURE_B")
add_definitions(-DUSE_FEATURE_D=1)
endif()


if (WITH_FEABURE_C)
MESSAGE(STATUS "WITH_FEATURE_C")
add_definitions(-DUSE_FEATURE_D=1)
endif()


if (WITH_FEABURE_D)
MESSAGE(STATUS "WITH_FEATURE_D")
endif()

in this case if I execute cmake with -DWITH_FEATURE_A=1 I'd love to see in the output:

WITH_FEATURE_A
WITH_FEATURE_B
WITH_FEATURE_D

actually this code print just

WITH_FEATURE_A
Community
  • 1
  • 1
andyinno
  • 1,021
  • 2
  • 9
  • 24
  • A build is about dependencies between targets. This is what cmake, IMO, simplifies compared to GNUmake. How did, in your original version, make handle the dependencies? IOW: how did it knew to recompile everything when `make with_feature_b` was called right after `make with_feature_b`? – Patrick B. Mar 04 '13 at 12:07
  • it simply write the file config_in.h that's the first file before every other. so every other file need to be recompiled. – andyinno Mar 04 '13 at 12:33
  • Running `make with_feature_b` two times consecutively will compile both times everything because the config_in.h is rewritten? – Patrick B. Mar 04 '13 at 12:45
  • actually yes because the timestamp of the file is changed. This is not a big problem. This is a firmware for a microprocessor. the build time is about 10 seconds and we don't build 2 times the same build. the problem is that this code is placed on different microprocessor and there's some adaptation about the cpu speed and pin outputs. so there's conditional inclusions of hardware config files. they are written using this kind of flow control. – andyinno Mar 04 '13 at 12:55
  • The update is full of typos, and also seems to have some logic errors, so it is not surprising that it does not conform to andyinno's expectations. – mabraham Dec 22 '14 at 17:38

4 Answers4

57

You can simplify things by avoiding creating the dummy targets and removing the config file. Instead, if you pass the requirements via the command line when you invoke CMake (or via the CMake GUI), you can run make only once.

For example, you could add the following to your CMakeLists.txt:

option(WITH_FEATURE_A "Option description" ON)
option(WITH_FEATURE_B "Option description" OFF)

if(WITH_FEATURE_A)
  add_definitions(-DUSE_FEATURE_A)
endif()
if(WITH_FEATURE_B)
  add_definitions(-DUSE_FEATURE_B)
endif()

By default, if you just run CMake, it will set the CMake variable WITH_FEATURE_A to ON which consequently adds USE_FEATURE_A as a preprocessor definition to the build. USE_FEATURE_B is undefined in the code.

This would be equivalent to doing #define USE_FEATURE_A in your code.


If you really need the equivalent of

#define USE_FEATURE_A 1
#define USE_FEATURE_B 0

then in your CMakeLists.txt you can do:

option(WITH_FEATURE_A "Option description" ON)
option(WITH_FEATURE_B "Option description" OFF)

if(WITH_FEATURE_A)
  add_definitions(-DUSE_FEATURE_A=1)
else()
  add_definitions(-DUSE_FEATURE_A=0)
endif()
if(WITH_FEATURE_B)
  add_definitions(-DUSE_FEATURE_B=1)
else()
  add_definitions(-DUSE_FEATURE_B=0)
endif()

To change these defaults from the command line, simply do (e.g.):

cmake . -DWITH_FEATURE_A=OFF -DWITH_FEATURE_B=ON
make

Once a variable has been set via the command line this way, it is cached and will remain unchanged until either it is overwritten with a different value on the command line, or you delete the CMakeCache.txt file in your build root.


Response to update:

As @Peter noted, you appear to be mixing up CMake variables (the WITH_FEATURE... ones) and the preprocessor definitions (the USE_FEATURE... ones). You can as suggested resolve all the dependencies between options first, then set the resulting preprocessor definitions, or in this case where the flow is quite straightforward, just do it all in one go:

if(WITH_FEATURE_A)
  message(STATUS "WITH_FEATURE_A")
  add_definitions(-DUSE_FEATURE_A=1)
  set(WITH_FEATURE_B ON)
endif()

if(WITH_FEATURE_B)
  message(STATUS "WITH_FEATURE_B")
  add_definitions(-DUSE_FEATURE_B=1)
  set(WITH_FEATURE_D ON)
endif()

if(WITH_FEATURE_C)
  message(STATUS "WITH_FEATURE_C")
  add_definitions(-DUSE_FEATURE_C=1)
  set(WITH_FEATURE_D ON)
endif()

if(WITH_FEATURE_D)
  message(STATUS "WITH_FEATURE_D")
  add_definitions(-DUSE_FEATURE_D=1)
endif()

Community
  • 1
  • 1
Fraser
  • 74,704
  • 20
  • 238
  • 215
  • thank you for your solution. I'll try it ASAP. Just a question more... do you think this way of go can be applied also to multiple cascade options? I try to explaine... if an ipotetical feature_c implies feature_a and feature_b can I add them? like if(WITH_FEATURE_A) option(WITH_FEATURE_B "Option" ON) option(WITH_FEATURE_C "Option" ON)? thank you again :) – andyinno Mar 05 '13 at 08:04
  • 1
    Yes - you can use [`set`](http://www.cmake.org/cmake/help/v2.8.10/cmake.html#command:set) inside the CMakeLists.txt to switch these variables. It can be used instead of `option`; it can write the variable to the cache if you want (almost as if you'd set it via the command line `-D` way) and it can even overwrite a command line option. That last maybe seems unhelpful, but you could for example use it if the user did `cmake . -DWITH_FEATURE_B=OFF -DWITH_FEATURE_C=ON` to force feature B to On since Feature C is On. Or you could fail by using `if`s and `message(FATAL_ERROR...)` – Fraser Mar 05 '13 at 08:15
  • By the way, the `option` and `set` commands are fairly similar. See [this answer](http://stackoverflow.com/a/11237572/424459) for differences. – Fraser Mar 05 '13 at 08:21
  • Thank you for your reply. I need to change some code in header files but I think I can use this approach. – andyinno Mar 05 '13 at 11:29
  • I'm writing again because I found a problem with your solution. I think this solution don't work well with cross compilations. I have a toolchain file that set some flag like the microprocessor and it's speed but when I use the add_definitions() I see my toolchain vanish and not being used. Any idea on the reason? Thank you again – andyinno Mar 05 '13 at 15:58
  • Sorry I need to reopen this. Probably this don't work as I expect. It don't do what I need. your solution don't seems to allow to expose different defines in a recursive way. I can do this in configuration file using c preprocessor. – andyinno Mar 05 '13 at 16:14
  • @andyinno Does the updated answer along with Peter's answer get you there? – Fraser Mar 05 '13 at 22:11
  • your upadate works correctly thank you. I misunderstood the difference between set and add_definitions. For what I saw in the first case I tought add_definitions was the correctly command for my case. Thank you again :) – andyinno Mar 06 '13 at 08:05
6

I stumbled on this question and I figured I shared another option: TARGET_COMPILE_DEFINITIONS. You could have two targets in your CMakeLists.txt file, one per configuration, and have something like this

ADD_EXECUTABLE (versionA, ...)
TARGET_COMPILE_DEFINITIONS (versionA, PUBLIC -DWITH_FEATURE_A=1 -DWITH_FEATURE_B=0)

ADD_EXECUTABLE (versionB, ...)
TARGET_COMPILE_DEFINITIONS (versionB, PUBLIC -DWITH_FEATURE_A=0 -DWITH_FEATURE_B=1)

This is tells cmake to add the preprocessors definitions of macros WITH_FEATURE_A and WITH_FEATURE_B (with the proper value), just like if you defined them in your *pp files. Then you can tell make which version to compile:

make versionA
make versionB
bartgol
  • 1,703
  • 2
  • 20
  • 30
  • hi bartgol, yes indeed this is a viable solution. I am using this type of solution on other projects that I wrote from 2013 on. The big difference is that I developed my code in order to use simple flags. The person that wrote the software in the beginning no. This solution has the advantage that you can build both solutions in the same time so making tests among both of them. The time spent for the compilation increases for every compilation so my actual solution was to split the target_compile_definition on a per library base and then link the executable at the end with different library. – andyinno Feb 02 '17 at 09:23
4

It sounds like you want to introduce relationships between your options. This will be easier if you separate the steps. First resolve the relationships, then set the C defines on the results. Remember that WITH_FEATURE_A is a cmake variable, and USE_FEATURE_A is C preprocessor define set with ADD_DEFINITIONS:

# Specify the inter-feature dependencies
if (WITH_FEATURE_A)
    # A requires B because <somereason>
    set(WITH_FEATURE_B ON)
endif()

if (WITH_FEATURE_B)
    set(WITH_FEATURE_D ON)
endif()

if (WITH_FEATURE_C)
    set(WITH_FEATURE_D ON)
endif()

# Now generate the C defines for passing the options to the compiler
if (WITH_FEATURE_A)
    MESSAGE(STATUS "WITH_FEATURE_A")
    add_definitions(-DUSE_FEATURE_A=1)
endif()

if (WITH_FEATURE_B)
    MESSAGE(STATUS "WITH_FEATURE_B")
    add_definitions(-DUSE_FEATURE_B=1)
endif()

if (WITH_FEATURE_C)
    MESSAGE(STATUS "WITH_FEATURE_C")
    add_definitions(-DUSE_FEATURE_C=1)
endif()

if (WITH_FEATURE_D)
    MESSAGE(STATUS "WITH_FEATURE_D")
    add_definitions(-DUSE_FEATURE_D=1)
endif()
Peter
  • 14,559
  • 35
  • 55
  • Yes - this is probably the safest choice. Although in this case the logic seems pretty straightforward, so it's maybe OK to do it all in one pass as per the OP's original attempt? +1 anyway. – Fraser Mar 05 '13 at 22:14
  • thank you for your example. I think it should absolutly work. @Fraser I posted a simple case on the code I currently have. Please keep in mind that when you develop software for microcontrollers you have to keep things small. Using a code base common in many projects you're forced to place many #if CASE #endif in your code and every library for accessing special feature is wrapped in #if HAVE_UART #if HAVE_SPI etc etc. so the example I posted is simple just for not need to rewrite the holy bible, or the lord of the rings if you prefer. usually a #define can cascade to 10 or more defines. – andyinno Mar 06 '13 at 08:03
1

configure_file can be used for this. For example, in CMakeLists.txt:

if(WITH_FEATURE_A)
    set(USE_FEATURE_A 1)
    # Feature A should enable feature B
    set(WITH_FEATURE_B ON)
endif()

if(WITH_FEATURE_B)
    set(USE_FEATURE_B 1)
    # Feature B should enable feature D
    set(WITH_FEATURE_D ON)
endif()

if(WITH_FEATURE_C)
    set(USE_FEATURE_C 1)
    # Feature C should enable feature D, too
    set(WITH_FEATURE_D ON)
endif()

if(WITH_FEATURE_D)
    set(USE_FEATURE_D 1)
endif()

configure_file(config.h.in config.h)

In config.h.in you can convert the CMake variables to defines with:

#cmakedefine USE_FEATURE_A 1
#cmakedefine USE_FEATURE_B 1
#cmakedefine USE_FEATURE_C 1

/* You can use the values of the cmake variables too, using @<var>@. Ex: */
#cmakedefine USE_FEATURE_D @USE_FEATURE_D@

When running cmake -DWITH_FEATURE_A=1, the resulting config.h will be:

#define USE_FEATURE_A 1
#define USE_FEATURE_B 1
/* #undef USE_FEATURE_C */

/* You can use the values of the cmake variables too, using @<var>@: */
#define USE_FEATURE_D 1

References:

recvfrom
  • 389
  • 6
  • 14
  • I think for #defines that are supposed to evaluate to 0 or 1, you can just use `#cmakedefine01`. (see the configure_file official doc.) – Scylardor Jan 19 '23 at 22:24