CMake 2.8.12 with nested dependencies, resulting in multiple 'inclusion' - c++

I have widely-separated dirs, each with a CMakeLists.txt. I'd like to keep them independently-buildable. Two are libs; libA, libB. libB uses libA. exeC uses libA and libB.
When I organize them via add_subdirectory(), as in:
libB (CMakeLists.txt):
add_subdirectory($DIRA somebuild)
target_linked_libraries(libB PUBLIC libA)
exeC (CMakeLists.txt):
add_subdirectory($DIRA somebuild)
add_subdirectory($DIRB somebuild)
target_linked_libraries(exeC PUBLIC libB libA)
and then building exeC, CMake complains about the target name of libA not being unique (CMP0002). If I squelch that warning, I get funky results as the docs warn against.
When I organize them via: add_library(), and add_dependencies() as in:
libB (CMakeLists.txt):
add_library(libA SHARED IMPORTED)
set_target_properties(libA PROPERTIES IMPORTED_LOCATION $DIRA)
add_dependencies(libB libA)
target_linked_libraries(libB PUBLIC libA)
exeC (CMakeLists.txt):
add_library(libA SHARED IMPORTED)
set_target_properties(libA PROPERTIES IMPORTED_LOCATION $DIRA)
add_dependencies(exeC libA)
add_library(libB SHARED IMPORTED)
set_target_properties(libB PROPERTIES IMPORTED_LOCATION $DIRB)
add_dependencies(exeC libB)
target_linked_library(exeC PUBLIC libA libB)
It doesn't seem like the dependencies propagate - when I build exeC, it chokes on not finding libA and libB there, as indeed the docs warn about, although I thought I saw some other doc that said they would build if the required libs were all based on CMake. I'm using CMake 2.8.12.
Is it possible to have nested inclusions like this, or is that not The CMake Way?
edit: it further turns out, that one solution - relying on the transitivity of add_subdirectory(), isn't enough because my case is actually diamond-shaped, I have libB1 and libB2 who each 'include' libA and are included by exeC. It looks like include guards may be the way to go. I'll acknowledge #Drop's answer as correct, if I get this working.

Related

Is it possible for CMake to show error when linking to two incompatible libraries?

Please see the below minimal example
cmake_minimum_required(VERSION 3.20)
project(sample)
add_library(libA A.cpp A.h)
add_library(libB B.cpp B.h)
add_executable(${PROJECT_NAME} main.cpp)
# Given it is known that libA and libB is incompatible,
# is it possible to write some extra cmake code to show error while doing cmake config,
# instead of having to compile to discover the error?
target_link_libraries(${PROJECT_NAME} libA libB) --> need to show error
# target_link_libraries(${PROJECT_NAME} libA) --> ok
# target_link_libraries(${PROJECT_NAME} libB) --> ok
Given that I can't alter the source of libA and libB to make them compatible, how can I discover the error earlier in the CMake configuration stage instead of having to compile the huge codebase to find out the error?
Yes! This is actually possible using a little-known feature called Compatible Interface Properties.
You'll define a custom property with an arbitrary name. Here I'll call it ABI_GROUP and use two different GUIDs for the values. Then you'll add that property to COMPATIBLE_INTERFACE_STRING. See the code below:
cmake_minimum_required(VERSION 3.23)
project(example)
add_library(libA A.cpp A.h)
set_property(TARGET libA PROPERTY INTERFACE_ABI_GROUP "5c6142b9-9b60-4327-bd71-122bd29a9646")
set_property(TARGET libA APPEND PROPERTY COMPATIBLE_INTERFACE_STRING ABI_GROUP)
add_library(libB B.cpp B.h)
set_property(TARGET libB PROPERTY INTERFACE_ABI_GROUP "e291a3cd-50e4-44dc-a90c-66899d34bde5")
set_property(TARGET libB APPEND PROPERTY COMPATIBLE_INTERFACE_STRING ABI_GROUP)
add_executable(example main.cpp)
target_link_libraries(example PRIVATE libA libB)
This results in the following error:
CMake Error: The INTERFACE_ABI_GROUP property of "libB" does
not agree with the value of ABI_GROUP already determined
for "example".
The values of all properties in the COMPATIBLE_INTERFACE_STRING list must either be empty or match among the transitive set of targets that are linked together. There are also a few other modes:
COMPATIBLE_INTERFACE_BOOL - same as string, but tolerant to "truthy" values, i.e. ON and 1 are considered compatible
COMPATIBLE_INTERFACE_NUMBER_MAX - the property evaluates to the largest number among the transitive target set.
COMPATIBLE_INTERFACE_NUMBER_MIN - same as above, but smallest

CMake Include paths - library that depend on external library

Take for example this project structure
project
CMakeLists.txt
main.cpp
libA
src.cpp
CMakeLists.txt
libB
foo.h
foo.cpp
CMakeLists.txt
src.cpp and main.cpp:
#include "foo.h"
.
.
.
I need both src.cpp and main.cpp to have libB in their include path, what I've tried to do is:
libB/CMakeLists.txt:
add_library(libB SHARED
src.cpp)
libA/CMakeLists.txt:
add_library(libA SHARED
foo.cpp)
target_link_libraries(libA libB)
project/CMakeLists.txt:
add_subdirectory(libA)
add_subdirectory(libB)
add_executable(App main.cpp)
target_include_directories(App PUBLIC libB)
target_link_libraries(App libA libB)
And yet I get an error that
src.cpp: fatal error: foo.h: No such file or directory
In the top-level:
cmake_minimum_required(VERSION 3.21)
project(project)
option(BUILD_SHARED_LIBS "Build shared libs instead of static" ON)
add_subdirectory(libA)
add_subdirectory(libB)
add_executable(App main.cpp)
target_link_libraries(App PRIVATE libA libB)
In libA:
add_library(libA src.cpp)
# Use PUBLIC below if libA exposes libB's types in its API.
target_link_libraries(libA PRIVATE libB)
In libB:
add_library(libB foo.cpp)
target_include_directories(
libB PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>"
)
PUBLIC and INTERFACE properties on targets propagate to their linkees. Thus, when we set up the include directories on libB, any target linking to it will get its source directory added it its include path.
We guard the include directory with $<BUILD_INTERFACE:...> so that if you ever install() the library somewhere it might be re-used in another CMake build, it won't remember details of your specific file system.

Can one CMake target link against a *shared* version of another library target?

I have a project with a core library (LibA in the example below), an executable, and a second library (LibB) that depends on the first library. The second library (LibB) is always built in shared (dynamic library) form.
Is there any way that I can force LibB to always link against the shared version of LibA?
Here is a small CMakeLists.txt file illustrating the problem:
cmake_minimum_required(VERSION 3.16)
project(my_project LANGUAGES CXX)
# LibA should be buildable in either static or shared form.
add_library(LibA A.cpp)
# In the real case, there are many customizations to `LibA`:
# compile flags, include dirs, target properties, etc.
add_executable(ExecutableA main.cpp)
target_link_libraries(ExecutableA LibA)
# I want library myB to *always* link against the dynamic library libLibA.so.
add_library(LibB SHARED B.cpp)
target_link_libraries(LibB PUBLIC LibA m pthread)
It can be built with the following commands:
echo 'int main() {}' > main.cpp
touch A.cpp B.cpp
mkdir -p build
cmake -B build/ -S .
cmake --build build/
I know that I can force LibA to be always shared with add_library(LibA SHARED A.cpp), but I want to be able to build LibA as a static library.
The real context for this is that I have a core library (LibA), and want to link against it statically when creating executables, but link against it dynamically (as LibA.so) when creating a Python extension module (LibB.so).
This is not possible, libA can only be either static or dynamic, not both.
You need to have two versions of libA, one dynamic, one static:
cmake_minimum_required(VERSION 3.16)
project(my_project LANGUAGES CXX)
function( addLibA suffix type )
set( libname LibA${suffix} )
add_library(${libname} ${type} A.cpp)
# specify library properties here
endfunction()
# LibA should be buildable in either static or shared form.
addLibA("s" STATIC)
addLibA("" SHARED)
# In the real case, there are many customizations to `LibA`:
# compile flags, include dirs, target properties, etc.
add_executable(ExecutableA main.cpp)
target_link_libraries(ExecutableA LibAs)
# I want library myB to *always* link against the dynamic library libLibA.so.
add_library(LibB SHARED B.cpp)
target_link_libraries(LibB PUBLIC LibA m pthread)
What you are specifically asking for will require multiple targets, as has been mentioned in comments / jpo38's answer
However you might be interested in using CMake's Object Libraries instead to share the objects between a library target and an executable target. For example:
cmake_minimum_required(VERSION 3.16)
project(my_project LANGUAGES CXX)
# Objects get built exactly once
add_library(LibA.objects OBJECTS
A.cpp
)
target_compile_commands( ... )
target_include_directories( ... )
# Shared library for libA
add_library(LibA SHARED
$<OBJECTS:LibA.objects>
)
# LibB -- always shared, always linked against dynamic LibA
add_library(LibB SHARED
B.cpp
)
target_link_libraries(LibB PUBLIC LibA)
# ExecutableA, built using LibA's objects
# (effectively the same linking a static LibA.a)
add_executable(ExecutableA
main.cpp
$<OBJECTS:LibA.objects>
)
Using an object library, your objects get built once, but are shared between multiple targets. Using multiple library targets, on the other hand, will force a rebuild of each object, since each target may use different compile commands.
This allows ExecutableA to have LibA's objects built into it, so it behaves effectively as though you had statically linked in a LibA.a.
In this case, LibA is now built as a SHARED library explicitly, since this is desired for LibB to dynamically link against.
If you're using newer versions of CMake, you can also avoid the $<OBJECTS:...> generator expression, and just link against the object library directly with target_link_libraries -- e.g.:
target_link_libraries(libA PRIVATE LibA.objects)
...
target_link_libraries(ExecutableA PRIVATE LibA.objects)
This approach will also allow properties to be applied once to their respective targets. Any properties regarding the sourcefiles themselves just needs to apply directly to the Object library. Any properties regarding the library setup/layout (e.g. output location, suffixes, etc) applies strictly to the library targets.

