CMake export package that relies on external library - c++

I have a project written using C++ and CMake, using Boost, that I'm trying to make a standalone binary/header package for to allow other people to link against my work. I'm using cmake installers for this. However, I'm running into issues with install(EXPORTS ...) when my library links to an external library. In particular, the Boost library and header directory locations are hard-coded into the exported file, and I can't figure out how to make it work better.
Have an example. (Untested; if it's not clear I can elaborate or fix it.)
CMakeLists.txt:
package(MyLibrary)
set(MyLibrary_VERSION 1.0)
find_component(BOOST 1.55.0 REQUIRED COMPONENTS serialization)
set(INSTALL_INCLUDE_DIR "C:/MyLibrary/include")
set(INSTALL_SRC_DIR "C:/MyLibrary/include")
set(INSTALL_BIN_DIR "C:/MyLibrary/bin")
set(INSTALL_LIB_DIR "C:/MyLibrary/lib")
set(INSTALL_CMAKE_DIR "C:/MyLibrary/cmake")
set(HEADERS myfile.hpp)
set(SOURCES myfile.cpp)
install(FILES ${HEADERS} DESTINATION ${INSTALL_INCLUDE_DIR} COMPONENT headers)
install(FILES ${SOURCES} DESTINATION ${INSTALL_SRC_DIR} COMPONENT sources)
add_library(MyLibrary STATIC
${HEADERS} ${SOURCES})
target_link_libraries(MyLibrary
${Boost_SERIALIZATION_LIBRARY})
target_include_directories(MyLibrary
PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR};${Boost_INCLUDE_DIRS}>"
PUBLIC "$<INSTALL_INTERFACE:include;${Boost_INCLUDE_DIRS}>")
install(TARGETS MyLibrary EXPORT MyLibrary-depends
DESTINATION ${INSTALL_LIB_DIR} COMPONENT libraries)
configure_package_config_file(MyLibraryConfig.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/MyLibraryConfig.cmake"
INSTALL_DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/MyLibraryConfigVersion.cmake"
VERSION ${MyLibrary_VERSION}
COMPATIBILITY AnyNewerVersion)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/MyLibraryConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/MyLibraryConfigVersion.cmake"
DESTINATION "${INSTALL_CMAKE_DIR}")
install(EXPORT MyLibrary-depends
FILE MyLibrary-depends.cmake
DESTINATION "${INSTALL_CMAKE_DIR}")
MyLibraryConfig.cmake.in
#PACKAGE_INIT#
if (NOT MyLibrary_FOUND)
set(MyLibrary_FOUND 1)
find_package(Boost 1.55.0 COMPONENTS SERIALIZATION)
include(MyLibrary-depends.cmake)
# random directory stuff, etc.
endif()
The issue is that MyProject-depends.cmake ends up with the value of ${Boost_INCLUDE_DIRS} and ${Boost_SERIALIZATION_LIBRARY}, which are both absolute paths and screw up the portability of the install.
I've tried a couple of things, none of which seem to fix all my problems.
target_include_directories:
I tried escaping the $, with the hope that MyProject-depends.cmake would pick up the value of the Boost_INCLUDE_DIRS variable on include-time:
target_include_directories(MyProject
PUBLIC "$<INSTALL_INTERFACE:include;\${Boost_INCLUDE_DIRS}>"
...)
But, of course, INSTALL_INTERFACE thinks that ${Boost_INCLUDE_DIRS} is a relative path and prefixes it wit {$_IMPORT_DIR} which breaks everything.
I can ditch the MyProject-depends.cmake route entirely, and add it into MyProjectConfig.cmake.in:
CMakeLists.txt:
target_include_directories(MyProject
PUBLIC "$<INSTALL_INTERFACE:include>"
PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR};${Boost_INCLUDE_DIRS>")
and MyProjectConfig.cmake.in:
include(MyProject-depends.cmake)
set_target_properties(MyProject
INTERFACE_INCLUDE_DIRECTORIES "${Boost_INCLUDE_DIRS}")
That option seems to work but is a pain.
target_link_libraries:
I'm having more trouble with the library linking. I tried the same trick, moving stuff into the MyProjectConfig.cmake.in file for more control, but
target_link_libraries(MyProject ${Boost_SERIALIZATION_LIBRARIES})
doesn't work on imported libraries, and
set_target_properties(MyProject INTERFACE_LINK_LIBRARIES ${Boost_SERIALIZATION_LIBRARY})
fails because ${Boost_SERIALIZATION_LIBRARY} expands to something like optimized;C:/boost/stage/lib/boost_serialization.lib;debug;C:/boost/stage/lib/boost_serialization.libd and set_target_properties doesn't like the keywords.
Now I'm left with some sort of remapping using
"$<$<CONFIG:DEBUG>:${Boost_SERIALIZATION_LIBRARY_DEBUG}>$;<$<CONFIG:RELEASE>:${Boost_SERIALIZATION_LIBRARY_RELEASE}>"
but I'll also have to detect whether or not a debug library is specified... which is doable, but seems like yak shaving to me.
So, sages of the stack... any advice? Is there some obvious module or clever method that I'm overlooking?
(And thanks for making it all the way through!
Also: the cmake install(EXPORTS ...) documentation contains the helpful line "If a library target is included in the export but a target to which it links is not included the behavior is unspecified." Yeah, basically, I'm looking for a workaround.

I ended up with the last target_link_libraries answer, ditching the built-in import structure entirely and writing the CMake module to remap
optimized;C:/boost/stage/lib/boost_serialization.lib;debug;C:/boost/stage/lib/boost_serialization.libd
into
$<$<CONFIG:DEBUG>:${Boost_SERIALIZATION_LIBRARY_DEBUG}>$;<$<CONFIG:RELEASE>:${Boost_SERIALIZATION_LIBRARY_RELEASE}>
Not at all pretty, but it was the best I could come up with. So it goes.

Related

How to create a cmake header-only library that depends on external non-header-only target?

How to create a cmake header-only library that depends on external header files? is close but different.
I have a single-header library, MyHeaderLib. In MyHeaderLib/MyHeader.h I have #include <QString>, so anyone doing #include "MyHeaderLib/MyHeader.h" had better have QString in their path (i.e., Qt5Core to CMake, I think(?)) and it they'll need to link to Qt5Core.
What belongs in my CMakeLists.txt for MyHeaderLib? I have
cmake_minimum_required(VERSION 3.12)
add_library(MyHeaderLib INTERFACE)
target_include_directories(MyHeaderLib include/)
# (^ Where include/ contains MyHeaderLib/MyHeader.h)
Anything I try with target_link_libraries(MyHeaderLib requires INTERFACE and if I do target_link_libraries(MyHeaderLib INTERFACE Qt5Core) that doesn't suffice.
Ultimately I got it to work as follows, but I don't understand what is going on:
cmake_minimum_required(VERSION 3.12)
find_package(Qt5Core REQUIRED) # <- Can't be Qt5::Core
add_library(MyHeaderLib INTERFACE)
target_include_directories(MyHeaderLib include/)
# (^ Where include/ contains MyHeaderLib/MyHeader.h)
target_link_libraries(MyHeaderLibrary
INTERFACE
Qt5::Core # <- Can't be Qt5Core
)
I gather the targets with :: in them are aliases, but I'm perplexed why it needs to be exactly like this. Furthermore, I can't find add_library(Qt5::Core ALIAS Qt5Core) anywhere. What is going on? Why do I have to find_package(Qt5Core REQUIRED) and not find_package(Qt5::Core REQUIRED) and why can't target_link_libraries take Qt5Core?
Packages are responsible for defining targets. The Qt maintainers chose to name the package Qt5Core while deciding to define the Qt5::Core target.
Usually the convention with CMake packages is that a package named package-name will define package-name::package-name with maybe other optional targets or subcomponents of package-name::package-name.
As to answer why Qt don't act like this, look inside Qt5CoreConfig.cmake, you'll see this line:
add_library(Qt5::Core SHARED IMPORTED)
Here you go. The file is named Qt5CoreConfig so it needs find_package(Qt5Core), but the target is under the Qt5 namespace as they choose to define it.
This is maybe because Qt5 Also has a general package which you can use components:
find_package(Qt5 REQUIRED COMPONENTS Core)
# Here Qt5::Core kinda make sense.

Does the header would be included explicitly when use find_package in CMake?

When I use PyTorch for C++, it's pretty easy to just use find_package to set up the dependency. And here is the CMakeLists.txt:
cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
project(dcgan)
set(CMAKE_PREFIX_PATH /User/root/libtorch) # I added this line, does it effect?
find_package(Torch REQUIRED)
add_executable(dcgan dcgan.cpp)
target_link_libraries(dcgan "${TORCH_LIBRARIES}")
set_property(TARGET dcgan PROPERTY CXX_STANDARD 14)
There isn't any explicit command to include the header, but the header could be found if target_link_libraries(dcgan "${TORCH_LIBRARIES}") exists. I am curious why the header file could be found even there is no target_include_directories(dcgan PUBLIC ${TORCH_INCLUDE_DIRS}).
The code is on the official website of PyTorch and it works on MacOS and Linux. What happened.
ADD:
The package is in a directory where the compiler knows nothing about it.
The include path can be set as propagated setting in the dependency:
target_link_libraries
Specify libraries or flags to use when linking a given target and/or
its dependents. Usage requirements from linked library targets will be
propagated. Usage requirements of a target’s dependencies affect
compilation of its own sources.
https://cmake.org/cmake/help/latest/command/target_link_libraries.html
That means that target_link_libraries will configure the target . It will set target_compile_features, target_compile_options, target_compile_directories, if they're set as INTERFACE or PUBLIC in the dependency.
E.g.
add_library(Lib ${SRCS_LIB})
target_include_directories(Lib INTERFACE ${DIRECTORY})
add_exectuable(Exe ${SRCS_EXE})
target_link_libraries(Exe PRIVATE Lib)
In this example Exe will inherit the include directories from Lib. You don't need to set them explicitly.
That's also how Conan works, e.g. Getting started
cmake_minimum_required(VERSION 2.8.12)
project(MD5Encrypter)
add_definitions("-std=c++11")
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()
add_executable(md5 md5.cpp)
target_link_libraries(md5 ${CONAN_LIBS})
and how it's described in Effective Modern CMake
Use exported targets of external packages.
Don’t fall back to the old
CMake style of using variables defined by external packages. Use the
exported targets via target_link_libraries instead.
Best practice is to not use target_include_directories for your dependencies.

Building an external static library in CMake, and including the result

My C++ project has a dependency to a library, that I have the source for in a subdirectory. This external library has an option 'build_static_lib' for building it as a static library in its CMakeLists.txt file.
In my top directory CMakeLists.txt, my understanding is that this should be simple:
add_executable(myproject ${SOURCES})
set(build_static_lib ON)
add_subdirectory(${CMAKE_SOURCE_DIR}/subdirectory/external_project)
unset(build_static_lib)
....
target_link_libraries(myproject PRIVATE externalproject another_library)
When I run this, my sources are built, then during the make, I am given an error that it cannot find -lexternalproject.
The strange thing is, when I change CMakeLists.txt (or delete the CMakeCache), and run it again, suddenly it successfully builds the libexternalproject and links it correctly.
Am I missing something? My journey with CMake has been unbelievably painful and any suggestions would be appreciated. It seems like every online resource has a very different strategy, and none of them quite achieve this presumably simple case. Thanks!
EDIT:
The content of the external project's CMakeLists.txt is https://github.com/muflihun/easyloggingpp/blob/master/CMakeLists.txt:
if (build_static_lib)
if (lib_utc_datetime)
add_definitions(-DELPP_UTC_DATETIME)
endif()
require_cpp11()
add_library(easyloggingpp STATIC src/easylogging++.cc)
install(TARGETS
easyloggingpp
ARCHIVE DESTINATION lib)
endif()

How to make cmake find a shared library in a subfolder

I'm trying to learn how to make a shared library. And the following seems to work (please comment if you have some feedback to this method, I basically have no idea what I'm doing).
In my library project, I've put the header files into a folder named "include", and the source files into "src".
My library's CMakeLists.txt:
cmake_minimum_required(VERSION 2.4.0)
project(mycustomlib)
# Find source files
file(GLOB SOURCES src/*.cpp)
# Include header files
include_directories(include)
# Create shared library
add_library(${PROJECT_NAME} SHARED ${SOURCES})
# Install library
install(TARGETS ${PROJECT_NAME} DESTINATION lib)
# Install library headers
file(GLOB HEADERS include/*.h)
install(FILES ${HEADERS} DESTINATION include)
My application's CMakeLists.txt:
cmake_minimum_required(VERSION 2.4.0)
project(myprogram)
# Find source files
file(GLOB SOURCES src/*.cpp)
# Create executable
add_executable(${PROJECT_NAME} ${SOURCES})
# Find and link library
find_library(MYCUSTOMLIB mycustomlib)
target_link_libraries(${PROJECT_NAME} ${MYCUSTOMLIB})
And this is working. The problem is that I want to put both the headers and the library into subfolders (specifically: /usr/local/include/mycustomlib/ for the headers, and /usr/local/lib/mycustomlib/ for the library).
So this is my attempt:
My library's new CMakeLists.txt:
cmake_minimum_required(VERSION 2.4.0)
project(mycustomlib)
# Find source files
file(GLOB SOURCES src/*.cpp)
# Include header files
include_directories(include)
# Create shared library
add_library(${PROJECT_NAME} SHARED ${SOURCES})
# Install library
install(TARGETS ${PROJECT_NAME} DESTINATION lib/${PROJECT_NAME})
# Install library headers
file(GLOB HEADERS include/*.h)
install(FILES ${HEADERS} DESTINATION include/${PROJECT_NAME})
My application's new CMakeLists.txt:
cmake_minimum_required(VERSION 2.4.0)
project(myprogram)
# Find source files
file(GLOB SOURCES src/*.cpp)
# Create executable
add_executable(${PROJECT_NAME} ${SOURCES})
# Find and link library
find_library(MYCUSTOMLIB mycustomlib/mycustomlib)
target_link_libraries(${PROJECT_NAME} ${MYCUSTOMLIB})
And this is not working. Now, I'm forced to specify the .so file of the library like this:
find_library(MYCUSTOMLIB mycustomlib/libmycustomlib.so)
How come?
I'll deal with your actual problem first and offer additional comments after that. Technically speaking, you are asking CMake to find a library named mycustomlib/mycustomlib, but what you really want to say is you want find mycustomlib and it can be found in a subdirectory called mycustomlib. A couple of alternative ways to call find_library() to achieve this for your second case would be:
find_library(MYCUSTOMLIB mycustomlib PATH_SUFFIXES mycustomlib)
find_library(MYCUSTOMLIB mycustomlib PATHS /usr/local/lib/mycustomlib)
The latter is making more assumptions than it should about where you have the library installed, so I'd favour the first option. The first option assumes CMake would already find libraries in /usr/local/lib, which it seems it is from your question. You can influence where CMake looks for libraries by modifying CMAKE_PREFIX_PATH and CMAKE_LIBRARY_PATH. I'd expect either of the above options to make your second case work.
Now to other observations. You've requested a very old minimum CMake version in the first line of each of your CMakeLists.txt files. You probably want to consider at the very least making this 2.8 (personally, I'd suggest more like 3.2 or later, but it depends on what your project needs to support).
You have used file globbing to obtain your list of sources and headers. This is not robust and should generally be avoided (see a discussion of this here). You will see plenty of example code use method this for simplicity, but it is not recommended for real world projects (the CMake documentation even says not to use it). Explicitly list out your source and header files individually if you want robust builds.
If you are happy to require CMake 2.8.11 or later (and you should be these days), rather than calling include_directories() which makes everything pick up the header search path you specified, you should prefer to attach the search path requirement to the target that needs it. You do this with target_include_directories(). The equivalent of your code above would be:
target_include_directories(${PROJECT_NAME} PUBLIC include)
This gives much better control of your inter-target dependencies as your project grows in size and complexity. For a more in-depth discussion of this topic, see this article and perhaps also this one (disclosure: I wrote both articles).
Are your library and program totally separate source code repositories? Can they be built in the same project? You can build multiple targets in one CMakeLists.txt file. The project name doesn't have to have any relationship to the names of any of the targets (you often see the PROJECT_NAME variable re-used for the target name in simple examples, which is unfortunate since it suggests a relationship between the two, but for all but simple projects this won't be the case). If they are in the same repository, building them together would be a much simpler build since you wouldn't have to install the library for the executable to find it and link to it.
If they must be built in separate projects, then something like the following for the application's project should get you close:
cmake_minimum_required(VERSION 2.8.11)
project(myprogram)
# List your program's sources here explicitly
add_executable(myprogram src/foo.cpp src/bar.cpp)
# Find and link library
find_library(MYCUSTOMLIB mycustomlib PATH_SUFFIXES mycustomlib)
target_link_libraries(myprogram PUBLIC ${MYCUSTOMLIB})
# Find library's headers and add it as a search path.
# Provide the name of one header file you know should
# be present in mycustomlib's include dir.
find_path(MCL_HEADER_PATH mycustomlib.h PATH_SUFFIXES mycustomlib)
target_include_directories(myprogram PUBLIC ${MCL_HEADER_PATH})
For extra points, you could try to confirm that the header path is in the same area as the library by checking the common path prefix, or you could just derive
the MCL_HEADER_PATH from the MYCUSTOMLIB path by assuming a directory structure. Both approaches have advantages and drawbacks. If you want to explore the latter, the get_filename_component() command will be your friend.
Hopefully that points you in the right direction.

Cmake cannot find library using "link_directories"

I Ubuntu, I am learning about cmake and make, and just trying a simple example. I have two directories: src and build. In src, I have two files: main.cpp, and CMakeLists.txt, which has (only) the following text:
add_executable(test main.cpp)
link_directories(/usr/lib/x86_64-linux-gnu)
target_link_libraries(test protobuf)
In /usr/lib/x86_64-linux-gnu, there is a shared library called libprotobuf.so, which I want to link against. My main.cpp uses functions in this library, by including the releveant header file, #include <google/protobuf/message.h>.
Now, in my build directory, I run cmake ../src, and then make. However, I then get linker errors telling me that there are undefined references to some of the functions in the protobuf library. If I do a search through all the files and subdirectories in build, there is not mention of anything related to protobuf.
However, if I remove the link_directories line in my CMakeLists.txt file, and instead write the full path to the library when specifying the executable, i.e. target_link_libraries(test /usr/lib/x86_64-linux-gnu/libprotobuf.so), it compiles and links fine.
Why is link_directories not allowing cmake to find this library?
Do not use link_directories like this in CMake.
This is a common beginner's mistake, as many other build environments work like this, but in CMake it's just asking for trouble. Even the official documentation specifically advises against it:
Note that this command [link_directories] is rarely necessary. Library locations returned
by find_package() and find_library() are absolute paths. Pass these
absolute library file paths directly to the target_link_libraries()
command. CMake will ensure the linker finds them.
So instead, always pass absolute paths to target_link_libraries and use find_library to resolve the link directory:
find_library(PROTOBUF_LIBRARY protobuf HINTS /usr/lib/x86_64-linux-gnu)
target_link_libraries(test PUBLIC ${PROTOBUF_LIBRARY})
This has the huge benefit that you will probably get a diagnostic at CMake configure time if the expected library cannot be found, instead of a random linker error at compile time. Also, this allows the user to specify a library location via the GUI if the target machine has a non-standard directory layout.
So if it doesn't work right away, be sure to check the result of the find_library call and consult the official documentation to track down why it doesn't find your library as intended.
Make sure that your call to link_directories takes place before your call to the relevant add_executable.
I had mistakenly believed it only needed to be before the call to target_link_libraries, but that's not the case. After moving the call, the library is linked properly.
Make sure that the order will be link_directories, set PROJECT_LINK_LIBS, add_executable and then target_link_libraries.
Below is example to demonstarte it:
cmake_minimum_required(VERSION 2.8.9)
project (Logging)
include_directories(include)
file(GLOB LOGGINGSOURCES "libsrc/*.cpp")
file(GLOB SOURCES "src/*.cpp")
add_library(convertString SHARED ${LOGGINGSOURCES})
install(TARGETS convertString DESTINATION /root/Deepak/)
link_directories( /root/Deepak/ )
set(PROJECT_LINK_LIBS libconvertString.so)
add_executable(hello ${SOURCES})
target_link_libraries(hello ${PROJECT_LINK_LIBS} )
Perhaps it's very old topic but none of proposed solutions worked for me. So I had to make my own dirty hack. I do crosscompiling with buildroot and include toolchainfile.cmake.
#...
set(LIB_PATH ${PROJECT_SOURCE_DIR}/relative/path/to/your/lib)
#...
include_directories(/path/to/library/include)
set(LIB_MYLIB ${LIB_PATH}/libmylib.so)
#...
add_executable(${PROJECT_NAME} ${APP_SOURCES})
target_link_libraries(${PROJECT_NAME}
${LIB_MYLIB}
)
Hope this will help