Now, I know how the question sounds... however, target_link_libraries does more than just linking. And I want this "more" but not the linking. How to do it?
My current setup looks (in a simplified view) like this:
The "3rd Party" target is a PkgConfig created target. The rest is my own.
However, this setup creates a problem in tests. In "Application UTs" I don't want to use the "3rd Party" library. I want to use its stub. To put it in a picture, I would like to have the following instead:
The dotted lines are "something" - it could be targets dependency (to avoid repeating the same information, like include paths), however, I consider it an implementation detail as long as it works.
If I would be in control of the "3rd Party" targets I would just do them like that. But I'm not. I'm receiving .pc files and I have the target created by PkgConfig. While the created target is this single target having it all.
For now, I have solved it by creating a custom function target_nonlink_libraries which propagates the following target properties:
INTERFACE_COMPILE_DEFINITIONS,
INTERFACE_COMPILE_FEATURES,
INTERFACE_COMPILE_OPTIONS,
INTERFACE_INCLUDE_DIRECTORIES,
INTERFACE_SYSTEM_INCLUDE_DIRECTORIES.
(Note, that this does not include all INTERFACE_... properties.)
The code looks like this:
function(target_nonlink_libraries TARGET_NAME SCOPE SOURCE_TARGET)
foreach(source_target IN ITEMS ${SOURCE_TARGET} ${ARGN})
get_target_property(interface_compile_definitions ${source_target} INTERFACE_COMPILE_DEFINITIONS)
if(interface_compile_definitions)
target_compile_definitions(${TARGET_NAME}
${SCOPE}
${interface_compile_definitions}
)
endif()
get_target_property(interface_compile_features ${source_target} INTERFACE_COMPILE_FEATURES)
if(interface_compile_features)
target_compile_definitions(${TARGET_NAME}
${SCOPE}
${interface_compile_features}
)
endif()
get_target_property(interface_compile_options ${source_target} INTERFACE_COMPILE_OPTIONS)
if(interface_compile_options)
target_compile_options(${TARGET_NAME}
${SCOPE}
${interface_compile_options}
)
endif()
get_target_property(interface_include_directories ${source_target} INTERFACE_INCLUDE_DIRECTORIES)
if(interface_include_directories)
target_include_directories(${TARGET_NAME}
${SCOPE}
${interface_include_directories}
)
endif()
get_target_property(interface_system_include_directories ${source_target} INTERFACE_SYSTEM_INCLUDE_DIRECTORIES)
if(interface_system_include_directories)
target_include_directories(${TARGET_NAME} SYSTEM
${SCOPE}
${interface_system_include_directories}
)
endif()
endforeach()
endfunction()
and is later used like this:
target_nonlink_libraries(ApplicationCommon
INTERFACE
PkgConfig::Library
)
target_link_libraries(Application
PRIVATE
PkgConfig::Library
)
The problem here, however, is that I'm not sure that I'm copying all properties (which I care about) and doing it in the correct way.
How to solve this in CMake? Is there any build-in to link targets explicitly skipping actual linking?
Related
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.
I am writing function in my cmake project that needs to make two targets from one, and alter slightly one of them:
option(BORG_STRIP_TEST_BINARIES OFF "Strip symbols from test binaries to reduce disk space usage" )
function(add_borg_test target)
add_test(${target} ${target} --gtest_color=yes)
if(BORG_STRIP_TEST_BINARIES)
# copy target, but make it optional
duplicate_target(FROM ${target} TO ${target}_debug )
set_target_properties(${target}_debug PROPERTIES EXCLUDE_FROM_ALL TRUE)
# alter
target_link_options(${target} PRIVATE -s)
endif()
endfunction()
So this is supposed to work like this:
I create binary target that uses gtest. Set up all target_link_libraries and everything. Example name example-utests
instead add generic add_test, I use custom add_borg_test
now example-utests links with flag -s and is included in all target.
example-utests_debug is NOT included in all target but when requested explicitly, it links without -s.
How to implement duplicate_target from above code snippet?
At the beginning I had a bunch of CMake projects handled separately: each one had its own target for generating documentation called doc. Now I need to wrap all these projects with a super-project: the problem is that super-project compilation fails complaining that exist multiple targets with the same name doc.
The simple solution I thought is to prepend each doc target with the name of the project, but it does not satisfy me.
I would like not to have to use make projectX_doc when compiling a single sub-project and to have a global target make doc for generating the documentation of all projects when compiling super-project.
Are my requests possible? Is there any mechanism to handle target collision?
well each subproject could verify if there are inside a super project with:
if("^${CMAKE_SOURCE_DIR}$" STREQUAL "^${PROJECT_SOURCE_DIR}$")
set(IS_SUBPROJECT FALSE)
else()
set(IS_SUBPROJECT TRUE)
endif()
Thus in your projects CMakeLists.txt you can do:
if (NOT IS_SUBPROJECT)
set(DOC_TGT doc)
else()
set(DOC_TGT ${PROJECT_NAME}_doc)
endif()
add_custom_target(${DOC_TGT} EXCLUDE_FROM_ALL ...
note: you can merge both snippets to avoid IS_SUBPROJECT variable
In your super project CMakeLists.txt:
add_custom_target(doc EXCLUDE_FROM_ALL
DEPENDS projectX_doc projectY_doc...
So when configuring/building each sub project standalone you have
make doc otherwise when you are in your super project target doc become a meta target...
note: You can also use this trick to modify default options etc...
e.g. gflags:
https://github.com/gflags/gflags/blob/master/CMakeLists.txt#L126
https://github.com/gflags/gflags/blob/master/cmake/utils.cmake#L61
https://github.com/gflags/gflags/blob/master/CMakeLists.txt#L163
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)
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)