Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to remove compile dependencies for cmake static libraries?

Tags:

cmake

Consider this CMake setup:

add_library( A STATIC modules/a/src/src1.cpp modules/a/src/src2.cpp )
target_include_directories( A PUBLIC modules/a/inc )
target_compile_definitions( A PUBLIC USING_A=1 )

add_library( B STATIC modules/b/src/src1.cpp modules/b/src/src2.cpp )
target_include_directories( B PUBLIC modules/b/inc )
target_compile_definitions( B PUBLIC USING_B_WRAPPER=1 )
target_link_libraries( B PUBLIC A )

add_library( C STATIC modules/c/src/src1.cpp )
target_include_directories( C PUBLIC modules/c/inc )
target_link_libraries( C PUBLIC B )

Let's assume that headers from modules/b/inc include headers from modules/a/inc, so any consumer of B library must also add modules/a/inc to its includes path and also add USING_A=1 preprocessor definition.

Let's use the Ninja Generator (however the problem occurs with any generator, including Makefile, Xcode and Visual Studio):

cmake -GNinja ../path/to/source

And let's build the C library:

ninja libC.a

Everything builds correctly, however lots of time gets wasted into building libA.a and libB.a just to build the libC.a.

If I change the C's dependency on B to INTERFACE, then libA.a and libB.a are not built, but compilation of modules/c/src/src1.cpp fails because include directories and compile definitions that should be inherited from B and its dependencies are not inherited.

Is there a way to tell CMake that specific target should not compile-depend on specific target specified in target_link_libraries list (link-dependency should remain)? I am aware that sometimes those dependencies are required (such as if, for example, A depended on some custom command generating headers for it dependees), but this is not the case here. I am searching for a solution to specify for each static library target whether or not it should compile-depend on another static library target, while still keeping the link dependency and obtaining all correct compile flags from its dependencies.

like image 958
DoDo Avatar asked Mar 20 '18 18:03

DoDo


2 Answers

Currently I know no way for issue target_link_libraries without complete target-level dependencies. And not sure that things will change in a near future. CMake developer's post from the bugreport:

Using target_link_libraries has always been enough to get an ordering dependency and many projects depend on this. We cannot change that for any target type.

What can be done without changing semantics is for the Ninja generator to detect when the transitive closure of a dependency doesn't have any custom commands and in that case drop ordering dependencies on it from the compilation rules and custom commands. Only the full dependency of the link step on the dependency's library file is needed. Work on this would be better discussed on the developer mailing list.

That answer on related question suggests to refuse from target_link_libraries between STATIC libraries, which removes some unneded dependencies. I have adapted that scenario for your purpose:

Non-natural way

Express "compile-time dependencies" with INTERFACE libraries. Real libraries will be used only for link an executable or a SHARED library.

# Compile-time dependency.
add_library( A_compile INTERFACE )
target_include_directories( A_compile PUBLIC modules/a/inc )
target_compile_definitions( A_compile PUBLIC USING_A=1 )
# Real library.
add_library( A STATIC modules/a/src/src1.cpp modules/a/src/src2.cpp )
target_link_libraries(A A_compile)

# Compile-time dependency.
add_library( B_compile INTERFACE )
target_include_directories( B_compile PUBLIC modules/b/inc )
target_compile_definitions( B_compile PUBLIC USING_B_WRAPPER=1 )
target_link_libraries( B_compile PUBLIC A_compile )
# Real library
add_library( B STATIC modules/b/src/src1.cpp modules/b/src/src2.cpp )
target_link_libraries(B B_compile)    

# Final STATIC library.
add_library( C STATIC modules/c/src/src1.cpp )
target_include_directories( C PUBLIC modules/c/inc )
target_link_libraries( C PUBLIC B_compile )

# ...
# Creation executable or non-STATIC library, linked with C
add_executable(my_exe ...)
# Need to manually list libraries, "compile-time linked" to C.
target_link_libraries(my_exe C B A)

Becase for executable or non-STATIC library target_link_libraries uses real STATIC libraries, these STATIC libraries will be built before even object files for such executable/SHARED library.

like image 61
Tsyvarev Avatar answered Nov 15 '22 12:11

Tsyvarev


We've been using the following function to link static libraries against one another without causing build order dependencies between them for several years now. While not perfect, we've been quite happy with it overall.

function(target_link_static_libraries target)
     if(BUILD_STATIC_LIBS_IN_PARALLEL)
        target_link_libraries(${target} INTERFACE ${ARGN})
        foreach(lib ${ARGN})
            target_include_directories(${target} PUBLIC $<TARGET_PROPERTY:${lib},INTERFACE_INCLUDE_DIRECTORIES>)
            target_include_directories(${target} SYSTEM PUBLIC $<TARGET_PROPERTY:${lib},INTERFACE_SYSTEM_INCLUDE_DIRECTORIES>)
            target_compile_definitions(${target} PUBLIC $<TARGET_PROPERTY:${lib},INTERFACE_COMPILE_DEFINITIONS>)
            target_compile_options(${target} PUBLIC $<TARGET_PROPERTY:${lib},INTERFACE_COMPILE_OPTIONS>)
        endforeach()
    else()
        target_link_libraries(${target} PUBLIC ${ARGN})
    endif()
endfunction()

Known drawbacks:

  • Boolean transitive properties like PIC don't get automatically get applied.
  • The dependencies aren't shown when generating dependency graphs.
  • When an error occurs finding a dependency, it often spits out hundreds of lines of error messages, since we essentially defer the checking for the existence of libraries until it has been propagated out to dozens of targets.

Improvements or fixes for any of the above would be very much appreciated.

like image 33
Parker Coates Avatar answered Nov 15 '22 10:11

Parker Coates