How to link shared library in Ament Tool ROS

I am building my packages with ROS ament tools. structure is as below
LibA (build as shared library)
LibB (depends on LibA & build as shared library)
AppB (depends on LinB)
in package.xml of LibB. I specified dependency as ...
package.xml (under dir LibB)
<build_depend>libA</build_depend>
in package.xml of AppB. I specified dependency as ...
package.xml (under dir AppB)
<build_depend>libA</build_depend>
<build_depend>libB</build_depend>
Now build order is correct as ..
# Topological order
- libA
- libB
- AppB
Now issue starts when bulding AppB, and it can't find link for LibA and LibB.
Question
How can I link LibA to LibB ? (what syntax should I use)
How can I link LibA,LibB to AppB ?
Perhaps you should use ament_export in LibA CMakeLists.txt, this page from ROS2 should help
# CMakeLists.txt for LibA
add_library(LibA SHARED src/LibA.cpp)
ament_target_dependencies(LibA rclcpp)
ament_export_interfaces(export_LibA HAS_LIBRARY_TARGET)
ament_export_libraries(LibA)
ament_export_include_directories(include)
ament_export_dependencies(
ament_cmake
rclcpp
)
install(
TARGETS LibA
EXPORT export_LibA
LIBRARY DESTINATION lib/${PROJECT_NAME}
ARCHIVE DESTINATION lib/${PROJECT_NAME}
RUNTIME DESTINATION lib/${PROJECT_NAME}
)
install(
DIRECTORY include/
DESTINATION include
)
and then in CMakeLists.txt from LibB you should have
# CMakeLists.txt for LibB
find_package(LibA REQUIRED)
add_library(LibB SHARED src/LibB.cpp)
target_include_directories(LibB PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
ament_target_dependencies(LibB
rclcpp
LibA
)
ament_export_libraries(LibB)
ament_export_include_directories(include)
ament_export_dependencies(
rclcpp
LibA
)
install(
TARGETS LibB
EXPORT export_LibB
LIBRARY DESTINATION lib/${PROJECT_NAME}
ARCHIVE DESTINATION lib/${PROJECT_NAME}
RUNTIME DESTINATION lib/${PROJECT_NAME}
)
install(
DIRECTORY include/
DESTINATION include
)
To use LibA and LibB with AppB, I think you need to use ament_target_dependencies with LibB as argument and then use add_executable() function
I think you could change the <build_depend> tag for <depend>

Various libs in subfolders, how do I export them to the parent?

I have a code folder structure like this:
project/
CMakeLists.txt
src/
project.cpp
lib/
libA/
CMakeLists.txt
src/
libA.cpp
include/
libA.h
libB/
CMakeLists.txt
src/
libB.cpp
include/
libB.h
Now, libA is totally standalone, so I have a CMakeLists.txt similar to this:
cmake_minimum_required(VERSION 2.8)
project ( libA )
set ( LIBA_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/include PARENT_SCOPE )
include_directories ( include/ )
file (GLOB LIBA_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}
src/libA.cpp
)
add_library ( libA STATIC ${LIBA_SOURCES} )
However, libB depends on libA. How can I export the libA.a output path to be used for linking to libB, and the corresponding include path?
And furthermore, how do I pass that data to the parent CMakeLists.txt for the main project?
I haven't done this personally, so this answer may only be a starting point, but Qt uses cmake and is able to tie all the include directories and library dependencies into each 'module' (library). If you accomplish that, then you can simply do
add_executable(myExe ...)
target_link_libraries(myExe libB)
to the executable in your main cmake project.
Looking through the Qt scripts, it appears what you need to set (for each library) is:
add_library(libA ...)
set_target_properties(libA PROPERTIES
"INTERFACE_LINK_LIBRARIES" "${LIBRARIES_LIBA_DEPENDS_ON}"
"INTERFACE_INCLUDE_DIRECTORIES" "${LIBA_INCLUDE_DIRECTORIES}"
)
then for libB
add_library(libB ...)
set_target_properties(libB PROPERTIES
"INTERFACE_LINK_LIBRARIES" "libA"
"INTERFACE_INCLUDE_DIRECTORIES" "${LIBB_INCLUDE_DIRECTORIES}"
)
The cool thing about this method is that when done properly, you can chain together as many libraries as you like without having to worry about the combinatorial explosion of dependencies and include dirs.
Taking #epicbrew and #NicolasHolthaus answers/comments I just wanted to show how the complete example would look in a newer version of CMake and discuss some of the implications:
project/CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project( project )
add_subdirectory( lib/libB )
add_subdirectory( lib/libA )
add_executable( project src/project.cpp )
target_link_libraries( project libB )
project/lib/libA/CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project( libA )
add_library( libA STATIC src/libA.cpp include/libA.h )
target_include_directories( libA PUBLIC include )
project/lib/libB/CMakeLists.txt
add_library( libB STATIC src/libB.cpp include/libB.h )
target_include_directories( libB PUBLIC include )
target_link_libraries( libB libA )
As you can see
You need to know in the main CMakeLists.txt file that you also need libA. I was deliberately reversing the add_subdirectory() calls to show that the order doesn't matter when using target_link_libraries() (sort of allows target forward declarations).
You don't need the local include_directories() call anymore it's an implicit part of the target_include_directories().
With target_include_directories(... PUBLIC ...) those include paths are self-propagating.
You don't need to add the ${CMAKE_CURRENT_SOURCE_DIR} prefixes, those are the defaults.
I just added the source/header files directly to the add_executable()/add_library() calls for compactness of this example code. Normally I also would place those in local variables.
libB is not standalone, but you could change that e.g. by adding add_subdirectory( ../libA libA ) to its CMakeLists.txt file. Obviously you then have to remove add_subdirectory( lib/libA ) from the main CMakeLists.txt.
If you want to optimize the CMake processing a little you should not repeat the project() commands. If you still want libA to be self-sufficient you could add if ( CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR )/endif() around the cmake_minimum_required(VERSION 3.0)/project( libA ) calls.
CMake knows about all of the targets that you've built, so there's no need to export information up to the parent. You simply tell CMake that libB links against libA and CMake takes care of putting the correct path on the linker command line. So just specify libA as a link dependency of libB in your libB/CMakeLists.txt:
target_link_libraries(libB libA)
For the include path you either need to set variables in the parent along the lines of the following in the top level project/CMakeLists.txt:
set(LIBA_INCLUDES libA/include)
set(LIBB_INCLUDES libB/include)
Then reference these in the subprojects CMakeList.txt files.
Or, you could use a relative path in your libB/CMakeLists.txt file:
include_directories(../libA/include)