构建

构建(build),是指将源代码变成独立的、具体可感的软件制品(software artifact)的过程。我们也会用“build”来称呼构建所得到的产品。

我们可以看下面几个例子。C/C++ 的源代码不能直接被机器执行,需要编译成对应平台的可执行文件;我们书写的 Markdown 文档不能直接预览得到效果,需要由“渲染器(renderer)”翻译成 HTML 文档,并为浏览器所读取并展示给用户。

以上这些过程都可以算作构建。生活中也有很多例子符合“构建”操作,比如要想烹饪一份菜肴,我们需要有原材料、炊具以及能源等。不难发现,构建过程有几个共同之处,即依赖(dependency)、目标(target)和规则(rule;recipe)。比如烹饪,我们不仅需要有原材料和炊具等(依赖),根据一定的食谱(规则),最后完成指定的菜肴(目标)。

编译一个单文件的 C/C++ 程序也属于构建,不过,当我们说构建一词时,其指的一般是一个有条理、有一定规模的事件。比如,许多个 C/C++ 源代码文件根据先后顺序生成若干库文件和若干可执行程序。烹饪菜肴时,也经常会出现需要先做好某些基本的菜品、再经过一些组合或步骤组合成其他菜品,最终生成目标菜肴的过程。

在构建过程中间生成的产物一般称为“中间产物”,比如 C/C++ 程序编译过程中的“目标文件”。这些中间产物也属于构建过程的依赖。比如,生成 a.exe 需要 a1.oa2.oa3.o 三个目标文件,这三个目标文件就称为 a.exe 的依赖;而生成每个目标文件,又需要对应的 .cpp 文件。

对于现实生活中,中间产物往往会转化为下一个阶段的产物,不会留存;而计算机中的中间产物往往为文件,并且为了减少后续的重新构建的时间,这些文件不会被删除,而仅在对应的源文件更改时被重新生成。

构建工具

当构建过程变得复杂时,由人工来进行这个过程就变得费时费力起来。因此,人们开发了构建工具,用户只需要定义依赖、目标和规则,构建工具就会自动寻找目标所需的依赖,并根据对应的规则生成目标。如果目标所对应的依赖没有改变,说明目标也不会发生改变,则再次执行构建工具并不会重新构建对应的目标。

依赖管理

我们的项目中的目标可能本身依赖其他的项目,这些所依赖的项目也称作依赖。依赖管理可以帮我们解决这个问题。

很多以来可以通过软件仓库来获取,从软件仓库获取软件(包)的程序叫做“包管理器”。软件仓库中的包存在不同的版本。不同的程序可能会依赖不同版本的包;某一程序可能依赖某一特定版本的包。

我们会在后面介绍包和包管理器。

不同的版本会有相应的版本编号。我们知道,“版本”这一概念的存在是为了使我们的程序能够正确依赖其他的程序或软件库,这是因为软件的开发过程中,其对外的应用程序接口(API)以及接口对应的功能都会发生变化。试想,A 程序依赖 B 程序的 C 接口,而 C 接口更名为了“D”,则 A 程序便不能正常工作了,因为其不再能够访问 B 程序的 C 接口;假如 C 接口对应的功能发生了变化,则 A 应用程序也不能正常工作。

针对版本号的命名,我们通常采用一种叫做“语义版本号(Semantic Versioning)”的标准,其格式为:

  1. 主版本号.次版本号.补丁号
  • 如果新的版本没有改变 API,则将补丁号递增;
  • 如果您添加了 API 并且该改动是向后兼容的,则将次版本号递增;
  • 如果您修改了 API 但是它并不向后兼容,则将主版本号递增。

参考资料: