65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2021年11月26日

MIT

4分钟阅读

viewsIcon

11947

downloadIcon

116

多个库的有效管理工具

引言

如今,软件开发领域几乎已经将 Git 作为版本控制系统的标准。不幸的是,当涉及到在不同仓库之间重用代码时,对于应该如何做,大家的共识就少了很多。只需在 Google 上搜索 Git 子模块或 Bitbucket 子树,您就会找到建议您使用其中一种的参考资料,紧接着,又会找到警告您要像躲避瘟疫一样避免使用这些的参考资料。在尝试了不同的解决方案后,我最终得到了自己的系统,我在之前的技巧中描述了它(吃你自己的狗粮)。它展示了如何使用符号链接组织多个 C/C++ 项目。收到的反馈非常积极。

现在我决定向前迈进一步,提供一个可以自动化这个过程的工具。结果就是 CPM(C 包管理器)。这是一个简单的工具,只做一件非常具体的工作

  • 构建一个依赖于许多其他 C/C++ 库的 C/C++ 项目。

不做许多其他的事情,包括但不限于

  • 炸薯条
  • 早上整理你的床铺
  • ...好吧,你明白了 :)

在使用此工具时,有两个部分:一个是项目布局必须遵循一定的模式,另一个是使用每个项目的小型 JSON 文件来描述项目的依赖关系树。

作为一个例子,让我们考虑两个库,cool_Acool_B,它们需要在一个应用程序 super_App 中使用。cool_Acool_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_Acool_B,则相应的 include 指令将是

#include <cool_A/hdr1.h>
#include <cool_B/hdr1.h>

规则 3 - 通过符号链接使依赖模块的包含文件夹可见

在之前显示的结构中,使用 cool_Acool_B 的应用程序将有一个 include 文件夹,但在此文件夹中,有指向 cool_Acool_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 日 - 初始版本
© . All rights reserved.