cmake: target_link_libraries use static library not shared - c++

Is it possible to tell cmake to link against a static library instead of shared?
At the top of my CMakeLists.txt I have the following configured:
set(CMAKE_FIND_LIBRARY_SUFFIXES .a ${CMAKE_FIND_LIBRARY_SUFFIXES})
Later, I add a binary, and tell it to link against tcmalloc in release mode:
target_link_libraries(${BIN_NAME} optimized tcmalloc_minimal)
The resulting makefile links aginst the shared version of tcmalloc:
$ make VERBOSE=1 | grep tcmalloc
/usr/bin/c++ ... -Wl,-Bdynamic ltcmalloc_minimal
Further proof:
$ ldd app
...
libtcmalloc_minimal.so.4 => /usr/local/lib/libtcmalloc_minimal.so.4 (0x00007eff89733000)
...
Both static and shared versions of tcmalloc exist:
$ ls -1 /usr/local/lib/libtcmalloc_minimal*
/usr/local/lib/libtcmalloc_minimal.a
/usr/local/lib/libtcmalloc_minimal_debug.a
/usr/local/lib/libtcmalloc_minimal_debug.la
/usr/local/lib/libtcmalloc_minimal_debug.so
/usr/local/lib/libtcmalloc_minimal_debug.so.4
/usr/local/lib/libtcmalloc_minimal_debug.so.4.2.6
/usr/local/lib/libtcmalloc_minimal.la
/usr/local/lib/libtcmalloc_minimal.so
/usr/local/lib/libtcmalloc_minimal.so.4
/usr/local/lib/libtcmalloc_minimal.so.4.2.6
Question:
How can I configure cmake to link against the static version of tcmalloc?

