CMake `INSTALL` for targets and its SO dependencies - c++

My target linked with several libraries using TARGET_LINK_LIBRARIES with PUBLIC keyword, The INSTALL command looks like INSTALL(TARGETS foo DESTINATION ${CMAKE_INSTALL_PREFIX}/bin). I want somehow to force the cmake to include all (preferably excluding system libraries) libraries (SOs only) I link with to be included in the installation process. I've tried EXPORT keyword but looks like it affects only libraries which I build with in my project and marked with the same EXPORT as foo library.
Is it possible?
EDIT001: Additional information that may affect answer.
I'm using vcpkg to manage third parties. So the TARGET_LINK_LIBRARIES looks like
TARGET_LINK_LIBRARIES(foo PUBLIC
GTest::GTest
GTest::Main
${GOOGLE_MOCK}
event
${THRIFT_LIBRARIES}
${Boost_LIBRARIES}
lzo2
sqlite3
${ZeroMQ_LIBRARY}
gRPC::grpc
gRPC::grpc++
xml2
stdc++fs
bfd
-l:libisal.so.2
sgutils2
pthread
uuid
rt
)
So, essentially what I want to achieve is to take all these libraries which are macro'ed by vcpkg, like ${THRIFT_LIBRARIES}, ${Boost_LIBRARIES} and gRPC::grpc and so on

As of cmake 3.21, you can now do with:
install(IMPORTED_RUNTIME_ARTIFACTS gRPC::grpc)
install(IMPORTED_RUNTIME_ARTIFACTS ${Boost_LIBRARIES})
etc.
See new Install command.

