Cmake: How to statically link packages to shared library? - c++

I want to create a .dll library with all its dependencies packed inside the .dll.
However, there seems to be no easy way to achieve that with Cmake. My setup:
cmake_minimum_required(VERSION 3.0.0)
project(Main VERSION 0.1.0)
add_library(Main SHARED Main.cpp)
find_package(libzippp REQUIRED)
target_link_libraries(Main PRIVATE libzippp::libzippp)
This will produce both Main.dll but also libzippp.dll.
I would like to have libzippp.dll packed (statically linked) into Main.dll.

Of course you can't pack one DLL into another. You have to make libzippp a static library in the first place. To do this, build libzippp with BUILD_SHARED_LIBS set to NO at the CMake command line. Then libzippp::libzippp will be a static library when you go to find_package it.
This is easy enough to show steps for:
$ git clone git#github.com:ctabin/libzippp.git
$ cmake -S libzippp -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=NO -DCMAKE_INSTALL_PREFIX=$PWD/local -DLIBZIPPP_BUILD_TESTS=NO
$ cmake --build build --target install
$ tree local
local/
├── include
│   └── libzippp
│   └── libzippp.h
├── lib
│   └── libzippp_static.a
└── share
└── libzippp
├── FindLIBZIP.cmake
├── libzipppConfig.cmake
├── libzipppConfigVersion.cmake
├── libzipppTargets.cmake
└── libzipppTargets-release.cmake

Related

CMake with multiple sub projects building into one directory

I'm not very familiar with CMake and still find it quite confusing. I have a project that has a server and client that I want to be able to run independent of each other but that builds together into the same directory (specifically the top level project build directory kind of like how games have the server launcher and game launcher in the same directory) Currently it just creates a builds directory in each sub project, so one in client, one in server etc.
This is my current project structure
.
├── CMakeLists.txt
├── builds
│   ├── debug
│   └── release
├── client
│   ├── CMakeLists.txt
│   ├── assets
│   └── source
│   └── Main.cpp
├── documentation
├── libraries
│   ├── glfw-3.3.7
│   └── glm
├── server
│   ├── CMakeLists.txt
│   └── source
│   └── Main.cpp
└── shared
├── PlatformDetection.h
├── Utility.h
├── events
└── platform
├── linux
├── macos
└── windows
This is my root CMake file
cmake_minimum_required(VERSION 3.20)
project(Game VERSION 1.0.0)
add_subdirectory(libraries/glfw-3.3.7)
add_subdirectory(client)
add_subdirectory(server)
Client CMake file
cmake_minimum_required(VERSION 3.20)
project(Launcher LANGUAGES CXX VERSION 1.0.0)
set(CMAKE_CXX_STANDARD 23)
set(SOURCE_FILES source/Main.cpp ../shared/events/Event.h ../shared/Utility.h
source/Client.cpp source/Client.h ../shared/PlatformDetection.h ../shared/events/EventManagementSystem.cpp
../shared/events/EventManagementSystem.h)
set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE)
set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
include_directories(${CMAKE_SOURCE_DIR}/libraries/glm)
include_directories(${CMAKE_SOURCE_DIR}/libraries/glfw-3.3.7/include/GLFW)
include_directories(${CMAKE_SOURCE_DIR}/shared)
add_executable(Launcher ${SOURCE_FILES})
target_link_libraries(Launcher LINK_PUBLIC glfw)
Server CMake file
cmake_minimum_required(VERSION 3.20)
project(ServerLauncher LANGUAGES CXX VERSION 1.0.0)
set(CMAKE_CXX_STANDARD 23)
set(SOURCE_FILES source/Main.cpp ../shared/events/Event.h ../shared/Utility.h
../shared/PlatformDetection.h ../shared/events/EventManagementSystem.cpp
../shared/events/EventManagementSystem.h)
include_directories(${CMAKE_SOURCE_DIR}/libraries/glm)
include_directories(${CMAKE_SOURCE_DIR}/shared)
add_executable(ServerLauncher ${SOURCE_FILES})
How can I make the client and server build into the same directory? And can these cmake file structures be improved at all? They seem quite messy and all over the place to me though that may just be due to my unfamiliarity with CMake.
You cannot have multiple subdirectories use the same build directory, but that doesn't seem what you're trying to achieve.
Assuming you don't set the variable CMAKE_RUNTIME_OUTPUT_DIRECTORY anywhere in your project, and you don't specify the RUNTIME_OUTPUT_DIRECTORY target property for any of your targets by some other means, you could simply set the variable in the toplevel CMakeLists.txt before using add_subdirectory:
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin)
add_subdirectory(...)
...
Note that for distributing the program you should be using install() logic:
Client CMakeLists.txt
...
install(TARGETS Launcher RUNTIME)
Server CMakeLists.txt
...
install(TARGETS ServerLauncher RUNTIME)
Note that you may need to add logic for installing dependencies.
Using those install commands allows you to use
cmake --install <build dir> --prefix <install dir>
to install the programs locally in the default directory for binaries on the system. Furthermore it's the basis for packaging your project using cpack.

