Using CMake with libraries with diamond depedencie

2019-04-13 02:32发布

问题:

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.

回答1:

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)


标签: cmake