You can create a helper function which sets CMAKE_FIND_LIBRARY_SUFFIXES at function scope (so therefore doesn't affect the parent scope) which searches for the library in question and sets an output variable with the result
function(find_static_library LIB_NAME OUT)
if (WIN32 OR MSVC)
set(CMAKE_FIND_LIBRARY_SUFFIXES ".lib")
elseif (UNIX)
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
endif()
find_library(
FOUND_${LIB_NAME}_STATIC
${LIB_NAME}
)
if (FOUND_${LIB_NAME}_STATIC)
get_filename_component(ABS_FILE ${FOUND_${LIB_NAME}_STATIC} ABSOLUTE)
else()
message(SEND_ERROR "Unable to find library ${LIB_NAME}")
endif()
set(${OUT} ${ABS_FILE} PARENT_SCOPE)
endfunction()
You can then call this function from somewhere in your CMakeLists.txt to populate a variable with the location of the library.
Failure to find it results in a hard failure
find_static_library(tcmalloc_minimal TCMALLOC)
You can then use this variable in your call to target_link_libraries and be sure you're linking against the static version
target_link_libraries(${BIN_NAME} optimized ${TCMALLOC})
Here you can see the result:
$ make VERBOSE=1 | grep tcmalloc
/usr/bin/c++ ... /usr/local/lib/libtcmalloc_minimal.a ...

If you only need to support non-Windows platforms, then this old email from the CMake mailing list from one of the Kitware developers gives the simplest method. In essence, use find_library() to find the location of the actual library, favouring static libraries over shared ones by listing them first in the names to look for. i.e.
find_library(TCMALLOC_LIB NAMES libtcmalloc_minimal.a tcmalloc_minimal)
You would then link to the library found in the usual way:
target_link_libraries(${BIN_NAME} ${TCMALLOC_LIB})
You could get smarter about how you define the static library name if you need to support platforms where a static library is named something other than lib???.a. You would use CMAKE_STATIC_LIBRARY_PREFIX and CMAKE_STATIC_LIBRARY_SUFFIX variables for that.
On Windows, the problem is that you cannot distinguish between a static library and the import library for a DLL, as discussed in this old issue in the Kitware bug tracker. Both have the file extension .lib, so you can't use the extension to work out if a particular file is a static library or not, unlike Unix-based platforms where you can.

You have to set your CMAKE_FIND_LIBRARY_SUFFIXES variable in this manner:
set(CMAKE_FIND_LIBRARY_SUFFIXES .a)
because in default CMAKE_FIND_LIBRARY_SUFFIXES there is also .so suffix (and it seems not searching in order of insertion). In order to allow portability other suffixes should be added (see here for default values of CMAKE_FIND_LIBRARY_SUFFIXES on different platforms).

Related

How can I use CMake and FindLibXml2 to link against a static version of LibXml2 that requires no DLL

I am modernizing our CMake build system and switching to static compilation of all dependencies so I can deploy the application as single binary. One of the dependencies is LibXml2 which is statically compiled (Environment MSVC 2019 x64 Native):
cscript configure.js iconv=no compiler=msvc cruntime=/MT debug=yes static=yes prefix=libxml
nmake Makefile.msvc libxml install
This generates the DLL win32\libxml\bin\libxml2.dll and the LIB files win32\libxml\lib\libxml2.liband win32\libxml\lib\libxml2_a.lib.
My CMake file looks like this:
find_package(LibXml2 REQUIRED)
add_executable(testapp WIN32)
target_sources(testapp
PRIVATE
Main.cpp
)
target_include_directories(testapp PUBLIC ${LIBXML_LIBRARIES})
target_link_libraries(testapp PRIVATE LibXml2::LibXml2)
Problem: It looks like the find module FindLibXml2 picks up the relocatable shared DLL, but not the static LIB archive. Thus the application is linked against the dynamic library.
Question: How can I use the find module script, but link against the static version of LibXml2? Is this even possible or do I have to write an own find script?
User #alex-reinking gave the important tip: Set the cached variable before calling the find module - and not afterwards.
Because I don't want to hardcode the path (and can't because I have a debug and release build of LibXml2), I use find_library to find the static library (Note: I added the libxml directory via CMAKE_PREFIX_PATH):
find_library(STATIC_LIBXML2_LIBRARY NAMES libxml2_a)
message(STATUS "Found library: ${STATIC_LIBXML2_LIBRARY}")
set(LIBXML2_LIBRARY ${STATIC_LIBXML2_LIBRARY})
find_package(LibXml2 REQUIRED)

cmake to link external library with IMPORT_SONAME ro IMPORT_LOCATION

I have a C++ project that links to a external library
The library was provided by a vendor which only contains a directory of .h headers and a shared object file "libabc.so".
in the CMakeLists of my project I have a obj(which I built it to bar.so) that uses the external lib.
when building the final exeuctable, I have tried mutilple ways to do it.
add_library(bar STATIC /some/source/file/bar.cpp)
add_library(abc_lib SHARED IMPORTED)
set_property(TARGET abc_lib PROPERTY IMPORTED_LOCATION /path/to/external/lib/libabc.so)
add_executable(foo /some/file/to/main.cpp)
target_link_libraries(foo bar abc_lib)
this builds ok and links ok, however when I do
ldd foo, the abc_lib does not appear in the form of
abc.so => /path/to/external/lib/libabc.so
instead it shows up in a standalone form
/path/to/external/lib/libabc.so, which indicates the library was not directly linked against according to some post I read recently.
But when I do chrpath -d foo or patchelf --remove-rpath foothe executable still contains the path and wont use the one I provided in LD_LIBRARY_PATH
so I tried the other way around
add_library(bar STATIC /some/source/file/bar.cpp)
add_library(abc_lib SHARED IMPORTED)
set_property(TARGE abd_lib PROPERTY IMPORTED_SONAME abc)
link_libraries(/path/to/external/lib)
add_executable(foo /some/file/to/main.cpp)
target_link_libraries(foo bar abc_lib)
however this time, it complains abc_lib-NOTFOUND
To sum up my question, I would like to have a project built link against a local shared object and at the same time I should be able to clean up the rpath using chrpath or patchelf so that I can copy the executable to a server with similiar environment but possible different path to the external lib, I would like to overwrite the path using LD_LIBRARY_PATH.
The key you are looking for is IMPORTED_NO_SONAME.
You can add an extra property to the shared imported library target:
set_property(TARGET abc_lib PROPERTY IMPORTED_NO_SONAME TRUE)
or better in one cmake command:
set_target_properties(abc_lib PROPERTIES
IMPORTED_LOCATION /path/to/external/lib/libabc.so
IMPORTED_NO_SONAME TRUE
)
CMake will use /path/to/external/lib/libabc.so instead of -L/path/to/external/lib -llibabc.so as link options if libabc.so has no soname in its ELF header.
You can verify this with:
readelf -d /path/to/external/lib/libabc.so
The offcial cmake doc:Imported Libraries explains more details.

