【问题标题】:Combining several static libraries into one using CMake使用 CMake 将几个静态库合并为一个
【发布时间】:2021-11-08 14:05:36
【问题描述】:

我有一个与 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。

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

【问题讨论】:

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

标签: c++ c cmake static-libraries


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    【讨论】:

      【解决方案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】:

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

        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 操作系统。
        【解决方案5】:

        如果您尝试合并的库来自第三方,那么(按照 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 项目导入“超级构建”变得越来越流行,而上面的答案在这种情况下会中断。
        【解决方案6】:

        给出我能想到的最简单的工作示例: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没有提供任何帮助方法,我们必须单独手动支持每个平台)
        猜你喜欢
        • 2022-11-25
        • 1970-01-01
        • 2012-06-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多