Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Combining several static libraries into one using CMake

I have a very similar problem to one described on the cmake mailing list where we have a project dependent on many static libraries (all built from source in individual submodules, each with their own CMakeLists.txt describing the build process for each library) that I'd like to combine into a single static library for release to the consumers. The dependencies of my library are subject to change, and I do not want to burden developers further down the chain with those changes. The neat solution would be to bundle all of the libs into one single lib.

Interestingly, the target_link_libraries command does not combine all of the statics when setting the target to mylib and using it like so . .

target_link_libraries(mylib a b c d) 

However, bizarrely, if I make the mylib project a submodule of an executable project, and only link against mylib in the top level executable CMAkeLists.txt, the library does seem to be combined. I.e. mylib is 27 MB, instead of the 3MB when I set the target to only build mylib.

There are solutions describing unpacking of the libs into object files and recombining (here, and here), but this seems remarkably clumsy when CMake seems perfectly capable of automatically merging the libs as described in the above example. It there a magic command I'm missing, or a recommended elegant way of making a release library?

like image 684
learnvst Avatar asked Jun 20 '16 13:06

learnvst


People also ask

Can CMake compile?

Once you have edited the CMakeCache. txt file you rerun cmake, repeat this process until you are happy with the cache settings. The type make and your project should compile.

What is Target_link_libraries?

Link a target to given libraries.


2 Answers

Given the most simple working example I can think of: 2 classes, a and b, where a depends on b . .

a.h

#ifndef A_H #define A_H  class aclass { public:     int method(int x, int y); };  #endif 

a.cpp

#include "a.h" #include "b.h"  int aclass::method(int x, int y) {     bclass b;     return x * b.method(x,y); } 

b.h

#ifndef B_H #define B_H  class bclass { public:     int method(int x, int y); };  #endif 

b.cpp

#include "b.h"  int bclass::method(int x, int y) {     return x+y; } 

main.cpp

#include "a.h" #include <iostream>  int main() {     aclass a;     std::cout << a.method(3,4) << std::endl;      return 0; } 

It is possible to compile these into separate static libs, and then combine the static libs using a custom target.

cmake_minimum_required(VERSION 2.8.7)  add_library(b b.cpp b.h) add_library(a a.cpp a.h) add_executable(main main.cpp)  set(C_LIB ${CMAKE_BINARY_DIR}/libcombi.a)  add_custom_target(combined         COMMAND ar -x $<TARGET_FILE:a>         COMMAND ar -x $<TARGET_FILE:b>         COMMAND ar -qcs ${C_LIB} *.o         WORKING_DIRECTORY ${CMAKE_BINARY_DIR}         DEPENDS a b         )  add_library(c STATIC IMPORTED GLOBAL) add_dependencies(c combined)  set_target_properties(c         PROPERTIES         IMPORTED_LOCATION ${C_LIB}         )  target_link_libraries(main c) 

It also works just fine using Apple's libtool version of the custom target . . .

add_custom_target(combined         COMMAND libtool -static -o ${C_LIB} $<TARGET_FILE:a> $<TARGET_FILE:b>         WORKING_DIRECTORY ${CMAKE_BINARY_DIR}         DEPENDS a b         ) 

Still seams as though there should be a neater way . .

like image 140
learnvst Avatar answered Oct 04 '22 06:10

learnvst


You can use this function to join any number of libraries.

function(combine_archives output_archive list_of_input_archives)     set(mri_file ${TEMP_DIR}/${output_archive}.mri)     set(FULL_OUTPUT_PATH ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/lib${output_archive}.a)     file(WRITE ${mri_file} "create ${FULL_OUTPUT_PATH}\n")     FOREACH(in_archive ${list_of_input_archives})         file(APPEND ${mri_file} "addlib ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/lib${in_archive}.a\n")     ENDFOREACH()     file(APPEND ${mri_file} "save\n")     file(APPEND ${mri_file} "end\n")      set(output_archive_dummy_file ${TEMP_DIR}/${output_archive}.dummy.cpp)     add_custom_command(OUTPUT ${output_archive_dummy_file}                        COMMAND touch ${output_archive_dummy_file}                        DEPENDS ${list_of_input_archives})      add_library(${output_archive} STATIC ${output_archive_dummy_file})     add_custom_command(TARGET ${output_archive}                        POST_BUILD                        COMMAND ar -M < ${mri_file}) endfunction(combine_archives) 

It has the benefits of using add_custom_command and not add_custom_target. This way, the library (and it's dependencies) are only built when needed and not every time. The drawback is the print of the generation of the dummy file.

like image 39
zbut Avatar answered Oct 04 '22 06:10

zbut