Integrate thirdpary shared libraries(*.so) into multiple projects using cmake

I have the following layout which contains multiple projects separate directories (It's a CPP project):
Demo
├── Lib
│   ├── CMakeLists.txt
│   ├── inc
│   └── lib
│   ├── crypto_ic.so
│   └── crypto.so
├── Proj1
│   ├── App1
│   └── CMakeLists.txt
└── Proj2
├── App2
└── CMakeLists.txt
I created one separate third-party-lib.bb file to install crypto_ic.so,crypto.so into /usr/lib/ in the target using do_install_append.
All applications are depending on third-party libraries. There are no parent cmake for proj1,proj2 those are independent projects built by *.bb files using yocto.
Now I need to integrate the third party library for all the applications. I created one interface library for the third party headers and using find_package I am able to find the headers and the compilation is working fine.
CMakeLists.txt(Lib)
cmake_minimum_required(VERSION 3.8)
project(crypto VERSION 1.0.0 LANGUAGES CXX)
include(GNUInstallDirs)
find_package(Boost REQUIRED)
add_library(crypto INTERFACE)
target_link_libraries(crypto INTERFACE
Boost::boost
)
target_include_directories(crypto INTERFACE
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/inc/>
$<INSTALL_INTERFACE:include>
)
# Create and Install *-config.cmake,*-version.cmake files
# Install exported targets, i.e. *-targets.cmake
# Install header files
CMakeLists.txt(Proj1)
cmake_minimum_required(VERSION 3.8)
project(App1 VERSION 1.0.0 LANGUAGES CXX)
include(GNUInstallDirs)
find_package(crypto REQUIRED)
add_executable(run Run.cpp)
target_link_libraries(run PUBLIC
crypto
)
But the problem is while linking I am getting a linker error.
Run.cpp: undefined reference to 'Intialize(std::string hashName)'
Initialize function definition is available in the shared library.
Any thoughts on how to fix the issue and how to remove the hardcode path to the library in CMake?.
I am new to CMake and not sure this is a good approach to follow, only reason I created the Interface library is to install the headers in target and find the package. I can modify it if there are some better solutions?
Edit:
I found the issue in the cmake, I didn't link the shared library files (*.so) in the application that's the reason I am getting the linker error. But I am not getting how to link that from the target?

How to automaticaly dowload library from github inside lib folder cmake

I want to create my first big project in c++.
And I need to use some library. So I have made this structure
├── CMakeLists.txt
├── README.md
├── src
│   ├── CMakeLists.txt
│   ├── HelloWorld.cpp
│   ├── HelloWorld.h
│   └── main.cpp
├── tst
| ├── CMakeLists.txt
| ├── HelloWorld-test.cpp
| └── main.cpp
└── lib
and I want to automatically download and place the library in the lib folder. In my case, I want to clone googletest int lib folder. So I have tried that in my main project CMakeLists.txt file:
if(UNIX)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib/googletest)
execute_process(
COMMAND git clone "https://github.com/google/googletest.git" googletest
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib)
add_subdirectory(lib/googletest)
endif(UNIX)
But when I build I have this error :
CMake Error at CMakeLists.txt:17 (add_subdirectory):
add_subdirectory given source "lib/googletest" which is not an existing
directory.
The issue here is that the repository is cloned to the binary directory, but add_subdirectory looks for the directory relative to the source directory.
You need to use add_subdirectory with an absolute path here and pass a build directory. Furthermore add quotes to avoid issues with spaces in the file path.
set(REPO_PARENT "${CMAKE_CURRENT_BINARY_DIR}/lib")
set(REPO_DIR "${REPO_PARENT}/googletest")
set(REPO_BIN_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest_build")
file(MAKE_DIRECTORY ${REPO_DIR})
file(MAKE_DIRECTORY ${REPO_BIN_DIR})
execute_process(
COMMAND git clone "https://github.com/google/googletest.git" googletest
WORKING_DIRECTORY ${REPO_PARENT})
add_subdirectory(${REPO_DIR} ${REPO_BIN_DIR})

Building with libfreenect2

Would anyone be able to post a simple example of how to compile code which uses libfreenect2? After installing the library, the following structure is created in my home directory:
→ tree freenect2
freenect2
├── include
│   └── libfreenect2
│   ├── config.h
│   ├── export.h
│   ├── frame_listener.hpp
│   ├── frame_listener_impl.h
│   ├── libfreenect2.hpp
│   ├── logger.h
│   ├── packet_pipeline.h
│   └── registration.h
└── lib
├── cmake
│   └── freenect2
│   └── freenect2Config.cmake
├── libfreenect2.so -> libfreenect2.so.0.2
├── libfreenect2.so.0.2 -> libfreenect2.so.0.2.0
├── libfreenect2.so.0.2.0
└── pkgconfig
└── freenect2.pc
I attempted to compile with the .pc file using a line similar to this found on the pkg-config wikipedia page:
gcc -o test test.c $(pkg-config --libs --cflags libpng)
But came with up with this error:
./test: error while loading shared libraries: libfreenect2.so.0.2: cannot open shared object file: No such file or directory
Obviously, I messed up the compilation process somewhere, but I'm not sure where to look since this is error occurs on runtime and not at compile time. There's also a .cmake file created with the library install, which I'm sure would lead to a more robust and proper solution, but I'm not entirely sure how to use that and haven't been able to find a simple guide showing how to do so. Any links to beginner-friendly documentation are also appreciated. In the documentation for libfreenect2, it says to use this line when compiling cmake -Dfreenect2_DIR=$HOME/freenect2/lib/cmake/freenect2 -- is this something that I'd have to use when making the library or when making my application?
Another tangentially related question, would it be better to move the /include and /lib directories to /usr/local/include and /usr/local/lib respectively? I believe that would "install" the library system-wide, but I imagine there's some reason that libfreenect2 doesn't do it automatically and I'm not sure what that is.
Well, I just use cmake with a CMakeLists.txt file that I create. Do like this:
Create a CMakeLists.txt file:
cmake_minimum_required(VERSION 2.8 FATAL_ERROR)
project("My Project")
set(CMAKE_CXX_FLAGS "-std=c++11")
find_package(freenect2 REQUIRED)
include_directories("/usr/include/libusb-1.0/")
INCLUDE_DIRECTORIES(
${freenect2_INCLUDE_DIR}
)
add_executable(main ./main.cpp)
target_link_libraries(main ${freenect2_LIBRARIES})
In this file, I assume we want to compile the main.cpp file that uses libfreenect2. So, in your local directory create a build folder, using the terminal:
mkdir build && cd build
Then, run the command in the terminal:
cmake -Dfreenect2_DIR=$HOME/freenect2/lib/cmake/freenect2 .. && make
this should create main executable in the build folder. Please, note that this cmake command specifies the freenect2 directory. In this case I assume it was placed in the /home directory.
However, I understand that having to type that long cmake command or search for it on the terminal history may be boring for some people. So, it is possible to embed the command like this:
cmake_minimum_required(VERSION 2.8 FATAL_ERROR)
project("My Project")
set(CMAKE_CXX_FLAGS "-std=c++11")
# Set cmake prefix path to enable cmake to find freenect2
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} $ENV{HOME}/freenect2/lib/cmake/freenect2)
find_package(freenect2 REQUIRED)
include_directories("/usr/include/libusb-1.0/")
INCLUDE_DIRECTORIES(
${freenect2_INCLUDE_DIR}
)
add_executable(main ./main.cpp)
target_link_libraries(main ${freenect2_LIBRARIES})
After, just run this in the terminal:
mkdir build && cd build && cmake .. & make
This answer was my source for this second way of compiling the code.
Hope this helps!

