cmake 的窘境

The cmake quandary

我正在做一个 C++ 项目。到目前为止它并不复杂,但依赖于一堆“流行”库(nlohmann/json、ToruNiina/toml11 仅举几例)。他们都有一些CMakeLists.txt,从我没有经验的角度来看,我认为他们结构很好。

现在我当然可以一个一个地编译这些库,或者在我的项目仓库中包含一个“副本”,但我想做得更好。在研究了可用的构建工具之后,我决定使用 cmake 来构建和管理 C++ 项目。承诺是获得一个稳定的、广泛支持的工具,这将有助于简化和统一构建过程。而且,从项目性质来看,我无权对目标机器强加任何要求;我需要为部署打包所有东西。

我花了几天时间阅读、观看和测试各种 cmake 教程、手册和手册。我不得不承认,我很快就开始觉得,一个本应澄清开发过程的工具却不断引入新的晦涩难懂的东西,这与其目的背道而驰。本来,我把这归功于我缺乏经验,然而...

我阅读了 target_link_librariesarticles about why not to bundle dependencies, only to be followed by methods of doing so. I have found recommendation to use one way A over B, C over B and later A over C. It took me a while to figure out the differences between 2.8 and 3.0, the obscurity,设置了 cxx 版本 and/or 编译器警告标志等等。

我的观点是,即使在 cmake 的海域进行了一次精疲力尽的探险之后,我仍然不确定一些基本问题:

How is cmake meant to be used?

What is a standard, what is a courtesy, and what is none of those?

How can I tell that something is a feature, an archaic backwards compatibility, or both?

现在我将在我的项目中说明这一点。我只需要这样的东西

cmake_minimum_required(VERSION 3.14)
project(CaseCore CXX)

add_executable(myBinary list/of/cpp/sources.cpp)
target_link_libraries(myBinary PUBLIC someExternalLibs likeForExample nlohmann_json::nlohmann_json oqs)

唯一的问题是库(反正没有 space 其他问题)。我想用项目构建它们,不想制作本地副本(不要一直拖放大量不相关的文件)。首先,我创建了库回购的分支,以便拥有可靠的来源并能够将更新的版本合并到我的分支中。

现在决定是使用 git submodule 还是其他一些方案,我读过子模块性能不佳并且更喜欢整个事情由 cmake 单独管理。我从 ExternalProject_Add 开始,但后来我发现了 FetchContent,它帮助我轻松地将外部依赖项添加到我的 cmake 列表中

FetchContent_Declare(nlohmann
    GIT_REPOSITORY https://github.com/my-reliable-fork-of/json
    GIT_TAG v3.7.3
)
message(STATUS  "Fetching Json...this may take a while")
FetchContent_MakeAvailable(nlohmann)

看起来和工作都很好而且很小。但是,我总是必须搜索库本身,以便 find/guess 哪个目标 link 到我的可执行目标 ?约定似乎接近于零,除非相应的 CMakeLists.txt 足够简单,可以阅读,否则我倾向于猜测目标名称,直到找到为止。

最近我想 link 来自 here 的 liboqs,但上述情况并没有真正帮助;出于某种原因,我可以 link oqs,#include "oqs/oqs.h",但未创建动态库并且执行终止。我很确定我可以在谷歌搜索和使用各种 cmake 变量的另一段时间后解决问题。然而,这并不是我期望 cmake 帮助我管理项目的方式;实际上恰恰相反。

为了清楚起见,我拒绝了其他方法,包括

add_subdirectory from local repo copy (git submodule)
ExternalProject_Add from local repo copy (git submodule)
ExternalProject_Add from online repo
find_package

因为它们似乎更多 obscure/old-style 等等(尽管经过数小时的研究,它们对我来说似乎仍然有很多方法可以做同样的事情)

现在我有

Am I doing something wrong, or is it really what working with cmake should look like?

Do I really have to "reverse-engineer" other people's CMakeLists in order to use a library?

Under these circumstances, how can I convince my coworkers to use similar work process?

最后

How can I adjust my work in order to ease these difficulities for others?

我越用越喜欢 C++。然而,我花费了大量的生产时间来解决依赖关系......我不想让 this guy 更加生气。

How is cmake meant to be used?

典型的 cmake 用法与旧的 autotools 用法相匹配:

$ cmake /path/to/src #replaces /path/to/src/configure
$ make
$ make install

一些目标已更改(例如,make check vs make test),并且 cmake 不提供所有相同的标准目标(例如,make distclean),但我的用法以上是大多数开发人员会做的事情(并且由于 cmake 会自行重新运行,所以大多数时候它实际上只是第二步)。

如果您的 CMakeLists.txt 不支持此工作流程,您应该有充分的理由。大多数工具都采用这样的工作流程,因此您严重限制了自己。

What is a standard, what is a courtesy, and what is none of those?

除此之外,cmake 几乎就是狂野的西部。由于更好的文档和培训,事情变得更加标准化,但它远非完美。

一个行为良好的 cmake 项目应该导出它的目标(Stack Overflow 上有很多关于这个的问题和答案)并传播标志和依赖项。这使得依赖项目更容易使用导出的目标,幸运的是这很容易做到。

How can I tell that something is a feature, an archaic backwards compatibility, or both?

据我所知,这些区别并不明显。通常,较新的方法利用 target_* 函数而不是全局函数(例如,target_include_directoriesinclude_directories)。 target_* 函数还用于传播标志,包括目录、编译器功能和我上面提到的依赖库。

Am I doing something wrong, or is it really what working with cmake should look like?

您在谈论管理外部依赖项,我将跳过此部分以避免产生意见。简而言之,C 和 C++ 依赖关系很难,并且在项目中有许多相互竞争的方式来管理它们。它们各有利弊,但大多数仍然是为作者的用例而设计的。您必须弄清楚您需要哪些用例,并根据此选择工​​具和工作流程。

Do I really have to "reverse-engineer" other people's CMakeLists in order to use a library?

一个行为良好的 cmake 项目将正确导出其目标,即使它们使用与您不同的依赖管理。如果他们不这样做,请向项目发送拉取请求(导出并不难,学习如何做是很好的)或者只是针对他们提交错误,特别是如果他们已经使用 cmake 作为构建系统。

Under these circumstances, how can I convince my coworkers to use similar work process?

这取决于您的同事,并且里程会有所不同。我曾与希望采用最佳实践并支持灵活性的同事打交道,也曾与那些只满足于解决我们目前面临的问题的同事打交道。