1

CMake cache variables can be set from virtually everywhere (see here @Florian's What's the CMake syntax to set and use variables?). I was under the assumption that the set value is visible everywhere, even to CMake lists parsed before, but this isn't the case.

Use case

  • Module A uses ${CMAKE_MYDEF}.
  • Module B sets the cache variable CMAKE_MYDEF.
  • add_subdirectory(A) is called before add_subdirectory(B).

Short example showing the behavior

cmake_minimum_required(VERSION 3.7)
project(test)
add_executable(EXEC test.cpp)
target_compile_definitions(EXEC PRIVATE MYDEF=${CMAKE_MYDEF})
set(CMAKE_MYDEF "MyValue" CACHE STRING "")

Questions

  • How can I make sure CMAKE_MYDEF has the desired value regardless the order I add module A and module B?
  • Are there any ways to ensure the CMake configuration step is re-run twice or, if applicable, as long as the cache variables get changed? (This isn't probably a clean solution, but since I'm working with legacy code not everything can be done beautifully.)
  • Are there alternatives to cache variables to achieve the same result without re-running the CMake configuration by hand?
  • Is it possible to set compiler definitions in the generation phase (i.e. when all CMake cache variables are known and set)? Using some kind of generator expressions?

Edit: Short example solution

Following @Florian's answer, here the adapted example showing the solution:

cmake_minimum_required(VERSION 3.7)
project(test)
add_executable(EXEC test.cpp)
target_link_libraries(EXEC MyOtherLib)

add_library(MyOtherLib INTERFACE)
set(CMAKE_MYDEF "MyValue" CACHE STRING "")
target_compile_definitions(MyOtherLib INTERFACE MYDEF=${CMAKE_MYDEF})
Roland Sarrazin
  • 1,203
  • 11
  • 29

2 Answers2

1

CMake processes scripts sequentially, starting from top-level CMakeLists.txt and executing its lines one by one.

So, if read variable before assigning it, you will get nothing. The only specific of CACHE variable in that scenario is possibility for that variable to be assigned on previous cmake invocation.


Needs for using a variable before its assigning taking a place usually signals about bad design. In many situations (even with legacy code), design can be fixed gracefully.

Forcing CMake to reconfigure the project can be accomplished e.g. by touching current script:

to force a re-configure, one could "cmake -E touch" the CMAKE_CURRENT_LIST_FILE, somehow during target building or some such.

Tsyvarev
  • 60,011
  • 17
  • 110
  • 153
  • Thanks for your answer, Tsyvatev. Your answer is also useful w.r.t. multiple runs but for my use case, @Florian's answer about transitive compiler definitions is more appropriate, hence accepting his. – Roland Sarrazin Nov 17 '17 at 08:44
  • @RolandSarrazin: When I wrote my answer, your question misses part about "Is it possible to set compiler definitions in the generation phase" and thus be very general. In your future questions **be specific**: not only ask "what *general* feature do you want", but also tell for which *specific purpose* you want it. Some general questions have no simple answer, but specific problems, based on that question, can very well being resolvable. E.g. CMake scripting doesn't work well as general programming language, but it is intended for build things, and works relatively well in that area. – Tsyvarev Nov 17 '17 at 08:59
1

Yes, I'm fully with @Tsyvarev's answer that CMake's parser works sequentially. So variables - even cached ones - or generator expressions - that can't read variables - are no good here.

I just wanted to add the possibilities you have using target and directory properties depending on the dependencies between A and B:

  1. When A depends on B with e.g.

    target_link_libraries(A PUBLIC B)
    

    then a simple

    target_compile_definitions(B PUBLIC MYDEF=SOME_DEF)
    

    would propagate the necessary define to A.

  2. When B depends an A and A is already known than it would be

    target_link_libraries(B PUBLIC A)
    target_compile_definitions(A PUBLIC MYDEF=SOME_OTHER_DEF)
    
  3. If you're working with sub-directories I would recommend putting the definition in the root CMakeLists.txt globally:

    add_definitions(-DMYDEF=GLOBAL_DEF)
    
  4. Finally the full variant with sub-directories letting B decide what to do:

    CMakeLists.txt

    cmake_minimum_required(VERSION 3.7)
    
    project(test)
    
    add_subdirectory(A)
    add_subdirectory(B)
    

    A\CMakeLists.txt

    file(WRITE a.cpp [=[
    #include <iostream>
    
    #ifndef MYDEF
    #define MYDEF "Hello from A!"
    #endif
    
    void a()
    {
        std::cout << MYDEF << std::endl;
    }
    ]=])
    
    add_library(A a.cpp)
    

    B\CMakeLists.txt

    file(WRITE b.cpp [=[
    
    void a();
    
    void main()
    {
        a();
    }
    ]=])
    
    add_executable(B b.cpp)
    target_link_libraries(B A)
    
    if (TARGET "A")
        target_compile_definitions(A PRIVATE MYDEF="Hello from B!")
    else()
        set_property(
            DIRECTORY ".." 
            APPEND PROPERTY 
                COMPILE_DEFINITIONS "MYDEF=\"Hello from Global!\""
        )
    endif()
    

Reference

Florian
  • 39,996
  • 9
  • 133
  • 149
  • I somehow missed CMake's transitive dependency features for compiler definitions although I'm completely within this topic at the moment... Thanks for the answer, I'll add the adapted example as a separate answer to illustrate how I'll use it eventually. – Roland Sarrazin Nov 17 '17 at 08:27