How to cross link libraries in CMake - c++

In a c++ CMake project I have an executable main and two libraries lib1 and lib2. A function in lib1 needs a function from lib2 and visa versa. Also, lib1 only contains .h files. The main executable will use both libraries. When I try and "make" the project, I get an error:
error: redefinition of ‘void lib1()’.
The file structure looks somewhat like this
/path/to/my/project
├── CMakeLists.txt # Project directory
├── main.cpp
├── Lib1
│ ├── ...files (.h only)...
│ ├── CMakeLists.txt # lib1 cmake
├── Lib2
│ ├── ...source files (.cpp & .h)...
│ ├── CMakeLists.txt # lib2 cmake
The CMakeLists.txt in the Project directory includes the following:
add_executable(${PROJECT_NAME} main.cpp)
add_subdirectory(Lib1)
add_subdirectory(Lib2)
target_link_libraries(${PROJECT_NAME}
lib2
lib1
)
The CMakeLists.txt in the Lib1 directory includes the following:
add_library(lib1 INTERFACE)
target_include_directories(lib1
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
target_link_libraries(lib1 INTERFACE
lib2
)
The CMakeLists.txt in the Lib2 directory includes the following:
add_library(lib2 ${SOURCES} ${HEADERS}) # SOURCES and HEADERS set in lines above
target_include_directories(lib2
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
target_link_libraries(lib2
lib1
)
If I had to guess, the issue is it is trying to import lib1 headers twice. Once from lib2 and once in my main executable. How do I link the libraries so that isn't an issue?

I dont think it's anything related to cmake. Although convoluted (I'd do it in another way but hey it's your code) I think you are defining the body of a function in lib1 where it should reside in a cpp file.
Make that function lib1 inline.
inline void lib1() {
...
}
or alternatively defined it in the header and implement it in a body file
//lib1.h
void lib1();
Then
//lib1.cpp
#include "lib1.h"
void lib1() {
...
}

Related

How can I include headers from find_package() function in CMake?

I have a CMake project in C++ and I have been struggling how to setup the CMakeLists.txt files properly. In the external folder there are some dependencies located, which are added to the root CMakelists.txt by the add_subdirectory command. It is important to mention that Project_B needs Project_A. Here is the simplified version of the folder structure:
$ tree
.
├── external
│ ├── Project_A
| ├──include
| ├── project_A.h
│ ├── projectA_0.cpp
│ └── CMakeLists.txt
|
| ├── Project_B
| ├──include
| ├── project_B.h
│ ├── projectB_0.cpp
│ └── CMakeLists.txt
|
├── root_0.cpp
├── root_1.cpp
└── CMakeLists.txt
Here is the simplified root CMakeLists.txt
# root CMakeLists.txt
cmake_minimum_required(VERSION 3.19)
project(foo)
add_subdirectory(external/Project_A)
add_subdirectory(external/Project_B)
add_executable(${PROJECT_NAME} root_0.cpp
root_1.cpp)
target_include_directories(${PROJECT_NAME} PUBLIC
external/Project_A/include
external/Project_B/include)
target_link_libraries(${PROJECT_NAME} PUBLIC
project_a
project_b)
Here is the CMakeLists.txt of Project_A, which is also a dependency for Project_B. I decided to create a config.cmake file for this project and import it by Project_B.
# Project_A CMakeLists.txt
cmake_minimum_required(VERSION 3.19)
project(project_a)
target_include_directories(${PROJECT_NAME} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
add_library(${PROJECT_NAME} STATIC projectA_0.cpp)
export(TARGETS ${PROJECT_NAME}
NAMESPACE project_a::
FILE ${CMAKE_SOURCE_DIR}/project_a_Config.cmake )
and here is the CMakeLists.txt of Project_B.The project_a_Config.cmake is loaded with the help of find_package() function. The path of the include dir from A is written in the project_a_Config.cmake. However, I can not make those headers included properly. Should I use a *.cmake.in file?
# Project_B CMakeLists.txt
cmake_minimum_required(VERSION 3.19)
project(project_b)
add_library(${PROJECT_NAME} STATIC projectB_0.cpp)
find_package(project_a CONFIG REQUIRED HINTS ../)
target_include_directories(${PROJECT_NAME} PUBLIC
include
project_a::project_a )
target_link_libraries(${PROJECT_NAME} PUBLIC
project_a )
If there are any further problems with the structure or the used functions, just let me know! Thank you!

CMake cannot detect symbol conflicts in different static libraries

I created a simple cmake project to reproduct it.
├── CMakeLists.txt
├── lib1
│   ├── CMakeLists.txt
│   ├── lib1.cpp
│   └── lib1.h
├── lib2
│   ├── CMakeLists.txt
│   ├── lib2.cpp
│   └── lib2.h
└── main.cpp
lib1/CMakeLists.txt:
add_library(lib1 "")
target_include_directories(lib1
PUBLIC
${CMAKE_CURRENT_LIST_DIR}
)
target_sources(lib1
PRIVATE
lib1.cpp
lib1.h
)
In lib1.cpp, there is a function "void say()":
#include <stdio.h>
void say()
{
printf("hello from lib1\n");
}
lib2/CMakeLists.txt:
add_library(lib2 "")
target_include_directories(lib2
PUBLIC
${CMAKE_CURRENT_LIST_DIR}
)
target_sources(lib2
PRIVATE
lib2.cpp
lib2.h
)
And in lib2/lib2.cpp, there is a function of the same signature:
#include <stdio.h>
void say()
{
printf("hello from lib2\n");
}
CMakeLists.txt:
cmake_minimum_required(VERSION 3.16)
project(shell LANGUAGES CXX)
add_subdirectory(lib1)
add_subdirectory(lib2)
add_executable(test2
main.cpp
)
target_link_libraries(test2
PRIVATE
lib1
lib2
)
Here is the main.cpp:
void say();
int main()
{
say();
return 0;
}
The output:
hello from lib1
There is no compile or link error, not even a warning.
The linker just picked one and symply ignored the other one.
I'm using cmake 3.16, and tested it with msvc 2017/2019 and g++ 7.5.
How to make the linker prompts errors when there are symbol conflicts in static libraries?
Thanks!
How to make the linker prompts errors when there are symbol conflicts in static libraries?
With gcc use the --whole-archive option to include every object file in the archive rather then search the archives for the required symbol.
As there is no cmake support that I know of, I find it's typically done when linking the executable:
target_link_libraries(test2 PRIVATE
-Wl,--whole-archive
lib1
lib2
-Wl,--no-whole-archive
)
"Symbol conflicts" is rather vague term. This will only detect multiple symbol definitions. Types of symbols are not stored anywhere after compilation.

Accessing include directories of a cmake project added as subdirectory

Consider a simple scenario where my cmake project adds a dependency as a subdirectory:
.
├── CMakeLists.txt
├── src
├── include
│
└── externals
└── BAR
├── CMakeLists.txt
├── src
└── include
The main CMakeLists.txt is something like:
cmake_minimum_required(VERSION 3.0)
project(FOO)
add_subdirectory(externals/BAR)
set(SOURCES ${CMAKE_SOURCE_DIR}/src/foo.cpp
${CMAKE_SOURCE_DIR}/include/bar.hpp)
add_library(${PROJECT_NAME} SHARED ${SOURCES})
target_include_directories(${PROJECT_NAME} PUBLIC
${CMAKE_SOURCE_DIR}/include # this works
${BAR_INCLUDE_DIR}) # this does not
The problem is, the include directories of the added project are not accessable from FOO project.
BAR is a huge dependency with its own sub directories. Its root cmake uses INCLUDE_DIRECTORIES command (instead of target_include_directories). It then does some FILE ( GLOB headers and use them when triggering make install
I am aware that there are many questions regarding subdirectories in cmake, however, in my case cannot modify the CMakeLists.txt in the subdirectory. It's a git submodule that gets updated constantly and it's a pain to modify it constantly.
How can I access BAR's include directories (and later its libraries) without changing its cmakes?
P.S. BAR gets compiled properly and shared library appears in the build folder.

verifying my understanding of a CMakeLists.txt file

I successfully played trial-and-error with a CMakeLists.txt file, and now everything builds. However, I want to make sure my understanding is correct about how it's working.
I have a project with the following structure:
./
├── bin
├── CMakeLists.txt
├── include
│   └── sql-writer
│   ├── credentials.h
│   ├── database.h
│   ├── datetime.h
│   ├── market_bar.h
│   └── writer.h
├── README.md
├── src
│   ├── CMakeLists.txt
│   ├── database.cpp
│   ├── main.cpp
│   └── writer.cpp
└── test
├── CMakeLists.txt
├── test-main.cpp
├── test_sym_table_pairs
└── writer_tests.cpp
This builds an executable, a static library, and a test executable. The problem was src/CMakeLists.txt:
set(HEADER_DIR ${sql-writer_SOURCE_DIR}/include/sql-writer)
add_executable(sql-writer-demo main.cpp ${HEADER_DIR})
target_include_directories(sql-writer-demo PUBLIC ${HEADER_DIR}) #!
target_link_libraries(sql-writer-demo sql-writer-lib mysqlcppconn ${Boost_LIBRARIES})
file(GLOB SOURCES ${sql-writer_SOURCE_DIR}/src/*.cpp)
file(GLOB HEADERS ${HEADER_DIR}/*.h)
add_library(sql-writer-lib ${HEADERS} ${SOURCES})
target_link_libraries(sql-writer-lib mysqlcppconn ${Boost_LIBRARIES})
target_include_directories(sql-writer-lib PUBLIC ${HEADER_DIR})
the first line's set command just defines HEADER_DIR. This is easy.
target_include_directories(sql-writer-demo PUBLIC ${HEADER_DIR}) must be included after the first add_execuable because otherwise header files will need to be included with relative paths. Is that right? Why does it come after? I've heard cmake treats include directories as "separate" from "sources" (cmake sources not .cpp files). What does that mean?
target_link_libraries(sql-writer-demo sql-writer-lib mysqlcppconn ${Boost_LIBRARIES}) needs to come after add_executable(sql-writer-demo main.cpp ${HEADER_DIR}) as well, and it tells how to link to other libraries.
file(GLOB SOURCES ${sql-writer_SOURCE_DIR}/src/*.cpp) is not recomended before only because the directory/file structure could change, and then this wouldn't work anymore
target_link_libraries(sql-writer-lib mysqlcppconn ${Boost_LIBRARIES}) needs to come after add_library(sql-writer-lib ${HEADERS} ${SOURCES}) because the compulation of my library sql-writer-lib needs to use other libs, and this is where we tell cmake to look out for those.
You typically don't include headers into add_executable and add_library, but only implementation files:
add_executable(sql-writer-demo main.cpp)
add_library(sql-writer-lib ${SOURCES})
add_executable and add_library create a target, target_include_directories specifies include directories for this target. The documentation is pretty clear:
The named <target> must have been created by a command such as add_executable() or add_library() and must not be an IMPORTED target.
If you use target_include_directories(sql-writer-lib PUBLIC ${HEADER_DIR}) with PUBLIC specifier, you don't need to use target_include_directories to specify include directories for targets that link against that library, because the dependency will be propagated automatically. The same applies to target_link_libraries.
So, src/CMakeLists.txt
file(GLOB SOURCES ${sql-writer_SOURCE_DIR}/src/*.cpp)
add_library(sql-writer-lib ${SOURCES})
target_link_libraries(sql-writer-lib PUBLIC mysqlcppconn ${Boost_LIBRARIES})
target_include_directories(sql-writer-lib PUBLIC ${HEADER_DIR})
add_executable(sql-writer-demo main.cpp)
target_link_libraries(sql-writer-demo sql-writer-lib)
should work. Whether you want to use PUBLIC on mysqlcppconn ${Boost_LIBRARIES} depends on the structure of your project. I specified it as an example only.

Cmake: Exporting subproject targets to main project

I currently have a project called LIBS with a structure like this:
├── Lib1
│ ├── CMakeLists.txt
│ ├── lib1-class.cpp
│ └── lib1-class.h
├── lib2
│ └── CMakeLists.txt
│ ├── lib2-class.cpp
│ ├── lib2-class.h
├── cmake
│ └── LIBSConfig.cmake.in
├── CMakeLists.txt
in the main cmake file, I have:
install(
TARGETS
lib1
lib2
DESTINATION
${PROJECT_DIRNAME_lib}
EXPORT
${PROJECT_NAME}Exports
)
install(
EXPORT
${PROJECT_NAME}Exports
DESTINATION
${PROJECT_DIRNAME_lib}
)
as I want to export these in a package that is discoverable by find_package().
My problem is that I generate lib1 and lib2 in their respective directories and when installing them, Cmake tells me that
Error:install TARGETS given target "lib1" which does not exist in this directory.
As suggested here, My understanding is that I should use Export() and in lib1 and lib2, have something of the form:
export(TARGETS lib1 FILE lib1Exports.cmake)
and in the LIBS project, have something like this:
ADD_LIBRARY(lib1 UNKNOWN IMPORTED)
set_property(TARGET lib1 PROPERTY IMPORTED_LOCATION lib1)
However it does not like me using the same name for this library that is being added from the parent project. It tells me:
Error:add_library cannot create imported target "lib1" because another target with the same name already exists.
so the library is available and I can link to it, etc. if I were to create another target in the parent directory, but I can't install it.
I have found the exact same problem in a bug report here but I believe cmake handles things differently now and I am just not doing it correctly.
So am I doing it wrong? I would like to avoid using external packages if possible.
Update: the accepted solution works only for cases where there is no dependency between lib1, lib2. In that case one should use the solution provided to this question.
As noted in the bugreport you refer to install() command should be issued from the same directory where target is created. As you have libraries target created in different directories, you need to assign different export names for them, and, consequently, different export files.
But you are free to include both export files into the LIBSConfig.cmake script:
cmake/LIBSConfig.cmake:
get_filename_component(SELF_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
include(${SELF_DIR}/LIBS-lib1.cmake)
include(${SELF_DIR}/LIBS-lib2.cmake)
lib1/CMakeLists.txt:
add_library(lib1 ...)
install(TARGET lib1 EXPORT lib1-export ...)
lib2/CMakeLists.txt:
add_library(lib2 ...)
install(TARGET lib2 EXPORT lib2-export ...)
CMakeLists.txt:
add_subdirectory(lib1)
add_subdirectory(lib2)
install(EXPORT lib1-export FILENAME LIBS-lib1.cmake DESTINATION lib/LIBS)
install(EXPORT lib2-export FILENAME LIBS-lib2.cmake DESTINATION lib/LIBS)
install(FILES cmake/LIBSConfig.cmake DESTINATION lib/LIBS)
Note, that export command exports build tree. It is usually not suitable for find_package, which is normally used for find installed files.