How do I properly link multiple kinds of libraries in CMake? - c

I'm making a CMake project on MacOS, and my project requires libraylib.a and multiple different frameworks. This is what I have:
cmake_minimum_required(VERSION 3.10)
project(RaylibHW)
# Adding executable
add_executable(rlhw my_app.c)
#linking C archives
# Linking frameworks
if(APPLE)
add_library(libraylib STATIC libraylib.a)
target_link_libraries(rlhw PRIVATE
"-framework CoreVideo"
"-framework IOKit"
"-framework Cocoa"
"-framework GLUT"
"-framework OpenGL"
)
endif()
I can link either the frameworks or the archive file, but since apparently it's doesn't work to use target_link_libraries more than once, I can't seem to do it.

Related

How to build static library from header-only library

I am trying to build static library of project stb, so I can link it in another project (not written in C/C++).
I have created CMakeLists.txt file to build it using CMake, however the built static library file is empty.
I am suspecting this is due to fact that the stb seems to be header-only library. I tried setting the LIBRARY_HEADER_ONLY flag with target_compile_definitions, this however did not solve my problem.
How can I build header-only library to static library file (*.a)? Or is it even possible? If not, what are workarounds if any?
This is my CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(stb C)
set(MAKE_GENERATOR_PLATFORM x64)
set(CMAKE_C_FLAGS -m64)
set(CMAKE_CXX_FLAGS -m64)
option(STB_IMAGE_IMPLEMENTATION "stb_image implementation" ON)
if(STB_IMAGE_IMPLEMENTATION)
add_definitions(-DSTB_IMAGE_IMPLEMENTATION)
endif()
option(POSITION_INDEPENDENT_LIB "Use position independent code for static library (if applicable)" ON)
set(SOURCE_FILES stb_image.h stb_truetype.h stb_dxt.h)
add_library(stb_static STATIC ${SOURCE_FILES})
target_compile_definitions(stb_static PUBLIC LIBRARY_HEADER_ONLY)
set_target_properties(stb_static PROPERTIES LINKER_LANGUAGE C)
set_target_properties(stb_static PROPERTIES
OUTPUT_NAME stb
POSITION_INDEPENDENT_CODE ${POSITION_INDEPENDENT_LIB})
To get an object files and build a static library from the header-only library the extra .c file must exists. This is because .c files are the ones which contains instructions and code.
Different toolchains may interpret .h file differently so the easy and portable way to create the implementation is to create a new .c file.
#define STB_IMAGE_IMPLEMENTATION
#define STB_DXT_IMPLEMENTATION
#define STB_TRUETYPE_IMPLEMENTATION
#include "stb_image.h"
#include "stb_dxt.h"
#include "stb_truetype.h"
Including the header files (stb_image.h) and defining the requried macros (STB_IMAGE_IMPLEMENTATION) in the .c file is the solution to get object files and static library (with proper content) out of the build.

linking a shared library with statics using CMake

