Search code examples
cmakec++17

generating header file in presence of not infinitely circular dependencies


Hum, it's a little difficult to explain, especially in the title...

Let's say I have the following files: main.cpp, domain.cpp, domain.hpp, generated.hpp and generator.cpp. domain.cpp includes domain.hpp, which in turn includes generated.hpp. I want to build two executables:

  • generator, consisting of generator.cpp and domain.cpp
  • main, consisting of main.cpp and domain.cpp

Here's the twist. Initially, generated.hpp is empty. Before building main, I want to build generator (if needed), and always run it before building main. generator calculates stuff related to domains.cpp, and rewrites generated.hpp - but only if its content would be different. Thus, running generator for the first time will rewrite the empty generated.hpp; running it again immediately afterwards will not touch generated.hpp; and running generator after making changes to the domain.?pp files may or may not rewrite generated.hpp.

Thus, there is a cycle because generator depends on its output, but it will be traversed only once.

Moreover, generator has to run before ninja/make/whatever starts examining the dependencies of main.

Can it be achieved in plain cmake, without playing tricks with scripts?

It would be nice if the generated generated.hpp goes in the build area.

The files are in C++17, thus solutions that involve __has_include are acceptable. But I doubt it can help.

PS In case you wonder about the context, it's for my YOMM2 open multi-methods library. generator calculates offsets in v-tables, and writes them as constexpr offsets. If present, they are used to speed up method dispatch.


Solution

  • CMakeLists.txt
    initially/generated.hpp   # "Initially, generated.hpp is empty."
    src/ main.cpp domain.cpp domain.hpp generator.cpp
    

    Then:

    # running generator for the first time will rewrite the empty generated.hpp
    add_executable(generator_firsttime src/generator.cpp)
    target_include_directories(generator_firsttime PRIVATE initially)
    add_custom_command(
       OUTPUT ${CMAKE_BINARY_DIR}/firsttime/generated.hpp
       COMMAND generator_firsttime 
          -o ${CMAKE_BINARY_DIR}/firsttime/generated.hpp
       DEPENDS generator_firsttime src/domains.cpp
    )
    add_custom_target(generated_firsttime_generated_hpp DEPENDS ${CMAKE_BINARY_DIR}/firsttime/generated.hpp)
    
    add_executable(generator_secondtime src/generator.cpp)
    target_include_directories(generator_secondtime PRIVATE ${CMAKE_BINARY_DIR}/firsttime)
    add_dependencies(generator_secondtime generated_firsttime_generated_hpp)
    add_custom_command(
       OUTPUT ${CMAKE_BINARY_DIR}/secondtime/generated.hpp
       COMMAND generator_secondtime 
          -o ${CMAKE_BINARY_DIR}/secondtime/generated.hpp
       DEPENDS generator_secondtime src/domains.cpp
    )
    add_custom_target(generated_firsttime_generated_hpp DEPENDS ${CMAKE_BINARY_DIR}/secondtime/generated.hpp)
    
    # for ease of use
    add_library(generated_hpp INTERFACE)
    add_dependency(firsttime_generated_hpp generated_firsttime_generated_hpp)
    target_include_directories(generated_hpp INTERFACE {CMAKE_BINARY_DIR}/secondtime)
    
    # not compile your program
    add_executable(main src/main.cpp)
    target_link_libraries(main PRIVATE generated_hpp)