CMake itself does not allow to install dependencies automatically. This would be a rather hard task, because it would have to consider a lot of corner cases.
Just think of transitive dependencies (I don't know if this is the right word), like: Your libA depends on libB, which depends on libC. How should CMake get this from the CMakeLists, where only libB is listed?
Or: What do you consider a system library? Everything that is not in PATH? How do you know which libraries are installed system-wide on the client's machine?
You see, there are some really tricky things to consider.
Here are some possibilities you have:
Ask your users to install the dependencies.
Statically link libraries into your binary.
Copy library files using install(FILES files... DESTINATION <dir>). Maybe your dependency manager can help creating the list of files.
Write a script that does something like windeployqt for Qt-based applications on Windows: Analyze the binary file (e.g. using ldd myApp) and automatically copy over the required dependencies.

Related

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:
lib/map_printer.cpp
lib/map_printer.hpp
app/main.cpp
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.
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.

Convert from Xcode to CMake

I have a C++ project that I initially developed using Xcode on my Mac and I would like to set up the very same project using CMake. The project makes use of some external libraries, including eigen and boost. Xcode has a rather long list of Build Settings, including paths to external libraries, specification of the compiler version, additional linker flags, compiler optimization level, etc... and I am wondering where all of this information goes in the CMakeLists.txt file. I've searched extensively for help on this but have found precious little. I am new to CMake and have never written make files before. If there were a utility that could convert my Xcode project into a CMake project, that would be ideal. But I would be very glad to know of a tutorial on this, or to have some specific guidance. Any help on this would be greatly appreciated.
Conversion Strategy
I am pretty sure that the easiest and fastest way to move to CMake is a mixture of looking at comparable projects that link the same dependencies, and maybe copying a few relative file paths to your source files from XCode.
A well-written CMakeLists.txt that uses Eigen and Boost can be very small (and platform-independent).
One reason for this is CMake has a very different way to use dependencies.
For well-known libraries such as Boost, CMake includes scripts to include headers and link libraries.
Minimal CMakeLists.txt
A minimal example may look like this:
find_package(Eigen3 REQUIRED)
find_package(Boost REQUIRED COMPONENTS system)
add_executable(
app
src/main.cpp
)
target_include_directories(
app
${Eigen3_INCLUDE_DIRS}
${Boost_INCLUDE_DIR}
)
target_link_libraries(
app
${Boost_LIBRARIES}
)
Example Projects
For Eigen, there is no such pre-installed script that can be called by find_package.
Here are two real-world example projects that link Eigen and Boost: https://github.com/ompl/ompl/blob/master/CMakeLists.txt
https://github.com/roboticslibrary/rl
Here is an example for a find script for Eigen.
As a piece of advice, some CMake projects are out there are convoluted and use old syntax, newer project files tend to use small letters in commands.
If it happens that directly target linking libraries doesn't work, you'll have to write your own Find*.cmake files by yourself.
Here's a spec of what you should cover to make it (this is just one approach):
Imagine you have a library XYZ:
Your file
FindXYZ.cmake should have as a result a set of variables to use:
XYZ_LIBRARIES
XYZ_INCLUDE_DIR
For example:
set(XYZ_LIBRARIES ${SOME_PATH_TO_LIBRARY1} ${SOME_PATH_TO_LIBRARY2})
find_path(XYZ_INCLUDE_DIR NAMES xyz/xyz.h HINTS ${PLACE_WHERE_INCLUDE_SHOULD_BE}
PATH_SUFFIXES include
)
From your main cmake maybe you 'use' it:
find_package(XYZ REQUIRED)
# Now we use both _INCLUDE_DIR and _LIBRARIES
include_directories(SYSTEM "${XYZ_INCLUDE_DIR}")
# TARGET_NAME is the current target, the 'client' of XYZ
# append XYZ to our list of libraries to link
target_link_libraries(${TARGET_NAME} ${XYZ_LIBRARIES})
I'm surely missing details but that's the general idea. Here are some cmake scripts that I know work in a big project:
Example with OpenSSL
Find:
https://github.com/highfidelity/hifi/blob/Android/cmake/modules/FindOpenSSL.cmake
Client (in this case is a library that indeed needs OpenSSL):
https://github.com/highfidelity/hifi/blob/Android/domain-server/CMakeLists.txt

propagate dependencies to header-only ExternalProject with cmake

I'm trying to build a header-only library using CMake (Microsoft/GSL), in such a way that I can use variables like GSL_INCLUDE_DIRS and GSL_LIBRARIES to link to the target and propagate the appropriate dependencies.
The project I'm working on has a TON of sub-directories, and all the external projects are built in their own sub-directories, hence why the variables are important.
I'm using CMake 3.2.3
Typically (for a library with an actual .lib or .a) I'd do something like:
SET(TARGET_NAME gsl)
include(ExternalProject)
ExternalProject_Add(
${TARGET_NAME}-ext
URL "http://target/url"
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
) # download/unzip the header-only project
# Specify include dir
SET(${TARGET_NAME}_INCLUDE_DIRS ${CMAKE_CURRENT_BINARY_DIR}/include CACHE STRING "${TARGET_NAME} include directory")
# Library
add_library(${TARGET_NAME} SHARED IMPORTED GLOBAL)
SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES
IMPORTED_LOCATION "some/path/to/some/lib"
)
add_dependencies(${TARGET_NAME} ${TARGET_NAME}-ext)
SET(${TARGET_NAME}_LIBRARIES ${TARGET_NAME} CACHE STRING "${TARGET_NAME} library location")
MARK_AS_ADVANCED(${TARGET_NAME_UPPER}_DIR ${TARGET_NAME_UPPER}_INCLUDE_DIRS ${TARGET_NAME_UPPER}_LIBRARIES)
The problem here is that the header-only library has no lib to set the imported path for, so I can't use an IMPORTED library. If I don't use a library at all, then I can't set the dependencies in other modules on GSL without building (i.e. downloading/unzipping) every single time, which I don't want to do. a custom_target would have the same problem, so that's a no-go.
I think what I want is an interface library, something like
add_library(${TARGET_NAME} INTERFACE)
add_dependencies(${TARGET_NAME} ${TARGET_NAME}-ext)
but then cmake complains that
CMake Error at 3rdParty/gsl/CMakeLists.txt:33 (add_dependencies):
add_dependencies Cannot add target-level dependencies to INTERFACE library
target "gsl".
Is there someway I can use the interface library (or something) to propagate the dependency on the external project?
Disallowing dependencies on INTERFACE libraries was an oversight that was corrected in CMake version 3.3. After upgrading to the latest-stable release, I was able to use the methodology described in the question, and it worked exactly as desired.

