CMake imported library rpath - cmake

I have an IMPORTED SHARED library for and I'm linking with it via target_link_libraries (the library has IMPORTED_LOCATION set).
But then after installation in ldd output I see smth like:
path/on-dev-machine/to/libxxx.so => not found
instead of just
libxxx.so => path/on-testing-machine/to/libxxx.so
Why is that / how do I fix it? I'm adding lib paths to /etc/ld.so.conf.d
Sample code:
include(GNUInstallDirs)
function(add_and_install_lib lib_name location external_dep)
if(${location} MATCHES ".*\\.so")
add_library(${lib_name} SHARED IMPORTED) # MODULE treated as shared
else()
add_library(${lib_name} STATIC IMPORTED)
endif()
set_property(TARGET ${lib_name} PROPERTY IMPORTED_LOCATION ${location})
add_dependencies(${lib_name} ${external_dep})
endfunction()
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_LIBDIR}") # this doesn't seem to help

If anyone cares, it was the IMPORTED_NO_SONAME property (absence of it set to TRUE) of each imported lib that forced the full path to be taken.
Also CMAKE_SKIP_RPATH and CMAKE_SKIP_INSTALL_RPATH are useful in my opinion to make sure you have clean runtime paths (not straightly related to the question but still).

Related

CMake: How to use LINK_INTERFACE_MULTIPLICITY?

