39

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.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Rolle
  • 2,547
  • 5
  • 31
  • 37
  • Do you have static versions of the libraries that need to be linked against? Or do you only have shared versions? – tpg2114 Jan 07 '13 at 16:19
  • So when you say it's not including them in your static, is it trying to link your static library against the other shared libraries? – tpg2114 Jan 07 '13 at 16:59
  • 1
    i have only static libraries, and i only try to build a static library, so no linking is taking place. The problem comes when a third part want to use my libs and have to link to lots of other stuff as well – Rolle Jan 07 '13 at 17:01

2 Answers2

19

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.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
tpg2114
  • 11,938
  • 6
  • 37
  • 54
  • 3
    Thanks, the corresponding way to do it on Windows is to add a custom command "lib.exe /OUT:combined.lib 1.lib 2.lib" – Rolle Jan 08 '13 at 08:03
  • After using this for a bit, I have noticed the "ar" command is very sketchy and produces combined libs that doesn't work in at least some versions of gcc. I think we are just better of not trying to link statically this way. – Rolle Feb 20 '13 at 13:53
  • For me only the add_custom_target command as described first worked. – Rip-Off Oct 21 '16 at 10:44
  • 1
    To make it truly platform-independent, the filename needs to be the following: `${CMAKE_STATIC_LIBRARY_PREFIX}combined${CMAKE_STATIC_LIBRARY_SUFFIX}` – Chris Watts Nov 10 '17 at 14:53
13

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!

Roland Sarrazin
  • 871
  • 7
  • 23