CMake: Recommended way for a Config-File Package to depend on a Find-Module Package

I've just been reading about CMake's Config-File Package "concept" which sounds very promising. What I like very much about it is that if I create a Config-File Package myself I can specify other packages on which it depends. My Question is: How can I create a Config-File package that is "relocatable" and depends on a Find-Module Package (e.g. boost)?
In more detail: Suppose I want to create a package named HyDi. The cmake documentation explains then very nicely how I can create the corresponding HydiConfig.cmake and HydiTargets.cmake files automatically. A very simple version of the CMakeLists.txt that does this is:
project(HyDi)
find_package(Boost COMPONENTS program_options)
add_library(HyDi foo.cpp foo.hpp)
target_include_directories(HyDi PUBLIC INTERFACE ${Boost_INCLUDE_DIRS})
target_link_libraries(HyDi ${Boost_LIBRARIES})
target_compile_options(HyDi INTERFACE PUBLIC "-std=c++11")
install(TARGETS HyDi EXPORT HyDiTarget
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
INCLUDES DESTINATION include
)
install(FILES foo.hpp DESTINATION include)
configure_file(cmake/HyDiConfig.cmake
"${CMAKE_CURRENT_BINARY_DIR}/HyDi/HyDiConfig.cmake"
COPYONLY
)
set(ConfigPackageLocation lib/cmake/HyDi)
install(EXPORT HyDiTarget FILE HyDiTargets.cmake
NAMESPACE Upstream:: DESTINATION ${ConfigPackageLocation} )
install(FILES cmake/HyDiConfig.cmake DESTINATION ${ConfigPackageLocation})
The corresponding HydiConfig.cmake is:
include(CMakeFindDependencyMacro)
find_dependency(Boost COMPONENTS program_options)
include("${CMAKE_CURRENT_LIST_DIR}/HyDiTargets.cmake")
However if I install this library, the HyDiTargets.cmake file will contain the include path to the Boost Libraries hardcoded and is thus not relocatable.
Note that the cmake documentation gives an example of how not to include the boost libraries that is essentially my version. But they unfortunately don't explain how to do it better.
I understand that I could build boost using cmake and could then import boost as a Config-file package so that my HydiTargets.cmake would relocatable. But this approach doesn't work with every other library that provides a Findxxx.cmake file.
Actually CMake doing this correct, when inject a "hardcoded" path to boost libraries (and you doing it wrong). Because after your library gets compiled and installed, it should "links" w/ very particular boost version (when it was at the moment of your library compilation) -- i.e. its header files and static/dynamic libraries.
Consider scenario: after successfull installation of your library, someone installs a new version of boost library (or any other third party library you depending on) in parallel (yeah, boost and some other libs could coexists in the same install prefix). To make things looks like a real world example, assume it is ABI incompatible w/ the previous version. Now if that "lucky" developer wants to use your (already compiled and installed) library (using exported targets and provided HyDiConfig.cmake) he'll get a trouble:
your library already linked to a "previous" boost version (remember ABI incompatible w/ a newer one);
so when you somehow replace that "hardcoded" paths and would find a newer version (as you trying to do in HyDiConfig.cmake) your "lucky" customer would angry on you for that mess!
That is not only about boost… same policy for all third party libraries: they should remains the same as it was at the moment of compilation (or at least ABI compatible if we are talking about dynamic linking at run time, but this is a separate story).
Moreover, your user probably even do not use boost in his app (but have more than one version installed) -- why you should find smth? You already know (thanks to CMake and hardcoded paths) what boost version "required" for your library! So finding smth new is completely wrong in this case! It is "too late… Your library already compiled, linked and installed!
Another case: he wants to use some other (newer) version of boost… Depending on order of find_package(boost) and find_package(yourLib) results may vary… but he'll be angry on you anyway!

Linking a static library to a shared library in cmake

