使用CMake将多个静态库合并为一个

lea*_*vst 25 c c++ cmake static-libraries

我有一个非常类似于cmake邮件列表中描述的问题,我们有一个依赖于许多静态库的项目(所有静态库都是从各个子模块中的源代码构建的,每个子模块都有自己的CMakeLists.txt描述每个库的构建过程)我想组合成一个静态库,以便向消费者发布.我的库的依赖性可能会发生变化,我不想让开发人员在这些变化的后面进一步负担.简洁的解决方案是将所有lib捆绑到一个单独的lib中.

有趣的是,target_link_libraries在设置目标mylib和使用它时,该命令不会结合所有静态..

target_link_libraries(mylib a b c d)
Run Code Online (Sandbox Code Playgroud)

但是,奇怪的是,如果我将mylib项目作为可执行项目的子模块,并且只mylib在顶级可执行文件CMAkeLists.txt中链接,那么该库似乎确实是组合在一起的.即,当我将目标设置为仅构建时,mylib为27 MB,而不是3 MB mylib.

有一些解决方案描述了将lib解压缩到目标文件并重新组合(这里这里),但是当CMake看起来完全能够自动合并lib时,这似乎非常笨拙,如上例所述.它有一个我失踪的神奇命令,或推荐的优雅方式制作发布库?

lea*_*vst 10

鉴于最简单的工作示例,我可以想到:2个类,a以及b在哪里a取决于b..

#ifndef A_H
#define A_H

class aclass
{
public:
    int method(int x, int y);
};

#endif
Run Code Online (Sandbox Code Playgroud)

a.cpp

#include "a.h"
#include "b.h"

int aclass::method(int x, int y) {
    bclass b;
    return x * b.method(x,y);
}
Run Code Online (Sandbox Code Playgroud)

BH

#ifndef B_H
#define B_H

class bclass
{
public:
    int method(int x, int y);
};

#endif
Run Code Online (Sandbox Code Playgroud)

b.cpp

#include "b.h"

int bclass::method(int x, int y) {
    return x+y;
}
Run Code Online (Sandbox Code Playgroud)

main.cpp中

#include "a.h"
#include <iostream>

int main()
{
    aclass a;
    std::cout << a.method(3,4) << std::endl;

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

可以将它们编译为单独的静态库,然后使用自定义目标组合静态库.

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)
Run Code Online (Sandbox Code Playgroud)

使用Apple的libtool自定义目标版本也可以正常工作...

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

