Build Systems à la Carte: Theory and practice
Build Systems à la Carte: Theory and practice
Background
Build systems are awesome, terrifying and unloved.
过去针对 Shake 等单一构建系统的方法研究提供了一定的设计思路改进,然而系统性研究并不充分(或者说完全没有),。本文提出了研究构建系统功能构造的方法,并具体分析了四个代表性的构建系统(Make, Shake, Bazel, Excel)。(是的这个 Excel 就是 MS Office Excel)
从功能上评判构建系统的若干标准:
- 高级构建特性:
- 静态/动态依赖(?)
- 本地/云端构建(这里可能指的是分布式构建或大规模并行支持,以及构建系统本身的性能开销)
- 确定性/非确定性构建任务(构建前是否得到确定的构建任务列表,例如 Nix 启用 import-from-derivation 后,允许构建任务依赖其他构建任务的产物,导致无副作用语境下无法确定构建任务描述)
- 是否支持提前截止(原文在此处和下文描述都很不清楚,实际上说的是构建系统如何判断并忽略不必重新执行的构建任务);自追踪(检测构建中间产物?);持久化构建信息/缓存内容
- 两个关键设计:如何安排构建任务的执行顺序;如何判断是否重新构建
- 构建系统的抽象语义;如何在现行的构建系统应用并组合该语义
The Venerable Make: Static Dependencies and File Modification Times
注解:
- Venerable:古老的、值得尊敬的,此处可解释为“德高望重”。
- Make 标准独立于具体实现,即使绝大多数项目项目都使用 GNU Make(BSD 发行版使用 BSD Make 以规避许可证问题)。
Make 作为传统意义上的构建系统,支持通过 makefile 声明构建任务,即以下形式:
util.o: util.h util.c
gcc -c util.c
main.o: util.h main.c
gcc -c main.c
main.exe: util.o main.o
gcc util.o main.o -o main.exe
<target>: <(blank-separated) dependencies>
<recipe>
makefile 可以等价转换为依赖图,其中 target 对应图上节点,dependencies 构成其入边集合。
\begin{figure}[htbp] \centering \begin{tikzpicture}[ x=1cm, y=1cm, scale=0.8, transform shape,
=stealth, edge/.style={->, line width=0.45pt}, file/.style={draw, rectangle, minimum width=1.25cm, minimum height=0.55cm, inner xsep=2pt, font=\large}, result/.style={draw, rectangle, rounded corners=7pt, minimum width=1.35cm, minimum height=0.55cm, inner xsep=2pt, font=\large}, rebuilt/.style={result, fill=blue!18}, modified/.style={file, fill=orange!25}, panel/.style={font=\large} ]
\begin{scope}[shift={(0,0)}] \node[result] (aexe) at (0,2.9) {main.exe}; \node[result] (auo) at (-0.95,1.45) {util.o}; \node[result] (amo) at (0.95,1.45) {main.o}; \node[file] (auc) at (-1.8,0) {util.c}; \node[file] (auh) at (0,0) {util.h}; \node[file] (amc) at (1.8,0) {main.c};
\draw[edge] (auc) -- (auo);
\draw[edge] (auh) -- (auo);
\draw[edge] (auh) -- (amo);
\draw[edge] (amc) -- (amo);
\draw[edge] (auo) -- (aexe);
\draw[edge] (amo) -- (aexe);
\node[panel] at (0,-0.9) {(a) Task dependency graph};
\end{scope}
\begin{scope}[shift={(5.1,0)}] \node[rebuilt] (bexe) at (0,2.9) {main.exe}; \node[rebuilt] (buo) at (-0.95,1.45) {util.o}; \node[rebuilt] (bmo) at (0.95,1.45) {main.o}; \node[file] (buc) at (-1.8,0) {util.c}; \nodemodified at (0,0) {util.h}; \node[file] (bmc) at (1.8,0) {main.c};
\draw[edge] (buc) -- (buo);
\draw[edge] (buh) -- (buo);
\draw[edge] (buh) -- (bmo);
\draw[edge] (bmc) -- (bmo);
\draw[edge] (buo) -- (bexe);
\draw[edge] (bmo) -- (bexe);
\node[panel] at (0,-0.9) {(b) Full rebuild};
\end{scope}
\begin{scope}[shift={(10.2,0)}] \node[rebuilt] (cexe) at (0,2.9) {main.exe}; \node[result] (cuo) at (-0.95,1.45) {util.o}; \node[rebuilt] (cmo) at (0.95,1.45) {main.o}; \node[file] (cuc) at (-1.8,0) {util.c}; \node[file] (cuh) at (0,0) {util.h}; \nodemodified at (1.8,0) {main.c};
\draw[edge] (cuc) -- (cuo);
\draw[edge] (cuh) -- (cuo);
\draw[edge] (cuh) -- (cmo);
\draw[edge] (cmc) -- (cmo);
\draw[edge] (cuo) -- (cexe);
\draw[edge] (cmo) -- (cexe);
\node[panel] at (0,-0.9) {(c) Partial rebuild};
\end{scope} \end{tikzpicture} \caption{A task dependency graph and two build scenarios. Input files are shown in rectangles, and output files are shown in rounded rectangles. Modified inputs and files that are rebuilt are highlighted.} \end{figure}
在任意两次构建之间,传递依赖被改变的构建任务应当仅执行一次,其他任务不执行。