External library dependency hierarchies - cmake

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.

Related

How to include target include directories transitive through multiple linked libraries

we are working on an embedded project in C/C++ and currently some special needs appeared. Background is there are two compiled libraries which define the same symbols. The compiler allows to create relocatable output modules (with partial linking) and to hide symbols for other compilation units when linking. This also means the output module does not need to have all the symbols defined, this will be done in the final linking. Compiler used is TI LTS1.3.0. I will link directly to the relocatable-section of the manual: https://software-dl.ti.com/codegen/docs/tiarmclang/rel1_3_0_LTS/compiler_manual/linker_description/04_linker_options/linker-output-options.html#stdz0756429
The other part of the project is hardly built on CMake with static libraries which are linked against each other via target_link_libraries.
To get this working I created an "add_executable"-target for each of those both output modules with the same symbols. To those I pass the static-libraries by CMake and get the linked with target_link_libraries.
But now I have a problem. All contents of the static libraries are compiled in each of those output modules. This is unwanted behaviour since as said the final linking does the job of linking the missing stuff - so the static-libraries - to it. This should be done with another add_executable command via CMake as well.
using the target include directories property is not suitable since it only adds the include directories of the given target itself but not of the target the target will include and link against.
So e.g. if you have (pseudo code):
#library A
function( create_libA )
add_library( libA src/A.c )
target_include_directories( libA PUBLIC /inc ) #contains A.h
endfunction()
#library B. different location
function( create_libB LIBA )
add_library( libB src/B.c )
target_link_libraries( libB PUBLIC ${LIBA} )
target_include_directories( libB PUBLIC /inc ) #contains B.h
endfunction()
#target output module with partial linking. Only should link and compile LIBTOBELINKEDIN, not libB. different location.
function( build_part_module LIBB LIBTOBELINKEDIN )
add_executable( outputModuleA src/func.c ) #func.c does include A.h
#following would cause libA and libB also to be compiled and linked in the output due to transitive stuff as I understood, which is unwanted.
target_link_libraries( outputModuleA PUBLIC ${LIBB} ${LIBTOBELINKEDIN} )
#trying this
get_target_property(libBInc ${LIBB} INTERFACE_INCLUDE_DIRECTORIES)
#will only include B.h but not A.h. compilation will fail.
target_include_directories(outputModuleA /inc ${libBInc})
I did not find any solution in Cmake itself to solve this problem. It's confusing me since all the include-directories must be known when the libraries are passed transitive, which is stated in the documentation. But I understand that getting the target include directories of just the passed lib does not include the other ones.
Since target_link_libraries does also not work this way I can only think of a maybe recursive solution? But for that my knowledge is just non-existent.
target_link_libraries with something like HEADERS_ONLY would be helpfull for this job.
Also one can say: if the output module contains all the definitions it won't be a problem, since the linker then knows them and will do its magic.
But this is also unwanted, since we use the generated static-libraries to place them into sections in different regions of the RAM directly. This would then mean to create another linker-script for partial linking which defines sections which then can be again moved. But the more we go this direction, the less we need CMake for it.
Instead of get_target_property use $<TARGET_PROPERTY> generator expression: the property's value, extracted by that expression, already includes transitive propagation:
target_include_directories(outputModuleA PRIVATE
$<TARGET_PROPERTY:libB,INTERFACE_INCLUDE_DIRECTORIES>
)
Note, that generator expressions has limited usage: not all functions expects them. Documentation for target_include_directories clearly states that the command supports generator expressions.

target type depending generic rule

