如何避免在CMake中生成大量共享依赖项的构建

huangapple go评论49阅读模式
英文:

How to avoid numerous builds of shared dependency in cmake

问题

我的团队有一个实用库,我们称之为Utils,它使用CMake构建。它在另一个库A的CMake中通过ExternalProject_Add引用。

我们的可执行文件App也使用ExternalProject_Add引用了A和Utils。问题在于,Utils被构建了两次,一次是A构建时,另一次是App构建时,但实际上它只需要构建一次。

以下是示例的CMake文件:

Utils:

cmake_minimum_required(VERSION 3.16.3)

project(Utils)

include_directories(${PROJECT_SOURCE_DIR}/Inc)
file(GLOB SOURCE_FILES ${PROJECT_SOURCE_DIR}/Src/*.cpp)
add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES})

install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_PREFIX})

A:

cmake_minimum_required(VERSION 3.16.3)

project(A)

include_directories(${PROJECT_SOURCE_DIR}/Inc)
file(GLOB SOURCE_FILES ${PROJECT_SOURCE_DIR}/Src/*.cpp)
add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES})

install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_PREFIX})

include(ExternalProject)

target_link_libraries(${PROJECT_NAME} ${PROJECT_SOURCE_DIR}/Lib/libUtils.a)
ExternalProject_Add(Utils
    GIT_REPOSITORY ${SOME_GIT_URL}/_git/Utils
    GIT_TAG master
    CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PROJECT_SOURCE_DIR}/Lib
)
ExternalProject_Get_Property(Utils SOURCE_DIR)
target_include_directories(${PROJECT_NAME} PRIVATE ${SOURCE_DIR}/Inc/)
add_dependencies(${PROJECT_NAME} Utils)

App:

cmake_minimum_required(VERSION 3.16.3)

project(App)

include_directories(${PROJECT_SOURCE_DIR}/Inc)
file(GLOB SOURCE_FILES ${PROJECT_SOURCE_DIR}/Src/*.cpp)
add_executable(${PROJECT_NAME} ${SOURCE_FILES})

install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_PREFIX})

include(ExternalProject)

target_link_libraries(${PROJECT_NAME} ${PROJECT_SOURCE_DIR}/Lib/libUtils.a)
ExternalProject_Add(Utils
    GIT_REPOSITORY ${SOME_GIT_URL}/_git/Utils
    GIT_TAG master
    CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PROJECT_SOURCE_DIR}/Lib
)
ExternalProject_Get_Property(Utils SOURCE_DIR)
target_include_directories(${PROJECT_NAME} PRIVATE ${SOURCE_DIR}/Inc/)
add_dependencies(${PROJECT_NAME} Utils)

target_link_libraries(${PROJECT_NAME} ${PROJECT_SOURCE_DIR}/Lib/libA.a)
ExternalProject_Add(A
    GIT_REPOSITORY ${SOME_GIT_URL}/_git/A
    GIT_TAG master
    CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PROJECT_SOURCE_DIR}/Lib
)
ExternalProject_Get_Property(A SOURCE_DIR)
target_include_directories(${PROJECT_NAME} PRIVATE ${SOURCE_DIR}/Inc/)
add_dependencies(${PROJECT_NAME} A)

如何实现所需的结果?

英文:

my team has a utility library, lets call it Utils which is built with CMake.
it is ExternalProject_Added in another library's CMake, let's call it A.

our executable, App, ExternalProject_Adds A, and Utils.
out problem is that utils is built twice, once when A is built and again when App is built when it could have been built only once.

Here are examplary CMake files:

Utils:

cmake_minimum_required(VERSION 3.16.3)

project(Utils)

include_directories(${PROJECT_SOURCE_DIR}/Inc)
file(GLOB SOURCE_FILES ${PROJECT_SOURCE_DIR}/Src/*.cpp)
add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES})


install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_PREFIX})

A:

cmake_minimum_required(VERSION 3.16.3)

project(A)

include_directories(${PROJECT_SOURCE_DIR}/Inc)
file(GLOB SOURCE_FILES ${PROJECT_SOURCE_DIR}/Src/*.cpp)
add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES})


install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_PREFIX})


include(ExternalProject)

target_link_libraries(${PROJECT_NAME} ${PROJECT_SOURCE_DIR}/Lib/libUtils.a)
ExternalProject_Add(Utils
    GIT_REPOSITORY ${SOME_GIT_URL}/_git/Utils
    GIT_TAG master
    CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PROJECT_SOURCE_DIR}/Lib
)
ExternalProject_Get_Property(Utils SOURCE_DIR)
target_include_directories(${PROJECT_NAME} PRIVATE ${SOURCE_DIR}/Inc/)
add_dependencies(${PROJECT_NAME} Utils)

App:

cmake_minimum_required(VERSION 3.16.3)

project(App)

include_directories(${PROJECT_SOURCE_DIR}/Inc)
file(GLOB SOURCE_FILES ${PROJECT_SOURCE_DIR}/Src/*.cpp)
add_executable(${PROJECT_NAME} ${SOURCE_FILES})


install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_PREFIX})


include(ExternalProject)

target_link_libraries(${PROJECT_NAME} ${PROJECT_SOURCE_DIR}/Lib/libUtils.a)
ExternalProject_Add(Utils
    GIT_REPOSITORY ${SOME_GIT_URL}/_git/Utils
    GIT_TAG master
    CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PROJECT_SOURCE_DIR}/Lib
)
ExternalProject_Get_Property(Utils SOURCE_DIR)
target_include_directories(${PROJECT_NAME} PRIVATE ${SOURCE_DIR}/Inc/)
add_dependencies(${PROJECT_NAME} Utils)


target_link_libraries(${PROJECT_NAME} ${PROJECT_SOURCE_DIR}/Lib/libA.a)
ExternalProject_Add(A
    GIT_REPOSITORY ${SOME_GIT_URL}/_git/A
    GIT_TAG master
    CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PROJECT_SOURCE_DIR}/Lib
)
ExternalProject_Get_Property(A SOURCE_DIR)
target_include_directories(${PROJECT_NAME} PRIVATE ${SOURCE_DIR}/Inc/)
add_dependencies(${PROJECT_NAME} A)

how can we achieve the wanted result?

答案1

得分: 1

ExternalProject_Add 可以获取、构建和安装一个独立的项目。它支持调用许多不同的构建系统:Makefiles、Automake、ninja、CMake,以及几乎任何其他构建系统,只要你提供适当的构建命令。然而,这意味着 ExternalProject_Add 没有真正的方式与它所调用的构建系统进行“通信”。它只是调用它,等待它运行,然后提供已安装的文件(二进制文件、头文件等)给正在构建的主项目。

在你的情况下,这意味着当构建“A”时,会启动一个单独的CMake实例来构建它。一旦这个单独的实例完成运行,就会认为“A”目标已构建完成,可以使用“libA.a”。在“A”的构建中定义的目标对“App”的构建不可见 - 它们是独立的CMake构建,根本不共享它们的目标。因此,“A”构建的“Utils”版本不会立即对“App”可见。

不过,有一种方法可以让这个工作:只需让“A”安装它构建的“Utils”库。最终,“A”的构建和安装应该将 libA.alibUtils.a 都放在目标安装目录中(即 ${CMAKE_INSTALL_PREFIX}/lib)。还必须将必要的头文件放在该安装目录中(即 ${CMAKE_INSTALL_PREFIX}/include,以符合惯例)。

一般来说,你也不应该将子项目的安装目录放在项目的源代码树中。相反,将它们放在 ${CMAKE_CURRENT_BINARY_DIR}/some_subdirectory。一旦库构建完成,你可以将其复制到安装目录(使用 install 命令),如果你希望库对项目的用户可见。

以下是“A”构建脚本的粗略(不完整)概述:

cmake_minimum_required(VERSION 3.16.3)

project(A)

include_directories(${PROJECT_SOURCE_DIR}/Inc)
file(GLOB SOURCE_FILES "${PROJECT_SOURCE_DIR}/Src/*.cpp")
add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES})

install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_PREFIX}/Lib)

# TODO: 还要将A的头文件安装到 ${CMAKE_INSTALL_PREFIX}/Inc

include(ExternalProject)

ExternalProject_Add(Utils
    GIT_REPOSITORY ${SOME_GIT_URL}/_git/Utils
    GIT_TAG master
    INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}/Utils_install
)
ExternalProject_Get_Property(Utils INSTALL_DIR)
target_include_directories(${PROJECT_NAME} PRIVATE ${INSTALL_DIR}/Inc/)
target_link_libraries(${PROJECT_NAME} ${INSTALL_DIR}/Lib/libUtils.a)
add_dependencies(${PROJECT_NAME} Utils)

# TODO: 安装来自 ${INSTALL_DIR}/Lib 的库到 ${CMAKE_INSTALL_PREFIX}/Lib

# TODO: 安装来自 ${INSTALL_DIR}/Inc 的头文件到 ${CMAKE_INSTALL_PREFIX}/Inc

类似地,“Utils”也必须安装它的二进制文件和头文件。 (示例脚本假设“Utils”将其二进制文件放在 ${CMAKE_INSTALL_PREFIX}/Lib,并将其头文件放在 ${CMAKE_INSTALL_PREFIX}/Include,就像“A”一样。)

一旦你像这样更改了“A”和“Utils”的构建脚本,“App”只需构建“A”,并可以使用“A”提供的“Utils”的副本。

英文:

ExternalProject_Add fetches, builds and installs a separate project. It supports invoking many different build systems: Makefiles, Automake, ninja, CMake, and pretty much anything else too if you supply it the appropriate build commands. This means, however, that ExternalProject_Add has no real way of "communicating" with the build system it's invoking. It simply invokes it, waits for it to run, and then provides the installed files (binaries, headers, etc) to the main project being built.

In your case, this means that when "A" is built, a separate instance of CMake is launched to build it. Once this separate instance has finished running, the "A" target is considered to be built, and "libA.a" can be used. The targets defined within the build of "A" are not visible to the build of "App" - they're separate CMake builds and simply don't share their targets. Therefore, the version of "Utils" that "A" has built is not immediately visible to "App".

There is a way to make this work, though: Simply make "A" install the "Utils" library that it builds. In the end, the build and installation of "A" should place both libA.a and libUtils.a in the target install directory (i.e. ${CMAKE_INSTALL_PREFIX}/lib). It must also place the necessary headers in that install directory (i.e. in ${CMAKE_INSTALL_PREFIX}/include to keep with conventions).

In general, you also shouldn't put the install directories of subprojects into the project's source tree. Instead, put them at ${CMAKE_CURRENT_BINARY_DIR}/some_subdirectory. Once the library is built, you can copy it to the install directory (with the install command) if you want the library to be visible to users of the project.

Here's a rough (incomplete) outline of what the build script of "A" will have to do:

cmake_minimum_required(VERSION 3.16.3)

project(A)

include_directories(${PROJECT_SOURCE_DIR}/Inc)
file(GLOB SOURCE_FILES "${PROJECT_SOURCE_DIR}/Src/*.cpp")
add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES})


install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_PREFIX}/Lib)

# TODO: Also install headers of A to ${CMAKE_INSTALL_PREFIX}/Inc


include(ExternalProject)

ExternalProject_Add(Utils
    GIT_REPOSITORY ${SOME_GIT_URL}/_git/Utils
    GIT_TAG master
    INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}/Utils_install
)
ExternalProject_Get_Property(Utils INSTALL_DIR)
target_include_directories(${PROJECT_NAME} PRIVATE ${INSTALL_DIR}/Inc/)
target_link_libraries(${PROJECT_NAME} ${INSTALL_DIR}/Lib/libUtils.a)
add_dependencies(${PROJECT_NAME} Utils)

# TODO: Install libraries from ${INSTALL_DIR}/Lib to ${CMAKE_INSTALL_PREFIX}/Lib

# TODO: Install headers from ${INSTALL_DIR}/Inc to ${CMAKE_INSTALL_PREFIX}/Inc

"Utils" will similarly have to install both its binary and its headers. (The example script assumes that "Utils" places its binary in ${CMAKE_INSTALL_PREFIX}/Lib and its headers in ${CMAKE_INSTALL_PREFIX}/Include, just like "A".)

Once you've changed the build scripts of "Utils" and "A" like that, "App" only has to build "A" and can use the copy of "Utils" that "A" provides.

huangapple
  • 本文由 发表于 2023年2月6日 21:48:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/75362143.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定