Search code examples
cmakemakefile

How can I support build-time variables for Makefiles in CMake?


While I have done some multi-platform (e.g. embedded vs. host run-time environments) CMake "projects", I have this seemingly simple use case that I cannot quite figure out. The typical CMake usage I know of is like this:

mkdir <build-dir>
cd <build-dir>
cmake -DCMAKE_TOOLCHAIN_FILE=../srcs/cmake/toolchain.cmake ../srcs
make

Here I am setting up a build directory, moving to it, running cmake to set up a "makefile build system" (i.e. set of Makefile and helper files), then building things. This is all fine and dandy.

For clarity in my question, consider a trivial program:

#include <stdio.h>
int main(int, char**)
{
#if defined(CFG_TEST)
    printf("Test configuration.\n");
    // or #pragma message "Test configuration."
#else
    printf("Normal configuration.\n");
    // or #pragma message "Normal configuration."
#endif
    return 0;
}

The thinking here is I'd like to just be able to switch between something like: gcc demo.c -o demo and gcc -DCFG_TEST demo.c -o demo

In other words, CMake generates the Makefiles, and when I run them (via make), how can I pass a simple #define into the C/C++ compiler without having to rerun CMake? Does that even make sense?

If this isn't feasible or conducive with the CMake philosophy, what is the simplest way to do the equivalent CMake via command line? In other words, how do I do handle something like cmake -DCFG_TEST ... and have that result in passing -DCFG_TEST to the compiler?


Solution

  • I'll start with your last question:

    If this isn't feasible or conducive with the CMake philosophy, what is the simplest way to do the equivalent at the cmake command line? In other words, how do I do handle something like cmake -DCFG_TEST ... and have that result in passing -DCFG_TEST to the compiler?

    Think a little bit outside the box. CMake is a buildsystem generator. Once you configure CMake, it's quite easy to generate as many buildsystems as you want. You could generate two buildsystems: one for your "normal" mode and one for your "test" mode:

    cmake -S srcs -B build_normal
    cmake -S srcs -B build_test -DCMAKE_CXX_FLAGS="-DCFG_TEST"
    

    To pass that as a macro to the compiler, either use add_compile_definitions or target_compile_definitions. Ex.

    target_compile_definitions(my_target PUBLIC "CFG_TEST=${CFG_TEST}")
    

    I'd say this is a pretty "idiomatic" way to do things in CMake. The tradeoff cost is the disk space cost of two buildsystems and their artifacts, but the benefit is being able to have them side-by-side: You can use one, and then quickly use the other without having to switch a flag and reconfigure + rebuild.


    That being said, to answer your initial question,

    how can I pass a simple #define into the C/C++ compiler without having to rerun CMake? Does that even make sense?

    I'm not sure if what you're asking for is possible- at least with the Unix Makefiles generator and without building a modified CMake executable.

    When writing Makefiles, you can specify variables and use them like $(foo). As far as I know, CMake's generated Makefiles don't have any "free-floating" variables for the user's use that it takes and plugs into the compile commands.

    For example, a really small CMake project I generated a Unix Makefiles buildsystem for on my machine has a line in its target's generated CMakeFiles/example.dir/build.make file like this:

    /usr/bin/c++ $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS) -S /.../test.cpp -o CMakeFiles/example.dir/test.cpp.s
    

    and the flags are generated into the target's CMakeFiles/example.dir/flags.make like this:

    CXX_DEFINES = ...
    CXX_INCLUDES = ...
    CXX_FLAGS = ...
    

    And the values of those things come from things like CMAKE_CXX_FLAGS, target_compile_options, target_compile_definitions, etc.

    So when invoking your generated buildsystem, following How to pass macro definition from "make" command line arguments (-D) to C source code?, you could technically do something like this:

    make CXX_FLAGS=-DCFG_TEST
    

    or (see the cmake --build command docs for what "--" means):

    cmake --build <build_dir> -- CXX_FLAGS=-DCFG_TEST
    

    But that would override the value of whatever you told CMake to generate for the CXX_FLAGS Makefile variable, so if you really wanted to do that, you'd need to copy the full generated value of CXX_FLAGS and then override it on the commandline with a paste of it plus whatever you want to add to avoid missing anything.

    If you wanted to insert your own Makefile variable "slots" instead of overwriting the generated ones like CXX_DEFINES, CXX_INCLUDES, and CXX_FLAGS, I'm pretty sure you'd need to build and install a modified version of CMake. The relevant file to modify would be cmMakefileTargetGenerator::WriteObjectRuleFiles in the Source/cmMakefileTargetGenerator.cxx.

    In the event that you decide you are okay with modifying the variable cache of a configured buildsystem and regenerating it before rebuilds (which honestly might not be a big deal at all (in terms of time taken) depending on the specifics of your project), you can use a CMake cache-editing tool like ccmake or cmake-gui, then run the configure command and then invoke the buildsystem. Or see Is there a safe way to edit a cache variable from the command line? (TL;DR you can also use the -D argument). You'd either be editing the CMAKE_CXX_FLAGS variable or similar variable, or one that you define youself and then use as a compile definition like target_compile_definitions(<target_name> <visibility> "<name_of_your_cache_variable>=${<name_of_your_cache_variable>}").