如何解决C ++:包含目录,但只允许一个文件夹可见
假设有人想在仓库(myrepo
)中包含一个大型开源项目作为子模块。对于此示例,让我们以Eigen为例。没问题,我可以
git submodule add https://gitlab.com/libeigen/eigen.git
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>
两个明显的次优解决方案是
这两种方法都有明显的缺点。具体来说,
-
使用
myrepo/eigen
作为include目录会将所有其他文件公开给编译器/链接器。由于存储库和其中包含的所有其他文件的大小,我觉得这是名称空间冲突之类的定时炸弹。例如,现在编译器看到一个test/
子文件夹,其内容现在是免费游戏。 -
在我看来,包括没有
Eigen
开头的标头对于代码清晰来说是一场灾难。#include <Dense> // ¯\_(ツ)_/¯ Where did this come from???
我知道的唯一其他选择是派生或复制原始存储库,并删除我要排除的所有内容。
解决方法
尽管编译器只会看到明确提供给他的文件,但 CMake 会从子模块文件夹中获取 CMakeLists.txt
并执行可能很混乱的构建步骤。
CMake 是一个非常强大的构建系统,因此它可以让您以优雅的方式处理这些问题。 CMake 能够将外部项目作为库包含在内。这与简单的 add_subdirectory()
不同。在外部项目的情况下,CMake 实际上会为该项目创建另一个构建实例,并将使用构建步骤下载、配置、构建和安装项目,然后构建才能开始在主要目标上工作。
有了外部构建,您可以构建库并将其安装在临时文件夹中(相对于您的源,或相对于您的构建文件夹)。这样,您就只能使用外部项目中构建过程的产品。
这里值得一提的是,外部项目可以使用不同的构建系统,而不是 CMake,这使得它的方法更加强大。
您可以使用我为 Eigen 库准备的这个小演示作为外部项目将其添加到您的构建中。
该项目在由 eigen
初始化的项目根目录中有 git submodule add https://gitlab.com/libeigen/eigen.git
作为子模块
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})
main.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
的其他配置来控制这一点,但这超出了本问题的范围。
ExternalProject
模块的附加功能
使用 git submodule
和 CMake 的 ExternalProject
等两个强大的概念,您可以完全控制您的构建过程,只需选择要构建的外部项目的哪个分支以及使用哪些配置参数。同时,您将保持所需的构建分离(源代码分离)。
您可以创建一个单独的空目录 include
并放置一个指向 Eigen
的软链接 myrepo/eigen/Eigen
。然后将该 include
添加为搜索目录,而不是 myrepo/eigen
。
Git“按原样”跟踪软链接,将链接的路径存储在存储库中,并在克隆时重新创建链接。它不会尝试验证链接是否有效。 只要:
- 该链接是作为同一存储库中文件/目录的相对路径提供的
- 存储库被克隆到能够保存软链接的文件系统上
一切都会按预期进行。
,如果你不能做其他答案,例如你的文件系统不支持链接,你可以复制“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)
我还没有尝试过这段代码,但理论上它应该可以工作。它应该是健壮的,尽管它有点老套。
,假设一个人想要包含一个大型开源项目作为子模块
然而,为了使用 Eigen 库,真正需要的是文件夹的内容
您要么希望将整个项目作为子模块包含在内,要么不希望包含在内。子模块专门用于包含整个源代码树。如果您不想逐字包含整个源代码树,请不要使用子模块。
3rd-party 库的常用方法是克隆他们自己的 repo,构建他们的可安装工件(公共头文件和静态和/或动态库),并将它们安装在一些 3rd-party 库区域。然后你编译和链接你的代码。第 3 方库是您的构建系统引用的外部依赖项,而不是您自己的存储库的一部分。
,我想分享我在上一个项目中是如何解决类似问题的——希望它对您有用。我的要求是相似的——只公开第三方项目的开发人员打算公开的头文件和库。我的项目本身使用 cmake,但这不是必需的。
我在我的 repo 的根目录中创建了一个 deps 文件夹,并在其中放置了一个 CMakeLists.txt。这不是构建主项目,而是下拉我关心的依赖项,构建它们(它们的开发人员打算构建的方式),并安装它们以供我的项目使用。看起来像这样:
myrepo/
deps/
CMakeLists.txt
与@jordanvrtanoski 类似,我使用ExternalProject 功能,但我写了一个宏,可以让我更简洁一些。我最初在 add_subdirectory
上挣扎,最终我找到了他避免 add_subdirectory
声音的建议:)。与@jordanvrtanoski 不同,我更喜欢为依赖项使用单独的 CMakeLists.txt,即使我的顶级项目是基于 cmake 的。否则,我发现 cmake 会做一些检查,确认依赖项已正确安装,这会占用我内部开发循环的时间……我还发现,将依赖项安装在内循环之外可以让您可靠地执行 { {1}},而不必担心在顶级项目中分别指定 include 和 lib 目录。
这是 CMakeLists.txt 的片段
find_package
您可以通过以下方式触发安装:
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)
请注意,您可以分别进行 Debug 和 Release 构建,并且工件最终位于 cmake -DCMAKE_BUILD_TYPE=Debug --log-level=VERBOSE -G "NMake Makefiles" ..
cmake --build . --config Debug
下的单独文件夹中。
这里有一点需要解压,但我希望这一切都有充分的理由。您可以看到,一旦宏退出,安装 deps
的代码片段就非常少了。它指定了 cmake 的 repo、标签和一些特定于 gflags 的选项。安装 gflags
的以下步骤同样简单,但我将其包含为传递依赖的示例……您可以看到它声明了对 glog
的依赖,这很重要……因为 {{ 1}} 可以在有或没有 gflags
支持的情况下安装,我想要后者。
另一个需要注意的更一般的事情是宏覆盖了依赖项的 glog
,因此它被安装在诸如 gflags
之类的东西中。安装本身会根据原始开发人员的意图在其下创建 CMAKE_INSTALL_PREFIX
和 myrepo/deps/Debug/install/...
,有时还会创建 include
文件夹。
编译 lib
时需要将编译器指向安装目录,特别是告诉它在 bin
中查找头文件,在 myrepo
中查找库。您如何执行此操作取决于您用于主项目的构建系统。如果是 cmake(就像我的一样),这对我有用:
myrepo/deps/Debug/install/include
我希望这对您有用,并且我真的在寻找解决此问题的其他人的反馈。我发现 C/C++ 中的整个依赖基础设施比为 Java(例如 Maven)或 JavaScript(例如 NPM)或 Python(例如 PyPI)开发的新的基础设施要脆弱得多,考虑到它已经存在了多久,老实说这有点令人惊讶。
我想 Eigen 在它的安装脚本中会在 myrepo/deps/Debug/install/libs
下创建 Eigen 目录,你的梦想就会成真:)
请注意,此方法不使用 git 子模块,但依赖项的来源最终会被 ...
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)
模块提取并包含在 .../deps/Debug/include/Eigen
下...因此您仍然可以去那里检查他们。如果您选择在 .../deps/Debug/src
中使用不同的标记或 rev 并如上所述重新运行安装部分,您可以轻松更新依赖项的修订版。
一些离别的想法和花絮,有些重要,但不值得在主要论述中占有一席之地...
- 如果你需要安装一些特殊的东西,你可以放弃我的宏,它不是基于 cmake,不是存储在 git 中,甚至是使用预构建的二进制文件......宏旨在解决最常见的快乐路径我,但您可以在所有其他情况下直接使用
ExternalProject
。
有一次我不得不在 Windows 上为 OpenSSL 执行此操作,并且我在 .../deps/CMakeLists.txt
中使用了此代码段:
ExternalProject_Add
- 我在
deps/CMakeLists.txt
上很幸运,但它更喜欢静态库,并且它们的 cmake 脚本做了一些非常低级的手术,以注入正确的编译器选项来完成当您有大量依赖项时会混淆的事情,所以我不得不向它传递一个标志来容忍共享库:
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
)
- 我的待办事项列表中的一件事是尝试将此技术应用于容器并使用标准安装目录,而不是像本示例中那样覆盖的目录。我会在这几天的某个时间完成它。
如果您有任何问题,请告诉我!
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。