I have been working on a fun project (a game engine) for awhile now and figured i could make it more portable and easy to compile across platforms if I use cmake. Right now i have it set up like so, with a main executable and then a bunch of shared libraries that the executable is linked to. I've found all the material needed to produce the libraries and the executable, and linking those to the executable, but what of linking a dependency like a static library or another shared library to one of the libraries i produce? Here is a visual
Sentiment (name of project)
-Root (all the interfaces and components of the engine. main.cpp is here
-Sentiment_OGL4Renderer (the files for the Renderer library)
-Sentiment_SFMLGui (the files for the Gui library)
-Sentiment_TestGame (the code for a game)
now i want all of these, the executable and the shared libraries built and put into the bin folder in the top level directory. What i found suggested online for a setup like this was to make cmakelists.txt files in each folder, and then one in the root, for each project. What i have thus far is this.
#Sentiment
cmake_minimum_required(VERSION 2.6)
project(Sentiment)
set(RENDERER Sentiment_OGL4Renderer)
set(GUI Sentiment_SFMLGui)
set(GAME Test_Game)
add_definitions(-DBUILD_DLL)
list( APPEND CMAKE_CXX_FLAGS "-std=c++11 ${CMAKE_CXX_FLAGS} -g -ftest-coverage -fprofile-arcs")
set(EXECUTABLE_OUTPUT_PATH "${Sentiment_SOURCE_DIR}/bin")
set(LIBRARY_OUTPUT_PATH "${EXECUTABLE_OUTPUT_PATH}")
link_directories("${LIBRARY_OUTPUT_PATH}")
add_subdirectory("${RENDERER}")
add_subdirectory("${GUI}")
add_subdirectory("${GAME}")
add_subdirectory(Root)
in root
project(Sentiment_exe)
link_directories("${Sentiment_SOURCE_DIR}/bin")
AUX_SOURCE_DIRECTORY(. new_source_list)
add_executable("${CMAKE_PROJECT_NAME}" ${new_source_list})
target_link_libraries("${CMAKE_PROJECT_NAME}" "${LIBRARY_OUTPUT_PATH}/${RENDERER}" "${LIBRARY_OUPUT_PATH}/${GUI}" "${LIBRARY_OUTPUT_PATH}/${GAME}" "${ADDITIONAL_DEPENDENCIES}")
in Sentiment_OGL4Renderer
project(Sentiment_OGL4-3Renderer)
include_directories(${Sentiment_SOURCE_DIR})
add_definitions(-DGLEW_STATIC)
add_library(Sentiment_OGL4-3Renderer SHARED Sentiment_OGL4Renderer.cpp GL/glew.cpp)
in Sentiment_SFMLGui
project(Sentiment_SFMLGui)
include_directories(${Sentiment_SOURCE_DIR})
add_library(Sentiment_SFMLGui SHARED Sentiment_SFMLGui.cpp)
in Sentiment_TestGame
project(Sentiment_Game)
include_directories(${Sentiment_SOURCE_DIR})
add_library(Sentiment_Game SHARED Game.cpp)
As you can tell there are a lot of third party libraries, and i tried various methods of linking, like with target_link_libraries, and i cannot for the life of me figure how to link an external library to the ones i've made. First off, the renderer uses GLEW but it needs no external dependency so ignore that. Then it needs OpenGL32.lib and Gdi32.lib from the windows sdk (only for windows). As for SFML, i've got the DLL's in the bin folder which need to be linked, (can easily get the .so's when working in linux and can distribute with the final product if I ever choose to do so). I need these all linked as dependencies to the libraries i create, but nothing seems to work. The project is all c++ and I am currently using mingw32 to compile it. I'm brand new to cmake so please be gentle if it is really simple.
To link external libraries, best practice is to use or create FindModule for given external library.
CMake comes with numerous modules that aid in finding various well-known libraries and packages.
The list of standard modules is in the official documentation
In case there is no standard module for your external library, you should write your own.
The OpenGL library has standard module FindOpenGL:
find_package (OpenGL)
if (OPENGL_FOUND)
include_directories(${OPENGL_INCLUDE_DIR})
target_link_libraries (Sentiment_OGL4-3Renderer ${OPENGL_gl_LIBRARY})
endif (OPENGL_FOUND)