Linking against the Intel MKL static libraries introduces circular dependencies. When I import the libraries,
set(LIBRARIES mkl_intel_lp64 mkl_sequential mkl_core)
foreach(_lib ${LIBRARIES})
add_library(${_lib} UNKNOWN IMPORTED)
set_target_properties(${_lib} PROPERTIES IMPORTED_LOCATION
/opt/intel/mkl/lib/intel64/lib${_lib}.a)
endforeach()
and link to my executable,
target_link_libraries(main PRIVATE ${LIBRARIES})
I get a ton of undefined references to the linear algebra calls. For example,
ztrevc3_gen.f:(.text+0x1af7): undefined reference to `mkl_blas_zdscal'
One way to get around this is to use the appropriate linker flags:
target_link_libraries(main PRIVATE -Wl,--start-group ${LIBRARIES} -Wl,--end-group)
Another option is to do this:
target_link_libraries(main PRIVATE ${LIBRARIES} ${LIBRARIES} ${LIBRARIES})
However, as I was searching for a more elegant solution I came across the LINK_INTERFACE_MULTIPLICITY property. If set this property along with the imported library location,
set(LIBRARIES mkl_intel_lp64 mkl_sequential mkl_core)
foreach(_lib ${LIBRARIES})
add_library(${_lib} UNKNOWN IMPORTED)
set_target_properties(${_lib} PROPERTIES IMPORTED_LOCATION
/opt/intel/mkl/lib/intel64/lib${_lib}.a
LINK_INTERFACE_MULTIPLICITY 3)
endforeach()
I get the same undefined references as before, so appearently this is not working. What is the proper way to use LINK_INTERFACE_MULTIPLICITY and is there a more elegant way to get around circular dependencies?
EDIT
Here is a minimal example that fails, this time with the correct IMPORTED_LINK_INTERFACE_MULTIPLICITY variable.
# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(test Fortran)
add_executable(main main.f90)
set(LIBRARIES mkl_intel_lp64 mkl_sequential mkl_core)
foreach(_lib ${LIBRARIES})
add_library(${_lib} UNKNOWN IMPORTED)
set_target_properties(${_lib} PROPERTIES IMPORTED_LOCATION
/opt/intel/mkl/lib/intel64/lib${_lib}.a
IMPORTED_LINK_INTERFACE_MULTIPLICITY 3)
endforeach()
list(APPEND LIBRARIES dl pthread)
target_link_libraries(main ${LIBRARIES})
#target_link_libraries(main ${LIBRARIES} ${LIBRARIES} ${LIBRARIES})
# main.f90
call zpotrf
end program
If you uncomment the last line then the build succeeds. Unfortunately, the MKL in not free (except in some cases) but hopefully somebody can test this. I should note that it fails with some linear algebra calls and not others like dgemm.
Property LINK_INTERFACE_MULTIPLICITY is for "normal" STATIC library. For IMPORTED library IMPORTED_LINK_INTERFACE_MULTIPLICITY property should be used instead:
set_target_properties(${_lib} PROPERTIES
IMPORTED_LOCATION /opt/intel/mkl/lib/intel64/lib${_lib}.a
IMPORTED_LINK_INTERFACE_MULTIPLICITY 3)

How do I specify the include directory for an imported shared library using CMake?

I am using cmake version 3.9.1.
I have a third party shared library and header file in my source tree. I am trying to add it as a link target.
All the documentation I can find says that this should work:
test.cpp
#include "ftd2xx.h"
int main(int argc, char **argv)
{
FT_HANDLE handle;
FT_STATUS status = FT_Open(1, &handle);
return 0;
}
CMakeLists.txt
cmake_minimum_required (VERSION 3.6)
project(test_proj CXX)
add_subdirectory(ftdi)
add_executable(mytest test.cpp)
target_link_libraries(mytest ftd2xx)
ftdi/CMakeLists.txt
add_library(ftd2xx SHARED IMPORTED)
set_target_properties(ftd2xx PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR})
set_target_properties(ftd2xx PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR})
However compiling test.cpp, which includes "ftd2xx.h", complains that it cannot find the header file and the relevant -I<path> entry is missing from the generated makefiles.
If I specify the library as INTERFACE rather than SHARED IMPORTED then the header file is found correctly, but CMake barfs on setting the IMPORTED_LOCATION property.
If I specify the library as INTERFACE rather than SHARED IMPORTED and then use target_link_libraries to point directly to the library file than this works for Windows but not for Linux.
I'd appreciate any help anyone can offer.
The CMake documentation does actually answer this one, but so concisely, and in the middle of a much larger paragraph, that it is easy to miss:
The target name has scope in the directory in which it is created and below, but the GLOBAL option extends visibility.
I am using the target name in a higher level directory, so I need to declare the library as SHARED IMPORTED GLOBAL rather than just SHARED IMPORTED.
Final code is:
add_library(ftd2xx SHARED IMPORTED GLOBAL)
set_target_properties(ftd2xx PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR})
And then for Windows:
set_target_properties(ftd2xx PROPERTIES IMPORTED_IMPLIB ${CMAKE_CURRENT_SOURCE_DIR}/win32/ftd2xx.lib)
set_target_properties(ftd2xx PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/win32/ftd2xx.dll)
And for Linux:
set_target_properties(ftd2xx PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/i386/libftd2xx.so)

Unifying CMake's FIND_PACKAGE

Do you know a trick to get a unified output terminology when getting include and library paths from CMake's FIND_PACKAGE?
Sometimes it's FOO_INCLUDE. Sometimes it's FOO_INCLUDE_PATH. Etc.
For example, I would like to find a way to ensure that FOO_INCLUDE and FOO_LIB be always defined when FOO_FOUND is set to TRUE after a call to FIND_PACKAGE.
Turning my comment into an answer
To avoid the need to know the include path, library, etc. dependencies and their variable notations modern find_package's implementation do provide IMPORTED targets like Foo::Foo. But this is - as #Tsyvarev has commented - far from unified through all of CMake's find modules.
So generalizing the CMake's Sample Find Module implementation you could unify your find_package() calls with an overwritten find_package() macro version like the following:
cmake_minimum_required(VERSION 3.2)
project(UnifiedFindPackage)
macro(unify_vars _result)
set(${_result} "")
foreach(_i IN ITEMS ${ARGN})
if (${_i})
list(APPEND ${_result} "${${_i}}")
endif()
endforeach()
endmacro()
macro(find_package _name)
_find_package(${_name} ${ARGN})
if (${_name}_FOUND AND NOT TARGET ${_name}::${_name})
add_library(${_name}::${_name} STATIC IMPORTED GLOBAL)
unify_vars(_var ${_name}_LIBRARY ${_name}_LIB)
if (_var)
set_target_properties(${_name}::${_name} PROPERTIES IMPORTED_LOCATION "${_var}")
endif()
if (${_name}_LIBRARY_RELEASE)
set_property(TARGET ${_name}::${_name} APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(${_name}::${_name} PROPERTIES IMPORTED_LOCATION_RELEASE "${${_name}_LIBRARY_RELEASE}")
endif()
if (${_name}_LIBRARY_DEBUG)
set_property(TARGET ${_name}::${_name} APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(${_name}::${_name} PROPERTIES IMPORTED_LOCATION_DEBUG "${${_name}_LIBRARY_DEBUG}")
endif()
if (${_name}_LIBRARIES)
set_property(TARGET ${_name}::${_name} APPEND PROPERTY INTERFACE_LINK_LIBRARIES "${${_name}_LIBRARIES}")
endif()
unify_vars(_var ${_name}_INCLUDE_DIRS ${_name}_INCLUDE_PATH)
if (_var)
set_property(TARGET ${_name}::${_name} APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${_var}")
endif()
unify_vars(_var ${_name}_COMPILE_FLAGS ${_name}_DEFINITIONS)
if (_var)
set_property(TARGET ${_name}::${_name} APPEND PROPERTY INTERFACE_COMPILE_OPTIONS "${_var}")
endif()
endif()
endmacro()
find_package(MPI REQUIRED)
add_executable(${PROJECT_NAME} main.c)
target_link_libraries(${PROJECT_NAME} MPI::MPI)
This should only demonstrate a possible unification and could be extended on a need-by basis.
Edit: Turning this into an community wiki answer. Please feel free to contribute.
The code was tested in this example with MPI find module results.

Transitively require other imported library's interface from imported library

CMake 3.5
I have an existing external library, which I have made an IMPORTED library in my CMakeLists.txt:
find_path(
FOO_INCLUDE_DIR NAMES foo.h
PATHS "${FOO_ROOT}"
NO_DEFAULT_PATH
PATH_SUFFIXES include/foo
)
find_library(
FOO_LIBRARY NAMES foo
PATHS "${FOO_ROOT}"
PATH_SUFFIXES lib
)
mark_as_advanced(FOO_INCLUDE_DIR FOO_LIBRARY)
find_package_handle_standard_args(
FOO REQUIRED_VARS
FOO_INCLUDE_DIR
FOO_LIBRARY
)
add_library(Foo::Foo SHARED IMPORTED)
set_property(TARGET Foo::Foo
PROPERTY INTERFACE_INCLUDE_DIRECTORIES
"${FOO_INCLUDE_DIR}" "${BAR_INCLUDE_DIR}"
"${Boost_INCLUDE_DIRS}" "${BAZ_INCLUDE_DIR}"
)
set_property(TARGET Foo::Foo
PROPERTY IMPORTED_LOCATION
"${FOO_LIBRARY}"
)
set_property(TARGET Foo::Foo
PROPERTY INTERFACE_LINK_LIBRARIES
"${Boost_LIBRARIES}" "${BAR_LIBRARY}" "${BAZ_LIBRARIES}"
)
Programs that link with this particular library need thread support. Adding thread support to something is fairly straightforward:
set(THREADS_PREFER_PTHREAD_FLAG on)
include(FindThreads)
...
target_link_libraries(something PUBLIC Threads::Threads)
I would like anything that links against Foo::Foo to automatically include Threads::Threads. But you can't use target_link_libraries() on an IMPORTED library. So how do I transitively require Threads::Threads from Foo::Foo?
I managed to work around this by doing the following, but it depended on me checking to see what properties FindThreads sets on Threads::Threads. Is there a better way?
set_property(TARGET Foo::Foo
PROPERTY INTERFACE_LINK_LIBRARIES
"${Boost_LIBRARIES}" "${BAR_LIBRARY}" "${BAZ_LIBRARIES}"
$<TARGET_PROPERTY:Threads::Threads,INTERFACE_LINK_LIBRARIES>
)
set_property(TARGET Foo::Foo
PROPERTY INTERFACE_COMPILE_OPTIONS
$<TARGET_PROPERTY:Threads::Threads,INTERFACE_COMPILE_OPTIONS>
)
It turns out the answer was quite simple. I simple needed to add Threads::Threads to the INTERFACE_LINK_LIBRARIES property of Foo::Foo. I have no idea why I had determined that would not work the first time around.

Are there other means of obtaining the properties of a target in cmake?

I have created a C++ static library, and in order to make it searchable easily, I create the following cmake files:
lib.cmake
# The installation prefix configured by this project.
set(_IMPORT_PREFIX "C:/------/install/win32")
# Create imported target boost
add_library(lib STATIC IMPORTED)
set_target_properties(lib PROPERTIES
INTERFACE_COMPILE_DEFINITIONS "lib_define1;lib_define2"
INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/../include"
)
# Load information for each installed configuration.
get_filename_component(_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
file(GLOB CONFIG_FILES "${_DIR}/lib-*.cmake")
foreach(f ${CONFIG_FILES})
include(${f})
endforeach()
lib-debug.cmake
# Import target "boost" for configuration "Debug"
set_property(TARGET lib APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(boost PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "CXX"
IMPORTED_LOCATION_DEBUG "${_IMPORT_PREFIX}/Debug/staticlib/lib.lib"
)
When I want to use this library in an executable, I can simply invoke it by calling find_package command:
find_package(lib REQUIRED)
if(lib_FOUND)
message("lib has been found")
else()
message("lib cannot be found")
endif(boost_FOUND)
It works and if I want to know the head file directory of the library, I will have to call it this way:
get_target_property(lib_dir lib INTERFACE_INCLUDE_DIRECTORIES)
I was just wondering whether there are other ways of obtaining the properties of an target. In this case I expect some variable like lib_INCLUDE_DIRECTORIES will exist.
No, CMake does not automatically define variables for the properties of a target (or of anything else). If you need the value of a property, you have to query it explicitly (using get_property or the specific getters like get_target_property etc.).
In your specific case, INTERFACE_INCLUDE_DIRECTORIES is a property which I would expect you would not need to query at all. The whole point of INTERFACE_* properties is to propagate usage requirements automatically; their propagation is implemented in CMake itself.