【问题标题】:C++: Include directory, but only allow one folder to be visible (for Eigen)C++:包含目录,但只允许一个文件夹可见(对于 Eigen)
【发布时间】:2020-10-22 10:06:30
【问题描述】:

假设有人想要将一个大型开源项目作为子模块包含在一个 repo (myrepo) 中。对于此示例,我们以Eigen 为例。没问题,我可以

git submodule add https://gitlab.com/libeigen/eigen.git

这将创建一个包含许多子文件夹的 eigen 子目录:

myrepo/
    eigen/
        bench
        blas
        ci
        cmake
        debug
        demos
        doc
        Eigen
        failtest
        lapack
        scripts
        test
        ...

然而,为了使用 Eigen 库,真正需要的只是文件夹 myrepo/eigen/Eigen 的内容。所以我们只希望该文件夹对编译器/链接器可见。但是,为了清楚起见,我更希望包含该文件夹内的文件,例如 myrepo/eigen/Eigen/Dense ,就像

#include <Eigen/Dense>

两个明显的次优解决方案是

  1. myrepo/eigen添加为包含目录,并包含类似的文件

    #include <Eigen/Dense>
    
  2. myrepo/eigen/Eigen添加为包含目录,并包含类似的文件

    #include <Dense>
    

这两种方法都有明显的缺点。具体来说,

  1. 使用myrepo/eigen 作为包含目录会将所有其他文件暴露给编译器/链接器。由于 repo 和其中包含的所有其他文件的大小,我觉得这是一个用于命名空间冲突或类似情况的定时炸弹。例如,现在编译器看到有一个test/ 子文件夹,其内容现在是免费游戏。

  2. 我相信,在没有Eigen 前导的情况下包含标题对于代码清晰度来说是一场灾难。

    #include <Dense> // ¯\_(ツ)_/¯ Where did this come from??? 
    

我知道的唯一其他选择是分叉或复制原始存储库并删除我想要排除的所有内容。

我主要关心将 Eigen 添加为子模块。因此,如果有将其作为子模块包含在内的最佳实践建议,我也很感兴趣。我知道有一个与此主题相关的未解决问题:https://gitlab.com/libeigen/eigen/-/issues/1133

【问题讨论】:

  • 我认为唯一正确的解决方案 - 使用 Eigen 作为静态或动态库。使用您的 CMake 查找对 FindLibrary 等的依赖项。通过源代码嵌入 3Dparty 库被视为一场噩梦。
  • 我同意这个评估。问题是包管理器在这种情况下远远落后于 repo。我通常将 Eigen 作为子模块包含在内,然后立即签出一个相当稳定的提交。例如,Ubuntu 18 包管理器中的版本缺少一些我需要的有用功能
  • 不仅缺少一些功能,而且由于使用了贬低的东西,还会导致编译器问题。如果你有类似 -Wall 的设置,gcc9 及更高版本真的不喜欢包管理器提供的 Eigen 版本,至少根据我的经验。

标签: c++ gcc eigen


【解决方案1】:

您可以创建一个单独的空目录include 并放置一个指向myrepo/eigen/Eigen 的软链接Eigen。然后将 include 添加为搜索目录,而不是 myrepo/eigen

Git“按原样”跟踪软链接,将链接的路径存储在存储库中,并在克隆时重新创建链接。它不尝试验证链接是否有效。 只要:

  • 链接以指向同一存储库中文件/目录的相对路径形式给出
  • 将存储库克隆到能够保存软链接的文件系统上

一切都会按预期进行。

