3

Lets say I have four separate projects. Three are libraries, Common, Foo, and Bar, and one of them is an executable, App. Both Foo and Bar depend on the Common library, and App depends on Foo and Bar. Furthermore, some of these projects have some scripts that need to run to generate some header and source files.

Right now I have a mess of calls like this:

if (NOT TARGET common)
  add_subdirectory(${common_lib_directory})
endif()

But this doesn't feel like the right solution. If I don't wrap it in that if guard, then there are errors because it tries to build Common more than once. Putting if guards in the CMakeLists for each project doesn't seem right either. Should I be writing Find<Library> scripts for each library? I've tried looking for examples or best practices on how to set up my CMakeLists files, but the only examples I can find are either too trivial or cover completely different use cases.


It was requested in the comments that I post some code. This is more or less what my CMakeLists.txt file looks like:

cmake_minimum_required(VERSION 2.8.12)

project(app)

# Temporary files (like object files) created while compiling projects.
set(tmp_dir ${CMAKE_BINARY_DIR}/obj)

# Directory which contains the source for 3rd party libraries.
if(NOT DEFINED dependencies_root)
  get_filename_component(
    dependencies_root "${CMAKE_CURRENT_SOURCE_DIR}/../../../../external"
    REALPATH)
endif()

set(dependencies_foo_dir "${dependencies_root}/foo"
    CACHE PATH "Directory containing the foo library.")
set(dependencies_bar_dir "${dependencies_root}/bar"
    CACHE PATH "Directory containing the bar library.")

if(NOT TARGET foo)
  add_subdirectory("${dependencies_foo_dir}" ${tmp_dir}/foo)
endif()

if(NOT TARGET bar)
  add_subdirectory("${dependencies_bar_dir}" ${tmp_dir}/bar)
endif()

include_directories(${dependencies_foo_dir}/include)
include_directories(${foo_generated_include_dir})

include_directories(${dependencies_bar_dir}/include)
include_directories(${bar_generated_include_dir})

set(app_srcs ...)

add_executable(app ${app_SRCS})

target_link_libraries(app foo bar common)

As previously mentioned, the reason I have the if (NOT TARGET blah) guards is because if I don't then I get errors like these:

CMake Error at /path/to/my/project/CMakeLists.txt:267 (add_library):
  add_library cannot create target "blah" because another target with the
  same name already exists.  The existing target is a static library created
  in source directory
  "/path/to/blah".
  See documentation for policy CMP0002 for more details.
Alex
  • 14,973
  • 13
  • 59
  • 94
  • 1
    If you are using `add_subdirectory()`, then these project aren't separate, but part of the single project. What's the point of `if(NOT TARGET t)` then? – arrowd Oct 12 '15 at 18:54
  • As stated, if I don't wrap it in that if guard, then there are errors because it tries to build Common more than once. I'm fully aware that add_subdirectory probably isn't the right way to go, which is why I'm asking this question. – Alex Oct 12 '15 at 19:00
  • CMake rebuild targets only if they aren't up to date. For regular targets it means almost never. I guess, you should post some code. – arrowd Oct 12 '15 at 19:25
  • 1
    If `Foo` and `Common` are separate projects, how `Foo` knows about `` for include it via `add_subdirectory` call? I am agreed with @arrowd that your projects *are not well-separated* and some more code may help in resolving this problem. – Tsyvarev Oct 12 '15 at 19:36
  • arrowd: That's true, but adding a directory twice means that it will try to create the target twice. It doesn't like it when you try add two targets with the same name. Tsyvarev: I know that my projects are not well separated. That's the motivation for asking this question, I'm trying to figure out how to separate them properly. I've edited my post to contain example code and an error message. – Alex Oct 12 '15 at 20:46
  • If I understand correctly, you are trying to do same thing I've asked in [CMake: How to setup Source, Library and CMakeLists.txt dependencies?](http://stackoverflow.com/questions/31512485/cmake-how-to-setup-source-library-and-cmakelists-txt-dependencies). I suggested overwriting `add_subdirectory()` there, but @m.s. was pointing me to a very interesting article series [Use CMake-enabled libraries in your CMake project](https://coderwall.com/p/qk2eog/use-cmake-enabled-libraries-in-your-cmake-project-ii). – Florian Oct 13 '15 at 07:40

1 Answers1

4

If you have such closely-linked projects, the best decision seems to use guard from re-including at the beginning of each project. Such guard can be easily implemented using return command, which returns from currently executed add_subdirectory() call:

Foo/CMakeLists.txt:

if(DEFINED Foo_GUARD)
    if(NOT Foo_GUARD STREQUAL ${CMAKE_CURRENT_BINARY_DIR})
        return() # Project has been already included by someone else
    endif()
else()
    set(Foo_GUARD ${CMAKE_CURRENT_BINARY_DIR} CACHE INTERNAL "Foo guard")
endif()

project(Foo)

...

So any project can use unprotected add_subdirectory() call for include given one:

App/CMakeLists.txt:

...
# Need *Foo* functionality? Simply include it!
add_subdirectory("${dependencies_foo_dir}" ${tmp_dir}/foo)

It is possible to implement guard as a macro, put it into the library and use it in every your project:

cmake/utils.cmake:

macro(project_guarded name)
    if(DEFINED ${name}_GUARD)
        if(NOT ${name}_GUARD STREQUAL ${CMAKE_CURRENT_BINARY_DIR})
            return() # return() *doesn't* terminate a macro!
        endif()
    else()
        set(${name}_GUARD ${CMAKE_CURRENT_BINARY_DIR} CACHE INTERNAL "${name} guard")
    endif()
    project(${name})
endmacro()

Macro usage is straightforward:

project_guarded(Foo)
Tsyvarev
  • 60,011
  • 17
  • 110
  • 153
  • Is this the standard way to deal with a libraries when there's diamond dependencies involved? What I'm trying to do seems like it would be a pretty standard thing to want to account for, so it seems weird that I have to do this extra stuff. Is `add_subdirectory` even the right thing here? Isn't there some way to set up a Find module so that the caller can just run FindFoo or FindBar or FindCommon? – Alex Oct 13 '15 at 12:32
  • The only commonly used pattern about dependencies I have seen is a *requirement* for dependee package(e.g. `Common` in your case) to be *installed* before dependent one(`Foo`). In that case dependent package uses simple `find_package()` for retrieve all needed information about dependee. There are many resources, describing this approach, and I am sure you know about it. But reality is so, that this approach doesn't fit nice for *every* situation, and your case is not the only exception. – Tsyvarev Oct 13 '15 at 13:26
  • Is there any case under which this would hurt a project? I've begun including this, and it works wonders in terms of diamond dependencies... – Alex Baum Sep 01 '22 at 15:03