Search code examples
cmake

Building a multiple module project with dependencies in CMake


I am trying to make a library in c++ with multiple different optional features and I want anyone building this library for themselves to be able to select which features they wan to include in their build. The problem is, since some features depend on others, the project has a complicated dependency tree and as such if one feature is enabled, its dependencies should also be enabled.

Suppose we have a dependency graph that looks like this:

    A
   / \
  B   D
 /
C

with feature C depending on B which itself depends on A. So, for instance, even if only C is enabled, A and B should also get enabled.

So far I have tried achieving this with the following CMake code, using a recursive function to traverse the dependency tree

set(FEATURES "A" "B" "C" "D")

set(DEPS_A)
set(DEPS_B A)
set(DEPS_C B)
set(DEPS_D A)

option(INCLUDE_A OFF)
option(INCLUDE_B OFF)
option(INCLUDE_C OFF)
option(INCLUDE_D OFF)

function(include_deps feature)
    set(INCLUDE_${feature} ON PARENT_SCOPE)
    foreach(dep ${DEPS_${feature}})
        include_deps(${dep})
    endforeach()
endfunction()

foreach(f ${FEATURES})
    if(${INCLUDE_${f}})
        include_deps(${f})
    endif()
endforeach()


foreach(f ${FEATURES})
    message("${f}: ${INCLUDE_${f}}")
endforeach()

But it appears that the PARENT_SCOPE option does not work that well with recursive functions as the set command only works for the first recursion step and not for the second step onward.

Now I am stuck on how to solve this problem. Is there a way I could make the PARENT_SCOPE work for the entire recursion process, or is there maybe a better/simpler way of solving this issue? Any help is appreciated.


Solution

  • In case anyone else runs into a similar problem, I ended up solving it by using an iterative algorithm to traverse the dependency tree instead of a recursive one.

    set(SOURCE)
    set(HEADER)
    
    set(ACTIVATION_QUEUE)
    foreach(module ${MODULES})
        if (${INCLUDE_${module}})
            list(APPEND ACTIVATION_QUEUE ${module})
        endif()
    endforeach()
    
    while(ACTIVATION_QUEUE)
        list(GET ACTIVATION_QUEUE 0 module)
    
        foreach(file ${MODULES_FILES_${module}})
            list(APPEND SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/Source/${file}.cpp")
            list(APPEND HEADER "${CMAKE_CURRENT_SOURCE_DIR}/Source/${file}.hpp")
        endforeach()
    
        foreach(dep ${DEPS_${module}})
            if (NOT ${INCLUDE_${module}})
                list(APPEND ACTIVATION_QUEUE ${dep})
            endif()
        endforeach()
    
        list(REMOVE_ITEM ACTIVATION_QUEUE ${module})
    endwhile()