Two versions of same library linked to target in CMake - cmake

I'm working on a project that has a setup like so:
Project
'-> CMakeLists.txt
'-> src
'-> ModuleA
'-> CMakeLists.txt
'-> main.cpp
'-> thirdparty
'-> CMakeLists.txt
'-> protobuf-3.11.3
'-> Foo
'-> CMakeLists.txt
'-> libs
'-> include
'-> deps
The protobuf-3.11.3 in thirdparty is compiled each time to a static library and is required by ModuleA.
'Foo' is another 3rd party project that only has the include and static libraries already built.
'Foo' also contains the transitive dependencies it requires stored in the Foo/deps folder
The CMakeLists.txt in 'Foo' looks like this:
add_library(libfoo STATIC IMPORTED GLOBAL)
cmake_path( SET FOO_DIR "${CMAKE_CURRENT_SOURCE_DIR}" )
cmake_path( APPEND FOO_DIR "include" OUTPUT_VARIABLE FOO_INCLUDE_DIR )
cmake_path( APPEND FOO_DIR "libs" OUTPUT_VARIABLE FOO_LIBRARY_DIR )
cmake_path( APPEND FOO_DIR "deps" OUTPUT_VARIABLE FOO_DEPS_DIR )
set_target_properties( libfoo PROPERTIES
IMPORTED_LOCATION "${FOO_LIBRARY_DIR}/libfoo.a"
INTERFACE_INCLUDE_DIRECTORIES "${FOO_INCLUDE_DIR}"
INTERFACE_LINK_LIBRARIES "${FOO_DEPS_DIR}/libprotobuf.a")
The libprotobuf.a dependency for libfoo was built with protobuf-3.13.0
The CMakeLists.txt for ModuleA looks like this:
add_executable(moduleA main.cpp)
target_link_libraries(moduleA PRIVATE
libfoo
libprotobuf)
Question
The problem I have is this won't compile because ModuleA has a direct dependency on libprotobuf.a which was built with protobuf-3.11.3 and libfoo has a dependency on protobuf-3.13.0. This leads to a lot of multiple definition errors for google::protobuf::<...>
I don't have control over the Foo packages dependency on 3.13.0 or the ModuleA dependency on 3.11.3. Is there a solution to this workaround e.g. having two different version of the same library linked into the main target?

Related

Cmake - Add files from other sub-projects