仍然接缝似乎应该有一个更简洁的方式..

  • @ctcchen 不,他从 a.cpp 和 b.cpp 创建了 2 个库“a”和“b”,然后将它们组合到库“c”中。 (4认同)
  • 这并不能回答问题。这个回复是关于组合几个目标文件,而问题是关于组合几个库。 (2认同)
  • 谢谢!在 Windows 上执行此操作的相应方法是添加自定义命令“lib.exe /OUT:combi.lib a.lib b.lib`”(很高兴知道 CMake 不提供任何辅助方法,并且我们有分别手动支持每个平台) (2认同)

小智 6

您可以使用此功能来加入任意数量的库。

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)
Run Code Online (Sandbox Code Playgroud)

它具有使用add_custom_command而不是add_custom_target的好处。这样,库(及其依赖项)仅在需要时才构建,而不是每次都构建。缺点是伪文件生成的打印。

  • 不适用于 macOS。Mac 上的“ar”命令中不存在“-M”:( (2认同)

use*_*323 5

这并没有直接回答问题,但我发现它很有用:

https://cristianadam.eu/20190501/bundling-together-static-libraries-with-cmake/

基本,定义一个 CMake 函数,它将收集目标所需的所有静态库并将它们组合成一个静态库:

add_library(awesome_lib STATIC ...);
bundle_static_library(awesome_lib awesome_lib_bundled)
Run Code Online (Sandbox Code Playgroud)

这是实际功能的复制和粘贴:

function(bundle_static_library tgt_name bundled_tgt_name)
  list(APPEND static_libs ${tgt_name})

  function(_recursively_collect_dependencies input_target)
    set(_input_link_libraries LINK_LIBRARIES)
    get_target_property(_input_type ${input_target} TYPE)
    if (${_input_type} STREQUAL "INTERFACE_LIBRARY")
      set(_input_link_libraries INTERFACE_LINK_LIBRARIES)
    endif()
    get_target_property(public_dependencies ${input_target} ${_input_link_libraries})
    foreach(dependency IN LISTS public_dependencies)
      if(TARGET ${dependency})
        get_target_property(alias ${dependency} ALIASED_TARGET)
        if (TARGET ${alias})
          set(dependency ${alias})
        endif()
        get_target_property(_type ${dependency} TYPE)
        if (${_type} STREQUAL "STATIC_LIBRARY")
          list(APPEND static_libs ${dependency})
        endif()

        get_property(library_already_added
          GLOBAL PROPERTY _${tgt_name}_static_bundle_${dependency})
        if (NOT library_already_added)
          set_property(GLOBAL PROPERTY _${tgt_name}_static_bundle_${dependency} ON)
          _recursively_collect_dependencies(${dependency})
        endif()
      endif()
    endforeach()
    set(static_libs ${static_libs} PARENT_SCOPE)
  endfunction()

  _recursively_collect_dependencies(${tgt_name})

  list(REMOVE_DUPLICATES static_libs)

  set(bundled_tgt_full_name 
    ${CMAKE_BINARY_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}${bundled_tgt_name}${CMAKE_STATIC_LIBRARY_SUFFIX})

  if (CMAKE_CXX_COMPILER_ID MATCHES "^(Clang|GNU)$")
    file(WRITE ${CMAKE_BINARY_DIR}/${bundled_tgt_name}.ar.in
      "CREATE ${bundled_tgt_full_name}\n" )
        
    foreach(tgt IN LISTS static_libs)
      file(APPEND ${CMAKE_BINARY_DIR}/${bundled_tgt_name}.ar.in
        "ADDLIB $<TARGET_FILE:${tgt}>\n")
    endforeach()
    
    file(APPEND ${CMAKE_BINARY_DIR}/${bundled_tgt_name}.ar.in "SAVE\n")
    file(APPEND ${CMAKE_BINARY_DIR}/${bundled_tgt_name}.ar.in "END\n")

    file(GENERATE
      OUTPUT ${CMAKE_BINARY_DIR}/${bundled_tgt_name}.ar
      INPUT ${CMAKE_BINARY_DIR}/${bundled_tgt_name}.ar.in)

    set(ar_tool ${CMAKE_AR})
    if (CMAKE_INTERPROCEDURAL_OPTIMIZATION)
      set(ar_tool ${CMAKE_CXX_COMPILER_AR})
    endif()

    add_custom_command(
      COMMAND ${ar_tool} -M < ${CMAKE_BINARY_DIR}/${bundled_tgt_name}.ar
      OUTPUT ${bundled_tgt_full_name}
      COMMENT "Bundling ${bundled_tgt_name}"
      VERBATIM)
  elseif(MSVC)
    find_program(lib_tool lib)

    foreach(tgt IN LISTS static_libs)
      list(APPEND static_libs_full_names $<TARGET_FILE:${tgt}>)
    endforeach()

    add_custom_command(
      COMMAND ${lib_tool} /NOLOGO /OUT:${bundled_tgt_full_name} ${static_libs_full_names}
      OUTPUT ${bundled_tgt_full_name}
      COMMENT "Bundling ${bundled_tgt_name}"
      VERBATIM)
  else()
    message(FATAL_ERROR "Unknown bundle scenario!")
  endif()

  add_custom_target(bundling_target ALL DEPENDS ${bundled_tgt_full_name})
  add_dependencies(bundling_target ${tgt_name})

  add_library(${bundled_tgt_name} STATIC IMPORTED)
  set_target_properties(${bundled_tgt_name} 
    PROPERTIES 
      IMPORTED_LOCATION ${bundled_tgt_full_name}
      INTERFACE_INCLUDE_DIRECTORIES $<TARGET_PROPERTY:${tgt_name},INTERFACE_INCLUDE_DIRECTORIES>)
  add_dependencies(${bundled_tgt_name} bundling_target)

endfunction()
Run Code Online (Sandbox Code Playgroud)