CPM - 一个 C/C++ 包管理器





5.00/5 (4投票s)
多个库的有效管理工具
引言
如今,软件开发领域几乎已经将 Git 作为版本控制系统的标准。不幸的是,当涉及到在不同仓库之间重用代码时,对于应该如何做,大家的共识就少了很多。只需在 Google 上搜索 Git 子模块或 Bitbucket 子树,您就会找到建议您使用其中一种的参考资料,紧接着,又会找到警告您要像躲避瘟疫一样避免使用这些的参考资料。在尝试了不同的解决方案后,我最终得到了自己的系统,我在之前的技巧中描述了它(吃你自己的狗粮)。它展示了如何使用符号链接组织多个 C/C++ 项目。收到的反馈非常积极。
现在我决定向前迈进一步,提供一个可以自动化这个过程的工具。结果就是 CPM(C 包管理器)。这是一个简单的工具,只做一件非常具体的工作
- 构建一个依赖于许多其他 C/C++ 库的 C/C++ 项目。
它不做许多其他的事情,包括但不限于
- 炸薯条
- 早上整理你的床铺
- ...好吧,你明白了 :)
在使用此工具时,有两个部分:一个是项目布局必须遵循一定的模式,另一个是使用每个项目的小型 JSON 文件来描述项目的依赖关系树。
作为一个例子,让我们考虑两个库,cool_A
和 cool_B
,它们需要在一个应用程序 super_App
中使用。cool_A
和 cool_B
都使用了来自另一个库 utils
的代码。每个库都有自己的 Git 仓库。
项目布局
您必须遵守之前提到的技巧中显示的原则。
规则 1 - 所有项目都有自己的文件夹,并且所有项目文件夹都在一个父文件夹中。环境变量
DEV_ROOT
指向此开发树的根目录。
这里有一些 ASCII 艺术,展示了一般的代码布局
DevTreeRoot
|
+-- cool_A
| |
| +-- include
| | |
| | +-- cool_A
| | |
| | +-- hdr1.h
| | |
| | +-- hdr2.h
| +-- src
| | |
| | +-- file1.cpp
| | |
| | +-- file2.cpp
| +-- project file (cool_A.vcxproj) and other stuff
|
+-- cool_B
|
+-- include
| |
| +-- cool_B
| |
| +-- hdr1.h
| |
| +-- hdr4.h
+-- src
| |
| +-- file1.cpp
| |
| +-- file2.cpp
|
+-- project file (cool_B.vcxproj) and other stuff
规则 2 - 需要对用户可见的包含文件放置在
include
文件夹的子文件夹中。子文件夹的名称与库的名称相同。
如果 cool_A 的用户可以像这样引用 hdr1.h 文件
#include <cool_A/hdr1.h>
这种组织方式的另一个优点是可以防止不同库之间的名称冲突。在这种情况下,如果一个程序同时使用 cool_A 和 cool_B,则相应的 include
指令将是
#include <cool_A/hdr1.h>
#include <cool_B/hdr1.h>
规则 3 - 通过符号链接使依赖模块的包含文件夹可见
在之前显示的结构中,使用 cool_A 和 cool_B 的应用程序将有一个 include
文件夹,但在此文件夹中,有指向 cool_A 和 cool_B 包含文件夹的符号链接。文件夹结构将如下所示(尖括号表示符号链接)
DevTreeRoot
|
+-- SuperApp
| |
| +-- include
| | |
| | +-- <cool_A>
| | | |
| | | +-- hdr1.h
| | | |
| | | +-- hdr2.h
| | |
| | +-- <cool_B>
| | | |
| | | +-- hdr1.h
| | | |
| | | +-- hdr4.h
| | other header files
| |
| +-- src
| | |
| | +-- source files
| other files
...
规则 4 - 所有库都驻留在开发树根目录的 lib 文件夹中。每个模块都包含一个指向此文件夹的符号链接。
无需重复已经显示的文件布局部分,这是与 lib 文件夹相关的部分(同样,尖括号表示符号链接)
DevTreeRoot
|
+-- cool_A
| |
| ...
| +-- <lib>
| |
| all link libraries are here
+-- cool_B
| |
| ...
| +-- <lib>
| |
| all link libraries are here
+-- SuperApp
| |
| ...
| +-- <lib>
| |
| all link libraries are here
+-- lib
|
all link libraries are here
如果链接库有不同的类型(调试、发布、32 位、64 位),它们可以作为 lib 文件夹的子文件夹来容纳。
依赖关系描述文件
为了描述项目之间的关系,每个项目都使用一个名为 CPM.JSON 的文件。
super_App 文件夹中的 CPM.JSON 具有以下内容
{ "name": "super_App", "git": "git@github.com:user/super_App.git",
"depends": [
{"name": "cool_A", "git": "git@github.com:user/cool_A.git"},
{"name": "cool_B", "git": "git@github.com:user/cool_B.git"}
],
"build": [
{"os": "windows", "command": "msbuild", "args": ["super_app.proj"]},
{"os": "linux", "command": "cmake"}
]
}
对于每个依赖的项目,都有一行描述依赖项并给出 Git 仓库的地址。build 部分指定了用于构建每个操作系统的包的命令。
类似地,cool_A 文件夹中的描述符如下所示
{ "name": "cool_A", "git": "git@github.com:user/cool_A.git",
"depends": [
{"name": "utils", "git": "git@github.com:user/utils.git"},
],
"build": [
{"os": "windows", "command": "msbuild", "args": ["cool_a.proj"]},
{"os": "linux", "command": "cmake"}
]
}
在 cool_B 中
{ "name": "cool_B", "git": "git@github.com:user/cool_B.git",
"depends": [
{"name": "utils", "git": "git@github.com:user/utils.git"},
],
"build": [
{"os": "windows", "command": "msbuild", "args": ["cool_b.proj"]},
{"os": "linux", "command": "cmake"}
]
}
最后,在 utils
中,没有依赖关系;只有 build
规则
{ "name": "utils", "git": "git@github.com:user/utils.git",
"build": [
{"os": "windows",
"command": "msbuild", "args": ["utils.proj"]},
{"os": "linux", "command": "cmake"}
]
}
操作 (Operation)
一旦您创建了依赖关系描述文件并设置了 DEV_ROOT
环境变量,您只需克隆最顶层的仓库(super_App
)并使用类似以下的命令调用 CPM 实用程序
cpm -v super_app
(-v
标志指定详细模式,让您看到中间步骤。)
它会读取 CPM.JSON 文件并递归地获取每个依赖包,并创建所需的符号链接。然后,它会启动构建每个依赖包的过程,从依赖树底部的那些包开始。
最终想法
我维护了一个规模适中的代码库,其依赖关系深度最多可达 10 层,而此工具可以轻松确保所有必需的项目都是最新的,并按正确的顺序构建。该程序是用 Go 编写的,因为我当时正在学习 Go,而且还有什么比用它来做一个小项目更好的学习新语言的方式呢?它可以从其 GitHub 仓库 下载,作为 Windows 或 Linux 的预编译二进制文件,或者您可以从附加的源代码进行编译。
历史
- 2021 年 11 月 26 日 - 初始版本