Let's say I have two sub-projects: A and B
This is my root CMakeLists.txt
cmake_minimum_required (VERSION 3.8)
project ("Project")
add_subdirectory ("A")
add_subdirectory ("B")
In /B I have file test.h
How can I include /B/test.h in A ?
I have tried to add target_include_directories(B) in /A/CMakeLists.txt but it does not seem to work
When you write add_subdirectory(B) (quotes are not necessary), you must have a CMakeLists.txt in the directory B.
This CMakeLists.txt may have the following content:
add_library(targetB INTERFACE)
target_include_directories(targetB INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
These 2 lines create a CMake target called targetB with the INTERFACE property. This is very handy for header only library. All cmake target that links with targetB will then include the CMAKE_CURRENT_SOURCE_DIR with is the directory A where your test.h is.
Then, you have add_subdirectory(A) with a CMakeLists.txt that may look like this (assuming you have a main in this dir A):
add_executable(foo ${CMAKE_CURRENT_SOURCE_DIR}/main.c)
target_link_libraries(foo PRIVATE targetB)
Then second line create the relation between the target foo and the target targetB and, when compiling, the path to your test.h will be set in the command line.

Problem using FetchContent_Declare together with shared library

I'm having problem using FetchContent_Declare with a shared library. I'm trying to apply a modular design (instead of creating one mega-repository with 20 modules I'm allocating a dedicated repository to each module). I'm trying to use FetchContent_Declare in order to link dependant modules together (I'm not a fan of git-submodules since those require that user manually initialises them). The problem I'm having is that the DLL generated by the project fetched via the mentioned function isn't copied to the binary output of the parent project. Here is a dependency graph to make it more clear.
Repo A:
- bin
- bin-etc
- lib
- include
- project_a
.. header files ..
- src
- CMakeLists.txt : 1
.. source files ..
- CMakeLists.txt : 2
Repo B: Includes A
- bin
- bin-etc
- lib
- output
.. cmake files are built from here ..
- _deps
- project_a-src
- bin
.. here the DLL file is being generated ..
- include
- project_a
.. header files ..
- src
- CMakeLists.txt : 3
.. source files ..
- CMakeLists.txt : 4
The DLL file is being generated in ./output/_deps/project_a-src/bin instead of ./bin.
Here are my CMakeLists.txt files:
# CMakeLists.txt : 1
cmake_minimum_required(VERSION 3.16)
set(PROJECTNAME project_a)
set(PROJECTDIR "${CMAKE_CURRENT_SOURCE_DIR}")
file(GLOB_RECURSE inc "${CMAKE_CURRENT_SOURCE_DIR}/../include/*.hpp")
file(GLOB_RECURSE src "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}/../include" FILES ${inc})
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${src})
if (MSVC)
add_compile_options(/W3) # warning level 3
add_compile_options(/MP) # Multi-processor compilation
endif()
add_library(
${PROJECTNAME}
SHARED
${inc}
${src}
)
target_include_directories(${PROJECTNAME} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include/")
# CMakeLists.txt : 2 and 4
cmake_minimum_required(VERSION 3.16)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set(CMAKE_SYSTEM_VERSION 10.0.19041.0)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin-etc")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/lib")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin")
project(project_a LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED)
add_subdirectory(src)
# CMakeLists.txt : 3
cmake_minimum_required(VERSION 3.16)
set(PROJECTNAME project_b)
set(PROJECTDIR "${CMAKE_CURRENT_SOURCE_DIR}")
file(GLOB_RECURSE inc "${CMAKE_CURRENT_SOURCE_DIR}/../include/*.hpp")
file(GLOB_RECURSE inc_src "${CMAKE_CURRENT_SOURCE_DIR}/*.hpp")
file(GLOB_RECURSE src "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}/../include" FILES ${inc})
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${inc_src})
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${src})
if (MSVC)
add_compile_options(/W3) # warning level 3
add_compile_options(/MP) # Multi-processor compilation
endif()
add_library(
${PROJECTNAME}
SHARED
${inc}
${inc_src}
${src}
)
target_include_directories(${PROJECTNAME} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include/")
include(FetchContent)
FetchContent_Declare(project_a
GIT_REPOSITORY <REPO LINK>
GIT_TAG master)
FetchContent_MakeAvailable(project_a)
target_link_libraries(${PROJECTNAME} project_a)
What would be the solution to this problem? Should I use another approach for the design like this? Is adding a simple copy command to cmake the right solution?
Variables like CMAKE_RUNTIME_OUTPUT_DIRECTORY affects on the output directory for all further created libraries ... unless the variable is changed.
The project_a in CMakeLists.txt(2) does
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin")
unconditionally, so it changes the variable even if it is built as a subproject (with FetchContent).
Generally, any project in its root CMakeLists.txt could check, whether it is actually top-level project. And perform some settings only in case it is:
cmake_minimum_required(VERSION 3.21)
project(project_a LANGUAGES CXX)
if (PROJECT_IS_TOP_LEVEL)
# Do global settings only if we are top-level project.
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set(CMAKE_SYSTEM_VERSION 10.0.19041.0)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin-etc")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/lib")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin")
endif()
# Other settings are treated as project-specific,
# so could be done in "subproject mode" too.
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED)
add_subdirectory(src)
Note, that variable PROJECT_IS_TOP_LEVEL appears only in CMake 3.21. For detect, whether the project is top-level in older CMake versions, consult that question and its answers: How to detect if current scope has a parent in CMake?.

How to create cmake project with components

Im trying to create project in Cmake which contains components.
So another project can use this project using:
find_package(ltk COMPONENTS Core Networking REQUIRED)
target_link_libraries(test ltk::Core ltk::Networking)
My library project (called ltk)
Here is a project tree:
ltk/
|--- CMakeLists.txt
|--- ltkConfig.cmake.in
|--- ltkConfigVersion.cmake.in
|
+--- Core/
| |--- core.cc
| |--- core.h
| |--- CMakeLists.txt
|
+--- Networking/
|--- networking.cc
|--- networking.h
|--- CMakeLists.txt
Full project is here: https://gitlab.com/T0maas/cmake-testing
The file ltk/CMakeLists.txt has this content:
set(project ltk)
set(LTK_VERSION 0.0.1)
cmake_minimum_required(VERSION 3.21)
project(${project})
add_subdirectory(Core)
add_subdirectory(Networking)
foreach(p LIB BIN INCLUDE CMAKE)
set(var INSTALL_${p}_DIR)
if(NOT IS_ABSOLUTE "${${var}}")
set(${var} "${CMAKE_INSTALL_PREFIX}/${${var}}")
endif()
endforeach()
export(TARGETS Core Networking FILE "${PROJECT_BINARY_DIR}/ltkTargets.cmake")
export(PACKAGE ltk)
file(RELATIVE_PATH REL_INCLUDE_DIR "${INSTALL_CMAKE_DIR}" "${INSTALL_INCLUDE_DIR}")
set(CONF_INCLUDE_DIRS "${PROJECT_SOURCE_DIR}" "${PROJECT_BINARY_DIR}")
configure_file(ltkConfig.cmake.in "${PROJECT_BINARY_DIR}/ltkConfig.cmake" #ONLY)
set(CONF_INCLUDE_DIRS "\${LTK_CMAKE_DIR}/${REL_INCLUDE_DIR}")
configure_file(ltkConfig.cmake.in "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/ltkConfig.cmake" #ONLY)
configure_file(ltkConfigVersion.cmake.in "${PROJECT_BINARY_DIR}/ltkConfigVersion.cmake" #ONLY)
install(FILES
"${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/ltkConfig.cmake"
"${PROJECT_BINARY_DIR}/ltkConfigVersion.cmake"
DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/cmake/${project}" )
install(EXPORT ltkTargets DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/cmake/${project}" )
And the Core/CMakeLists.txt contains this:
set (component Core)
add_library(${component} SHARED core.cc )
add_library(${project}::${component} ALIAS ${component})
set_target_properties(${component} PROPERTIES PUBLIC_HEADER "core.h")
install(TARGETS ${component}
EXPORT ltkTargets
COMPONENT ${component}
LIBRARY DESTINATION lib/ltk
ARCHIVE DESTINATION lib/ltk
RUNTIME DESTINATION bin
PUBLIC_HEADER DESTINATION include/ltk/
)
The Networking/CMakeLists.txt is similar to Core/CMakeLists.txt
File ltkConfig.cmake.in contains this:
get_filename_component(LTK_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
set(LTK_INCLUDE_DIRS "#CONF_INCLUDE_DIRS#")
if(NOT TARGET Core AND NOT LTK_BINARY_DIR)
include("${LTK_CMAKE_DIR}/ltkTargets.cmake")
endif()
if(NOT TARGET Networking AND NOT LTK_BINARY_DIR)
include("${LTK_CMAKE_DIR}/ltkTargets.cmake")
endif()
set(LTK_LIBRARIES Core Networking)
And the ltkConfigVersion.cmake.in:
set(PACKAGE_VERSION "#LTK_VERSION#")
# Check whether the requested PACKAGE_FIND_VERSION is compatible
if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}")
set(PACKAGE_VERSION_COMPATIBLE FALSE)
else()
set(PACKAGE_VERSION_COMPATIBLE TRUE)
if ("${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}")
set(PACKAGE_VERSION_EXACT TRUE)
endif()
endif()
After install this project I see some files in /usr/lib/cmake/ltk:
ltkTargets-noconfig.cmake
ltkTargets.cmake
ltkConfigVersion.cmake
ltkConfig.cmake
Testing
But now when I try to configure some testing project, which has following CMakeLists.txt:
cmake_minimum_required(VERSION 3.21)
project(test)
find_package(ltk COMPONENTS Core REQUIRED)
add_executable(main main.cc)
target_link_libraries(main ltk::Core)
It shows following errors:
-- Configuring done
CMake Error at CMakeLists.txt:4 (add_executable):
Target "main" links to target "ltk::Core" but the target was not found.
Perhaps a find_package() call is missing for an IMPORTED target, or an
ALIAS target is missing?
-- Generating done
I've read following links, but this doesn't helped me:
https://gitlab.kitware.com/cmake/community/-/wikis/doc/tutorials/How-to-create-a-ProjectConfig.cmake-file
How to configure project with COMPONENTS in cmake
Edit
Now I added NAMESPACE ltk:: to install(EXPORT) in ltk/CMakeLists.txt:
install(EXPORT ltkTargets DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/cmake/${project}" NAMESPACE ltk::)
But the testing project which contains #include <core.h> complains:
fatal error: core.h: No such file or directory
Path to /usr/include/ltk has not been exported.
Edit 2
Fixed include problems with:
Added to {Core, Networking}/CMakeLists.txt
target_include_directories(${component} PUBLIC $<INSTALL_INTERFACE:include/ltk>)
Now I can use in my testing project this:
target_include_directories(main PUBLIC ltk)

Cannot specify compile options for imported target "..."

I want to provide the users of my library with two targets: one that specifies the include path etc., and one that carries useful extra compile options. However, for the extra target some of my users are getting the error
Cannot specify compile options for imported target "myproject::extra"
so it seems on older CMake versions.
I tested with CMake 3.9.2. The test project, including CI is on GitHub, with failing build here.
(How) can my approach be rendered robust for all CMake versions?
The project's main CMakeLists.txt:
cmake_minimum_required(VERSION 3.0)
project(myproject)
add_library(myproject INTERFACE)
set(MYPROJECT_VERSION "1.0.0")
target_include_directories(myproject INTERFACE
$<INSTALL_INTERFACE:include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>)
include(CMakePackageConfigHelpers)
include(GNUInstallDirs)
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/" DESTINATION include)
install(TARGETS myproject EXPORT myproject-targets)
install(EXPORT myproject-targets FILE myprojectTargets.cmake DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/myproject")
write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/myprojectConfigVersion.cmake" VERSION ${MYPROJECT_VERSION} COMPATIBILITY AnyNewerVersion)
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/myprojectConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/myprojectConfigVersion.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/myproject")
The project's myprojectConfig.cmake:
include(CMakeFindDependencyMacro)
if(NOT TARGET myproject)
include("${CMAKE_CURRENT_LIST_DIR}/myprojectTargets.cmake")
endif()
if(NOT TARGET myproject::extra)
add_library(myproject::extra INTERFACE IMPORTED)
if(MSVC)
target_compile_options(myproject::extra INTERFACE /W4)
else()
target_compile_options(myproject::extra INTERFACE -Wall)
endif()
endif()
The user's project CMakeLists.txt could then look as follows:
cmake_minimum_required(VERSION 3.0)
project(myexec)
find_package(myproject REQUIRED)
add_executable(myexec main.cpp)
target_link_libraries(myexec PRIVATE myproject myproject::extra)
List of functions applicable for IMPORTED and INTERFACE targets changes as CMake evolves.
Most of such functions affects only on specific target properties. So, instead of calling a function, you may set the property directly. This will work in any CMake version:
# Works only in new CMake versions
target_compile_options(myproject::extra INTERFACE /W4)
# Equivalent which works in any CMake version
set_property(TARGET myproject::extra PROPERTY INTERFACE_COMPILE_OPTIONS /W4)

CMake submodules dependencies

I have a CMake project with two submodules A and B. B depends on A. In submodule B I would like to search for A using find_package(A CONFIG). My minimal (not) working example would be:
CMakeLists.txt
cmake_minimum_required(VERSION 3.13)
project(AB)
add_subdirectory(B)
add_subdirectory(A)
A/CMakeLists.txt:
message(STATUS "CMake: A")
add_library(A SHARED A.hpp A.cpp)
target_include_directories(A PUBLIC "${CURRENT_SOURCE_DIR}")
install(TARGETS A EXPORT AA LIBRARY DESTINATION lib/)
export(TARGETS A NAMESPACE AA:: FILE ${CMAKE_BINARY_DIR}/A/AConfig.cmake)
export(PACKAGE AA)
A/A.hpp (some non-sense code)
A/A.cpp
B/CMakeLists.txt
find_package(A CONFIG)
message(STATUS "---> ${A_FOUND}")
add_library(B B.hpp B.cpp)
target_link_libraries(B AA::A)
B/B.hpp (some non-sense code)
B/B.cpp
A/CMakeList.txt correctly produces a AConfig.cmake. But as I understand it does that after(!) find_package(A CONFIG) is called and therefore AConfig.cmake is not found.
Any idea how to force find_package() to run after A is executed?
Of course I know that in this example find_package does not make any sense. In my actual project the submodules are external software which I do not want to modify (in my case parallelSTL and TBB).
Indeed cmake currently does not properly support this. A discussion for future plans can be found here. Here is my ugly workaround:
A/CMakeLists.txt:
message(STATUS "CMake: A")
add_library(A SHARED A.hpp A.cpp)
target_include_directories(A PUBLIC "${CURRENT_SOURCE_DIR}")
install(TARGETS A EXPORT AA LIBRARY DESTINATION lib/)
export(TARGETS A NAMESPACE AA:: FILE ${CMAKE_BINARY_DIR}/A/AConfig.cmake)
export(PACKAGE AA)
add_library(AA::A ALIAS A)
file(WRITE ${CMAKE_BINARY_DIR}/A/AConfig.cmake "")
# include(CMakePackageConfigHelpers)
# write_basic_package_version_file(
# ${CMAKE_BINARY_DIR}/A/AConfigVersion.cmake
# VERSION 1.0.0
# COMPATIBILITY AnyNewerVersion)
So the idea is to create a dummy file AConfig.cmake. This is overwritten later but it ensures that find_package() does not fail before it is actually created. Then we need to alias the target A to the name after the import AA::A. In case that there is a requirement on the version of A, than a file AConfigVersion.cmake needs to be created as well.
I created a MWE on github.