How can I remove the duplicates libraries when using target_link_libraries in the CMake? - cmake

I am trying to make several libraries(A,B,C libraries) to reduce the build time.
Through that, if I change files in A, I can build only A, neither B nor C.
I believe this is because I split the libraries.
By the way, when I add libraries, the "Eigen" libraries are duplicated in the A,B,C libraries.
I am worrying that the duplication would slow down my process and make burden.
file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/A.cpp
)
file(GLOB_RECURSE SRC_FILES_FRICTION CONFIGURE_DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/B.cpp
)
file(GLOB_RECURSE SRC_FILES_DATA CONFIGURE_DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/C.cpp
)
add_library(A STATIC ${SRC_FILES_A} )
add_library(B STATIC ${SRC_FILES_B} )
add_library(C STATIC ${SRC_FILES_C} )
find_package(Eigen3 3.3 REQUIRED)
find_package(pinocchio REQUIRED)
find_package(qpSWIFT REQUIRED)
find_package(yaml-cpp REQUIRED)
include_directories(${CMAKE_SOURCE_DIR}/include)
include_directories(${EIGEN3_INCLUDE_DIRS} )
include_directories(${pinocchio_INCLUDE_DIRS} )
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../thirdParty/)
target_include_directories(A PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(A PUBLIC Eigen3::Eigen qpSWIFT ${YAML_CPP_LIBRARIES})
target_compile_options(A PUBLIC -Wall )
target_include_directories(B PUBLIC ${YAML_INCLUDE_DIRS})
target_link_libraries(B PUBLIC ${pinocchio_LIBRARIES} Eigen3::Eigen ${YAML_CPP_LIBRARIES})
target_compile_options(B PUBLIC -Wall )
target_include_directories(C PUBLIC ${YAML_INCLUDE_DIRS})
target_link_libraries(C PUBLIC ${pinocchio_LIBRARIES} Eigen3::Eigen ${YAML_CPP_LIBRARIES})
target_compile_options(C PUBLIC -Wall )
Will the duplication in the target_link_libraries make problems?
Is there any way to solve the duplication?

As already mentioned in the comments, duplication in target_link_libraries is not a problem at all, however if you still want to remove the duplicates from there you can do something like this.
set(TARGET_LIBS ${pinocchio_LIBRARIES})
list(APPEND TARGET_LIBS Eigen3::Eigen ${YAML_CPP_LIBRARIES})
list(REMOVE_DUPLICATES TARGET_LIBS)
The takeaway is list(REMOVE_DUPLICATES <list>)

Related

CMake INTERFACE_LINK_LIBRARIES entries get decorated

I recently did some CMake cleanup on a library I have, the main change being that instead of exporting my targets to a config file directly. At the same time I wanted to move adding the target to the toplevel CMakeLists.txt and then just add sources to that single target as I recurse down into subdirectories.
So I have the toplevel CMakelists.txt that contains:
add_library(Foo "")
add_library(Bar::Foo ALIAS Foo)
add_subdirectory(src)
in src the CMakeLists.txt has
add_subdirectory(Buz)
add_subdirectory(Fuz)
in Fuz the CMakeLists.txt has
target_sources(Foo
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/FooSource.hpp>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/FooSource.hpp>
PRIVATE
FooSource.cpp
)
target_include_directories(Foo
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}>
)
target_link_libraries(Foo
PUBLIC
Bar::Buz # target from first sub dir add...eventually to be merged in like manner
PRIVATE
avcodec
avformat
avutil
swscale
)
Before the change the resulting FooConfig.cmake that would be installed would contain these lines:
set_target_properties(Foo::Foo PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include/Foo/klv_parser;${_IMPORT_PREFIX}/include/Foo/frame_source"
INTERFACE_LINK_LIBRARIES "\$<LINK_ONLY:avcodec>;\$<LINK_ONLY:avformat>;\$<LINK_ONLY:avutil>;\$<LINK_ONLY:swscale>;\$<LINK_ONLY:Foo::foo_common>"
INTERFACE_SOURCES "${_IMPORT_PREFIX}/include/Foo/klv_parser/KlvParser.hpp;${_IMPORT_PREFIX}/include/Foo/frame_source/FrameSource.hpp"
where the LINK_ONLY libraries are ones I listed as PRIVATE in a target_link_libraries. However since the change, these private libraries have been decorated with a seemingly random hex number:
set_target_properties(Foo::Foo PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include/Foo/klv_parser;${_IMPORT_PREFIX}/include/Foo/frame_source"
INTERFACE_LINK_LIBRARIES "\$<LINK_ONLY:avcodec::#<0x2aaeb30>>;\$<LINK_ONLY:avformat::#<0x2aaeb30>>;\$<LINK_ONLY:avutil::#<0x2aaeb30>>;\$<LINK_ONLY:swscale::#<0x2aaeb30>>;\$<LINK_ONLY:Foo::foo_common::#<0x2aaeb30>>"
INTERFACE_SOURCES "${_IMPORT_PREFIX}/include/Foo/klv_parser/KlvParser.hpp;${_IMPORT_PREFIX}/include/Foo/frame_source/FrameSource.hpp"
)
Libraries in the PUBLIC section of target_link_libraries are not decorated--only ones in the PRIVATE section are.
I've been able to narrow it down to moving the add_library command to a higher level directory. i.e. if my CMakeLists.txt in the Fuz directory has:
add_library(Foo "")
add_library(Bar::Foo ALIAS Foo)
target_sources(Foo
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/FooSource.hpp>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/FooSource.hpp>
PRIVATE
FooSource.cpp
)
target_include_directories(Foo
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}>
)
target_link_libraries(Foo
PUBLIC
Bar::Buz # target from first sub dir add...eventually to be merged in like manner
PRIVATE
avcodec
avformat
avutil
swscale
)
(and no add_library commands are in the top level CMakeLists.txt) they all works as it should. But if the only change I make is to move the two lines
add_library(Foo "")
add_library(Bar::Foo ALIAS Foo)
to either the toplevel CMakeLists.txt or even the one in src then this weird decoration occurs. To me this really seems like a bug in CMake, but maybe not?
I'm using CMake 3.15.5 on Ubuntu 18.04. Full minimal example is here: https://github.com/jasonbeach/cmake_bug.git
This appears to be a bug within CMake. I was originally using CMake 3.15.5 and in the course of troubleshooting, updated to CMake 3.19.6 and it started working as it should. It appears to have been fixed somewhere in there.

Are targets appended?

I'm not entirely clear if calling, for instance, target_link_libraries() more than once for the same target will append the target's dependencies. For example, can I add options for the target main as follows?
option(ASSERT "Evaluate assertions" ON)
find_package(MyLib REQUIRED)
add_executable(main main.cpp)
target_link_libraries(main PRIVATE MyLib::MyLib)
if (ASSERT)
target_link_libraries(main PRIVATE MyLib::enable_assert)
endif()
Yes, from the target_link_libraries() documentation:
Repeated calls for the same <target> append items in the order called.
So in your example, this:
target_link_libraries(main PRIVATE MyLib::MyLib)
...
target_link_libraries(main PRIVATE MyLib::enable_assert)
is equivalent to this:
target_link_libraries(main PRIVATE MyLib::MyLib MyLib::enable_assert)
If you are using a simple boolean check to determine whether or not to include MyLib::enable_assert as an argument, you can use a conditional generator expression instead to simplify this logic:
target_link_libraries(main PRIVATE
MyLib::MyLib
$<IF:$<BOOL:${ASSERT}>, MyLib::enable_assert, >
)

Approximating a Fortran_COMPILER_ID CMake generator expression

I am (attempting to) leverage CMake's generator expressions to support generation-time compiler flag configuration based on the compiler vendor for a Fortran project.
Unfortunately, while the relevant documentation describes how maybe accomplished in C and C++ projects (see the C_COMPILER_ID and CXX_COMPILER_ID logical expressions, respectively), there is no mention of a Fortran analogue.
I have confirmed there is no Fortran_COMPILER_ID generator expression, which emits the following error when used:
CMake Error at CMakeLists.txt:<line number> (target_compile_options):
Error evaluating generator expression:
$<Fortran_COMPILER_ID:GNU>
Expression did not evaluate to a known generator expression
Is it possible to approximate this behavior for a Fortran project?
Edit:
minimum example of desired behavior
cmake_minimum_required( VERSION 3.2 )
project( myProject LANGUAGES Fortran )
set( fortran_module_directory "${CMAKE_BINARY_DIR}/modules" CACHE PATH "directory for fortran modules" )
file( MAKE_DIRECTORY ${fortran_module_directory} )
set( CMAKE_Fortran_MODULE_DIRECTORY ${fortran_module_directory} )
set( myProject_GNU_DEBUG_flags "flag1" "flag2" )
set( myProject_GNU_RELEASE_flags "flag3" "flag4" )
set( myProject_Intel_DEBUG_flags "flag5" "flag6")
set( myProject_Intel_RELEASE_flags "flag7" "flag8" )
# other compilers
add_library( myProject STATIC
${CMAKE_CURRENT_SOURCE_DIR}/src/mySource.f90
${CMAKE_CURRENT_SOURCE_DIR}/src/myOtherSource.f90 )
target_include_directories( myProject PUBLIC ${fortran_module_directory} )
target_compile_options( njoy PRIVATE
$<$<Fortran_COMPILER_ID:GNU>:
$<$<CONFIG:Debug>:${myProject_GNU_DEBUG_flags}>>
$<$<CONFIG:Release>:${myProject_GNU_RELEASE_flags}>>>
$<$<Fortran_COMPILER_ID:Intel>:
$<$<CONFIG:Debug>:${myProject_Intel_DEBUG_flags}>>
$<$<CONFIG:Release>:${myProject_Intel_RELEASE_flags}>>>
# other compilers
)
I found in a HDF5 CMakeLists.txt inspiration for a way that might work.
For example:
set (Fortran_COMPILER_ID CMAKE_Fortran_COMPILER_ID)
target_link_libraries(target PUBLIC
$<$<STREQUAL:"${Fortran_COMPILER_ID}","GNU">:"-lgfortran">
$<$<STREQUAL:"${Fortran_COMPILER_ID}","Intel">:"-lifcore">
)

External library dependency hierarchies

I am building a project that depends on a third party library that is composed of a fairly large numbers of libraries. The DAG of dependencies between these libraries is clearly defined so for example it might be the following where letters indicate libraries and arrows are dependencies
x -> a, b
y -> a, c
z -> x, b // note I don't need to specify a here as it is implied by x
So what I really want is to be able to express this DAG in CMake and be able to expand dependencies without repetitions. So
Expand( y, z ) -> y, z, x, a, b, c // Note only one a
I will spare you my attempts to do the expand function as I couldn't come up with anything elegant, I am not that good at CMake.
An extra feature would be to detect redundancy in the top level dependencies so
Expand( z, x ) // x not needed as z depends on it
Note CMake already does something like this for internal project library dependencies and uses it in thing like target_link_libraries but these are external so CMake does not know the external dependency tree.
As I understand from CMake documentation, target_link_libraries doesn't work with imported library as first argument. But you can easily develop function, which reads properties from dependent (imported) libraries, and fill corresponded properties for dependee (imported) library. So, resulted imported library can be used with target_link_libraries at the right side.
# _combine_targets_property(VAR PROP target1 target2 ...)
# Helper function: Collects #PROP properties (as lists) from #target1, #target2 ..,
# combines these lists into one and store into variable #VAR.
function(_combine_targets_property VAR PROP)
set(values) # Resulted list
foreach(t ${ARGN})
get_property(v TARGET ${t} PROPERTY ${PROP})
list(APPEND values ${v})
endforeach()
set(${VAR} ${values} PARENT_SCOPE)
endfunction()
# imported_link_libraries(t_dest target1 target2 ...)
# Make imported library target #t_dest effectively linked with #target1, #target2 ...
function(imported_link_libraries t_dest)
# IMPORTED_LOCATION's and INTERFACE_LINK_LIBRARIES's from dependencies
# should be appended to target's INTERFACE_LINK_LIBRARIES.
get_property(v1 TARGET ${t_dest} PROPERTY INTERFACE_LINK_LIBRARIES)
_combine_targets_property(v2 IMPORTED_LOCATION ${ARGN})
_combine_targets_property(v3 INTERFACE_LINK_LIBRARIES ${ARGN})
set(v ${v1} ${v2} ${v3})
list(REMOVE_DUPLICATES v)
set_property(TARGET ${t_dest} PROPERTY INTERFACE_LINK_LIBRARIES ${v})
# INTERFACE_INCLUDE_DIRECTORIES' from dependencies
# should be appended to corresponded target's property.
get_property(v1 TARGET ${t_dest} PROPERTY INTERFACE_INCLUDE_DIRECTORIES)
_combine_targets_property(v2 INTERFACE_INCLUDE_DIRECTORIES ${ARGN})
set(v ${v1} ${v2})
list(REMOVE_DUPLICATES v)
set_property(TARGET ${t_dest} PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${v})
# Process other interface properties if needed. E.g., INTERFACE_COMPILE_DEFINITIONS.
...
endfunction()
Usage example:
add_library(a IMPORTED)
set_target_properties(a IMPORTED_LOCATION <a_lib> INCLUDE_DIRECTORIES <a_include_dirs>)
add_library(b IMPORTED)
set_target_properties(b IMPORTED_LOCATION <b_lib> INCLUDE_DIRECTORIES <b_include_dirs>)
add_library(x IMPORTED)
set_target_properties(x IMPORTED_LOCATION <x_lib> INCLUDE_DIRECTORIES <x_include_dirs>)
imported_link_libraries(x a b)
add_executable(my_exec ...)
target_link_libraries(my_exec x)
Note, that imported target names are not tracked in the dependencies, this is similar to target_link_libraries() behaviour. Also redudancy is not detected (but duplicates are removed automatically). Both these features are not natural for CMake, but you can implement them if you want.

Query headers added to cmake project

I would like to get all headers added to cmake project. The use case is that I'd get this list of headers and call some custom validation on them. I would really love this to be a query mechanism to mitigate errors in oversight.
I am not interested in globbing the file system as headers may exist that are not appropriate for every platform. It's also bad.
This is what I would like the usage to look like.
add_library(example_lib
foo.h
foo.cpp
bar.h
bar.cpp
)
add_executable(example main_example.cpp)
target_link_libraries(example example_lib)
# this is the feature I am interested in
get_target_headers(example_header example)
# alternatively
get_target_headers(example_header example example_lib)
do_custom_thing("${example_header}")
A more manual way of doing this would be something like the below. I'd just reuse the example_header variable to do the custom validation.
set(example_header
foo.h
bar.h
)
set(example_source
foo.cpp
bar.cpp
)
add_library(example_lib
${example_header}
${example_source}
)
add_executable(example main_example.cpp)
target_link_libraries(example example_lib)
do_custom_thing("${example_header}")
This is what I'm doing now and it works, I am just wondering if there is a better way.
If all your headers have a ".h" suffix, you could use something like:
function(get_target_headers Headers MainTarget)
# Gather list of MainTarget's dependencies
get_target_property(Dependencies ${MainTarget} LINK_LIBRARIES)
set(AllTargets ${MainTarget})
foreach(Dependency ${Dependencies})
# If this is a CMake target, add it to the list
if(TARGET ${Dependency})
list(APPEND AllTargets ${Dependency})
endif()
endforeach()
# Gather each target's list of source files ending in ".h"
foreach(Target ${AllTargets})
get_target_property(Sources ${Target} SOURCES)
foreach(Source ${Sources})
string(REGEX MATCH "^.*\\.h$" Header ${Source})
if(Header)
list(APPEND AllHeaders ${Header})
endif()
endforeach()
endforeach()
# Since functions have their own scope, set the list in the parent scope
set(${Headers} ${AllHeaders} PARENT_SCOPE)
endfunction()
and invoke it using your first choice:
get_target_headers(example_header example)