【问题标题】:Is it possible to get CMake to build both a static and shared library at the same time?是否可以让 CMake 同时构建静态库和共享库?
【发布时间】:2011-01-10 06:13:21
【问题描述】:

相同的来源,只是想要一个静态和共享版本。容易吗?

【问题讨论】:

  • 这个问题的所有答案都是错误的或不完整的。我写了一个blog post about this here。感谢 PIC(除其他外),最好的办法就是创建 一个 目标并构建两次。

标签: static cmake shared


【解决方案1】:

是的,这很容易。只需使用两个“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 上的名称。)

【讨论】:

  • 希望他们有相同的名字,但是哦。另一个问题:你能告诉 CMake 在可能的情况下将静态库链接到共享库吗?
  • 更多关于“同名”的信息:如果您在 Windows 上并且想要两个库的名称相同并且您不需要共享的 .lib 文件,则可以创建一个静态 .lib 和一个共享的 .dll。但是,如果您将库用于普通编译时链接,则需要该共享 .lib 文件。
  • 我不确定我是否理解您关于将静态库链接到共享库的问题。
  • 注意,这不再是建议的方式。对于非平凡大小的项目(那些需要几分钟而不是几秒钟来编译的项目),避免将编译时间加倍是奇妙的。有关对象库的用法或文档,请参阅下面的 user465139 答案:cmake.org/cmake/help/v3.8/command/…
  • @KymikoLoco:对象库方法确实将编译时间减少了一半,但它需要将静态库构建为与位置无关的代码(即使用-fPIC),这会增加少量的运行时开销当使用这些静态库时。因此,为了获得最佳性能,这个答案仍然是最好的。
【解决方案2】:

从 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 docs

对象库编译源文件但不归档或链接 他们的目标文件到一个库中。而是由创建的其他目标 add_library()add_executable() 可以使用 $&lt;TARGET_OBJECTS:objlib&gt; 形式的表达式作为源,其中 objlib 是对象库名称。

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

您付出的代价是目标文件必须构建为与位置无关的代码,因为共享库需要这个(静态库不在乎)。请注意,与位置无关的代码可能效率较低,因此如果您的目标是获得最大性能,那么您会选择静态库。此外,更容易分发静态链接的可执行文件。

【讨论】:

  • 这对我来说就像一个魅力——唯一需要注意的是,依赖于你的库的后续 target_link_libraries() 调用不能使用“对象库”链接;那些必须以新的共享或静态库为目标(并且可能重复)。但与第一批评论者的经验相反,这非常有用,让我可以删除所有重复的目标并将我的所有CMakeLists.txt 文件削减近一半。
  • 设置目标属性时是否需要“转义”obblib?即 set_property(TARGET ${objlib} PROPERTY ...) vs set_property(TARGET objlib PROPERTY ...)
  • @user465139 或许您应该解释一下为什么可以为静态和共享目标重用目标文件。特别是,SO中的一般知识仍然对此非常困惑,旧的/档案也无助于澄清它,例如。 cmake.org/pipermail/cmake/2008-March/020315.html 需要对现状进行扎实的解释。 p.s.投反对票的不是我
  • @mloskot 谢谢,我添加了一个额外的段落试图更好地解释这个想法,并且还引用了 CMake 文档中的一段话。
  • @gnac 我无法确认这一点。就我而言,set_property 仅在我使用 objlib 时有效,而在使用 ${objlib} 时无效。所以也许这个答案可以更正?
【解决方案3】:

通常不需要为您的目的重复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 项目,无需适配是最“自然”或“CMake”的方式。
【解决方案4】:

正如前面的答案所建议的那样,可以在同一个编译过程中打包所有内容,但我建议不要这样做,因为最终它是一种仅适用于简单项目的 hack。例如,您可能在某些时候需要为不同版本的库使用不同的标志(尤其是在 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 库来相互编译编译(上面提到的警告,所以您需要一个令人信服的理由这样做),您仍然可以将最终库放在两个不同的项目中。

【讨论】:

    【解决方案5】:

    请注意,以前的答案不适用于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。它与MSVC 完美搭配。

    【讨论】:

      【解决方案6】:

      确实有可能。正如@Christopher Bruns 在他的回答中所说,您需要添加两个版本的库:

      set(libsrc source1.c source2.c source3.c)
      add_library(mylib-static STATIC ${libsrc})
      add_library(mylib-shared SHARED ${libsrc})
      

      然后,如here 所述,您需要指定两个目标应使用相同的输出名称并且不覆盖彼此的文件:

      SET_TARGET_PROPERTIES(mylib-static PROPERTIES OUTPUT_NAME mylib CLEAN_DIRECT_OUTPUT 1)
      SET_TARGET_PROPERTIES(mylib-shared PROPERTIES OUTPUT_NAME mylib CLEAN_DIRECT_OUTPUT 1)
      

      这样,您将获得 libmylib.a 和 libmylib.so(在 Linux 上)或 mylib.lib 和 mylib.dll(在 Windows 上)。

      【讨论】:

      • 在使用高于 2.8.[0?] 的 CMake 版本时这是不必要的,因为该属性在 2009 年被删除,它提供的行为现在是默认的。这可能对 2.8 以下的人有用,但如果您仍在使用 CMake github.com/Kitware/CMake/commit/…
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-12-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多