MateuszL
MateuszL

Reputation: 2983

Use CMake to enforce restriction "one of alternative libraries is needed to link executable"

I use static libraries on linux. I have two static libriaries ("implementers") that share interface which is packed into third library ("common"). Exactly one of them (implementers) is needed to build binary. I need to build both versions of exec at the same time (separate targets). However I can create static libraries that use only common part. There is so many of them that

I want CMake to protect me from creating executables that are missing an implementer. This is needed because finding out about this takes long time (long compilation, even longer linking).

Pseudo-c++ and cmake look like following (I simplified by ignoring includes, which are actually important part of "common" lib).

// libFooBar - "common"
void foo(); // only declaration for includers. Is implemented by implementers
void bar() {
    foo();
}

// libFooBarPrintf - "implementer1"
void foo() {
    printf("Foo");
}

// libFooBarCout - "implementer2"
void foo() {
    std::cout<<"Foo";
}

// userlib - uses only common interface

void useBar() {
    bar();
}

// foobar_user.exe 
// needs "common" for compilation and one of "implementers" for linking
int main () {
    useBar();
    return 0;
}
add_library(foobar        STATIC foobar.cpp)
add_library(foobar_printf STATIC foobar_printf.cpp)
add_library(foobar_cout   STATIC foobar_cout.cpp)

add_library(          userlib STATIC userlib.cpp)
target_link_libraries(userlib PUBLIC foobar)

# this passes cmake, but fails to link with undefined ref to foo()
add_executable(       user_bad main.cpp)
target_link_libraries(user_bad userlib)

#those two are working as intended
add_executable(       user_printf main.cpp)
target_link_libraries(user_printf userlib foobar_printf)
add_executable(       user_cout   main.cpp)
target_link_libraries(user_cout   userlib foobar_cout)

I want to achieve cmake error on user_bad executable but not on userlib library. I imagine it to look like (very-pseudo cmake):

set_target_properties(foobar_printf foobar_cout 
    PROPERTIES 
        provides_implementation_for_foobar    1)
set_target_properties(foobar 
    PROPERTIES 
    INTERFACE_FOR_EXECUTABLES_needs_property    provides_implementation_for_foobar)

Is this possible?

Duplicating userlib to two versions (1 per implementer) is not scalable for my project.

Upvotes: 0

Views: 68

Answers (1)

ComicSansMS
ComicSansMS

Reputation: 54589

This is problematic. You create a target with incomplete dependencies (foobar), which CMake very much does not expect you to do, and is in no way obliged to handle correctly.

One way to get out of this without having to duplicate the libraries everywhere is through the use of object libraries:

add_library(foobar OBJECT ...)

add_library(foobar_printf STATIC foobar_printf.cpp $<TARGET_OBJECTS:foobar>)

add_executable(user_printf main.cpp)
target_link_libraries(user_printf userlib foobar_printf)

Take care though to at no point in the build tree end up with an incomplete target.

For instance, taking into account your example where foobar is being consumed by an intermediate userlib: At the point of declaration of userlib you will need to decide which implementation to use. Otherwise you would end up with the same problematic situation as before, only now at the level of userlib.

If you absolutely 100% need to defer this decision to the point of declaration of the executable, you should push it all the way into runtime and use dynamic libraries instead.

Note that while technically you may be able to solve this differently for certain build generators, if you're looking for a clean and robust solution, those are the only viable options I see.

Upvotes: 3

Related Questions