My project contains a lot of libraries and executables. On Windows a .rc file is used to embed meta informations (version, original file name, ...). One of these meta informations is FILETYPE (VFT_DLL, VFT_APP). So I add a -D TYPE definition to the resource compiler (which I evaluate inside the .rc) to distinguish between dll/exe.
Example:
add_library (myLib SHARED "src/myLib.cpp"
"src/myLib.rc")
target_include_directories (myLib PUBLIC "include")
set_source_files_properties("src/myLib.rc" APPEND_STRING PROPERTY COMPILE_FLAGS "-DDLL")
I won't repeat myself in dozens of CMakeLists, is there a possibility to create a generic rule (e.g. in a .cmake include) that .rc files will be compiled with a target depending flag (-DDLL in case of SHARED library)?
I would write a small wrapper function:
# Or maybe not object, depending on what you want
add_library(rc_for_shared OBJECT src/myLib.rc)
target_compile_definitions(rc_for_shared PRIVATE -DDLL)
add_library(rc_for_executable OBJECT src/myLib.rc)
# This is a draft from my memory
function(target_add_the_rc_file target)
get_target_properties(target PROPERTIES TYPE type)
if (type STREQUAL "SHARED")
target_link_libraries(${target} rc_for_shared)
# or maybe target_source(${target} $<TARGET_OBJECTS:rc_for_shared>)
# depending on what you want
elseif(type STREQUAL "EXECUTABLE")
target_link_libraries(${target} rc_for_executable)
endif()
endfunction()
then you would:
add_library(myLib SHARED src/myLib.cpp)
target_include_directories(myLib PUBLIC)
target_add_the_rc_file(myLib)

target_compile_definitions for multiple CMake targets?

I've been told it's bad practice to do things like seting CFLAGS directly in CMake, and that, instead, I should use the target_compile_definitions() command.
Ok, but - what if I want to use similar/identical definitions for multiple (independent) targets? I don't want to repeat myself over and over again.
I see three possible ways:
The preferred one using target_compile_definitions(... INTERFACE/PUBLIC ...) which would self-propagate the compiler definitions to targets depending on it via target_link_libraries() command.
Using the set_property(TARGET target1 target2 ... APPEND PROPERTY COMPILE_DEFINITIONS ...) to set the same definitions to multiple targets.
You may still use the "old commands" of add_definitions() and remove_definitions() to modify COMPILE_DEFINITIONS directory property (which would pre-set all COMPILE_DEFINITIONS target properties in this directories scope).
References
Is Cmake set variable recursive?
CMake: Is there a difference between set_property(TARGET ...) and set_target_properties?
tl;dr: You can iterate the targets in a loop.
If you have a bunch of targets with some common/similar features, you may want to simply manipulate them all in a loop! Remember - CMake is not like GNU Make, it's a full-fledged scripting language (well, sort of). So you could write:
set(my_targets
foo
bar
baz)
foreach(TARGET ${my_targets})
add_executable(${TARGET} "${TARGET}.cu")
target_compile_options(${TARGET} PRIVATE "--some_option=some_value")
target_link_libraries(${TARGET} PRIVATE some_lib)
# and so on
set_target_properties(
${TARGET}
PROPERTIES
C_STANDARD 99
C_STANDARD_REQUIRED YES
C_EXTENSIONS NO )
endforeach(TARGET)
And you could also initialize an empty list of targets, then add to it here-and-there, and only finally apply your common options and settings to all of them, centrally.
Note: In this example I added PRIVATE compile options, but if you need some of them to propagate to targets using your targets, you can make them PUBLIC).
another neat solution is to define an interface library target (a fake target that does not produce any binaries) with all required properties and compiler definitions, then link the other existing targets against it
example:
add_library(myfakelib INTERFACE)
target_compile_definitions(myfakelib INTERFACE MY_NEEDED_DEFINITION)
add_executable(actualtarget1 main1.cpp)
add_executable(actualtarget2 main2.cpp)
set_property(
TARGET actualtarget1 actualtarget2
APPEND PROPERTY LINK_LIBRARIES myfakelib
)
refs:
https://cmake.org/cmake/help/latest/command/add_library.html#interface-libraries

CMake: Is there a way to get a list of imported targets that belong to a package

Sometimes I wish I could get a list of the imported targets that belong to a package. Is there a variable that holds them?
This would allow me to write something like this
find_package(Qt5 CONFIG REQUIRED)
message("Imported Qt5 targets: ${Qt5_IMPORTED_TARGETS}") # speculative code
With my current knowledge I have to rely on the documentation of the package to give me the names of all imported targets. Reading them from a variable or property would be easier.
CMake 3.21 introduced the directory property IMPORTED_TARGETS that can be used to get a list of all imported targets. This can be used to derive a list of targets that were imported by a single find_package() call when it is queried before and after the call to find_package(). The code could look something like this:
...
get_property(importTargets DIRECTORY "${CMAKE_SOURCE_DIR}" PROPERTY IMPORTED_TARGETS)
find_package(Qt5 COMPONENTS Core Widgets REQUIRED)
get_property(importTargetsAfter DIRECTORY "${CMAKE_SOURCE_DIR}" PROPERTY IMPORTED_TARGETS)
list(REMOVE_ITEM importTargetsAfter ${importTargets})
message("${importTargetsAfter}")
...
Usually it is good enough to only print the list of all imported targets and guess from the names which of them were imported by the package of interest.
Not precisely what you asked for, but for Qt5, one can do:
cmake_minimum_required(VERSION 3.14)
project(so)
find_package(Qt5 COMPONENTS Core Widgets REQUIRED)
get_cmake_property(_variableNames VARIABLES)
foreach(_variableName ${_variableNames})
if(_variableName MATCHES "^Qt5.*LIBRARIES")
message(STATUS "${_variableName}")
message(STATUS "\t${${_variableName}}")
endif()
endforeach()
Example output:
-- Qt5Core_LIBRARIES
-- Qt5::Core
-- Qt5Gui_EGL_LIBRARIES
-- Qt5::Gui_EGL
-- Qt5Gui_LIBRARIES
-- Qt5::Gui
-- Qt5Gui_OPENGL_LIBRARIES
-- Qt5::Gui_GL
-- Qt5Widgets_LIBRARIES
-- Qt5::Widgets
-- Configuring done
-- Generating done
-- Build files have been written to: /path/to/build
Caveat with approach: One needs to know the component names.

External library specific COMPILE_DEFINITIONS in cmake

I've written a cmake module for finding QCustomPlot. However, to use the shared library, one needs to #define QCUSTOMPLOT_USE_LIBRARY. I'd like to provide this define through cmake, automatically adding the definition to any project that uses QCustomPlot.
Here is a snippet of my cmake module and my current attempted solution:
SET(QCP_FOUND "NO")
IF(QCP_LIBRARY AND QCP_INCLUDE_DIR)
SET(QCP_FOUND "YES")
SET_PROPERTY(
GLOBAL
APPEND
PROPERTY COMPILE_DEFINITIONS QCUSTOMPLOT_USE_LIBRARY
)
ENDIF(QCP_LIBRARY AND QCP_INCLUDE_DIR)
However, no linkers append the -DQCUSTOMPLOT_USE_LIBRARY flag in my compilations. What's the right way to approach this problem?
There is no global property COMPILE_DEFINITIONS. But there are such properties for directory, target and source file (see documentation). So probably the closest commands for you are:
set(QCP_FOUND "NO")
if(QCP_LIBRARY AND QCP_INCLUDE_DIR)
set(QCP_FOUND "YES")
set_directory_properties(
PROPERTIES COMPILE_DEFINITIONS QCUSTOMPLOT_USE_LIBRARY
)
endif()
But I think that this kind of job is for imported targets:
add_library(QCP::qcp UNKNOWN IMPORTED)
set_target_properties(
QCP::qcp
PROPERTIES
IMPORTED_LOCATION "${QCP_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${QCP_INCLUDE_DIR}"
INTERFACE_COMPILE_DEFINITIONS QCUSTOMPLOT_USE_LIBRARY
)
and usage:
add_executable(foo ...)
target_link_libraries(foo PUBLIC QCP::qcp)