Managing runtime dependencies for large projects with checks

Context

I’ve been struggling to design a robust system within cmake for handling runtime dependencies. With runtime dependencies, I mean stuff like configuration json files, pre build dlls, and other resources which are not needed to build but are needed to actually run an executable.

Fundamentally, This would involve adding the correct custom commands and targets to check if something is out of date and copy it. This part I do not need help with. My problem is that there seems to be no way to “protect” a developer from forgetting a copy command or to warn if a copy command is no longer needed.

In order to correctly identify which resources are needed, you would need to manually check all the dependencies and see if they have any resources and add commands for each dependency, for each executable.

CMake has some related tools such as $<TARGET_RUNTIME_DLL_DIRS:tgt> which allow you to achieve what I want for certain limited cases, but nothing general.

Question

At a basic level It would be nice if you could have

add_library( Lib1 STATIC ... )
target_resources( Lib1 "resource1.json" ) #custom function 

add_library( Lib2 STATIC ... )
target_resources( Lib2 "resource2.json" ) #custom function

...

# assume a transitive dependency to Lib1 and Lib2
add_executable( Main ... )

copy_resources( Main # custom function
  Lib1 "./Lib1/"
  Lib2 "./Lib2/"
)

This would register and then copy the resources as needed. This on its own is doable in CMake, What is not is to check if all needed resources are actually included in copy_resources. If you for example omit the Lib2 line, CMake would work, Your project would build, but when you run your code, you could end up with a cryptic error from which “you forgot a line in cmake” would not be obvious.

It is this extra check I would like. The problem is that you cannot know on which targets something depends until after configure time. At which point (bar from some horifying tricks) you cannot inject custom logic as far as I am aware.

I would assume I’m not the only one who has had to manage a project with multiple sources of extra resources and multiple executables. So how would you practically solve this? Manually adding custom targets is quickly becoming infeasible.

Things I’ve looked at

  • file(generate) + some extra script after cmake, Yes this could work, but a lot of IDE’s automatically re-execute cmake when something changes, which would bypass this extra step
  • implementing the entire logic in generator expressions, I estimate this having a 50% chance of being possible if you sacrifice all sanity. Then you could maybe do this using lots of GENEX_EVAL expressions and other tricks
  • using custom add_library functions, etc. This still does not tackle the issue of not knowing the dependency tree at config time, a dependency could be of the form $<$<NOT:$<CONFIG:DEBUG>>:Lib>
  • Manually interpreting generator expressions. This could be possible but would not be a good solution and would still have limitations