How to include target include directories transitive through multiple linked libraries - cmake

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.

Related

CMAKE: find a shared(module) library

Why this statement works if mylib is a static library and does not work if it's a dynamic(shared) one:
find_library(_found mylib PATHS ${mydirs})
where mydir keeps a list of directories, containing libraries (static and shared).
I'd like to find a dynamic library in the same manner as it is done for the static libraries (in order to get it close to the executable). There would be a list of directories but for simplicity sake, first this must be made working with a trivial test case:
cmake_minimum_required(VERSION 3.21)
project(myproj)
add_executable(myproj main.cpp)
find_library(a a${CMAKE_STATIC_LIBRARY_SUFFIX} PATHS "c:/work/libs/debug")
find_library(b b${CMAKE_SHARED_LIBRARY_SUFFIX} PATHS "c:/work/libs/debug")
message("dll=" "${CMAKE_SHARED_LIBRARY_SUFFIX}")
message("dll=" "${CMAKE_STATIC_LIBRARY_SUFFIX}")
message("a=" "${a}")
message("b=" "${b}")
And, the directory structure is as this:
c:/work (CMakeLists.txt_main.cpp)
\_libs
\_debug (a.lib b.dll)
\_release (a.lib b.dll)
The output:
1>a=C:/work/libs/debug/a.lib
1>b=b-NOTFOUND
1>dll=.dll
1>dll=.lib
Anything?

How to structure CMake projects

I started using CMake but am not sure how to use it in the following setup.
I have some library-cmake-projects (source code), which might depend on each other.
Libs
-A
-B
-C
(Lets say A depends on C and B depends on C)
How to make cmake be aware of the needed modules in the other directories?
With regards to add_subdirectory, you should structure it like your source code is structured.
So, Let's say your folder structure looks like this:
/
-- App
-- libs
-- A
-- B
-- C
Then you would put CMakeLists.txt like this:
/
-- CMakeLists.txt
-- App
-- CMakeLists.txt
-- libs
-- CMakeLists.txt
-- A
-- CMakeLsts.txt
-- B
-- CMakeLists.txt
-- C
-- CMakeLists.txt
/CMakeLists.txt
add_subdirectory(App)
add_subdirectory(libs)
/App/CMakeLists.txt
add_executable(App ...)
target_link_libraries(App A B)
/libs/CMakeLists.txt
add_subdirectory(A)
add_subdirectory(B)
add_subdirectory(C)
/libs/A/CMakeListst.txt
add_library(A ...)
target_link_libraries(A C)
/libs/B/CMakeListst.txt
add_library(B ...)
target_link_libraries(B C)
/libs/C/CMakeListst.txt
add_library(C ...)
It's not good to think about add_subdirectory as having anything to with dependency management, it is about how your source and build files are organized. It also has scoping, but most of the time you don't need to worry about it. You don't necessarily need the /libs/CMakelists.txt if you directly put all three add_subdirectory calls in the top-level CMakeLists.txt but it can help with organizing.
I would refrain from using add_subdirectory to include a neighboring folder, for example. Assume the target is there and include from somewhere higher up.
Note that this is what I would recommend in general because it seems to be what the designers had in mind, but if you need something else for some reason, or have different opinion about what is correct design, then feel free to structure it differently.
Is add_subdirectory the correct way to do this?
No, add_subdirectory() is used to traverse to other CMakeLists.txt files within your CMake project. It doesn't specify dependencies. In modern CMake, you really only need target_link_libraries to specify dependencies, as suggested in this answer.
How to add C to A to make this build in one call possible?
I do not want to add C directly to App.
You can make A depend on C by linking the previously-defined target C to A
add_library(C SHARED ... )
...
add_library(A SHARED ... )
target_link_libraries(A PUBLIC C)
Note, the use of PUBLIC here ensures C is added to the link interface of A, so it will be propagated to consuming targets. Now, when you link A to App, you will get C along with it, and there is no need to specify C explicitly.
# Link A (and C) to App.
target_link_libraries(App PRIVATE A)
How to handle the case that App also depends on B? With add_subdirectory and no caution this would try to define C twice. Are include guards the correct solution?
In my experience, this hasn't been an issue, and the linker was able to figure out the duplicated linked C library. If it does become an issue, you can simply make the scope of the B dependency PRIVATE. This way, C will not be propagated to App through B:
target_link_libraries(B PRIVATE C)
...
target_link_libraries(App PRIVATE B A)
I encourage you to read through the linked target_link_libraries() documentation, which has many examples for recommended CMake usage with these types of dependencies.

Remove library added with link_libraries()

