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


114

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

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

现在,我想将Project A用作Project 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的CMakeLists时,包含文件尚不存在。

有没有办法使include依赖于正在构建的外部项目?

更新:我基于此问题和遇到的其他常见问题编写了一个简短的 CMake by Example教程

Answers:


67

我认为您在这里混淆了两种不同的范例。

如您所述,高度灵活的ExternalProject模块在构建时运行其命令,因此您不能直接使用Project A的导入文件,因为它仅在安装Project A后才创建。

如果你想include项目A的导入文件,你就必须调用项目B的的CMakeLists.txt之前手动安装Project 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)

2
感谢您的回答。您的建议与我以前的建议类似。我希望能找到一种方法,利用导出的目标,因为它似乎是比手动指定的lib路径有着更好的接口...
mirkokiefer

7
我想避免必须在源代码树中包含外部项目的源代码。如果ExternalProject_Add只是表现得像add_subdirectory并且暴露所有目标,那将是很好的。您上面描述的解决方案可能仍然是最干净的。
mirkokiefer

2
考虑将它们都做成ExternalProject构建,然后让B依赖A,然后项目B的CMakeLists文件将包括项目A的目标文件,但是您的“超级构建” CMakeLists只会先构建A,然后构建B,都作为ExternalProjects ...
DLRdave

3
@DLRdave-我已经看过几次推荐的Super Build解决方案,但是我不确定与仅通过包含一些外部项目相比,它提供了什么好处ExternalProject。它是一致性,还是更具规范性,还是其他?我确定我在这里遗漏了一些基本知识。
Fraser 2013年

6
该解决方案的问题之一是我们刚刚对库名称(alib.lib)进行了硬编码,这使得构建系统不会跨平台,因为不同的OS对共享库使用不同的命名方案,并适应这些不同的命名方案是CMake的功能之一。
nsg 2015年

22

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

CMakeLists.txt.in

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      ""
)

CMakeLists.txt

# 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

但是,它似乎确实很hacky。我想提出一个替代解决方案-使用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()默认情况下未使用子目录中的命令。


这可能只是我过于谨慎,因为这只是一个示例,并且gtest可能非常稳定,但是我强烈建议您始终GIT_TAG在克隆过程中使用特定的方法,否则可能会丢失构建的可重复性,因为从现在起两年后,运行构建脚本的人将获得与您所做的版本不同。CMake的文档也建议这样做。
jrh

5

编辑:CMake现在对此具有内置支持。查看新答案

您还可以在辅助制造过程中强制构建从属目标

请参阅对相关主题的回答


1

cmake ExternalProject_Add确实可以使用,但是我不喜欢它-它在构建,连续轮询等过程中执行某些操作。ExternalProject_Add不幸的是,我尝试了几次尝试来覆盖,但是没有成功。

然后我也尝试添加git子模块,但这会拖拽整个git仓库,而在某些情况下,我只需要整个git仓库的子集。我检查过的内容-确实可以执行稀疏git checkout,但这需要单独的功能,我在下面编写了该功能。

#-----------------------------------------------------------------------------
#
# 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)

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

有人可能不喜欢检出主机/中继,因为这样可能会损坏-那么总是可以指定特定的标签。

签出仅执行一次,直到您清除缓存文件夹。


1

我在寻找类似的解决方案。此处的答复以及顶部的教程提供了信息。我研究了这里提到的帖子/博客,以成功建立我的网站。我正在为我发布完整的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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.