Install nested static library, and target_link_library not working
File structure:
HelloLib
WorldLib
CMakeLists.txt
WorldLib.cpp
WorldLib.h
CMakeLists.txt
HelloLib.cpp
HelloLib.h
CMakeLists.txt
main.cpp
main.cpp
#include <iostream>
#include "HelloLib/HelloLib.h"
int main() {
std::cout << hello() << std::endl;
return 0;
}
CMakeLists.txt (root)
cmake_minimum_required(VERSION 3.16.3)
project(HelloWorld)
add_executable(${PROJECT_NAME} main.cpp)
add_subdirectory(HelloLib)
target_link_libraries(
${PROJECT_NAME}
HelloLib
)
HelloLib.cpp
#include <string>
#include "WorldLib/WorldLib.h"
std::string hello() { return "Hello " + world(); }
HelloLib.h
#pragma once
#include <string>
std::string hello();
CMakeLists.txt (HelloLib)
add_library(HelloLib HelloLib.cpp)
add_subdirectory(WorldLib)
target_link_libraries(
HelloLib
WorldLib
)
WorldLib.cpp
#include <string>
std::string world() { return "World!"; }
WorldLib.h
#pragma once
#include <string>
std::string world();
CMakeLists.txt (WorldLib)
add_library(WorldLib WorldLib.cpp)
Build and run, successfully print out Hello World!
Now I would like to make HelloLib as static library
Change CMakeLists.txt (HelloLib), install to /usr/lib and /usr/include
add_library(HelloLib HelloLib.cpp)
add_subdirectory(WorldLib)
target_link_libraries(
HelloLib
WorldLib
)
install(
TARGETS HelloLib
ARCHIVE DESTINATION lib
)
install(
DIRECTORY "${CMAKE_SOURCE_DIR}/" # source directory
DESTINATION "include" # target directory
FILES_MATCHING # install only matched files
PATTERN "*.h" # select header files
)
Run (make install) (CMAKE_INSTALL_PREFIX=/usr)
mkdir -p out/build
cmake -S src -B out/build -DCMAKE_INSTALL_PREFIX=/usr
cd out/build
make
make install
[ 33%] Built target WorldLib
[ 66%] Built target HelloLib
[100%] Built target HelloWorld
Install the project...
-- Install configuration: ""
-- Installing: /usr/lib/libHelloLib.a
-- Up-to-date: /usr/include
-- Installing: /usr/include/HelloLib
-- Installing: /usr/include/HelloLib/HelloLib.h
-- Installing: /usr/include/HelloLib/WorldLib
-- Installing: /usr/include/HelloLib/WorldLib/WorldLib.h
Static library (.a) should generate to /usr/lib/libHelloLib.a
Let's test it, change CMakeLists.txt (root)
cmake_minimum_required(VERSION 3.16.3)
project(HelloWorld)
add_executable(${PROJECT_NAME} main.cpp)
add_subdirectory(HelloLib)
target_link_libraries(
${PROJECT_NAME}
/usr/lib/libHelloLib.a
)
Then build, it give undefined reference error
/bin/ld: /usr/lib/libHelloLib.a(HelloLib.cpp.o): in function `hello[abi:cxx11]()':
HelloLib.cpp:(.text+0x20): undefined reference to `world[abi:cxx11]()'
What's wrong with me? This can be successful with no nested library
What is the correct way to install nested static library?
The issue here is not the install process but the fact that you link only to libHelloLib.a.
Your libHelloLib.a need the symbol in libWorldLib.a because libHelloLib.a is a static lib and so only contains its own symbol. It does not contains the symbol world that is defined in libWorldLib.a.
To make your project works, you need to install WorldLib and HelloLib and links against this two lib.
target_link_libraries(
${PROJECT_NAME}
HelloLib
WorldLib
)
Or you can change HelloLib into a shared library. This way the libHelloLib.so will also contains the symbol of WorldLib.
You can also look at this Exported Target.
It's an install command that will create some FindXX.cmake file that you will be able to use with the find_package command. You also will be able to defined the dependency of your lib. But if you want that HelloLib stay a static library you will have to install WorldLib
Related
I am trying to install a custom library with CMake.
It consists of 2 'sub-libraries' and a main header file which includes the 'sub-libraries'.
I think it installs okay (The output looks like this:)
-- Configuring done
-- Generating done
-- Build files have been written to: /home/pi/Desktop/Projects/MaxLib/build
[ 20%] Building CXX object Geom/CMakeFiles/Geom.dir/Geom.cpp.o
[ 40%] Linking CXX shared library libGeom.so
[ 40%] Built target Geom
[ 60%] Building CXX object File/CMakeFiles/File.dir/File.cpp.o
[ 80%] Linking CXX shared library libFile.so
[ 80%] Built target File
[100%] Built target MaxLib
Install the project...
-- Install configuration: "Debug"
-- Installing: /usr/local/lib/libMaxLib.a
-- Installing: /usr/local/include/MaxLib.h
-- Installing: /usr/local/include/MaxLib/File.h
-- Installing: /usr/local/include/MaxLib/Geom.h
However, when I try to compile a program using it, I receive a number of "undefined reference to" errors. Where have I gone wrong?
The Main Header looks like:
#include "MaxLib/File.h"
#include "MaxLib/Geom.h"
It's CMakefile looks like this:
cmake_minimum_required(VERSION 3.5)
project(MaxLib)
add_library(${PROJECT_NAME}
"${CMAKE_CURRENT_SOURCE_DIR}/MaxLib.h"
)
add_subdirectory(File)
add_subdirectory(Geom)
target_link_libraries(${PROJECT_NAME} File)
target_link_libraries(${PROJECT_NAME} Geom)
# Install Library
install (TARGETS ${PROJECT_NAME}
LIBRARY DESTINATION lib)
# Install Main Header File
INSTALL(FILES "${CMAKE_CURRENT_SOURCE_DIR}/MaxLib.h" DESTINATION include) #
INSTALL(FILES ...) or install(DIRECTORY ...)
# Build list of header files to install from other directorys. Root == "."
set(HEADER_DIRS "File" "Geom")
## Add Source Files from the other directories
foreach(DIR ${HEADER_DIRS})
# Find all source files & append to list
if(DIR STREQUAL ".")
file(GLOB HEADER_FILES_IN_FOLDER *.h)
else()
file(GLOB HEADER_FILES_IN_FOLDER ${CMAKE_CURRENT_SOURCE_DIR}/${DIR}/*.h)
endif()
list(APPEND HEADER_FILES ${HEADER_FILES_IN_FOLDER})
endforeach()
# Install Header files
INSTALL(FILES ${HEADER_FILES} DESTINATION include/${PROJECT_NAME}) # INSTALL(FILES ...) or install(DIRECTORY ...)
target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic -g)
set_target_properties(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX)
The 'sub-libraries's CMakefiles look like this:
project(Geom)
add_library(${PROJECT_NAME} SHARED
${CMAKE_CURRENT_SOURCE_DIR}/Geom.cpp
)
target_include_directories(${PROJECT_NAME}
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
I am trying to include the library like this:
#include <MaxLib.h>
And I am adding -lMaxLib to its makefile
Edit:
A basic program like this:
#include <MaxLib.h>
int main() {
float y1 = MaxLib::Geom::CleanAngle(7654);
}
Will produce the undefined reference error:
/usr/bin/ld: warning: libFile.so, needed by //usr/local/lib/libMaxLib.so, not found (try using -rpath or -rpath-link) // This shows when add_library marked SHARED
/usr/bin/ld: warning: libGeom.so, needed by //usr/local/lib/libMaxLib.so, not found (try using -rpath or -rpath-link) // This shows when add_library marked SHARED
/home/pi/Desktop/Projects/TestProgram/main.cpp:43: undefined reference to `MaxLib::Geom::CleanAngle(double)'
I have set up a CMake project whose directory structure looks as follows:
src/
--CMakeLists.txt
--libA/
----CMakeLists.txt
----foo.h
----foo.cpp
--main/
----CMakeLists.txt
----main.cpp
src/CMakeLists.txt uses add_subdirectory to pull in libA and main. libA/CMakeLists.txt uses add_library to define a library called libA, which exports foo.h via target_include_directories. If I now link against libA in main using target_link_library, I can include foo.h via #include <foo.h> in main.cpp.
Question: Is it possible to provide the public interface of libA with a prefix, so that I can (and have to) write #include <libA/foo.h> in main.cpp instead?
This is an old question but I was having exactly the same issue. I ended up getting around this by adding an export_headers() function that creates symbolic links to the headers within the binary:
function(export_headers TARGET HEADER_SOURCE_DIR HEADER_DEST_DIR)
# Put all headers that are in the source directory into EXPORT_HEADERS variable
file(GLOB_RECURSE EXPORT_HEADERS CONFIGURE_DEPENDS
RELATIVE "${HEADER_SOURCE_DIR}"
"${HEADER_SOURCE_DIR}/*.h"
)
# For each header that will be exported
foreach(HEADER ${EXPORT_HEADERS})
# Get the directory portion that needs to be created
get_filename_component(HEADER_DIRECTORY "${HEADER}" DIRECTORY)
# Create the directory
add_custom_command(TARGET ${TARGET} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "${HEADER_DEST_DIR}/${HEADER_DIRECTORY}"
)
if (MSVC)
# Make a hard link to the file
add_custom_command(TARGET ${TARGET} POST_BUILD
COMMAND if not exist "${HEADER_DEST_DIR}/${HEADER}" \( mklink /h "${HEADER_DEST_DIR}/${HEADER}" "${HEADER_SOURCE_DIR}/${HEADER}" \)
)
else()
# Make a symbolic link to the file
add_custom_command(TARGET ${TARGET} POST_BUILD
COMMAND ln -sf "${HEADER_SOURCE_DIR}/${HEADER}" "${HEADER_DEST_DIR}/${HEADER}"
)
endif()
endforeach(HEADER)
endfunction()
You would call this with something like:
add_library(libA STATIC ${LIBA_SOURCES}
export_headers(libA ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}/include/libA)
target_include_directories(libA INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/include)
Then, if you link to libA, you will be able to #include <libA/foo.h>.
You can use the root source directory (or some other directory which is a parent to libA) in the target_include_directories() call. This will allow the INTERFACE_INCLUDE_DIRECTORIES target property to be defined with respect to another directory (in this example, the CMAKE_SOURCE_DIR). So it would look something like this:
In libA/CMakeLists.txt:
add_library(libA foo.cpp)
# Define the include files with respect to the directory above this.
target_include_directories(libA PUBLIC ${CMAKE_SOURCE_DIR})
The main/main.cpp file:
#include <iostream>
#include <libA/foo.h>
int main() {
FooClass fooclass;
fooclass.myFunction();
std::cout << "Hello World!" << std::endl;
return 0;
}
I'm stuck to import a shared library in another project with CMake.
my-utils-lib
My lib files are generated in my-utils-lib project:
CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(my-utils-lib VERSION 1.0.0 DESCRIPTION "Utils for C++.")
set(CMAKE_CXX_STANDARD 14)
add_library(
my-utils-lib SHARED
./src/string_utils/find_matches.cpp
./src/string_utils/split.cpp
./src/string_utils/format.cpp
./src/vector_utils/print_vector.cpp
)
set_target_properties(
my-utils-lib PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION 1
)
target_include_directories(my-utils-lib PRIVATE src)
include(GNUInstallDirs)
install(
TARGETS my-utils-lib
DESTINATION ${CMAKE_INSTALL_LIBDIR}
EXPORT my-utils-lib
)
configure_file(my-utils-lib.pc.in my-utils-lib.pc #ONLY)
install(
FILES ${CMAKE_BINARY_DIR}/my-utils-lib.pc
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig
)
I run mkdir build && cd ./build/ && cmake .. && sudo make install to generate and install my library files.
Finally, my-utils-lib project tree looks like this:
CMakeLists.txt
my-utils-lib.pc.in
src/
|_main.cpp
|_main.h
|_string_utils/
|_find_matches.cpp
|_find_matches.h
|_format.cpp
|_format.h
|_split.cpp
|_split.h
|_vector_utils/
|_print_vector.cpp
|_print_vector.h
my-project
In another project, I'd like to import "split.h" in one of my local headers without having to enter the full path to the actual file. Something like:
#include "my-utils-lib/string_utils/split.h"
I tried many solution so far, none of them is working. My last attempt is:
CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(longer_sub_sequence)
set(CMAKE_CXX_STANDARD 14)
find_package(Qt5Widgets REQUIRED)
find_library(my-utils-lib libmy-utils-lib.1.0.0.dylib)
add_library(my-utils-lib SHARED IMPORTED)
add_executable(
longer_sub_sequence
main.cpp
visualization/draw_matrix.cpp
visualization/draw_matrix.h
)
target_link_libraries(longer_sub_sequence my-utils-lib Qt5::Widgets)
CMake autobuild runs fine (I am using CLion as an IDE) but I can't find how to import any code from my library.
I checked in /usr/local/lib (where CMake generated my lib files), and found the correct lib files :
find . -maxdepth 1 -name "*my-utils-lib*" -print
./libmy-utils-lib.1.0.0.dylib
./libmy-utils-lib.1.dylib
./libmy-utils-lib.dylib
What am I missing here ?
You are missing to install the library header.
You should add an install command for header just like you did with the library binary.
Moreover you should consider using comparison signs (< and >) instead of double quote for include directive :
#include <my-utils-lib/string_utils/split.h>
One last remark, you are generating and installing a .pc file which is used by pkg-config only and you're not using it.
I am trying to learn CMake and for that purpose I am working with an example project of the structure shown below. I am trying to configure all the CMakeLists.txt files such that after make install I can copy the resulting build directory and copy-paste it around freely such that other people are able run the executable.
Problem: after running make install (maxOS 10.14.6 (Darwin 18.7.0)) everything works if I run the executable inside build BUT if I move the build directory from its original location - for example to Desktop - the executable is unable to find the shared libraries. It seems that the reason for this is that the paths to the shared libraries are defined as absolute paths instead of relative paths with respect to the build directory.
Question: How can I build the project such that the executable finds the shared libs?
Project structure:
myapp
|
CMakeLists.txt (top-level)
- build
- app
| CMakeLists.txt (app)
| - inc
| - app
| app.h
| - src
| app.cpp
| main.cpp
|
- external
|
- mylib
CMakeLists.txt (mylib)
- inc
- mylib
mylib.h
- src
mylib.cpp
CMakeLists:
CMakeLists.txt (top-level)
cmake_minimum_required(VERSION 3.0)
project(myapp)
add_subdirectory(external/mylib)
add_subdirectory(app)
CMakeLists.txt (app)
# myapp program
cmake_minimum_required(VERSION 3.0)
project(myapp_prog)
set(SOURCES ./src/)
set(HEADERS ./inc/app/)
set(SOURCE_FILES
${SOURCES}/app.cpp)
set(HEADER_FILES
${HEADERS}/app.h)
# All sources that need to be tested in unit test go into a static library
add_library(myapp_lib SHARED ${HEADER_FILES} ${SOURCE_FILES})
target_include_directories(myapp_lib PUBLIC ${HEADERS})
# The main program
add_executable(prog ./src/main.cpp)
target_include_directories(prog PUBLIC ./inc/)
# Link the libraries
target_link_libraries(prog PRIVATE myapp_lib mylib)
install(TARGETS myapp_lib DESTINATION ${CMAKE_BINARY_DIR}/lib)
CMakeLists.txt (mylib)
# mylib
cmake_minimum_required(VERSION 3.0)
project(mylib)
set(SOURCES src/)
set(HEADERS inc/mylib/)
set(SOURCE_FILES
${SOURCES}/mylib.cpp)
set(HEADER_FILES
${HEADERS}/mylib.h)
add_library(mylib SHARED ${HEADER_FILES} ${SOURCE_FILES})
target_include_directories(mylib PUBLIC inc/)
install(TARGETS mylib DESTINATION ${CMAKE_BINARY_DIR}/lib)
C++ code:
mylib.h
void mylib_print_hello();
mylib.cpp
#include "library.h"
#include <iostream>
void mylib_print_hello() {
std::cout << "Hello from mylib!" << std::endl;
}
app.h
void myapp_hello();
app.cpp
#include <iostream>
#include "app.h"
void myapp_hello()
{
std::cout << "Hello from myapp!" << std::endl;
}
main.cpp
#include <iostream>
#include "app/app.h"
#include "mylib/mylib.h"
int main()
{
myapp_hello();
mylib_print_hello();
}
I think the issue is that you might be confusing the build step with the install step. By using ${CMAKE_BINARY_DIR}, you are passing an absolute path to your binary directory (build), which may interfere with the moving of the build afterwards. If you would like to ouput your libraries to a folder after the build, you may set the target of the library with:
set_target_property(<library-name> PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
This will affect only the build stage of your project. As far as installs go, you are better off using only relative paths in the install directives. These paths will be relative to your ${CMAKE_INSTALL_PREFIX} variable. For example, you would use:
install(TARGETS mylib DESTINATION lib)
To install the library under the ${CMAKE_INSTALL_PREFIX}/lib folder. Best practice states that you should NEVER manipulate this variable directly in your CMakeLists.txt files, as it could break a build somewhere down the line. Starting with CMake 3.15, it is possible to use cmake --install to install a project. This command allows setting an installation prefix, such as:
cmake --install . --prefix desired/install/path
With this, all your link path should stay valid.
Please add a macos tag to this question.
I think that you are looking for a relative RPATH. CMake builds the executable with an absolute RPATH, so if you move or rename the build directory the program stop working. In your example, you only need to modify the main program CMakeLists.txt, to change the value of the RPATHs that CMake inserts in your executable.
# The main program
add_executable(prog ./src/main.cpp)
target_include_directories(prog PUBLIC ./inc/)
set_target_properties(prog PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE INSTALL_RPATH "#executable_path/;#executable_path/../external/mylib")
You can verify the values of the LC_RPATHs with this command from your build directory:
$ otool -l app/prog
For dependencies between libraries, it may also be useful "#loader_path" instead of #executable_path. And for other Unix operating systems, use "$ORIGIN". More information here.
In my situation I have two CMake projects (built using MSYS Makefiles if that matters):
One is a shared library that uses Zlib and Libzip to provide zipping functionality to the library user, let's call it mylib.
Another is an app that uses mylib to do some zipping stuff. It doesn't have to worry about Zlib, Libzip or anything. Let's call it app.
I have created a bare-bones example below that demonstrates the bizarre issue I'm having. Simply put, app refuses to link when running make, but only if some Libzip code is left uncommented in mylib (which compiles and links fine, no matter what).
Zlib and Libzip source code is located in C:/Dev/Libs.
lib/CMakeLists.txt
cmake_minimum_required(VERSION 3.7)
# Set project
project(mylib)
set(LIBS_DIR "C:/Dev/Libs")
# Add mylib sources
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/src")
file(GLOB_RECURSE SRCS "src/*.cpp")
add_library(mylib SHARED ${SRCS})
# Add Zlib
set(ZLIB_ROOT "${LIBS_DIR}/zlib-1.2.11")
include_directories(${ZLIB_ROOT})
target_include_directories(mylib PUBLIC ${ZLIB_ROOT})
file(GLOB ZLIB_SRCS "${ZLIB_ROOT}/*.c")
add_library(zlib STATIC ${ZLIB_SRCS})
# Add Libzip
set(LIBZIP_ROOT "${LIBS_DIR}/libzip-1.1.3")
include_directories("${LIBZIP_ROOT}/lib")
target_include_directories(mylib PUBLIC "${LIBZIP_ROOT}/lib")
file(GLOB LIBZIP_SRCS "${LIBZIP_ROOT}/lib/*.c")
add_library(libzip STATIC ${LIBZIP_SRCS})
set(LIBS_LINK zlib libzip)
set(LIBS_INSTALL zlib libzip)
set(LIBS_EXPORT zlib libzip)
target_link_libraries(mylib PUBLIC ${LIBS_LINK})
# https://rix0r.nl/blog/2015/08/13/cmake-guide/
# Define headers for this library. PUBLIC headers are used for
# compiling the library, and will be added to consumers' build
# paths.
target_include_directories(mylib PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
$<INSTALL_INTERFACE:include>
PRIVATE src)
# 'make install' to the correct locations (provided by GNUInstallDirs).
include(GNUInstallDirs)
install(TARGETS mylib ${LIBS_INSTALL} EXPORT mylibConfig
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) # This is for Windows
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
# This makes the project importable from the install directory
# Put config file in per-project dir (name MUST match), can also
# just go into 'cmake'.
install(EXPORT mylibConfig DESTINATION share/mylib/cmake)
# This makes the project importable from the build directory
export(TARGETS mylib ${LIBS_EXPORT} FILE mylibConfig.cmake)
lib/src/lib.hpp
#pragma once
namespace lib
{
int func();
}
lib/src/lib.cpp
#include "lib.hpp"
#define ZIP_STATIC
#include "zip.h"
int lib::func()
{
zip_source_t *src;
zip_error_t error;
src = zip_source_buffer_create(0, 1, 0, &error);
return 10;
}
app/CMakeLists.txt
cmake_minimum_required(VERSION 3.7)
project(app)
# Add app sources
file(GLOB_RECURSE SRCS "src/*.cpp")
add_executable(app ${SRCS})
# Add mylib
set(mylib_DIR "${CMAKE_CURRENT_BINARY_DIR}/../lib")
find_package(mylib REQUIRED)
configure_file("${mylib_DIR}/libmylib.dll" "libmylib.dll" COPYONLY)
target_link_libraries(app mylib)
app/src/app.hpp
#pragma once
#include <lib.hpp>
app/src/app.cpp
#include <iostream>
#include "app.hpp"
int main()
{
std::cout << lib::func() << std::endl;
return 0;
}
Output from building mylib
$ make
[ 13%] Built target zlib
[ 98%] Built target libzip
Scanning dependencies of target mylib
[ 99%] Building CXX object CMakeFiles/mylib.dir/src/lib.cpp.obj
[100%] Linking CXX shared library libmylib.dll
[100%] Built target mylib
Output from building app
$ make
Scanning dependencies of target app
[ 50%] Building CXX object CMakeFiles/app.dir/src/app.cpp.obj
[100%] Linking CXX executable app.exe
CMakeFiles/app.dir/objects.a(app.cpp.obj):app.cpp:(.text+0x17): undefined reference to `lib::func()'
collect2.exe: error: ld returned 1 exit status
make[2]: *** [app.exe] Error 1
make[1]: *** [CMakeFiles/app.dir/all] Error 2
make: *** [all] Error 2
If I comment src = zip_source_buffer_create(0, 1, 0, &error); in lib/src/lib.cpp, rebuild and then rebuild app:
$ make
-- Configuring done
-- Generating done
-- Build files have been written to: C:/Dev/Builds/cmaketest/app
[ 50%] Linking CXX executable app.exe
[100%] Built target app
$ ./app
10
Which yields the expected result.
The issue must lie in the CMakeLists of app, since mylib builds fine without any linking errors. Or am I adding zlib/libzip incorrectly in mylib? I need to link to them statically and I couldn't get it to work with find_package.
Thanks!
Got it working after copying over the .dll.a file as well:
configure_file("${mylib_DIR}/libmylib.dll.a" "libmylib.dll.a" COPYONLY)
and prefixing all my methods in lib with the following:
__declspec(dllexport)