CMake: How to use LINK_INTERFACE_MULTIPLICITY? - cmake

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)

Related

how to used cmake link the pthread and CURSES [duplicate]

I'm running RHEL 5.1 and use gcc.
How I tell cmake to add -pthread to compilation and linking?
#Manuel was part way there. You can add the compiler option as well, like this:
If you have CMake 3.1.0+, this becomes even easier:
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
target_link_libraries(my_app PRIVATE Threads::Threads)
If you are using CMake 2.8.12+, you can simplify this to:
find_package(Threads REQUIRED)
if(THREADS_HAVE_PTHREAD_ARG)
target_compile_options(my_app PUBLIC "-pthread")
endif()
if(CMAKE_THREAD_LIBS_INIT)
target_link_libraries(my_app "${CMAKE_THREAD_LIBS_INIT}")
endif()
Older CMake versions may require:
find_package(Threads REQUIRED)
if(THREADS_HAVE_PTHREAD_ARG)
set_property(TARGET my_app PROPERTY COMPILE_OPTIONS "-pthread")
set_property(TARGET my_app PROPERTY INTERFACE_COMPILE_OPTIONS "-pthread")
endif()
if(CMAKE_THREAD_LIBS_INIT)
target_link_libraries(my_app "${CMAKE_THREAD_LIBS_INIT}")
endif()
If you want to use one of the first two methods with CMake 3.1+, you will need set(THREADS_PREFER_PTHREAD_FLAG ON) there too.
The following should be clean (using find_package) and work (the find module is called FindThreads):
cmake_minimum_required (VERSION 2.6)
find_package (Threads)
add_executable (myapp main.cpp ...)
target_link_libraries (myapp ${CMAKE_THREAD_LIBS_INIT})
Here is the right anwser:
ADD_EXECUTABLE(your_executable ${source_files})
TARGET_LINK_LIBRARIES( your_executable
pthread
)
equivalent to
-lpthread
target_compile_options solution above is wrong, it won't link the library.
Use:
SET(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -pthread")
OR
target_link_libraries(XXX PUBLIC pthread)
OR
set_target_properties(XXX PROPERTIES LINK_LIBRARIES -pthread)

CMake: target_link_libraries include as SYSTEM to suppress compiler warnings

To suppress compiler warnings that originate from libraries I use in my application, I manually include their directories with target_include_directories(myapp SYSTEM ...) as system libraries before adding them with target_link_libraries like so:
add_executable(myapp myapp.cpp)
target_include_directories(myapp SYSTEM
PRIVATE "extern/lib/include"
)
target_link_libraries(myapp lib::lib)
However, that kind of feels hacky and will also break if the developers of lib decide to change the include path. This wouldn't be a problem if using only target_link_library but then, of course, they are included via -I and again I would get compiler warnings coming from this include.
Is there any more elegant and fail-safe way of doing this? It would be great if target_link_libraries had a SYSTEM option to tell cmake to include it as a system library.
I defined a function to handle this for me:
function(target_link_libraries_system target)
set(libs ${ARGN})
foreach(lib ${libs})
get_target_property(lib_include_dirs ${lib} INTERFACE_INCLUDE_DIRECTORIES)
target_include_directories(${target} SYSTEM PRIVATE ${lib_include_dirs})
target_link_libraries(${target} ${lib})
endforeach(lib)
endfunction(target_link_libraries_system)
I can now call target_link_libraries_system(myapp lib::lib) and the include directories are read from the target's properties.
This can be extended to optionally specify the PUBLIC|PRIVATE|INTERFACE scope:
function(target_link_libraries_system target)
set(options PRIVATE PUBLIC INTERFACE)
cmake_parse_arguments(TLLS "${options}" "" "" ${ARGN})
foreach(op ${options})
if(TLLS_${op})
set(scope ${op})
endif()
endforeach(op)
set(libs ${TLLS_UNPARSED_ARGUMENTS})
foreach(lib ${libs})
get_target_property(lib_include_dirs ${lib} INTERFACE_INCLUDE_DIRECTORIES)
if(lib_include_dirs)
if(scope)
target_include_directories(${target} SYSTEM ${scope} ${lib_include_dirs})
else()
target_include_directories(${target} SYSTEM PRIVATE ${lib_include_dirs})
endif()
else()
message("Warning: ${lib} doesn't set INTERFACE_INCLUDE_DIRECTORIES. No include_directories set.")
endif()
if(scope)
target_link_libraries(${target} ${scope} ${lib})
else()
target_link_libraries(${target} ${lib})
endif()
endforeach()
endfunction(target_link_libraries_system)
This extended version will also print a warning if a library didn't set its INTERFACE_INCLUDE_DIRECTORIES property.
I modified Sebastian's solution to include the scope.
function(target_link_libraries_system target scope)
set(libs ${ARGN})
foreach(lib ${libs})
get_target_property(lib_include_dirs ${lib} INTERFACE_INCLUDE_DIRECTORIES)
target_include_directories(${target} SYSTEM ${scope} ${lib_include_dirs})
target_link_libraries(${target} ${scope} ${lib})
endforeach(lib)
endfunction(target_link_libraries_system)
This was asked in the CMake discourse and #ben.boeckel (CMake developer) answered:
IMPORTED targets should already have their include directories
treated as SYSTEM though. There is the NO_SYSTEM_FROM_IMPORTED target
property to disable it.
To start with, you haven't specified whether your target that you're linking to is IMPORTED or not. I'm assuming that it is IMPORTED because include directories for IMPORTED targets are SYSTEM by default. Note: This behaviour, can be disabled. Pre-CMake v3.25, one would use one of NO_SYSTEM_FROM_IMPORTED or IMPORTED_NO_SYSTEM. For CMake 3.25 and later, one would modify the SYSTEM property of the target.
That typically just leaves the cases of targets added via add_subdirectory, which includes those added by FetchContent in its non-find_package mode. In that case, you can see the FetchContent Q&A here, and the add_subdirectory Q&A here. In summary, for pre-CMake v3.25, use a workaround in which you copy/move the INTERFACE_INCLUDE_DIRECTORIES target property to the INTERFACE_SYSTEM_INCLUDE_DIRECTORIES target property, and for CMake v3.25 or later, you can modify the SYSTEM target property, or the SYSTEM directory property, or use the SYSTEM argument of add_subdirectory/FetchContent_Declare.
Possible gotcha: If you are like me, and start every project by enabling all warnings in the toplevel directory…
# Warning level
add_compile_options(
-Wall -Wextra -Wpedantic
-Werror=switch
-Werror=return-type
-Werror=uninitialized
-Werror=format-security
-Werror=reorder
-Werror=delete-non-virtual-dtor
$<$<CONFIG:Debug>:-Werror>
)
… then, this obviously gets inherited by all subprojects (like git submodules, or what have you).
If this is the case, the solution is simple – be specific: Do it in a subdirectory, and/or use target_compile_options, for good measure:
target_compile_options(myTarget PRIVATE
...
)

CMake imported library rpath

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).

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.