How to let subdirectories decide what other subdirectories are added by parent

How to let a subdirectory of the same project decide if the parent should add a subdirectory. Those subdirectories (x, y) are dependencies of one or more subdirectories (a, b), but not on the parent/root. Sometimes this dependency can contain only a header file (y), sometimes it can contain both a header file and a source file (x).

I have the following situation:

Directory structure:

a/
  CMakeLists.txt
  a.c
  a.h
  b/
    CMakeLists.txt
    b.c
    b.h
  c/
    CMakeLists.txt
    c.c
    c.h
  x/
    CMakeLists.txt
    x.c
    x.h
  y/
    CMakeLists.txt
    y.h

a/CMakeLists.txt:

cmake_minimum_required(VERSION 3.23)
project(a)

set(CMAKE_C_STANDARD 99)

add_library(${PROJECT_NAME} STATIC a.c)

target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

add_subdirectory(b)
add_subdirectory(c)

# TODO: x should only be added if 'b/CMakeLists.txt' and/or 'c/CMakeLists.txt' says so
add_subdirectory(x)

# TODO: y should only be added if 'c/CMakeLists.txt' says so
add_subdirectory(y)

b/CMakeLists.txt (c is similar):

cmake_minimum_required(VERSION 3.23)

target_sources(${PROJECT_NAME} PRIVATE b.c)

target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

# TODO: Tell parent it should add x as a dependency to add to the target

# This works for includes, but it will not let the parent build any required C files.
# target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/x)

I solved it by moving everything to a CMakeLists.txt in the project’s root directory:

cmake_minimum_required(VERSION 3.23)
project(a)

set(CMAKE_C_STANDARD 99)

add_library(${PROJECT_NAME} STATIC a.c a.h)
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

if(USE_B)
  target_sources(${PROJECT_NAME} PRIVATE b/b.c)
  target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/b)
  set(USE_X 1)
endif()

if(USE_C)
  target_sources(${PROJECT_NAME} PRIVATE c/c.c)
  target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/c)
  set(USE_X 1)
endif()

if(USE_X)
  target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/x)
  target_sources(${PROJECT_NAME} PRIVATE x/x.c)
endif()

If you want to keep the subdirectory structure, I can see two ways to handle this.

You could use set(... PARENT_SCOPE) to set a variable that will also be set in the parent directory .

Or, you can use INTERFACE libraries. You make them with add_library(LibName INTERFACE). You can put the INTERFACE libraries in the subdirectories. the ‘libraries’ won’t be built themselves, but anything you add to them with the INTERFACE specifier will be applied to anything that uses target_link_libraries to depend on the INTERFACE library. You can also use if(TARGET TargetName) as a condition for add_subdirectory.

cmake_minimum_required(VERSION 3.23)
project(MyProject)

add_executable(Baz)
target_sources(Baz PRIVATE baz.c)

if(USE_FOO)
	add_library(Foo INTERFACE)
	target_sources(Foo INTERFACE foo.c foo.h)
	target_compile_definitions(Foo INTERFACE USE_FOO=1)
	target_link_libraries(Baz PRIVATE Foo)
endif()

if(TARGET Foo)
  add_subdirectory(bar)
endif

And in bar/CMakeLists.txt:

add_library(Bar INTERFACE)
target_sources(Bar INTERFACE bar.c bar.h)
target_compile_definitions(Foo INTERFACE USE_BAR=1)
target_link_libraries(Baz PRIVATE Bar)