How to link external C++ library to my project on Mac OSX [duplicate]

I have 2 folders "inc" and "lib" in my project which have headers and static libs respectively. How do I tell cmake to use those 2 directories for include and linking respectively?
The simplest way of doing this would be to add
include_directories(${CMAKE_SOURCE_DIR}/inc)
link_directories(${CMAKE_SOURCE_DIR}/lib)
add_executable(foo ${FOO_SRCS})
target_link_libraries(foo bar) # libbar.so is found in ${CMAKE_SOURCE_DIR}/lib
The modern CMake version that doesn't add the -I and -L flags to every compiler invocation would be to use imported libraries:
add_library(bar SHARED IMPORTED) # or STATIC instead of SHARED
set_target_properties(bar PROPERTIES
IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/lib/libbar.so"
INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_SOURCE_DIR}/include/libbar"
)
set(FOO_SRCS "foo.cpp")
add_executable(foo ${FOO_SRCS})
target_link_libraries(foo bar) # also adds the required include path
If setting the INTERFACE_INCLUDE_DIRECTORIES doesn't add the path, older versions of CMake also allow you to use target_include_directories(bar PUBLIC /path/to/include). However, this no longer works with CMake 3.6 or newer.
You had better use find_library command instead of link_directories. Concretely speaking there are two ways:
designate the path within the command
find_library(NAMES gtest PATHS path1 path2 ... pathN)
set the variable CMAKE_LIBRARY_PATH
set(CMAKE_LIBRARY_PATH path1 path2)
find_library(NAMES gtest)
the reason is as flowings:
Note This command is rarely necessary and should be avoided where there are other choices. Prefer to pass full absolute paths to
libraries where possible, since this ensures the correct library will
always be linked. The find_library() command provides the full path,
which can generally be used directly in calls to
target_link_libraries(). Situations where a library search path may be
needed include: Project generators like Xcode where the user can
switch target architecture at build time, but a full path to a library
cannot be used because it only provides one architecture (i.e. it is
not a universal binary).
Libraries may themselves have other private library dependencies that
expect to be found via RPATH mechanisms, but some linkers are not able
to fully decode those paths (e.g. due to the presence of things like
$ORIGIN).
If a library search path must be provided, prefer to localize the
effect where possible by using the target_link_directories() command
rather than link_directories(). The target-specific command can also
control how the search directories propagate to other dependent
targets.
might fail working with link_directories, then add each static library like following:
target_link_libraries(foo /path_to_static_library/libbar.a)

How to know library variable names for CMakeLists?

