Search code examples
cmakedependencies

How can I handle duplicate target errors when multiple add_subdirectory-ed git dependencies have a common add_subdirectory-ed git dependency?


I maintain 3 static libraries: a, b and c, that use CMake. They are located in separate git repositories and can be standalone.

  • a is a very basic library, that has no dependencies.

  • b depends on a. It includes a as a submodule and adds it with add_subdirectory.

  • c depends on both of a and b. It includes them as submodules and adds with add_subdirectory.

So, the structure of c repo is as follows:

/c (repository root)
|
+-- CMakeLists.txt (add_subdirectory(a), add_subdirectory(b))
|
+--/a (git submodule)
|  |
|  +-- CMakeLists.txt (add_library(a))
|
+--/b (git submodule)
   |
   +-- CMakeLists.txt (add_subdirectory(a))
   |
   +--/a (git submodule added recursively)
      |
      +-- CMakeLists.txt (add_library(a))

We can see, that a is duplicated: add_library(a) is called twice and cause an error about duplicated target.

I can wrap every add_subdirectory into if (NOT TARGET ...), but it seems to be improper solution to the problem. I could also just make c use a from b, but I don't want c to "know about" b's internals and dependencies.

I saw here on SO some advice not to use add_subdirectory on projects, that can be included into another ones, but can't figure out alternative way.

To sum up: what is the right way to deal with such dependencies using CMake? The main restriction is that b must be standalone, i.e. I want to build it without building c.

Moreover, I don't want to "install" these libs. If I would build b, then a must be built once just before b. If I would build c, then a and b must be built once just before c.


Solution

  • I suppose you could "play it fair" and make neither B nor C assume that they can add A without conflict.

    What you suggested with if (NOT TARGET ...) would work. You could also check if something like <PROJECT-NAME_SOURCE_DIR> is defined- assuming that each of A, B, and C are their own projects in the CMake sense of the word.


    But actually, if you're using git subdirectories for them anyway, you might as well look into switching to use FetchContent- in which case the call to FetchContent_MakeAvailable will only do things if the thing to fetch hasn't already been fetched. In this case, you would FetchContent_Declare A in B, and A and B in C, and do the same for FetchContent_MakeAvailable. Make sure to heed the guidance in the docs which says:

    The FetchContent_Declare() function records the options that describe how to populate the specified content. If such details have already been recorded earlier in this project (regardless of where in the project hierarchy), this and all later calls for the same content <name> are ignored. This "first to record, wins" approach is what allows hierarchical projects to have parent projects override content details of child projects.

    [...]

    Projects should aim to declare the details of all dependencies they might use before they call FetchContent_MakeAvailable() for any of them. This ensures that if any of the dependencies are also sub-dependencies of one or more of the others, the main project still controls the details that will be used (because it will declare them first before the dependencies get a chance to).