How to include external (shared) library with several dlls/dylibs - c++

I need to integrate an externally built (shared) library as a logical
build target into my CMake configuration which consists of several
shared library files (dlls/dylibs).
Usually I'd do the following:
find_path(MyLib_INCLUDE_DIR mylib.h HINTS ${MyLib_PATH}/include)
find_library(MyLib_LIBRARY NAMES MyLib HINTS ${MyLib_PATH}/bin)
find_library(MyLib_LIBRARY_DEBUG NAMES MyLib_d ${MyLib_PATH}/bin)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(MyLib DEFAULT_MSG
MyLib_LIBRARY MyLib_LIBRARY_DEBUG MyLib_INCLUDE_DIR)
if(MyLib_FOUND AND NOT TARGET MyLib::MyLib)
set(MyLib_LIBRARIES ${MyLib_LIBRARY})
set(MyLib_INCLUDE_DIRS ${MyLib_INCLUDE_DIR})
add_library(MyLib::MyLib UNKNOWN IMPORTED)
set_target_properties(MyLib::MyLib PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${MyLib_INCLUDE_DIR}
IMPORTED_LOCATION ${MyLib_LIBRARY}
IMPORTED_LOCATION_DEBUG ${MyLib_LIBRARY_DEBUG})
mark_as_advanced(MyLib_INCLUDE_DIR MyLib_LIBRARY MyLib_LIBRARY_DEBUG)
endif()
My understanding is that add_library(... UNKOWN IMPORTED) will CMake
figure out shared/static libraries by itself (and based on the option
BUILD_SHARED_LIBS probably, but this shouldn't be necessary). However,
CMake doesn't allow to add a list of libraries to the property
IMPORTED_LOCATION. For Instance, in case I want to import MyLib,
MyLibFoo and MyLibBar both as debug and release builds, this is not
possible using the listed approach.
I'm aware of add_library(MyLib::MyLib INTERFACE IMPORTED) and set a
list of libraries to the property INTERFACE_LINK_LIBRARIES, but this
is problematic in the case of creating relocatable packages (as
mentioned in the CMake documentation). Furthermore, there's no such
property as INTERFACE_LINK_LIBRARIES_DEBUG, thus it would only be
possible to refer to either the release or debug builds of my
externally imported library.
So, how am I supposed to import my externally built library as an
relocatable-ready logical CMake target which actually includes several
either debug or release build shared library files?
Disclaimer: I've already asked this on the CMake mailing list, but haven't received any answer so far.

However, CMake doesn't allow to add a list of libraries to the property IMPORTED_LOCATION.
This is logical, because a single library (even IMPORTED) should have a single file.
Just create a library IMPORTED target per actual library:
add_library(MyLib::MyLibFoo UNKNOWN IMPORTED)
set_target_properties(MyLib::MyLibFoo PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${MyLibFoo_INCLUDE_DIR}
IMPORTED_LOCATION ${MyLibFoo_LIBRARY}
IMPORTED_LOCATION_DEBUG ${MyLibFoo_LIBRARY_DEBUG})
add_library(MyLib::MyLibBar UNKNOWN IMPORTED)
set_target_properties(MyLib::MyLibBar PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${MyLibBar_INCLUDE_DIR}
IMPORTED_LOCATION ${MyLibBar_LIBRARY}
IMPORTED_LOCATION_DEBUG ${MyLibBar_LIBRARY_DEBUG})
And create a single INTERFACE IMPORTED library target, which links above targets:
add_library(MyLib::MyLib INTERFACE IMPORTED)
set_property(TARGET MyLib::MyLib PROPERTY
INTERFACE_LINK_LIBRARIES MyLib::MyLibFoo MyLib::MyLibBar)
So, when user links MyLib::MyLib, he actually links all of your libraries.

Related

cmake to link external library with IMPORT_SONAME ro IMPORT_LOCATION

I have a C++ project that links to a external library
The library was provided by a vendor which only contains a directory of .h headers and a shared object file "libabc.so".
in the CMakeLists of my project I have a obj(which I built it to bar.so) that uses the external lib.
when building the final exeuctable, I have tried mutilple ways to do it.
add_library(bar STATIC /some/source/file/bar.cpp)
add_library(abc_lib SHARED IMPORTED)
set_property(TARGET abc_lib PROPERTY IMPORTED_LOCATION /path/to/external/lib/libabc.so)
add_executable(foo /some/file/to/main.cpp)
target_link_libraries(foo bar abc_lib)
this builds ok and links ok, however when I do
ldd foo, the abc_lib does not appear in the form of
abc.so => /path/to/external/lib/libabc.so
instead it shows up in a standalone form
/path/to/external/lib/libabc.so, which indicates the library was not directly linked against according to some post I read recently.
But when I do chrpath -d foo or patchelf --remove-rpath foothe executable still contains the path and wont use the one I provided in LD_LIBRARY_PATH
so I tried the other way around
add_library(bar STATIC /some/source/file/bar.cpp)
add_library(abc_lib SHARED IMPORTED)
set_property(TARGE abd_lib PROPERTY IMPORTED_SONAME abc)
link_libraries(/path/to/external/lib)
add_executable(foo /some/file/to/main.cpp)
target_link_libraries(foo bar abc_lib)
however this time, it complains abc_lib-NOTFOUND
To sum up my question, I would like to have a project built link against a local shared object and at the same time I should be able to clean up the rpath using chrpath or patchelf so that I can copy the executable to a server with similiar environment but possible different path to the external lib, I would like to overwrite the path using LD_LIBRARY_PATH.
The key you are looking for is IMPORTED_NO_SONAME.
You can add an extra property to the shared imported library target:
set_property(TARGET abc_lib PROPERTY IMPORTED_NO_SONAME TRUE)
or better in one cmake command:
set_target_properties(abc_lib PROPERTIES
IMPORTED_LOCATION /path/to/external/lib/libabc.so
IMPORTED_NO_SONAME TRUE
)
CMake will use /path/to/external/lib/libabc.so instead of -L/path/to/external/lib -llibabc.so as link options if libabc.so has no soname in its ELF header.
You can verify this with:
readelf -d /path/to/external/lib/libabc.so
The offcial cmake doc:Imported Libraries explains more details.

In cmake, can we avoid using find_package to import other packages?

Okay, the title is obviously weird. I hope anyone who can define my curiosity cleanly, would edit the title into proper one.
When using CMakeLists.txt, we usually import third-party packages via command find_package() macro, which looks for proper find_XXX.cmake script, and then results outputting variables such as XXX_LIBRARIES, XXX_INCLUDE_DIRECTORIES, etc.
However, recently I found some instruction guide for add_library() macro, that can include actual library binaries as its source(?), like this way:
add_library(my_target STATIC IMPORTED foo.lib)
And as I guess, as long as the name my_target is actually names a target, then we can add another compositions into that target, like any other cmake targets, like below.
target_inlcude_directories(my_target PUBLIC ${PKG_PREFIX}/include)
target_link_directories(my_target PUBLIC ${PKG_PREFIX}/lib)
target_compile_features(my_target PUBLIC std_cxx_17)
If this is possible, then wouldn't this be much cleaner way to describe exported package's compositions, rather than linking and adding those verbose *_LIBS *_DIRS variables manually for each? Like below?
add_executable(foo ${MY_SOURCES})
find_package(xxx)
# BEFORE
target_link_directories(foo PRIVATE ${XXX_LINK_DIRECTORIES}) # note: I don't remember what was it exactly.
target_link_libraries(foo PRIVATE ${XXX_LIBRARIES})
target_include_directories(foo PRIVATE ${XXX_INCLUDE_DIRECTORIES})
# AFTER
target_link_libraries(foo PRIVATE xxx::my_target)
Am I thinking something invalid?
can we avoid using find_package to import other packages?
You can, but it's the tool to be used just for that. find_package() is basically an include() with some special options.
wouldn't this be much cleaner way to describe exported package's compositions, rather than linking and adding those verbose *_LIBS *_DIRS variables manually for each? Like below?
It would be and it is used.
cmake is still ongoing relative big changes in a very short time. I think interface libraries weren't so popular or weren't available some time ago. The only (except some checks, etc) thing find_package does is it includes a file named FindXXX.cmake (or other file named differently). Only. It's completely dependent on what's inside the FindXXX.cmake file what happens.
Newer cmake FindXXX.cmake does exactly what you propose: they create an INTERFACE IMPORTED library, for example on my pc:
#/usr/share/cmake-3.20/Modules
$ grep add_library -wr --include='Find*' .
.... a lot of results ....
./FindBoost.cmake: add_library(Boost::diagnostic_definitions INTERFACE IMPORTED)
./FindBoost.cmake: add_library(Boost::disable_autolinking INTERFACE IMPORTED)
./FindBoost.cmake: add_library(Boost::dynamic_linking INTERFACE IMPORTED)
./FindThreads.cmake: add_library(Threads::Threads INTERFACE IMPORTED)
./FindHDF5.cmake: add_library(HDF5::HDF5 INTERFACE IMPORTED)
./FindHDF5.cmake: add_library("hdf5::${hdf5_target_name}" INTERFACE IMPORTED)
./FindHDF5.cmake: add_library("hdf5::${hdf5_target_name}" UNKNOWN IMPORTED)
./FindHDF5.cmake: add_library("hdf5::${hdf5_target_name}" INTERFACE IMPORTED)
./FindHDF5.cmake: add_library("hdf5::${hdf5_target_name}" UNKNOWN IMPORTED)
./FindGTest.cmake: add_library(GTest::GTest INTERFACE IMPORTED)
./FindGTest.cmake: add_library(GTest::Main INTERFACE IMPORTED)
./FindGTest.cmake: add_library(GTest::gtest ${GTEST_LIBRARY_TYPE} IMPORTED)
./FindGTest.cmake: add_library(GTest::gtest_main ${GTEST_MAIN_LIBRARY_TYPE} IMPORTED)
./FindBZip2.cmake: add_library(BZip2::BZip2 UNKNOWN IMPORTED)
Older or "more standard" (ie. older :) indeed create multiple variables - XXX_LINK_DIRECTORIES XXX_LIBRARIES etc. Note that does variable names are not the same everywhere (XXX_LIB? XXX_INC_DIRS? etc.) and change depending on which FindXXX.cmake you call because some developers decided on different names. A lot of documentation was written in the time the find_package used variables, so it's still visible in documentation, but nowadays interface libraries clearly dominate and what you propose is already used.
Note that the imported library is not set up properly in this case. Configuration scripts (<packageName>Config.cmake) which are used as fallback for find_package or if you use the CONFIG version usually do exactly this: create one or more imported targets for you to use in your project. I strongly recommend using those instead of the *_LIBS/*_DIRS variables, if available.
Here's the correct version of importing the library:
add_library(my_target STATIC IMPORTED)
# INTERFACE "visibility" needed here;
# also quote the path concatenation to avoid issues with spaces in PKG_PREFIX
target_include_directories(my_target INTERFACE "${PKG_PREFIX}/include")
target_link_directories(my_target INTERFACE "${PKG_PREFIX}/lib")
# do you really need the linking library to use the C++ 17 standard?
target_compile_features(my_target INTERFACE std_cxx_17)
# you need specify the absolute path to the lib as IMPORTED_LOCATION target property
# you may need to adjust the location according to the exact location of the lib file
#
# variables CMAKE_CURRENT_SOURCE_DIR (if used from CMakeLists.txt) or
# CMAKE_CURRENT_LIST_DIR (if used from a script) may be helpful
#
# requires seperate treatment for different OS; this is not included here
set_target_properties(my_target PROPERTIES IMPORTED_LOCATION "${PKG_PREFIX}/lib/my_target.a")
Note that in this case the library you need to link is my_target, not xxx::my_target.

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.

Possible to add an imported library to target_link_libraries that takes care of include directories too?

somehow I am struggling with finding out whether it is possible to define an imported library in CMake, specifying target properties (include_directories and library path) and hoping that CMake will append the include directories once I add that project to target_link_libraries in another project.
Let's say I have an imported library in a file called Module-Conf.cmake:
add_library(mymodule STATIC IMPORTED)
set_target_properties(mymodule PROPERTIES IMPORTED_LOCATION "${OUTPUT_DIR}/lib")
set_target_properties(mymodule PROPERTIES INCLUDE_DIRECTORIES "${OUTPUT_DIR}/include")
And in a project I add the dependency:
include(Module-Conf)
target_link_libraries(${PROJECT_NAME} mymodule)
Will CMake append the include_directories property to the include path? Right now I cannot see the path so it seems that I have to do it by myself by using get_target_property?
Question: Can I do some CMake magic to automatically append the include to the include directories of another project?
Thanks a lot.
Martin
The difference between the INCLUDE_DIRECTORIES property and the INTERFACE_INCLUDE_DIRECTORIES property is transitivity.
Set INTERFACE_INCLUDE_DIRECTORIES instead.
http://www.cmake.org/cmake/help/latest/manual/cmake-buildsystem.7.html#transitive-usage-requirements
Starting from CMake 3.11, it's possible to use target_include_directories() with IMPORTED targets.
add_library(mymodule SHARED IMPORTED)
target_include_directories(mymodule INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
Another way is set_property(), which also allows using generator expressions.
set_property(TARGET mymodule PROPERTY INTERFACE_INCLUDE_DIRECTORIES
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)

Is there something wrong with my CMakeLists?

The following is my CMakeLists file. By default, I expect it to define the symbol ALLEGRO_STATICLINK in the agui_allegro5 library, but it does not, however when I check off WANT_SHARED it defines it, which it shouldnt, but does not define AGUI_BACKEND_BUILD as it should. Is my logic flawed or something?
cmake_minimum_required(VERSION 2.6)
project(agui)
OPTION(WANT_SHARED "Build agui and the backend as a shared library" OFF)
OPTION(WANT_ALLEGRO5_BACKEND "Build the Allegro 5 backend" ON)
set(AGUI_SOURCES
src/Agui/ActionEvent.cpp
src/Agui/ActionListener.cpp
src/Agui/BaseTypes.cpp
src/Agui/BlinkingEvent.cpp
src/Agui/BorderLayout.cpp
src/Agui/Color.cpp
src/Agui/Dimension.cpp
src/Agui/EmptyWidget.cpp
src/Agui/EventArgs.cpp
src/Agui/FlowLayout.cpp
src/Agui/FocusListener.cpp
src/Agui/FocusManager.cpp
src/Agui/Font.cpp
src/Agui/FontLoader.cpp
src/Agui/Graphics.cpp
src/Agui/GridLayout.cpp
src/Agui/Gui.cpp
src/Agui/Image.cpp
src/Agui/ImageLoader.cpp
src/Agui/Input.cpp
src/Agui/KeyboardListener.cpp
src/Agui/Layout.cpp
src/Agui/MouseListener.cpp
src/Agui/Point.cpp
src/Agui/Rectangle.cpp
src/Agui/ResizableText.cpp
src/Agui/ResizableBorderLayout.cpp
src/Agui/SelectionListener.cpp
src/Agui/TopContainer.cpp
src/Agui/Widget.cpp
src/Agui/WidgetListener.cpp
src/Agui/Widgets/Button/Button.cpp
src/Agui/Widgets/Button/ButtonListener.cpp
src/Agui/Widgets/CheckBox/CheckBox.cpp
src/Agui/Widgets/CheckBox/CheckBoxListener.cpp
src/Agui/Widgets/DropDown/DropDown.cpp
src/Agui/Widgets/DropDown/DropDownListener.cpp
src/Agui/Widgets/Frame/Frame.cpp
src/Agui/Widgets/Frame/FrameListener.cpp
src/Agui/Widgets/Label/Label.cpp
src/Agui/Widgets/Label/LabelListener.cpp
src/Agui/Widgets/ListBox/ListBox.cpp
src/Agui/Widgets/ListBox/ListBoxListener.cpp
src/Agui/Widgets/RadioButton/RadioButton.cpp
src/Agui/Widgets/RadioButton/RadioButtonListener.cpp
src/Agui/Widgets/RadioButton/RadioButtonGroup.cpp
src/Agui/Widgets/ScrollBar/HScrollBar.cpp
src/Agui/Widgets/ScrollBar/HScrollBarListener.cpp
src/Agui/Widgets/ScrollBar/VScrollBar.cpp
src/Agui/Widgets/ScrollBar/VScrollBarListener.cpp
src/Agui/Widgets/ScrollPane/ScrollPane.cpp
src/Agui/Widgets/Slider/Slider.cpp
src/Agui/Widgets/Slider/SliderListener.cpp
src/Agui/Widgets/Tab/Tab.cpp
src/Agui/Widgets/Tab/TabbedPane.cpp
src/Agui/Widgets/Tab/TabbedPaneListener.cpp
src/Agui/Widgets/TextBox/TextBox.cpp
src/Agui/Widgets/TextBox/TextBoxListener.cpp
src/Agui/Widgets/TextBox/ExtendedTextBox.cpp
src/Agui/Widgets/TextField/TextField.cpp
src/Agui/Widgets/TextField/TextFieldListener.cpp
)
set(ALLEGRO5_BACKEND_SOURCES
src/Agui/Backends/Allegro5/Allegro5Font.cpp
src/Agui/Backends/Allegro5/Allegro5FontLoader.cpp
src/Agui/Backends/Allegro5/Allegro5Graphics.cpp
src/Agui/Backends/Allegro5/Allegro5Image.cpp
src/Agui/Backends/Allegro5/Allegro5ImageLoader.cpp
src/Agui/Backends/Allegro5/Allegro5Input.cpp
)
include_directories (./include)
if(WANT_SHARED)
add_library(agui SHARED ${AGUI_SOURCES})
set_target_properties(agui PROPERTIES DEFINE_SYMBOL "AGUI_BUILD")
if(WANT_ALLEGRO5_BACKEND)
add_library(agui_allegro5 SHARED ${ALLEGRO5_BACKEND_SOURCES})
set_target_properties(agui_allegro5 PROPERTIES DEFINE_SYMBOL "AGUI_BACKEND_BUILD")
target_link_libraries (agui_allegro5 agui)
endif()
else()
add_library(agui STATIC ${AGUI_SOURCES})
if(WANT_ALLEGRO5_BACKEND)
add_library(agui_allegro5 STATIC ${ALLEGRO5_BACKEND_SOURCES})
set_target_properties(agui_allegro5 PROPERTIES DEFINE_SYMBOL "ALLEGRO_STATICLINK")
endif()
endif()
Thanks
According to CMake documentation "DEFINE_SYMBOL sets the name of the preprocessor symbol defined when compiling sources in a shared library" while you are trying to use it on a static library, hence no effect. You may also find this discussion useful.
Cmake documentation says that property DEFINE_SYMBOL attended only for shared libraries, try to use COMPILE_DEFINITIONS property.