When using CMakeLists to compile an OpenGL project, I have the following line to link glut and gl:
target_link_libraries(my_exe ${OPENGL_gl_LIBRARY} ${GLUT_LIBRARIES})
I looked up how to link glut and gl with CMake so I saw that I could use ${OPENGL_gl_LIBRARY} and ${GLUT_LIBRARIES}. But how would I know the variables to use otherwise? I am used to just doing ${THELIBRARY_LIBRARES}, but in the case of gl, it changed to adding that "gl" into the variable name. How would I know that without googling it (for any library I want to use)?
Besides consulting the find module's documentation, you could also use CMake's VARIABLES property to give you the variables that were defined by your find_package() call.
For an example the following code:
cmake_minimum_required(VERSION 3.2)
project(FindPackageVars)
get_directory_property(_vars_before VARIABLES)
find_package(OpenGL)
get_directory_property(_vars VARIABLES)
list(REMOVE_ITEM _vars _vars_before ${_vars_before})
foreach(_var IN LISTS _vars)
message(STATUS "${_var} = ${${_var}}")
endforeach()
Outputs on my machine:
-- Found OpenGL: /usr/lib/x86_64-linux-gnu/libGL.so
-- FIND_PACKAGE_MESSAGE_DETAILS_OpenGL = [/usr/lib/x86_64-linux-gnu/libGL.so][/usr/include][v()]
-- OPENGL_FOUND = TRUE
-- OPENGL_GLU_FOUND = YES
-- OPENGL_INCLUDE_DIR = /usr/include
-- OPENGL_INCLUDE_PATH = /usr/include
-- OPENGL_LIBRARIES = /usr/lib/x86_64-linux-gnu/libGLU.so;/usr/lib/x86_64-linux-gnu/libGL.so
-- OPENGL_LIBRARY = /usr/lib/x86_64-linux-gnu/libGLU.so;/usr/lib/x86_64-linux-gnu/libGL.so
-- OPENGL_XMESA_FOUND = NO
-- OPENGL_gl_LIBRARY = /usr/lib/x86_64-linux-gnu/libGL.so
-- OPENGL_glu_LIBRARY = /usr/lib/x86_64-linux-gnu/libGLU.so
-- OPENGL_xmesa_INCLUDE_DIR = OPENGL_xmesa_INCLUDE_DIR-NOTFOUND
Those variables are obtained via find_package(XXX) calls.
Such calls are redirected, depended from the library, either to FindXXX.cmake script (shipped with CMake or contained in the project which uses it) or to XXXConfig.cmake script (shipped with the library itself).
So, for determine meaningful variable's names you need to consult appropriate script. Usually, interface of the script (input-output variables) is described in comments at the beginning of the script.
Documentation for FindXXX.cmake scripts shipped with CMake may be read in CMake documentation pages about modules.
You don't. It is dependent on the find-module for the library.
See here.
Under Writing find modules you see that variables are set in the module. When checking the FindOpenGL.cmake module in your CMake-Modules directory you will see the name of the variable.

CMake: include library dependencies in a static library

