英文:
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_Add
ed in another library's CMake, let's call it A.
our executable, App, ExternalProject_Add
s 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.a
和 libUtils.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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论