cmake 2.8
gcc (GCC) 4.8.1
Edit ----------
Wrapping the static libraries in whole-archive works for every library except the pjmedia-videodev The problem now is that when I try and build I get the following error.
cbar_factory_init': colorbar_dev.c:(.text+0x2a0): undefined reference to pjmedia_format_init_video'
Hello,
I have created a shared library and I need to link that library with about 10 static libraries. I then link my executable with the shared library.
My question is that when I run make it fails to link as it wants the static libraries as well. The purpose of is to create a wrapper for the static libraries. So the executable only has to link with 1 single shared library. As I am linking the shared library with the statics, then the statics will automatically become part of the source code of the shared library.
Only code sippnets to make it short.
In my CMakeLists.txt that creates the shared library and links the static libraries:
add_library(app_module_sip SHARED app_module_sip_init.c)
set(PJSIP_LIBRARIES
g7221codec
gsmcodec
ilbccodec
milenage
pj
pjlib-util
pjmedia
pjmedia-codec
pjmedia-audiodev
pjmedia-videodev
pjnath
pjsip
pjsip-simple
pjsip-ua
pjsua
portaudio
resample
speex
srtp
)
target_link_libraries(app_module_sip pthread m uuid nsl rt asound crypto ssl ${PJSIP_LIBRARIES})
Now my CMakeLists.txt that makes the executable
add_executable(app sip_test.c)
target_link_libraries(app app_module_sip)
Is this correct what I am doing here. I don't want to link the executable with the static libraries. Just the single shared library as that is my wrapper what I will be calling the functions in.
It does link ok, if I link all the statics libraries when making the executable, but that is not the result I want.
Many thanks for any suggestions,
I tried to test my solution but your CMakeLists.txt worked for me without any changes. Still, looking at this question:
Include static lib in dynamic lib,
it appears that you should try
target_link_libraries(app_module_sip ... ssl -Wl,-whole-archive ${PJSIP_LIBRARIES} -Wl,-no_whole-archive)
(scroll to the end, it's a long line)
It's not that simple.
You can look at using '-Wl,--whole-archive' or '-Wl,--export-all-symbols' depending on your platform, but there's no good cross platform way of doing this. Everything does it differently, and windows plays a completely different game using lib.exe.
You probably want to do something like this:
http://www.mail-archive.com/cmake#cmake.org/msg01890.html
...and add support specifically for the platforms you want to support, one at a time.
# Location for shared library
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/libs)
# Create shared library
add_library(app_module_sip SHARED app_module_sip_init.c)
# compile and link for 32 bit mode
set_target_properties(app_module_sip PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
# PJSIP static libraries
set(PJSIP_LIBRARIES
pjsua
pjsip-ua
pjsip-simple
pjsip
pjmedia-codec
pjmedia-videodev
pjmedia
pjmedia-audiodev
pjnath
pjlib-util
resample
milenage
srtp
gsmcodec
speex
ilbccodec
g7221codec
portaudio
pj
)
# Wrap the static libraries in to the shared library
target_link_libraries(app_module_sip -Wl,--start-group ${PJSIP_LIBRARIES} -Wl,--end-group
m uuid nsl rt pthread asound crypto ssl)
Need to wrap the pjsip libraries will the following linker command -Wl,--start-group *.a -Wl,--end-group.
That solved my problem.

CMAKE - How to properly copy static library's header file into /usr/include?

I'm getting into CMAKE usage with C and actually I'm creating two very small static libraries.
My goal is:
The libraries are compiled and linked into *.a files. [THIS
WORKS]
Then I wish to copy that *.a files into /usr/local/lib [THIS ALSO WORKS]
As far as I know about libraries (very little), they are linked using -lnameoflib, which is a compiler flag. OK. I have prepared my CMakeLists.txt and it actually copies *.a files into /usr/local/lib. However, to be able to use them in a program, I also need to copy their header files into /usr/local/include, then I can include them the easy way #include <mylibheader.h>. That's how I understand it now.
And my question is - how is the proper way of copying header files into /usr/include folder with CMAKE? I would like it to copy them automatically when make install is executed, like *.a files are.
For both of the libraries I have a smiliar CMakeLists.txt:
project(programming-network)
add_library(programming-network STATIC
send_string.c
recv_line.c
)
INSTALL(TARGETS programming-network
DESTINATION "lib"
)
A better way for newest cmake version is to use target's PUBLIC_HEADER properties.
project(myproject)
add_library(mylib some.c another.c)
set_target_properties(mylib PROPERTIES PUBLIC_HEADER "some.h;another.h")
INSTALL(TARGETS mylib
LIBRARY DESTINATION some/libpath
PUBLIC_HEADER DESTINATION some/includepath
)
Some ref:
PUBLIC_HEADER
CMake install command
In a much better way, will copy all files that match the pattern and will preserve the directory structure.
INSTALL (
DIRECTORY ${CMAKE_SOURCE_DIR}/include/
DESTINATION include
FILES_MATCHING PATTERN "*.h*")
Years later, with CMake 3.23, we can use FILE_SET for public headers:
project(programming-network)
add_library(programming-network STATIC)
target_include_directories(programming-network PRIVATE "${PROJECT_SOURCE_DIR}")
target_sources(programming-network
PRIVATE send_string.c recv_line.c
PUBLIC FILE_SET HEADERS
BASE_DIRS ${PROJECT_SOURCE_DIR}
FILES publicheader1.h publicheader2.h)
install(TARGETS programming-network FILE_SET HEADERS)
Now let's see what these commands do:
add_library(): defines the name of the target, STATIC for a static library, SHARED for a shared library, OBJECT for objects.
target_include_directories(): this line here is only for when you have subdirectories and private headers referencing each other relative to the project directory. But generally, this command is used for including external headers in a project.
target_sources(): This command is used to add definition files and private headers with PRIVATE keyword. Also, it is used to add public headers via FILE_SET keyword. BASE_DIRS is to turn the absolute path of public headers into a relative path by deducting the base directory from their path. So the this public header
/home/someuser/programming-network/sub1/publicheader1.h
with base dir of
/home/someuser/programming-network/
will be installed in
/cmake/install/prefix/include/sub1/publicheader.h
Note target_sources() can be used in CMakeLists.txt of subdirectories as well.
install(): is to install binaries, static/shared libraries and public headers. The default installation subdirectories are bin, lib and include. You can also change that like this
install(TARGETS myTarget
# for executables and dll on Win
RUNTIME DESTINATION bin
# shared libraries
LIBRARY DESTINATION lib
# for static libraries
ARCHIVE DESTINATION lib
# public headers
INCLUDES DESTINATION include)
And finally, the project is built and installed with (for multi-configuration generators: MS Visual C++, Xcode)
# in project directory
mkdir build
cd build
cmake ..
cmake --build . --config Release
cmake --install . --prefix "/usr/local/" --config Release
For single-configuration generators (make, Ninja), drop the above --config Release terms and change cmake .. to cmake -DCMAKE_BUILD_TYPE=Release ...
I don't think your solution is the correct one. /usr/include should be reserved for your vendor to put files in.
The proper thing to do IMO is to install the header in /usr/local/include and then instruct the user to export CPATH="/usr/local/include:${CPATH}".
It seems /usr/local/lib was search automatically but if you wish to use another dir export LIBRARY_PATH="/usr/local/lib:${LIBRARY_PATH}" works similar for the .a binary (but may or may not work good for shared libraries depending on your os).
Optionally, but more cumbersome is to add -I /usr/local/include and -L /usr/local/lib while compiling.
This is a somewhat subjective answer, but it's been working well for me.
In addition to the accepted answer, if you are creating a lot of libraries and the set_property syntax throws you off. You could wrap it in a very simple macro, such as:
# File: target_public_headers.cmake
macro(target_public_headers TARGET)
set_target_properties(${TARGET} PROPERTIES PUBLIC_HEADER "${ARGN}")
endmacro()
Then you can use it like:
project(myproject)
include(target_public_headers)
add_library(mylib some.c another.c)
target_public_headers(mylib some.h another.h) # <<<<<
# If you're exporting this library then you need to tell
# CMake how to include the "installed" version of the headers.
target_include_directories(mylib
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
PUBLIC $<INSTALL_INTERFACE:some/includepath>
)
INSTALL(TARGETS mylib
LIBRARY DESTINATION some/libpath
PUBLIC_HEADER DESTINATION some/includepath
)

Cmake basic library linking problem

I have a simple problem with the linking of libraries with CMake (I don't know CMake very well).
My configuration is the following :
project/src/CMakeLists.txt (with all .cpp and .h files)
project/support/linux/gmp/include/gmp.h
project/support/linux/gmp/include/gmpxx.h
project/support/linux/gmp/include/libgmp.a
project/support/linux/gmp/include/libgmpxx.a
How to include the library gmp in the process of compilation ? (I am lost between FIND_PACKAGE, INCLUDE_DIRECTORIES, TARGET_LINK_LIBRARIES, ADD_LIBRARY ... commands)
Thank you very much.
CMake is not so hard to understand.
First Step
Use find_package to locate the libary.
find_package(GMP REQUIRED)
Second step
Use include_directories to include the libary header files.
include_directories(${GOBJECT_INCLUDE_DIR})
Third Step
Use target_link_libraries to link your binary against the libary.
add_executable(ExecutableName Main.cpp)
target_link_libraries(ExecutableName ${GOBJECT_LIBRARIES})

Is it possible to get CMake to build both a static and shared library at the same time?

Same source, all that, just want a static and shared version both. Easy to do?
Yes, it's moderately easy. Just use two "add_library" commands:
add_library(MyLib SHARED source1.c source2.c)
add_library(MyLibStatic STATIC source1.c source2.c)
Even if you have many source files, you can place the list of sources in a Cmake variable, so it's still easy to do.
On Windows you should probably give each library a different name, since there is a ".lib" file for both shared and static. But on Linux and Mac you can even give both libraries the same name (e.g. libMyLib.a and libMyLib.so):
set_target_properties(MyLibStatic PROPERTIES OUTPUT_NAME MyLib)
But I don't recommend giving both the static and dynamic versions of the library the same name. I prefer to use different names because that makes it easier to choose static vs. dynamic linkage on the compile line for tools that link to the library. Usually I choose names like libMyLib.so (shared) and libMyLib_static.a (static). (Those would be the names on linux.)
Since CMake version 2.8.8, you can use "object libraries" to avoid the duplicated compilation of the object files. Using Christopher Bruns' example of a library with two source files:
# list of source files
set(libsrc source1.c source2.c)
# this is the "object library" target: compiles the sources only once
add_library(objlib OBJECT ${libsrc})
# shared libraries need PIC
set_property(TARGET objlib PROPERTY POSITION_INDEPENDENT_CODE 1)
# shared and static libraries built from the same object files
add_library(MyLib_shared SHARED $<TARGET_OBJECTS:objlib>)
add_library(MyLib_static STATIC $<TARGET_OBJECTS:objlib>)
From the CMake docs:
An object library compiles source files but does not archive or link
their object files into a library. Instead other targets created by
add_library() or add_executable() may reference the objects using an
expression of the form $<TARGET_OBJECTS:objlib> as a source, where
objlib is the object library name.
Simply put, the add_library(objlib OBJECT ${libsrc}) command instructs CMake to compile the source files to *.o object files. This collection of *.o files is then referred to as $<TARGET_OBJECT:objlib> in the two add_library(...) commands that invoke the appropriate library creation commands that build the shared and static libraries from the same set of object files. If you have lots of source files, then compiling the *.o files can take quite long; with object libraries you compile them only once.
The price you pay is that the object files must be built as position-independent code because shared libraries need this (static libs don't care). Note that position-independent code may be less efficient, so if you aim for maximal performance then you'd go for static libraries. Furthermore, it is easier to distribute statically linked executables.
There is generally no need to duplicate ADD_LIBRARY calls for your purpose. Just make use of
$> man cmake | grep -A6 '^ *BUILD_SHARED_LIBS$'
BUILD_SHARED_LIBS
Global flag to cause add_library to create shared libraries if on.
If present and true, this will cause all libraries to be built shared unless the library was
explicitly added as a static library. This variable is often added to projects as an OPTION
so that each user of a project can decide if they want to build the project using shared or
static libraries.
while building, first (in one out-of-source directory) with -DBUILD_SHARED_LIBS:BOOL=ON, and with OFF in the other.
Please be aware that previous answers won't work with MSVC:
add_library(test SHARED ${SOURCES})
add_library(testStatic STATIC ${SOURCES})
set_target_properties(testStatic PROPERTIES OUTPUT_NAME test)
CMake will create test.dll together with test.lib and test.exp for shared target. Than it will create test.lib in the same directory for static target and replace previous one. If you will try to link some executable with shared target it will fail with error like:
error LNK2001: unresolved external symbol __impl_*.`.
Please use ARCHIVE_OUTPUT_DIRECTORY and use some unique output directory for static target:
add_library(test SHARED ${SOURCES})
add_library(testStatic STATIC ${SOURCES})
set_target_properties(
testStatic PROPERTIES
OUTPUT_NAME test
ARCHIVE_OUTPUT_DIRECTORY testStatic
)
test.lib will be created in testStatic directory and won't override test.lib from test target. It works perfect with MSVC.
It's possible to pack eveything in the same compilation breath, as suggested in the previous answers, but I would advise against it, because in the end it's a hack that works only for simple projects. For example, you may need at some point different flags for different versions of the library (esp. on Windows where flags are typically used to switch between exporting symbols or not). Or as mentionned above, you may want to put .lib files into different directories depending on whether they correspond to static or shared libraries. Each of those hurdles will require a new hack.
It may be obvious, but one alternative that has not been mentionned previously is to make the type of the library a parameter:
set( ${PROJECT_NAME}_LIBTYPE CACHE STRING "library type" )
set_property( CACHE ${PROJECT_NAME}_LIBTYPE PROPERTY STRINGS "SHARED;STATIC" )
add_library( ${PROJECT_NAME} ${PROJECT_NAME}_LIBTYPE ${SOURCE_FILES} )
Having shared and static versions of the library in two different binary trees makes it easier to handle different compilation options. I don't see any serious drawback in keeping compilation trees distinct, especially if your compilations are automated.
Note that even if you intend to mutualize compilations using an intermediate OBJECT library (with the caveats mentionned above, so you need a compelling reason to do so), you could still have end libraries put in two different projects.

Resources