CMake: 如何构建外部项目并包含其目标

我有一个项目 A,它将静态库作为目标导出:

install(TARGETS alib DESTINATION lib EXPORT project_a-targets)
install(EXPORT project_a-targets DESTINATION lib/alib)

现在我想使用项目 A 作为项目 B 的一个外部项目,并包括它的构建目标:

ExternalProject_Add(project_a
URL ...project_a.tar.gz
PREFIX ${CMAKE_CURRENT_BINARY_DIR}/project_a
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
)


include(${CMAKE_CURRENT_BINARY_DIR}/lib/project_a/project_a-targets.cmake)

问题是,在运行项目 B 的 CMakeList 时,包含文件还不存在。

是否有方法使包含依赖于正在构建的外部项目?

更新 : 我写了一个简短的 示例制作教程基于这个和其他我遇到的常见问题。

151913 次浏览

我觉得你把两种不同的模式混淆了。

正如您提到的,高度灵活的 ExternalProject模块在构建时运行其命令,所以您不能直接使用项目 A 的导入文件,因为它只在项目 A 安装之后才创建。

如果你想要 include项目 A 的导入文件,你需要 在调用项目 B 的 CMakeLists.txt 之前手动安装项目 A-就像任何其他的第三方依赖添加这种方式或通过 find_file/find_library/find_package

如果您想要使用 ExternalProject_Add,您需要向 CMakeLists.txt 添加以下内容:

ExternalProject_Add(project_a
URL ...project_a.tar.gz
PREFIX ${CMAKE_CURRENT_BINARY_DIR}/project_a
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
)


include(${CMAKE_CURRENT_BINARY_DIR}/lib/project_a/project_a-targets.cmake)


ExternalProject_Get_Property(project_a install_dir)
include_directories(${install_dir}/include)


add_dependencies(project_b_exe project_a)
target_link_libraries(project_b_exe ${install_dir}/lib/alib.lib)

编辑: CMake 现在已经内置了对此的支持。参见使用 FetchContent新答案

还可以在辅助 make 过程中强制构建依赖目标

有关相关主题,请参阅 我的回答

这篇文章有一个合理的答案:

返回文章页面

cmake_minimum_required(VERSION 2.8.2)


project(googletest-download NONE)


include(ExternalProject)
ExternalProject_Add(googletest
GIT_REPOSITORY    https://github.com/google/googletest.git
GIT_TAG           master
SOURCE_DIR        "${CMAKE_BINARY_DIR}/googletest-src"
BINARY_DIR        "${CMAKE_BINARY_DIR}/googletest-build"
CONFIGURE_COMMAND ""
BUILD_COMMAND     ""
INSTALL_COMMAND   ""
TEST_COMMAND      ""
)

返回文章页面

# Download and unpack googletest at configure time
configure_file(CMakeLists.txt.in
googletest-download/CMakeLists.txt)
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download )
execute_process(COMMAND ${CMAKE_COMMAND} --build .
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download )


# Prevent GoogleTest from overriding our compiler/linker options
# when building with Visual Studio
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)


# Add googletest directly to our build. This adds
# the following targets: gtest, gtest_main, gmock
# and gmock_main
add_subdirectory(${CMAKE_BINARY_DIR}/googletest-src
${CMAKE_BINARY_DIR}/googletest-build)


# The gtest/gmock targets carry header search path
# dependencies automatically when using CMake 2.8.11 or
# later. Otherwise we have to add them here ourselves.
if (CMAKE_VERSION VERSION_LESS 2.8.11)
include_directories("${gtest_SOURCE_DIR}/include"
"${gmock_SOURCE_DIR}/include")
endif()


# Now simply link your own targets against gtest, gmock,
# etc. as appropriate

我想提出一个替代的解决方案——使用 Git 子模块。

cd MyProject/dependencies/gtest
git submodule add https://github.com/google/googletest.git
cd googletest
git checkout release-1.8.0
cd ../../..
git add *
git commit -m "Add googletest"

然后在 MyProject/dependencies/gtest/CMakeList.txt中你可以这样做:

cmake_minimum_required(VERSION 3.3)


if(TARGET gtest) # To avoid diamond dependencies; may not be necessary depending on you project.
return()
endif()


add_subdirectory("googletest")

我还没有广泛地尝试过这种方法,但它似乎更干净。

编辑: 这种方法有一个缺点: 子目录可能运行您不希望运行的 install()命令。这篇文章提供了一个禁用它们的方法,但它是错误的,不适合我。

编辑2: 如果您使用 add_subdirectory("googletest" EXCLUDE_FROM_ALL),似乎意味着子目录中的 install()命令在默认情况下不会被使用。

Cmake 的 ExternalProject_Add确实可以使用,但是我不喜欢它的地方在于它在构建、连续轮询等过程中执行了一些操作。.我更喜欢在构建阶段构建项目,没有别的。我曾多次尝试覆盖 ExternalProject_Add,但不幸未能成功。

然后,我还尝试添加 git 子模块,但这会拖累整个 git 存储库,而在某些情况下,我只需要整个 git 存储库的子集。我已经检查过了——确实可以执行稀疏 git 签出,但是这需要单独的函数,我在下面写了这个函数。

#-----------------------------------------------------------------------------
#
# Performs sparse (partial) git checkout
#
#   into ${checkoutDir} from ${url} of ${branch}
#
# List of folders and files to pull can be specified after that.
#-----------------------------------------------------------------------------
function (SparseGitCheckout checkoutDir url branch)
if(EXISTS ${checkoutDir})
return()
endif()


message("-------------------------------------------------------------------")
message("sparse git checkout to ${checkoutDir}...")
message("-------------------------------------------------------------------")