I am building a static library in CMake, which is dependent on many other static libraries. I would like them all to be included in the output .lib/.a file, so I can just ship a big lib file to customers. In Visual Studio 2010 there is an option, "Link Library Dependencies", which does exactly this.
But I can't find how to do it in CMake. Can you set this flag via CMake, or get the same result some other way? I have tried target_link_libraries(...) and also add_dependencies(...), but CMake seems to simply ignore this line for static libraries.
Okay, so I have a solution. First it's important to recognize that static libraries do not link other static libraries into the code. A combined library must be created, which on Linux can be done with ar. See Linking static libraries to other static libraries for more info there.
Consider two source files:
test1.c:
int hi()
{
return 0;
}
test2.c:
int bye()
{
return 1;
}
The CMakeLists.txt file is to create two libraries and then create a combined library looks like:
project(test)
add_library(lib1 STATIC test1.c)
add_library(lib2 STATIC test2.c)
add_custom_target(combined ALL
COMMAND ${CMAKE_AR} rc libcombined.a $<TARGET_FILE:lib1> $<TARGET_FILE:lib2>)
The options to the ar command are platform-dependent in this case, although the CMAKE_AR variable is platform-independent. I will poke around to see if there is a more general way to do this, but this approach will work on systems that use ar.
Based on How do I set the options for CMAKE_AR?, it looks like the better way to do this would be:
add_custom_target(combined ALL
COMMAND ${CMAKE_CXX_ARCHIVE_CREATE} libcombined.a $<TARGET_FILE:lib1> $<TARGET_FILE:lib2>)
This should be platform-independent, because this is the command structure used to create archives internally by CMake. Provided of course the only options you want to pass to your archive command are rc as these are hardwired into CMake for the ar command.
I'd like to enhance the other solutions by providing my CMakeLists.txt file that actually works also in terms of building dependencies.
Solution misusing CMake
cmake_minimum_required(VERSION 2.8)
add_library(lib1 test1.cpp)
add_library(lib2 test2.cpp)
include_directories(${CMAKE_CURRENT_DIR})
add_executable(mainexec main.cpp)
target_link_libraries(mainexec combinedLib) # Important to place before add_custom_target
set(LIBNAME "combinedLib.lib")
add_custom_command(
OUTPUT ${LIBNAME}
COMMAND lib.exe /OUT:${LIBNAME} $<TARGET_FILE:lib1> $<TARGET_FILE:lib2>
DEPENDS lib1 lib2
COMMENT "Combining libs..."
)
add_custom_target(combinedLib
DEPENDS ${LIBNAME}
)
Note that this solution works so far with Visual Studio but I guess it can be made multi-platform compliant. I can imagine that the following version might work for Unix-based platforms:
set(LIBNAME "libCombinedLib.a")
add_custom_command(
OUTPUT ${LIBNAME}
COMMAND ar -rcT ${LIBNAME} $<TARGET_FILE:lib1> $<TARGET_FILE:lib2>
DEPENDS lib1 lib2
COMMENT "Combining libs..."
)
Note that these solutions somehow misuse CMake as it would complain about a target of type UTILITY (instead of STATIC or SHARED) if you place the target_link_libraries call after the add_custom_target declaration.
CMake target-declaration-compliant solution
To make it CMake compliant, you can replace the `target_link_libraries' call by
target_link_libraries(mainexec ${LIBNAME})
add_dependencies(mainexec combinedLib)
In my case it is not entirely satisfactory because mainexec has to know about combinedLib although it expects all dependencies to be handled by the target_link_libraries call.
Alternative solution with less coupling
Looking a bit further towards imported targets I eventually found a solution that solves my last problem:
cmake_minimum_required(VERSION 2.8)
add_library(lib1 test1.cpp)
add_library(lib2 test2.cpp)
include_directories(${CMAKE_CURRENT_DIR})
add_executable(mainexec main.cpp)
set(LIBNAME "combinedLib.lib")
add_custom_command(
OUTPUT ${LIBNAME}
COMMAND lib.exe /OUT:${LIBNAME} $<TARGET_FILE:lib1> $<TARGET_FILE:lib2>
DEPENDS lib1 lib2
COMMENT "Combining libs..."
)
add_custom_target(combinedLibGenerator
DEPENDS ${LIBNAME}
)
add_library(combinedLib STATIC IMPORTED)
set_property(TARGET combinedLib PROPERTY IMPORTED_LOCATION ${LIBNAME})
add_dependencies(combinedLib combinedLibGenerator)
target_link_libraries(mainexec combinedLib)
If you intend to modularize the whole add GLOBAL after STATIC IMPORTED to make the imported target globally visible.
Portable CMake solution
With the current CMake versions CMake provides full support for transitive dependencies and interface libraries. An interface library can then "link" against other libraries and this interface library can, in turn, be "linked" against. Why quotation marks? While this works good, this actually doesn't create a physical, combined library but rather creates a kind of an alias to the set of "sub-libs". Still this was the solution we eventually needed, which is why I wanted to add it here.
add_library(combinedLib INTERFACE)
target_link_libraries(combinedLib INTERFACE lib1 lib2)
target_link_libraries(mainexec combinedLib)
That's it!