Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configuring Transitive Dependencies with Modern CMake #11965

Open
guevara opened this issue Feb 12, 2025 · 0 comments
Open

Configuring Transitive Dependencies with Modern CMake #11965

guevara opened this issue Feb 12, 2025 · 0 comments

Comments

@guevara
Copy link
Owner

guevara commented Feb 12, 2025

Configuring Transitive Dependencies with Modern CMake



https://ift.tt/bwXKetn



Li Miu


Introduction

近日某个项目临近结束,书文档,写配置,发现网上的 CMake 教程颇旧,混乱不堪,且缺乏实际作用,难及需求。遂系统读了一些 Modern CMake 资料,撰文记录,以供参考。

实际项目包含上万行代码,依赖三四个第三方库,欲生成支持 find_package() 查找的动态库,并自动传递依赖,供用户直接使用。

下面将其简化为一最小示例,便于演示流程。示例项目结构为:

lib.hlib.cc 内容为:

该库只包含两个函数,foo()bar(),分别依赖 libtorchfmtlib 这两个第三方库。

现在需要编写 CMakeLists.txt 来配置依赖,生成动态库,并传递依赖,以使用户无需重复导入依赖的第三方库。

这可以分为三个步骤进行思考,先确保输入无误(Input),再确保依赖传递等逻辑无误 (Process),最后确保输出无误(Output),就是常用的系统分析 IPO 模型。

Input

先看第一步,输入。我们项目的源文件、头文件及第三方库的所有依赖就是这里所说的输入,该步要保证各库路径的正确性和 ABI 的一致性,否则后续可能会产生奇怪的错误。

首先是基本配置,包含项目名称、版本和语言等信息。这样编写即可:

其次,导入第三方库。如果库安装在标准路径(/usr/local/lib etc.),find_package() 可以直接找到,而若是自定义路径,则需要手动指定。暂且这样写上:

写 CMake 要步步为营,我们先运行一下以确保目前不存在任何问题,再继续前进。在 mylib/ 目录下,尝试运行以下命令:

由于 libtorch 并未处于标准目录,故而需要通过 CMAKE_PREFIX_PATH 手动为其指定搜索路径。倘若输出为:

即表示当前无误。libtorch 库的 CMake 配置写法不规范,是老式写法,而 fmtlib 是新式写法,是以输出形式也略有不同。本文是新式写法。

这个搜索路径也可以写在 CMake 中,存在两种方式,一种是 CMAKE_PREFIX_PATH 路径,另一种是 Torch_DIR 变量。

但不应写死,而是通过 CMake 命令指定,因为实际运行时,用户的路径不可能与你相同。

最后,处理源文件输入,并将依赖库附加至动态库。

Modern CMake 是 target-oriented 的, target 就是编译的目标,可以是静态库、动态库和可执行文件,各类目录就是这个 target 的属性,权限就是 target 的依赖传递性。完全可以类比对象,若是想让用户在使用源码时,也可以使用 fmtliblibtorch 的特性而无需再次链接,只需指定 PUBLIC,对 mylib 所依赖的库进行传递。

但需要注意,find_package() 本身并不具备传递依赖的能力,依赖传递需要单独处理,否则用户侧不仅需要导入你的 mylib 库,还需要重复导入 fmtliblibtorch,而这些第三方库其实已经在你提供的 CMake 中导入过了。

此时,在 mylib/ 目录下,运行以下命令:

如无报错,则第一步结束。

Process

再看第二步,导出与安装。

首先,使我们的库能够安装。就是指定一些安装目录、导出头文件之类的操作,ARCHIVE 是静态库目录,LIBRARY 是动态库目录,INCLUDES 是头文件目录。

此处创建了一个导出对象 mylib-targets,指定了一些导入信息,主要就是某些信息保存的目录,如库存入 ${CMAKE_INSTALL_LIBDIR} (其实就是 lib)下,头文件存入 ${CMAKE_INSTALL_INCLUDEDIR} (其实就是 include)下,这两个变量来自 GNUInstallDirs。但此时尚未实际生成文件,只是指定一些信息而已。