How do I remove libraries added with link_libraries()?
Yes, I know I should use target_link_libraries(). I can`t because I must link a library to every future target. See this. The library is CMake built.
This should be invisible to the C++/CMake developer. He should not have to worry about this lib.
Example:
add_library(link-to-all a.cpp)
link_libraries(link-to-all)
add_executable(e1 e1.cpp) # with link-to-all
add_executable(e2 e2.cpp) # with link-to-all
unlink_libraries(link-to-all) #does not exist!
add_executable(e3 e3.cpp) # without link-to-all
# all further targets link without link-to-all!
In my case link-to-all is a library with implementation for coverage checking functions. It is enabled depending on a configuration option and should be implicitly used for all coming targets. Coverage analysis may be disabled for specific targets, so I want to be able to disable it.
The coverage is enabled by prepending CMAKE_<LANG>_COMPILE_OBJECT and disabled by removing the prefix. Afaik this cannot be done target specific, only global for coming targets. So unlink_libraries() would be a function that i can call symmetrically.
function(enable_coverage)
prepend_compiler();
link_libraries(cov);
# alternative with loosing target information/dependency
# prepend_system_libs(<path>/libcov.a)
endfunction()
function(disable_coverage)
reset_compiler();
unlink_libraries(cov);
# reset_system_libs()
endfunction()
I could use CMAKE_<LANG>_STANDARD_LIBRARIES, (and also remove it there) but I would need the LOCATION of the library (generator expression: TARGET) in there. But I would also lose the interfaces of link-to-all. Also, that would probably remove the build dependencies.
While I agree with #Guillaume, I'll combine the suggestions into an answer, as the linked answer is not very clear. As #Tsyvarev confirmed in the CMake source, the link_libraries() call sets the LINK_LIBRARIES target property; the target_link_libraries() call does the same. While your executable e3 will initially be set to link with all libraries, you can remove one (or multiple) libraries from the list using a combination of get_target_property() and set_property(). Here is an example to demonstrate how:
# Library to be linked to all targets.
add_library(link-to-all a.cpp)
# Library to be linked to (almost) all targets.
add_library(link-to-almost-all b.cpp)
link_libraries(link-to-all link-to-almost-all)
# Gets our link-everywhere libraries. Oops!
add_executable(e3 test.cpp)
# Get the LINK_LIBRARIES property for this target.
get_target_property(E3_LINKED_LIBS e3 LINK_LIBRARIES)
message("Libraries linked to e3: ${E3_LINKED_LIBS}")
# Remove one item from the list, and overwrite the previous LINK_LIBRARIES property for e3.
list(REMOVE_ITEM E3_LINKED_LIBS link-to-almost-all)
set_property(TARGET e3 PROPERTY LINK_LIBRARIES ${E3_LINKED_LIBS})
# Verify only one library is now linked.
get_target_property(E3_LINKED_LIBS_NEW e3 LINK_LIBRARIES)
message("Libraries linked to e3: ${E3_LINKED_LIBS_NEW}")
The messages printed here confirm the library was removed from the LINK_LIBRARIES target property for e3:
Libraries linked to e3: link-to-all;link-to-almost-all
Libraries linked to e3: link-to-all
As an additonal answer I repeat what I commented on squarekittles answer (for future reference):
You should use the target property <LANG>_COMPILER_LAUNCHER instead of CMAKE_<LANG>_COMPILE_OBJECT. It can be set on target base.

Different linker options for each executable

I would like to create two separate executable from the same source files but with different linker parameters.
With the lines above, I can generate one executable without problem:
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --specs=nano.specs -T libs.ld -T mem.ld -T sections.ld -L\"${CMAKE_CURRENT_SOURCE_DIR}/script\" -Wl,-Map,${MAP_NAME}")
add_executable(${ELF_NAME} ${PRJ1_SOURCE_FILES} ${PRJ1_HEADER_FILES})
target_link_libraries(${ELF_NAME} PRIVATE liba libb libc)
When I add the following two lines at the end of the code above, I can very well generate the second executable (with the same linker flags) besides the first one without problem:
add_executable(${ELF2_NAME} ${PRJ1_SOURCE_FILES} ${PRJ1_HEADER_FILES})
target_link_libraries(${ELF2_NAME} PRIVATE liba libb libc)
But my problem is that I have to generate the second executable (at the same time with the first one) with different linker parameters. I don't want to use conditional statements to generate one after another. My goal is to automate the process.
How can achieve this?
Use set_target_properties with LINK_FLAGS property. From set_target_properties manual:
set_target_properties(target1 target2 ...
PROPERTIES prop1 value1
prop2 value2 ...)
Set properties on a target.
...
See Properties on Targets for the list of properties known to CMake.
In the link properties on targets we can find LINK_FLAGS:
Additional flags to use when linking this target.
The LINK_FLAGS property can be used to add extra flags to the link step of a target. LINK_FLAGS_ will add to the configuration , for example, DEBUG, RELEASE, MINSIZEREL, RELWITHDEBINFO.
So use something similar to:
set_target_properties(${ELF_NAME} PROPERTIES LINK_FLAGS " --specs=rdimon.specs")
While Kamil answer is good for now, I believe question you're asking for is properly addressed in upcoming CMake v3.13.
target_link_options was introduced for that purpose,
"Specify link options to use when linking a given target. The named must have been created by a command such as add_executable() or add_library() and must not be an ALIAS target."
https://cmake.org/cmake/help/v3.13/command/target_link_options.html#command:target_link_options

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.