相同的来源,所有这些,只是想要一个静态和共享版本。 容易做吗?
从 CMake 2.8.8 版本开始,您可以使用“对象库”来避免对象文件的重复编译。使用 Christopher Bruns 的带有两个源文件的库示例:
# list of source files
set(libsrc source1.c source2.c)
# this is the "object library" target: compiles the sources only once
add_library(objlib OBJECT ${libsrc})
# shared libraries need PIC
set_property(TARGET objlib PROPERTY POSITION_INDEPENDENT_CODE 1)
# shared and static libraries built from the same object files
add_library(MyLib_shared SHARED $<TARGET_OBJECTS:objlib>)
add_library(MyLib_static STATIC $<TARGET_OBJECTS:objlib>)
来自 CMake 文档:
对象库编译源文件但不存档或链接 将它们的目标文件放入库中。相反,创建的其他目标
或add_library()
可以使用add_executable()
形式的表达式作为源,其中 objlib 是对象库名称。$<TARGET_OBJECTS:objlib>
简单来说,
add_library(objlib OBJECT ${libsrc})
命令指示CMake将源文件编译为*.o
目标文件。这个 *.o
文件集合在两个 $<TARGET_OBJECT:objlib>
命令中被称为 add_library(...)
,这两个命令调用相应的库创建命令,这些命令从对象文件的同一组构建共享库和静态库。如果您有很多源文件,那么编译 *.o
文件可能会花费很长时间;使用对象库,您只需编译一次。
您付出的代价是目标文件必须构建为位置无关的代码,因为共享库需要它(静态库不关心)。请注意,与位置无关的代码可能效率较低,因此如果您的目标是获得最大性能,那么您会选择静态库。此外,分发静态链接的可执行文件更容易。
是的,这相当容易。 只需使用两个“add_library”命令即可:
add_library(MyLib SHARED source1.c source2.c)
add_library(MyLibStatic STATIC source1.c source2.c)
即使您有很多源文件,您也可以将源列表放在
Cmake
变量中,因此仍然很容易做到。
在 Windows 上,您可能应该为每个库指定不同的名称,因为共享和静态都有一个“.lib”文件。 但在 Linux 和 Mac 上,您甚至可以为两个库指定相同的名称(例如
libMyLib.a
和 libMyLib.so
):
set_target_properties(MyLibStatic PROPERTIES OUTPUT_NAME MyLib)
但我不建议给库的静态和动态版本使用相同的名称。 我更喜欢使用不同的名称,因为这样可以更轻松地在编译行上为链接到库的工具选择静态链接和动态链接。 通常我选择像
libMyLib.so
(共享)和 libMyLib_static.a
(静态)这样的名称。 (这些是 Linux 上的名称。)
通常无需为了您的目的而重复
ADD_LIBRARY
呼叫。 只要利用
$> man cmake | grep -A6 '^ *BUILD_SHARED_LIBS$'
BUILD_SHARED_LIBS
Global flag to cause add_library to create shared libraries if on.
If present and true, this will cause all libraries to be built shared unless the library was
explicitly added as a static library. This variable is often added to projects as an OPTION
so that each user of a project can decide if they want to build the project using shared or
static libraries.
在构建时,首先(在一个源外目录中)使用
-DBUILD_SHARED_LIBS:BOOL=ON
,然后在另一个目录中使用 OFF
。
请注意,以前的答案不适用于
MSVC
:
add_library(test SHARED ${SOURCES})
add_library(testStatic STATIC ${SOURCES})
set_target_properties(testStatic PROPERTIES OUTPUT_NAME test)
CMake 将与
test.dll
和 test.lib
一起为 test.exp
目标创建 shared
。它将在 test.lib
目标的同一目录中创建 static
并替换前一个。如果您尝试将某些可执行文件与 shared
目标链接,它将失败并出现如下错误:
error LNK2001: unresolved external symbol __impl_*.`.
请使用
ARCHIVE_OUTPUT_DIRECTORY
并为 static
目标使用一些唯一的输出目录:
add_library(test SHARED ${SOURCES})
add_library(testStatic STATIC ${SOURCES})
set_target_properties(
testStatic PROPERTIES
OUTPUT_NAME test
ARCHIVE_OUTPUT_DIRECTORY testStatic
)
test.lib
将在 testStatic
目录中创建,并且不会覆盖 test.lib
目标中的 test
。与MSVC
完美配合。
可以将所有内容打包在同一个编译过程中,正如前面的答案中所建议的那样,但我建议不要这样做,因为最终它是一个只适用于简单项目的黑客。例如,在某些时候,您可能需要针对不同版本的库使用不同的标志(尤其是在 Windows 上,标志通常用于在导出符号与否之间进行切换)。或者如上所述,您可能希望将
.lib
文件放入不同的目录中,具体取决于它们是否对应于静态库或共享库。每一个障碍都需要新的技巧。
这可能是显而易见的,但之前没有提到的一种替代方法是将库的类型作为参数:
set( ${PROJECT_NAME}_LIBTYPE CACHE STRING "library type" )
set_property( CACHE ${PROJECT_NAME}_LIBTYPE PROPERTY STRINGS "SHARED;STATIC" )
add_library( ${PROJECT_NAME} ${PROJECT_NAME}_LIBTYPE ${SOURCE_FILES} )
在两个不同的二叉树中拥有库的共享版本和静态版本可以更轻松地处理不同的编译选项。我认为保持编译树不同没有任何严重的缺点,特别是如果您的编译是自动化的。
请注意,即使您打算使用中间
OBJECT
库来相互编译(带有上面提到的警告,因此您需要一个令人信服的理由来这样做),您仍然可以将最终库放入两个不同的项目中。
其他答案提到的“对象库”方法不继承 OBJECT 库的 target_link_libraries() 。
为什么不先简单地构建静态版本,然后从静态版本构建共享版本?
add_library(MyLibStatic STATIC ${SOURCES})
# target_link_libraries(MyLibStatic PRIVATE ThirdPartyLib1 ThirdPartyLib2)
add_library(MyLibShared SHARED)
target_link_libraries(MyLibShared PRIVATE MyLibStatic)
这样源文件只编译一次,第三方库依赖会自动继承。