Paul
Paul

Reputation: 10883

Linking against built static libraries rather than using add_subdirectory?

Given a project with "app" and "lib" sibling directories, where "app" builds an executable depending on the (static) library built by "lib". What I'd like is a situation where the normal build process builds only the library, but if I build "app" it builds both "lib" and "app".

What I'm currently doing now is that in app, I include lib with add_subdirectory, but for whatever reason this is pulling in all of lib's indirect dependencies into the link line through a mechanism I'm not aware of. What I'd like is to have my app just build libmylib.a and libmylib.pc, then app could just calculate its own link line from libmylib.pc (or specify it manually), but I'm not sure how that's done.

Here's a minimum working example I've got set up now:

lib/CMakeLists.txt

cmake_minimum_required(VERSION 3.8.0)
project(mylib CXX)

find_package(PkgConfig REQUIRED)
pkg_check_modules("mylib" "libssl")

find_package(Boost REQUIRED)
set(LIBDIR "${PROJECT_SOURCE_DIR}")


set(HEADERS "${LIBDIR}/map_printer.hpp")
set(SOURCES "${LIBDIR}/map_printer.cpp")

add_library("mylib" "${SOURCES}")

target_include_directories("mylib" PUBLIC "${LIBDIR}"
                                          "${Boost_INCLUDE_DIR}"
                                          "${mylib_INCLUDE_DIRS}")
target_link_libraries("mylib" "${Boost_LIBRARIES}" "${mylib_LIBRARIES}")

install(TARGETS "mylib" ARCHIVE DESTINATION "lib")
install(FILES ${HEADERS} DESTINATION "include")

app/CMakeLists.txt

cmake_minimum_required(VERSION 3.8.0)
project(mylib CXX)

set(APPDIR "${PROJECT_SOURCE_DIR}")
set(LIBDIR "${APPDIR}/../lib")

set(SOURCES "${APPDIR}/main.cpp")

add_subdirectory("${LIBDIR}" "build")
list(APPEND LIBS "mylib")

add_executable("myapp" "${SOURCES}")

target_include_directories("myapp" PUBLIC "${LIBDIR}")
target_link_libraries("myapp" "${LIBS}")

install(TARGETS "myapp" DESTINATION "bin")

To get a working example, here are some source files that pull in libssl in the lib (but this function is not used in the app) - I put them in gists because they are only included for completeness and I didn't want to clutter up the question text:

The problem is that when I cmake app and then do make VERBOSE=1, the linker command generated is:

/usr/lib/hardening-wrapper/bin/c++     CMakeFiles/myapp.dir/main.cpp.o  -o myapp build/libmylib.a -lssl 

But I have not specified -lssl anywhere in app. Normally this would be fine, but in my real app, -lssl and several other unnecessary symbols are being included as .so files because of indirect dependencies. When I remove them from the linker command manually, the task builds and runs just fine. Ideally I'd be pulling in the built .a with its .pc file (not generated in this example) and if excess dependencies are necessarily being pulled in, I could tweak the link line manually, but with this method, the linker flags (and possibly other things) are leaking out of the lib scope in some way I don't understand.

Upvotes: 0

Views: 537

Answers (1)

legalize
legalize

Reputation: 2251

Linking is all about resolving symbols so that the final target (standalone executable or shared object) has everything it needs to be launched. Things are simple when you depend only on a single library or upon a collection of libraries that in turn depend on nothing else. This is less likely to be the case for any program of moderate or larger size. The underlying issue is how to deal with transitive dependencies, e.g. the dependencies of the things directly used by your program.

CMake understands all of this and is designed to make it simple for you to consume libraries without understanding the entire dependency graph. If you look at the documentation for target_link_libraries, you'll see the PRIVATE, PUBLIC, and INTERFACE keywords described. This allows you to describe the private requirements of the library (compile definitions, compile arguments, dependent libraries, etc.) that it needs when you build the library. The public part allows you to specify things that both the library and its dependents (consumers) need. The interface part lets you specify things that the dependents need, but not the library itself. The commands target_compile_definitions and target_include_directories operate similarly.

The upshot of all of this is that with a properly declared dependency in CMake, the client of that dependency just adds it to its list of dependencies in its own target_link_libraries command and it naturally picks up all compile definitions, include directories and transitory link dependencies necessary for successful compilation and linking.

A CppCon 2017 presentation Modern CMake for modular design goes over this in more detail.

Upvotes: 0

Related Questions