install(DIRECTORY ...) 实际将头文件拷贝至 ${CMAKE_INSTALL_INCLUDEDIR} 目录,前面的命令不会自动复制这些内容,需要自己手动写。

接着,根据导出名称,实际导出 targets 文件。

该部分会实际导出并生成 mylib-targets.cmake 文件,也可以命名为 mylibTargets,则会生成 mylibTargets.cmake 文件,这里面包含着 find_packages() 查找库所需重要信息。

如果你的库不依赖第三方库,那么直接将上面的 mylib-targets 改为 mylib-config 文件就可以满足需求,find_packages() 实际需要的是 mylib-config.cmake 这个名称的文件。这里先生成 mylib-targets 是为了稍后处理依赖传递,后面 mylib-targets.cmake 依旧会包含在 mylib-config.cmake 文件中。

这里暂先不管依赖传递,放到下一节专门讲解。先为库生成版本信息:

版本信息通过 write_basic_package_version_file 函数实现,SameMajorVersion 表示和 project(VERSION) 中指定的主版本相同。这将实际生成 mylib-config-version.cmake 文件,它必须和 mylib-config.cmake (目前这个文件还未创建,我们将其命名为 mylib-targets.cmake 了)文件处于同一目录,后续可以导入不同版本的库。

至此,第二步结束,运行如下命令测试:

若无意外,将会生成以下文件:

测试之时,指定到临时路径,以防止污染系统目录,待测试完毕,则不用指定目录,将安装到系统标准路径。

此时只剩下一个重要文件,mylib-config.cmake,里面需要处理实际的依赖传递。

Output

这节专门讲依赖传递,完成最终输出。此节的内容请添加在 include(CMakePackageConfigHelpers) (即生成版本导库) 代码的下方。

最后一部分,也是全文最核心的代码如下:

这将根据 mylib/mylib-config.cmake.in 模板文件生成 mylib-config.cmake 文件,该模板文件需要手动创建,内容如下:

这模板文件用来自动生成一些安装信息,并检查库所依赖的组件是否已经找到。

所有的子依赖皆需要我们手动处理(多么不可理喻!),即我们还需要手动处理 libtorchfmtlib 的依赖传递,否则用户无法找到 mylib 依赖的这些库。这项工作可以通过 find_dependency 来实现,因此要进行文件读写,在 mylib-config.cmake 的文件末尾追加写入这些依赖,libtorch 库是老式实现,不支持 find_dependency,还是写成 find_package(Torch)

最终生成的 mylib-config.cmake 文件内容为:

通过上节最后一段的运行指令,最后得到的文件结构应该如下:

由此,万事具备。

Consumer

库已导毕,如今在用户方测试,创建的用户项目结构如下:

main.cpp 测试内容为:

CMakeLists.txt 这样写:

./mylib-consumer 路径下运行以下命令:

得到程序输出:

测试程序中只导入了 mylib,却可以直接使用 fmtliblibtorch,这表示依赖传递配置成功。

Conclusion

CMake 不易调试,报错亦难顾名思义,教程更是参差不齐,然而实现细节却不可赀计,教人头痛。

本人查询无数资料,调试多次,才跑通多级依赖间的传递问题,还遇到不规范的库和其他库存在 ABI 不一致的问题,追查许久方定位错误。

要让你的库支持 find_package(),其实有两种做法,一种是 config file,另一种是 find module。本文属于前者,许多较新的库皆是采用此种做法,当然也有的库二者皆提供。

本文示例依赖的 libtorch 是较旧的做法,并不友好,用来遍地是坑,而 fmtlib 是本文这种新式做法,使用起来方便许多。要同时依赖这些不同手法配置的库,需要多加留心,依照文中做法,则可保证无误。

    </div></div></div><br>





via CppMore https://www.cppmore.com

February 12, 2025 at 03:19PM
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant