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

时间:2013-03-02 13:59:48

标签: cmake external-project

我有一个项目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的CMakeLists运行时,包含文件还不存在。

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

更新 : 我根据这个和我遇到的其他常见问题写了一篇简短的CMake by Example tutorial

6 个答案:

答案 0 :(得分:55)

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

正如您所指出的,高度灵活的ExternalProject模块在​​构建时运行其命令,因此您无法直接使用Project A的导入文件,因为只有在安装了项目A后才会创建它。

如果你想要include项目A的导入文件,你将拥有来手动安装Project A,然后再调用Project B的CMakeLists.txt - 就像添加任何其他第三方依赖项一样这种方式或通过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)

答案 1 :(得分:13)

This post有一个合理的答案:

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

然而,它看起来确实非常黑客。我想提出一个替代解决方案 - 使用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()命令。 This post has an approach to disable them但它有些错误,对我不起作用。

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

答案 2 :(得分:4)

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

请参阅my answer相关主题。

答案 3 :(得分: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)

答案 4 :(得分:0)

您可以尝试做的是在project_a中使用cmake的export命令。与在install中使用EXPORT option命令相比,它的工作方式略有不同,因为运行cmake时会生成project_a-targets.cmake文件。 project_a-targets.cmake文件中生成的导入目标最初指向项目二进制目录中不存在的库文件,这些文件只有在运行build命令后才会生成。

要更好地理解我在说的内容,只需创建一个小cmake项目,该项目先创建一个简单的库,然后再执行export命令(未测试以下代码):

add_library (project_a lib.cpp)
export (
  TARGETS 
    project_a
  FILE
    project_a-targets.cmake
)

在简单的示例上运行cmake命令后,您应该能够在二进制目录(或其子文件夹之一)中找到project_a-targets.cmake。检查文件时,您可能会注意到它当前指向不存在的库文件。只有运行了build命令之后,库才会存在。

因此,回到您的问题,您将需要更新project-a的{​​{1}}以包括CMakeLists.txt命令。然后,需要确保在处理export之后,它调用配置步骤,该步骤将产生ExternalProject_Add,然后可以调用应该起作用的project_a-targets.cmake。最后,您将需要在include(.../project_a-targets.cmake)project_b之间添加一个依赖关系,以便在尝试构建project_a之前强制构建project_a

答案 5 :(得分:0)

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)

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

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

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