CMake 'no rule to make target' with external library

I am trying link one of my programs to libevent. I am using CMake as build system. My project structure is as follows:
my_project
├── CMakeLists.txt
├── README.md
├── build
│  └── Build stuff
└── software
├── README.md
├── CMakeLists.txt
├── include
├── libraries
│   ├── libevent
│ │   └── CMakeLists.txt
│   └── anotherlibrary
│      └── CMakeLists.txt
├── prog1
│   ├── CMakeLists.txt
├── prog2
│   ├── CMakeLists.txt
└── prog3
└── CMakeLists.txt
CMakeList.txt of prog1 (the one that's needs to be linked to libevent)
cmake_minimum_required(VERSION 2.6)
project (prog1)
file(GLOB prog1
"*.h"
"*.cpp"
)
include_directories("${PROJECT_INCLUDE_DIR}/libevent/include")
add_executable(${PROJECT_NAME} ${prog1})
target_link_libraries(${PROJECT_NAME} event_core)
But when I build the project make can't find the library build by libevent. it searched for: libraries/libevent/lib/libevent_core.a this is the wrong path since libevent builds it libs inside: my_project/build/software/libraries/libevent/lib/libevent_core.a
How do I tell CMake to search there for the library? I already added the following lines to my Cmake file but this wasn't working
link_directories(/my_project/build/software/libraries/libevent/lib/)
SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/build/lib)
SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/build/bin)
Anyone a suggestion?
I fixed the problem myself by removing the content from the build directory and re running cmake .. inside the build directory.
I think CMake was somehow not aware of the changes I made and by rebuilding the project the problem was fixed.