【讨论】:

    【解决方案2】:

    虽然编译器只会看到明确提供给他的文件,但 CMake 会从子模块文件夹中获取 CMakeLists.txt 并执行可能很混乱的构建步骤。

    CMake 是一个非常强大的构建系统,因此它允许您以优雅的方式处理这些问题。 CMake 能够将外部项目包含为库。这与简单的add_subdirectory() 不同。在外部项目的情况下,CMake 实际上会为该项目创建另一个构建实例,并将使用构建步骤来下载、配置、构建和安装项目,然后构建才能开始在主目标上工作。

    有了外部构建,您可以构建库并将其安装在一个临时文件夹中(相对于您的源代码,或相对于您的构建文件夹)。这样,您就可以只使用来自外部项目的构建过程的产品。

    这里值得一提的是,外部项目可以使用不同的构建系统,而不需要 CMake,这使得它变得更加强大。

    您可以使用我为 Eigen 库准备的这个小演示作为外部项目将其添加到您的构建中。

    该项目在由git submodule add https://gitlab.com/libeigen/eigen.git初始化的项目根目录中有eigen作为子模块

    CMakeLists.txt

    cmake_minimum_required(VERSION 3.17)
    project(EigenTest)
    
    include(ExternalProject)
    
    set(CMAKE_CXX_STANDARD 17)
    set(EIGEN_INCLUDE eigen_install)
    
    ExternalProject_Add(eigen
            SOURCE_DIR ${CMAKE_SOURCE_DIR}/eigen
            CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR} -DINCLUDE_INSTALL_DIR=${EIGEN_INCLUDE}
            )
    
    ExternalProject_Get_Property(eigen install_dir)
    
    add_executable(EigenTest main.cpp)
    add_dependencies(EigenTest eigen)
    target_include_directories(EigenTest PRIVATE ${install_dir})
    target_include_directories(EigenTest PRIVATE ${CMAKE_BINARY_DIR}/${EIGEN_INCLUDE})
    
    

    ma​​in.cpp

    #include <iostream>
    #include <Eigen/Dense>
    
    using Eigen::MatrixXd;
    
    int main() {
        MatrixXd m(2,2);
        m(0,0) = 3;
        m(1,0) = 2.5;
        m(0,1) = -1;
        m(1,1) = m(1,0) + m(0,1);
        std::cout << m << std::endl;
    
        return 0;
    }
    

    这将在构建目标中创建一个名为 eigen_install 的文件夹,其中将包含构建库的最终产品。请注意,在我的简单示例中,您应该避免使用 eigen 作为目标,因为在构建库期间,CMake 在构建目标中会创建一个 eigen 文件夹。您可以通过 ExternalProject 的附加配置来控制它,但这超出了本问题的范围。

    CMake documents 中了解更多关于ExternalProject 模块的附加功能

    使用 git submodule 和 CMake 的 ExternalProject 等两个强大的概念,您可以完全控制构建过程,只需选择要构建的外部项目的哪个分支以及使用哪些配置参数。同时,您将保持所需的构建分离(源代码分离)。

    【讨论】:

    • 我已经 +1 了这个答案,但有一点:即使在示例中,我也不会使用 include_directories。您应该将 Eigen 库模块化并使用 set_target_dependencies(EigenTest PRIVATE Eigen)
    【解决方案3】:

    如果你不能做其他答案,例如你的文件系统不支持链接,你可以复制“Eigen”目录到一个单独的目录

    file(COPY
      ${CMAKE_CURRENT_SOURCE_DIR}/lib/eigen/Eigen
      ${CMAKE_BUILD_FILES_DIRECTOR}/lib/Eigen
    )
    // And then link to that folder
    
    include_directories(${CMAKE_BUILD_FILES_DIRECTOR}/lib/Eigen)
    

    我没有尝试过这段代码,但理论上它应该可以工作。它应该有点健壮,尽管它有点 hacky。

    cmake documentation

    【讨论】:

      【解决方案4】:

      假设一个人想要包含一个大型开源项目作为子模块

      但是,为了使用 Eigen 库,真正需要的只是文件夹的内容

      您要么希望将整个项目包含为子模块,要么不这样做。子模块专门用于包含整个源代码树。如果您不想逐字包含整个源代码树,请不要使用子模块。

      第 3 方库的常用方法是克隆他们自己的存储库,构建他们的可安装工件(公共头文件和静态和/或动态库),并将它们安装在某些第 3 方库区域中。然后你编译并链接你的代码。 3rd-party library 是您的构建系统引用的外部依赖项,而不是您自己的 repo 的一部分。

      【讨论】:

        【解决方案5】:

        我想分享我在上一个项目中是如何解决类似问题的——希望它对你有用。我的要求是相似的——只公开第三方项目的开发人员打算公开的头文件和库。我的项目本身使用 cmake,但这不是必需的。

        我在 repo 的根目录中创建了一个 deps 文件夹,并将 CMakeLists.txt 放在那里。这不是构建主项目,而是拉下我关心的依赖项,构建它们(他们的开发人员打算构建的方式),并安装它们以供我的项目使用。看起来像这样:

        myrepo/
            deps/
                CMakeLists.txt
        

        与@jordanvrtanoski 类似,我使用ExternalProject 功能,但我编写了一个宏,这让我更加简洁。我最初在add_subdirectory 上苦苦挣扎,最终我找到了他的建议来避免add_subdirectory 声音:)。与@jordanvrtanoski 不同,我更喜欢为依赖项使用单独的 CMakeLists.txt,即使我的顶级项目是基于 cmake 的。否则,我发现 cmake 会进行一些检查,确认依赖项已正确安装,并且从我的内部开发循环中抢夺时间......我还发现,将依赖项安装在内循环之外可以让您可靠地执行 @ 987654324@ 在 cmake 中,不用担心在顶层项目中分别指定 include 和 lib 目录。

        这是 CMakeLists.txt 的 sn-p

        cmake_minimum_required(VERSION 3.17)
        
        project(deps)
        
        include(ExternalProject)
        
        function(install NAME GIT_REPO GIT_TAG)
            ExternalProject_Add(
                    ${NAME}
                    GIT_REPOSITORY ${GIT_REPO}
                    GIT_TAG ${GIT_TAG}
                    PREFIX ${CMAKE_SOURCE_DIR}/${CMAKE_BUILD_TYPE}
        
                    ${ARGN}
                    CMAKE_ARGS
                    --config ${CMAKE_BUILD_TYPE}
                    -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
                    -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded$<$<CONFIG:Debug>:Debug>
                    -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_SOURCE_DIR}/${CMAKE_BUILD_TYPE}/install
            )
        endfunction()
        
        install(gflags
                https://github.com/gflags/gflags.git
                v2.2.0
        
                CMAKE_ARGS
                -DREGISTER_INSTALL_PREFIX=FALSE
                -DGFLAGS_BUILD_TESTING=FALSE)
        
        install(glog
                https://github.com/google/glog.git
                v0.4.0
        
                DEPENDS gflags
        
                CMAKE_ARGS
                -DBUILD_TESTING=FALSE)
        

        您可以通过以下方式触发安装:

        cmake -DCMAKE_BUILD_TYPE=Debug --log-level=VERBOSE -G "NMake Makefiles" ..
        cmake --build . --config Debug
        

        请注意,您可以分别进行 Debug 和 Release 构建,并且工件最终位于 deps 下的单独文件夹中。

        这里有一些需要解开的东西,但我希望这都是有充分理由的。您可以看到,一旦宏退出,安装gflags 的sn-p 就非常少了。它为 cmake 指定 repo、标签和一些特定于 gflags 的选项。安装glog 的以下步骤同样简单,但我将其作为传递依赖的示例包含在内...您可以看到它声明了对gflags 的依赖,这很重要...因为可以安装glog有或没有gflags 支持,我想要后者。

        另一个需要注意的更普遍的事情是,宏覆盖了依赖项的CMAKE_INSTALL_PREFIX,因此它被安装在myrepo/deps/Debug/install/... 之类的地方。安装本身将根据原始开发人员的意图在其下创建 includelib 和有时 bin 文件夹。

        在编译myrepo 时,需要将编译器指向安装目录,明确告诉它在myrepo/deps/Debug/install/include 中查找头文件,在myrepo/deps/Debug/install/libs 中查找库文件。如何执行此操作取决于您用于主项目的构建系统。如果是 cmake(像我的一样),这对我有用:

        ...
        set(CMAKE_PREFIX_PATH ${CMAKE_SOURCE_DIR}/deps/${CMAKE_BUILD_TYPE}/install)
        
        find_package(gflags REQUIRED NO_MODULE)
        find_package(glog REQUIRED NO_MODULE)
        
        add_binary(mybinary
                ...)
        target_link_libraries(mybinary      
                glog::glog)
        

        我希望这对你有用,我真的在寻找其他解决这个问题的人的反馈。我发现 C/C++ 中的整个依赖基础架构比为 Java(例如 Maven)或 JavaScript(例如 NPM)或 Python(例如 PyPI)开发的较新的基础架构更脆弱,考虑到它已经存在了多长时间,这确实有点令人惊讶。

        我想 Eigen 在其安装脚本中会在 .../deps/Debug/include/Eigen 下创建 Eigen 目录,你的梦想就会成真 :)

        请注意,这种方法不使用 git 子模块,但依赖项的来源最终会被ExternalProject 模块获取并包含在.../deps/Debug/src 下......所以你仍然可以去那里检查它们。如果您选择在 .../deps/CMakeLists.txt 中使用不同的标签或 rev 并如上所述重新运行安装部分,则可以轻松更新依赖项的修订版。

        一些离别的想法和花絮,有些重要,但不应该在主要阐述中占有一席之地......

        1. 如果你需要安装一些特殊的东西,你可以放弃我的宏,它不是基于 cmake,不存储在 git 中,甚至使用预构建的二进制文件......这个宏旨在解决最常见的快乐路径我,但您可以在所有其他情况下直接使用ExternalProject_Add

        曾经我必须为 Windows 上的 OpenSSL 执行此操作,然后我在 deps/CMakeLists.txt 中使用了这个 sn-p:

        ExternalProject_Add(
                openssl
                URL https://github.com/CristiFati/Prebuilt-Binaries/raw/master/OpenSSL/v1.1.1/OpenSSL-1.1.1i-Win-pc064.zip
                PREFIX ${CMAKE_SOURCE_DIR}/${CMAKE_BUILD_TYPE}
                CONFIGURE_COMMAND ""
                BUILD_COMMAND ""
                INSTALL_COMMAND ${CMAKE_COMMAND} -E echo installing from `<SOURCE_DIR>/OpenSSL/1.1.1i` to `${CMAKE_SOURCE_DIR}/${CMAKE_BUILD_TYPE}/install`
                COMMAND ${CMAKE_COMMAND} -E copy_directory <SOURCE_DIR>/OpenSSL/1.1.1i/include ${CMAKE_SOURCE_DIR}/${CMAKE_BUILD_TYPE}/install/include
                COMMAND ${CMAKE_COMMAND} -E copy_directory <SOURCE_DIR>/OpenSSL/1.1.1i/lib ${CMAKE_SOURCE_DIR}/${CMAKE_BUILD_TYPE}/install/lib
                COMMAND ${CMAKE_COMMAND} -E copy_directory <SOURCE_DIR>/OpenSSL/1.1.1i/bin ${CMAKE_SOURCE_DIR}/${CMAKE_BUILD_TYPE}/install/bin
        )
        
        1. 我对@9​​87654349@ 很幸运,但它更喜欢静态库,并且它们的 cmake 脚本会做一些相当低级的手术,以注入正确的编译器选项来完成当你有大量依赖项时会混淆的事情,所以我必须向它传递一个标志以容忍共享库:
        install(googletest
                https://github.com/google/googletest.git
                release-1.10.0
        
                CMAKE_ARGS
                #FIXME: figure out how to make all of them static!!!
                -Dgtest_force_shared_crt=ON)
        
        1. 我的待办事项清单上的一件事是尝试将此技术应用于容器并使用标准安装目录,而不是像本示例中那样被覆盖的目录。我会在这些日子里解决它。

        如果您有任何问题,请告诉我!

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-05-04
          • 1970-01-01
          • 2019-08-13
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多