有没有可能让 CMake 同时构建一个静态库和共享库?

同样的来源,所有这一切,只是需要一个静态和共享的版本都。容易做到吗?

95271 次浏览

是的,它相当简单。只需使用两个“ add _ library”命令:

add_library(MyLib SHARED source1.c source2.c)
add_library(MyLibStatic STATIC source1.c source2.c)

即使您有许多源文件,您也可以将源列表放在 Cmake变量中,因此这仍然很容易做到。

在 Windows 上,你可能应该给每个库起一个不同的名字,因为有一个“。共享和静态的 lib”文件。但是在 Linux 和 Mac 上,你甚至可以给这两个库起同样的名字(例如 libMyLib.alibMyLib.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

自从 CMake 版本2.8.8以来,您可以使用“对象库”< strong > 以避免重复编译目标文件 。使用克里斯托弗•布鲁斯(Christopher Brun)关于具有两个源文件的库的例子:

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

来自 制作文件:

对象库编译源文件,但不存档或链接 创建的其他目标 add_library()add_executable()可以使用 表达形式 $<TARGET_OBJECTS:objlib>作为源,其中 Objeclib 是对象库的名称。

简单地说,add_library(objlib OBJECT ${libsrc})命令指示 CMake 将源文件编译成 *.o对象文件。然后,这个 *.o文件集合在两个 add_library(...)命令中被称为 $<TARGET_OBJECT:objlib>,这两个命令调用适当的库创建命令,这些命令从目标文件的 一样的构建共享和静态库。如果您有很多源文件,那么编译 *.o文件可能会花费很长时间; 对于对象库,您只需要编译它们一次。

你所付出的代价是对象文件必须以地址无关代码的形式构建,因为共享库需要这样做(静态库不在乎)。请注意,地址无关代码可能效率较低,因此如果你的目标是获得最大的性能,那么你会选择静态库。此外,分发静态链接的可执行文件更加容易。

正如前面的回答所建议的那样,将所有东西都打包在同一个编译器中是可能的,但是我建议不要这样做,因为到最后,这是一种只适用于简单项目的黑客技术。例如,在某些情况下,您可能需要为库的不同版本设置不同的标志(尤其是。在 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库共享编译(上面提到的警告,所以您需要一个令人信服的理由来这样做) ,您仍然可以将结束库放在两个不同的项目中。

请注意,以前的答案不适用于 MSVC:

add_library(test SHARED ${SOURCES})
add_library(testStatic STATIC ${SOURCES})
set_target_properties(testStatic PROPERTIES OUTPUT_NAME test)

CMake 将为 shared目标创建 test.dll以及 test.libtest.exp。然后,它将在同一目录中为 static目标创建 test.lib,并替换前一个目标。如果你将尝试链接一些可执行文件与 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目标覆盖 test.lib