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

Posted

技术标签:

【中文标题】使用 CMake 将几个静态库合并为一个【英文标题】:Combining several static libraries into one using CMake 【发布时间】:2016-06-20 13:55:26 【问题描述】:

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

有趣的是,target_link_libraries 命令在将目标设置为mylib 并像这样使用它时并没有结合所有的静态数据。 .

target_link_libraries(mylib a b c d)

但是,奇怪的是,如果我将 mylib 项目设为可执行项目的子模块,并且仅在***可执行文件 CMAkeLists.txt 中链接到 mylib,则该库似乎确实被合并了。 IE。 mylib 是 27 MB,而不是当我将目标设置为仅构建 mylib 时的 3MB。

有一些解决方案描述了将库解包到目标文件中并重新组合(here 和 here),但是当 CMake 似乎完全能够如上例中所述自动合并库时,这似乎非常笨拙。是否有我缺少的魔术命令,或者推荐的制作发布库的优雅方式?

【问题讨论】:

但是您将如何处理所有这些包含文件和目录? @Drop 它们都被剔除或隐藏在mylib的公共界面后面。 deps 应该对消费者不可见 如果您使用 gcc,并且不要求您的 makefile 独立于编译器,您可以尝试 --whole-archive 选项。 @KarstenKoop 需要同时是 Apple Clang 和 GCC 嗯@n.m。我希望让 CMake 以独立于平台的方式执行此操作,因为这是该工具的重点。我下面的答案有效,但由于其平台依赖性而很糟糕 【参考方案1】:

给出我能想到的最简单的工作示例:2 个类,ab,其中 a 取决于 b。 .

啊.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;

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

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)

使用 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
        )

仍然接缝,好像应该有一个更整洁的方式。 .

【讨论】:

这不能回答问题。这个回复是关于组合几个目标文件,而问题是关于组合几个库。 @ctcchen:嗯,看起来CMakeLists.txt 将生成一个组合库libcombi.a。但是我要学习的问题是:这真的需要自定义目标吗?没有更标准的机制吗? @ctcchen 不,他从 a.cpp 和 b.cpp 创建了 2 个库“a”和“b”,然后将它们组合到库“c”中。 谢谢!在Windows上对应的方法是添加自定义命令“lib.exe /OUT:combi.lib a.lib b.lib”(很高兴知道CMake没有提供任何帮助方法,我们必须单独手动支持每个平台)【参考方案2】:

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

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)

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

【讨论】:

虽然这对于组合目标文件很有用,但我认为可以通过添加 target_link_libraries 调用来获取传递依赖项来改进它。 您能详细说明一下吗?缺少哪些依赖项? 不适用于 macos。 -M 在 macos 上的 ar 命令中不存在 :( 这应该是公认的答案,谢谢!可以轻松修改上述内容以在 MacOS 上使用libtool,使其适用于所有类 Unix 操作系统。【参考方案3】:

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

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

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

add_library(awesome_lib STATIC ...);
bundle_static_library(awesome_lib awesome_lib_bundled)

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

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()

【讨论】:

【参考方案4】:

如果您尝试合并的库来自第三方,那么(按照 learnvst 示例)此代码会处理可能的 .o 文件替换(例如,如果 liba 和 libb 都具有文件名 zzz.o)

## Create static library (by joining the new objects and the dependencies)
ADD_LIBRARY("$PROJECT_NAME-static" STATIC $SOURCES)
add_custom_command(OUTPUT lib$PROJECT_NAME.a
                   COMMAND rm ARGS -f *.o
                   COMMAND ar ARGS -x $CMAKE_BINARY_DIR/lib$PROJECT_NAME-static.a
                   COMMAND rename ARGS 's/^/lib$PROJECT_NAME-static./g' *.o
                   COMMAND rename ARGS 's/\\.o/.otmp/g' *.o
                   COMMAND ar ARGS -x $CMAKE_SOURCE_DIR/lib/a/liba.a
                   COMMAND rename ARGS 's/^/liba./g' *.o
                   COMMAND rename ARGS 's/\\.o/.otmp/g' *.o
                   COMMAND ar ARGS -x $CMAKE_SOURCE_DIR/lib/b/libb.a
                   COMMAND rename ARGS 's/^/libb./g' *.o
                   COMMAND rename ARGS 's/\\.o/.otmp/g' *.o
                   COMMAND rename ARGS 's/\\.otmp/.o/g' *.otmp
                   COMMAND ar ARGS -r lib$PROJECT_NAME.a *.o
                   COMMAND rm ARGS -f *.o
                   DEPENDS "$PROJECT_NAME-static")

add_custom_target($PROJECT_NAME ALL DEPENDS lib$PROJECT_NAME.a)

否则,如果库是你的,你应该使用 CMake OBJECT 库,这是一个很好的合并它们的机制。

【讨论】:

您几乎肯定应该使用PROJECT_BINARY_DIRPROJECT_SOURCE_DIR 而不是CMAKE_... 变体。将其他 CMake 项目导入“超级构建”变得越来越流行,而上面的答案在这种情况下会中断。【参考方案5】:

https://cmake.org/pipermail/cmake/2018-September/068263.html

CMake 似乎不支持。

【讨论】:

【参考方案6】:

做到这一点的正确方法不是通过组合静态库来捏造,而是向用户提供CMake Config files,其中包含所有必要的位,这些位以应该链接的方式链接所有内容。 CMake 可用于生成这些文件,或生成 pkg-config 文件,可能还有其他格式的“告诉我如何链接这个和那个库”工具。

有些用户可能会对您链接到的库感兴趣,他们甚至可能在链接您的库时使用自己的相同库的副本/版本。正是在这种情况下,您的解决方案很糟糕,并阻止用户集成多段代码,因为您决定他们必须绝对使用您的该依赖项的副本(当您将静态库依赖项组合到一个静态库中时,您会这样做) .

【讨论】:

以上是关于使用 CMake 将几个静态库合并为一个的主要内容,如果未能解决你的问题,请参考以下文章

vs+cmake+使用静态库

vs+cmake+使用静态库

opencv静态链接库cmake链接顺序问题

Linux环境编译动态库和静态库总结

如何使用cmake生成基于静态库的动态链接库

CMake 使用其他静态库构建静态库