项目与构建

通常, 在最开始的编程学习中, 或者日常的使用过程中, 编写的程序通常由单文件组成. 这些程序通常不会用于解决复杂的问题, 且多数如流水式描述一个问题的处理过程 (也称 "脚本").

而随着待解决的问题逐渐变得复杂, 程序也将变得具有多种功能以应对各种需求. 这些程序通常会包含很多模块, 程序抑或调用这些模块完成工作, 抑或与这些模块相互协作配合, 完成任务.

程序调用的这些模块可能是自行编写而得, 亦有可能来自他人. 而纵观这些程序, 其中总会包含重复的部分. 而我们总是希望将这些重复的部分抽取出来, 一方面可以减少冗余, 另一方面可以方便维护.

我们通常将程序使用到的这些模块称为程序的 "依赖 (dependency)", 这种依赖关系可以是宏观上的经由用户界面的使用 (比如使用某个程序给出的命令行界面), 或者是编程语言层面的 API 调用. 后面一种形式被使用的依赖通常也被称作 "库 (library)".

何为项目

为了开发对于某个问题的解决方案, 我们通常会创建一个相应的项目. 一个项目往往会产出一个或多个可供最终使用的程序, 或者产出可被其他程序使用的 "依赖"; 当然, 项目也可能仅仅提供一些思路或者概念, 其概念是灵活的.

何为构建

维基百科上是这样定义 "构建 (build)" 1 的:

在软件开发领域, 构建 (build), 是指将源代码变成能够运行在计算机上的独立的 (standalone) 软件制品 (software artifact) 的过程; 也可以指上述过程的产物.

这样说起来可能比较抽象, 用具体的例子来说, 下面的场景就可以属于 "构建":

  • 高级语言 (如 C++) 的源代码经过编译链接等操作后, 变成目标操作系统上可执行的二进制文件;
  • 若干源文件经由 "静态网站生成器 (Static Site Generator, SSG)" 生成可以部署的静态网页2;
  • ...

结合前面的内容, 生成项目目标产物的过程, 就是构建.

而构建的两个核心概念, 一个是要生成的 "目标", 另一个就是生成目标所需要的 "依赖".

构建工具

对于只有单个或者几个源文件的程序, 其构建过程往往不会很复杂, 通常只需若干命令就能完成.

而实际使用中, 就算只有几个文件, 其构建过程也不一定就能轻松管理.

以 C++ 多文件编程 (将在之后介绍) 为例, 单是完成一次单个目标的构建就需要键入多行命令来实现. 并且, 并不是每次生成可执行程序都需要编译所有的源文件: 只编译发生变动的文件, 可以节省相当一部分时间.

但是要由人工去核查每个文件是否发生变动却是很困难的事情. 且不说一个个核对文件的修改日期很容易出错, 源文件中还涉及到文件的相互包含 --- 如果一个文件发生了变化, 那么所有包含了该文件的源文件, 都需要被重新编译.

此外, 实际情况中, 一个包含多个程序构建目标的项目, 各个项目之间会涉及到很多文件, 有相同的也有不同的; 如若手动完成构建过程, 将会涉及到大量的记忆以及很多命令的键入.

总而言之, 这些过程对于常人来说是非常繁琐且容易出错的. 于是, 人们开发了构建工具: 只需要编写好构建工具能够读取的脚本, 描述整个项目该如何被构建 (比如哪些构建目标使用哪些文件), 构建工具就会自动完成构建过程, 生成所需的各个构建目标.

构建管理工具

然而构建工具的脚本编写也是容易出错的. 此外, 有些项目可能可以使用多种构建工具进行构建, 分别为这些工具编写脚本是费时费力的工作.

也有些项目需要对依赖进行管理, 比如依赖的获取, 甚至指定所使用依赖的版本等等.

相应的, 构建管理工具可以解决这些问题. 通过读取对应的配置文件, 构建管理可以完成依赖的获取安装, 构建脚本的生成, 构建等操作的自动化执行等等.