【问题标题】:CMake share library with multiple executables具有多个可执行文件的 CMake 共享库
【发布时间】:2016-01-31 07:15:44
【问题描述】:

我的项目包含几个共享一些通用代码的可执行文件。我想将通用代码放在可执行文件可以链接到的静态库中。 (通用代码很小,我不想处理共享库)。

源代码树看起来像这样:

  • 项目
    • CMakeLists.txt
    • 常见
      • CMakeLists.txt
      • 源代码
      • 包括
    • 应用程序1
      • 源代码
      • CMakeLists.txt
    • 应用程序2
      • 源代码
      • CMakeLists.txt

app1 和 app2 都依赖于共同的代码。

这个通用代码是非常特定于应用程序的,永远不需要被这个目录树之外的另一个项目使用。出于这个原因,我不希望将库安装在任何类型的全球位置。

顶层 CMakeLists.txt 文件只是添加了子目录:

project(toplevel)
cmake_minimum_required(VERSION 3.1)

add_subdirectory(common)
add_subdirectory(app1)
add_subdirectory(app2)

通用库的 CMakeLists.txt 文件创建静态库并设置包含目录:

add_library(common STATIC common.cpp) 
target_include_directories(common PUBLIC "${CMAKE_CURRENT_LIST_DIR}/include")

可执行文件的文件如下所示:

project(app1)
cmake_minimum_required(VERSION 3.1)

add_executable(${PROJECT_NAME} main.cpp)
target_link_libraries(${PROJECT_NAME} common)

现在回答我的问题。如果我从顶级项目目录运行 CMake,我可以构建 app1 和 app2 并且它们构建成功。但是,如果我想构建其中一个项目(例如,通过从 app1 运行 CMake)而不是从顶级目录构建,我会收到错误消息,因为 common/include 未添加到标头搜索路径中。

我明白为什么会发生这种情况。 app1 或 app2 的 CMakeLists.txt 文件中没有任何“引入”常见的内容。这仅在顶层完成。

有没有办法解决这个问题,或者这种行为通常被认为是可以接受的?我的设置不是最理想的吗?我只是想,如果我们开始开发越来越多使用这个公共库的可执行文件,那么能够单独构建项目而不是从顶层构建项目会很好,但也许这是我不应该做的事情担心。

【问题讨论】:

  • 你真的需要project(app1)吗?您可以在顶级构建目录中执行 make app1 以仅编译 app1 及其依赖项。
  • 我的一个项目中遇到了类似的问题,除了我没有顶级CMakeLists.txt:见CMake: How to setup Source, Library and CMakeLists.txt dependencies?。我想在那里覆盖add_subdirectory()。但是@m.s.向我指出了一个非常有趣的系列文章Use CMake-enabled libraries in your CMake project。主要是告诉export() 通用库,所以你可以通过 CMake 代码 sn-p 导入它。
  • @arrowd 我不确定。也许答案是我不知道。我不太明白什么时候子目录应该是它自己的项目。

标签: cmake


【解决方案1】:

当您设置构建环境时,您应该考虑以下三个主题(除其他主题外,但对于本次讨论/答案,我将其简化为我认为与此处相关的三个主题):

  1. 依赖/耦合
  2. 部署
  3. 团队

“强耦合”

我开始认为add_subdirectory() 命令支持“强耦合”,并且您当前的设置隐式支持:

  • 一个经常变化的common
  • 所有应用的单一部署(流程和时间安排)
  • 一个团队致力于完整的源代码库
    • IDE 将在一个解决方案中显示所有内容
    • 您为所有内容生成一个构建环境

“松散耦合”

如果您想要更多 "loose coupling",您可以使用其他语言的外部脚本或使用 CMake 的 ExternalProject_Add() 宏。因此,如果您设置 common 库(甚至可能包括“二进制交付”)并将每个 app 作为您支持的单独项目:

  • 不经常更改的common
    • 可能有自己的发布周期
  • 每个应用都有独立的开发/部署周期
  • 一个由不同开发人员组成的团队,每个 app

两者兼而有之

因此,您可以看到有很多事情需要考虑,CMake 可以为您提供各种方法的支持。考虑到您的项目可能处于早期阶段,您可能会采用混合方法(不是立即解耦common 库):

CMakeLists.txt

project(toplevel)
cmake_minimum_required(VERSION 3.1)

include(ExternalProject)

ExternalProject_Add(
    app1 
    SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/app1"
    PREFIX app1
    INSTALL_COMMAND ""
)
ExternalProject_Add(
    app2 
    SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/app2"
    PREFIX app2
    INSTALL_COMMAND ""
)

app1/CMakeLists.txt

project(app1)
cmake_minimum_required(VERSION 3.1)

add_subdirectory(../common common)

add_executable(${PROJECT_NAME} src/main.cpp)
target_link_libraries(${PROJECT_NAME} common)

这实际上会生成三个构建环境。一个直接在您的二进制输出目录中,一个在app1app2 子目录中。

在这种方法中,您可能需要考虑常见的 CMake 工具链文件。

参考文献

【讨论】:

  • 感谢您提供详细的答案和有用的链接。我从来没有考虑过add_subdirectory(../common common)
  • 这给了我:MSBUILD:错误 MSB1009:项目文件不存在。 (MSCV 构建中没有详细信息)
  • @StepanYakovenko 感谢您的提示。是的,我的示例的子项目没有install() 命令/指令。由于ExternalProject_Add() 确实执行了子项目的安装过程,所以你/我需要添加INSTALL_COMMAND "" 参数。
  • 我会修补这个例子。
  • 混合方法是迄今为止最糟糕的方法。各个应用程序需要通过add_subdirectory(../common common) 调用了解它们嵌入的父项目结构。这实质上创建了双向依赖关系,其中“单个”项目和父项目都不能在不通过文件系统层次结构(强)耦合在一起的情况下构建。在此基础上构建实际项目就像把纸牌屋放在一起。但是,正如答案中所指出的,其他方法各有其优点。
【解决方案2】:

只有当这个子项目打算作为独立项目和顶层项目的一部分构建时,你才应该在子目录中使用project() 命令。 LLVM 和 Clang 就是这种情况,例如:Clang 可以单独编译,但是当 LLVM 构建系统检测到 Clang 源时,它也包含它的目标。

在您的情况下,您不需要子项目。在项目构建目录中仅编译 app1app2 目标问题 make app1/make app2

【讨论】:

    猜你喜欢
    • 2023-04-01
    • 2021-06-16
    • 1970-01-01
    • 2016-09-08
    • 2014-08-26
    • 2021-07-17
    • 1970-01-01
    • 2015-09-05
    • 2022-11-10
    相关资源
    最近更新 更多