我有一个与 cmake 邮件列表上的问题非常相似的问题,其中我们有一个依赖于许多静态库的项目(所有静态库都是从各个子模块中的源代码构建的,每个子模块都有自己的 CMakeLists.txt 描述每个库的构建过程) )我想将其合并到一个静态库中以发布给消费者。我的库的依赖项可能会发生变化,我不想让这些变化给开发人员带来进一步的负担。巧妙的解决方案是将所有库捆绑到一个库中。 有趣的是,当将目标设置为
target_link_libraries
并像这样使用它时,
mylib
命令不会组合所有静态数据。 。 target_link_libraries(mylib a b c d)
但是,奇怪的是,如果我将
mylib
项目作为可执行项目的子模块,并且仅链接到顶级可执行文件 CMAkeLists.txt 中的
mylib
,则该库似乎确实被合并了。 IE。 mylib 是 27 MB,而不是当我将目标设置为仅构建时的 3MB mylib
。有一些解决方案描述了将库解包到目标文件中并重新组合(here
和here),但是当 CMake 似乎完全能够自动合并库(如上面示例中所述)时,这似乎非常笨拙。是否有我缺少的神奇命令,或者推荐的制作发布库的优雅方法?
a
和
b
,其中 a
取决于 b
。 .啊啊
#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;
}
主.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
)
仍然有接缝,好像应该有一个更简洁的方法。 .
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 的好处。这样,库(及其依赖项)仅在需要时构建,而不是每次都构建。 缺点是打印虚拟文件的生成。
基本,定义一个 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
DEPENDS ${static_libs}
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}
DEPENDS ${static_libs}
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()
## 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 库,这是一种很好的合并它们的机制。
的解决方案,但它支持从给定目标检索输入库路径,并且还支持多配置生成器,如 Ninja Multi-Config:
# Combine a list of library targets into a single output archive
# Usage:
# combine_archives(output_archive_name input_target1 input_target2...)
function(combine_archives output_archive)
# Generate the MRI file for ar to consume.
# Note that a separate file must be generated for each build configuration.
set(mri_file ${CMAKE_BINARY_DIR}/$<CONFIG>/${output_archive}.mri)
set(mri_file_content "create ${CMAKE_BINARY_DIR}/$<CONFIG>/lib${output_archive}.a\n")
FOREACH(in_target ${ARGN})
string(APPEND mri_file_content "addlib $<TARGET_FILE:${in_target}>\n")
ENDFOREACH()
string(APPEND mri_file_content "save\n")
string(APPEND mri_file_content "end\n")
file(GENERATE
OUTPUT ${mri_file}
CONTENT ${mri_file_content}
)
# Create a dummy file for the combined library
# This dummy file depends on all the input targets so that the combined library is regenerated if any of them changes.
set(output_archive_dummy_file ${CMAKE_BINARY_DIR}/${output_archive}.dummy.cpp)
add_custom_command(OUTPUT ${output_archive_dummy_file}
COMMAND touch ${output_archive_dummy_file}
DEPENDS ${ARGN})
add_library(${output_archive} STATIC ${output_archive_dummy_file})
# Add a custom command to combine the archives after the static library is "built".
add_custom_command(TARGET ${output_archive}
POST_BUILD
COMMAND ar -M < ${mri_file}
COMMENT "Combining static libraries for ${output_archive}"
)
endfunction(combine_archives)
从
libTargetC.a
和
libTargetA.a
生成 libTargetB.a
的用法类似于:add_library(TargetA STATIC ...)
add_library(TargetB STATIC ...)
combine_archives(TargetC TargetA TargetB)
而不是直接使用 ar
命令(并假设它可用。)。即使在交叉编译场景中,CMake 的变量也已配置为指向正确的存档工具。