【问题标题】:CMake: How to build external projects and include their targetsCMake:如何构建外部项目并包含其目标
【发布时间】:2013-02-16 23:48:50
【问题描述】:

我有一个将静态库导出为目标的项目 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

【问题讨论】:

    标签: cmake external-project


    【解决方案1】:

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

    如您所述,高度灵活的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)

    【讨论】:

    • 感谢您的回答。您的建议与我之前的建议相似。我希望找到一种方法来利用导出的目标,因为它看起来比手动指定 lib 路径更好......
    • 我想避免在我的源代码树中包含外部项目的源代码。如果ExternalProject_Add 表现得像add_subdirectory 并暴露所有目标,那就太好了。您上面描述的解决方案可能仍然是最干净的。
    • 考虑将它们都构建为 ExternalProject 构建,然后让 B 依赖于 A,然后项目 B 的 CMakeLists 文件将包含来自项目 A 的目标文件,但您的“超级构建”CMakeLists 只会构建A 然后 B,都作为 ExternalProjects...
    • @DLRdave - 我已经看过几次推荐的 Super Build 解决方案,但我想我不确定它比仅包含 一些 外部项目有什么好处ExternalProject。是一致性,还是更规范,还是别的什么?我确定我在这里遗漏了一些基本的东西。
    • 这个解决方案的一个问题是我们只是硬编码了库名称(alib.lib),这使得构建系统不是跨平台的,因为不同的操作系统使用不同的命名方案来共享库,并且适应这些不同的命名方案是 CMake 的特性之一。
    【解决方案2】:

    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
    

    但它看起来确实很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() 命令。 This post has an approach to disable them 但它有问题,对我不起作用。

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

    【讨论】:

    • 这可能只是我过于谨慎,因为这只是一个示例,gtest 可能相当稳定,但我强烈建议在克隆期间始终使用特定的GIT_TAG,您可能会失去构建可重复性,因为 2几年后,运行构建脚本的人将获得与您所做的不同的版本。 CMake 的docs 也推荐这个。
    【解决方案3】:

    编辑:CMake 现在对此具有内置支持。请参阅使用FetchContentnew answer

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

    有关相关主题,请参阅 my answer

    【讨论】:

      【解决方案4】:

      我正在寻找类似的解决方案。此处的回复和顶部的教程提供了丰富的信息。我研究了这里提到的帖子/博客以成功建立我的。我发布了完整的 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)
      

      【讨论】:

        【解决方案5】:

        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)
        

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

        有些人可能不喜欢检查 master/trunk,因为那个可能坏了 - 然后总是可以指定特定的标签。

        结帐只会执行一次,直到您清除缓存文件夹。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-28
          相关资源
          最近更新 更多