file(MAKE_DIRECTORY ${checkoutDir})


set(cmds "git init")
set(cmds ${cmds} "git remote add -f origin --no-tags -t master ${url}")
set(cmds ${cmds} "git config core.sparseCheckout true")


# This command is executed via file WRITE
# echo <file or folder> >> .git/info/sparse-checkout")


set(cmds ${cmds} "git pull --depth=1 origin ${branch}")


# message("In directory: ${checkoutDir}")


foreach( cmd ${cmds})
message("- ${cmd}")
string(REPLACE " " ";" cmdList ${cmd})


#message("Outfile: ${outFile}")
#message("Final command: ${cmdList}")


if(pull IN_LIST cmdList)
string (REPLACE ";" "\n" FILES "${ARGN}")
file(WRITE ${checkoutDir}/.git/info/sparse-checkout ${FILES} )
endif()


execute_process(
COMMAND ${cmdList}
WORKING_DIRECTORY ${checkoutDir}
RESULT_VARIABLE ret
)


if(NOT ret EQUAL "0")
message("error: previous command failed, see explanation above")
file(REMOVE_RECURSE ${checkoutDir})
break()
endif()
endforeach()


endfunction()




SparseGitCheckout(${CMAKE_BINARY_DIR}/catch_197 https://github.com/catchorg/Catch2.git v1.9.7 single_include)
SparseGitCheckout(${CMAKE_BINARY_DIR}/catch_master https://github.com/catchorg/Catch2.git master single_include)

我在下面添加了两个函数调用,以说明如何使用该函数。

有些人可能不喜欢签出 master/象限,因为那个象限可能已经坏了——那么总是可以指定特定的标记。

签出只执行一次,直到清除缓存文件夹为止。

我也在寻找类似的解决方案。这里的回复和最上面的教程是信息量很大的。我研究了这里提到的帖子/博客来成功构建我的博客。我发布完整的 CMakeLists.txt 为我工作。我想,作为初学者的基本模板,这会很有帮助。

"CMakeLists.txt"

cmake_minimum_required(VERSION 3.10.2)


# Target Project
project (ClientProgram)


# Begin: Including Sources and Headers
include_directories(include)
file (GLOB SOURCES "src/*.c")
# End: Including Sources and Headers




# Begin: Generate executables
add_executable (ClientProgram ${SOURCES})
# End: Generate executables




# This Project Depends on External Project(s)
include (ExternalProject)


# Begin: External Third Party Library
set (libTLS ThirdPartyTlsLibrary)
ExternalProject_Add (${libTLS}
PREFIX          ${CMAKE_CURRENT_BINARY_DIR}/${libTLS}
# Begin: Download Archive from Web Server
URL             http://myproject.com/MyLibrary.tgz
URL_HASH        SHA1=<expected_sha1sum_of_above_tgz_file>
DOWNLOAD_NO_PROGRESS ON
# End: Download Archive from Web Server


# Begin: Download Source from GIT Repository
#    GIT_REPOSITORY  https://github.com/<project>.git
#    GIT_TAG         <Refer github.com releases -> Tags>
#    GIT_SHALLOW     ON
# End: Download Source from GIT Repository


# Begin: CMAKE Comamnd Argiments
CMAKE_ARGS      -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_CURRENT_BINARY_DIR}/${libTLS}
CMAKE_ARGS      -DUSE_SHARED_LIBRARY:BOOL=ON
# End: CMAKE Comamnd Argiments
)


# The above ExternalProject_Add(...) construct wil take care of \
# 1. Downloading sources
# 2. Building Object files
# 3. Install under DCMAKE_INSTALL_PREFIX Directory


# Acquire Installation Directory of
ExternalProject_Get_Property (${libTLS} install_dir)


# Begin: Importing Headers & Library of Third Party built using ExternalProject_Add(...)
# Include PATH that has headers required by Target Project
include_directories (${install_dir}/include)


# Import librarues from External Project required by Target Project
add_library (lmytls SHARED IMPORTED)
set_target_properties (lmytls PROPERTIES IMPORTED_LOCATION ${install_dir}/lib/libmytls.so)
add_library (lmyxdot509 SHARED IMPORTED)
set_target_properties(lmyxdot509 PROPERTIES IMPORTED_LOCATION ${install_dir}/lib/libmyxdot509.so)


# End: Importing Headers & Library of Third Party built using ExternalProject_Add(...)
# End: External Third Party Library


# Begin: Target Project depends on Third Party Component
add_dependencies(ClientProgram ${libTLS})
# End: Target Project depends on Third Party Component


# Refer libraries added above used by Target Project
target_link_libraries (ClientProgram lmytls lmyxdot509)

我建议首先使用 FetchContent:

include(FetchContent)
FetchContent_Declare(glog
GIT_REPOSITORY    https://github.com/google/glog.git
SOURCE_DIR        ${CMAKE_CURRENT_SOURCE_DIR}/third_party/glog
SUBBUILD_DIR      third_party/glog/subbuild
BINARY_DIR        third_party/glog/build
)
option(WITH_GFLAGS "" OFF)
option(WITH_GTEST "" OFF)
option(WITH_GMOCK "" OFF)
option(WITH_UNWIND "" OFF)
option(BUILD_SHARED_LIBS "" OFF)
option(BUILD_TESTING "" OFF)
FetchContent_MakeAvailable(glog)


....


add_library(libsomething STATIC)
target_link_libraries(libsomething PUBLIC glog::glog)


注意,所有的 CMake魔术都按预期工作: 您不需要为 libsomething指定 glog的 include 目录或构建构件。