为什么我们用makefile而不是shell脚本来描述构建过程?

为什么我们用makefile而不是shell脚本来描述构建过程?,shell,build,makefile,bsdmake,Shell,Build,Makefile,Bsdmake,备注这是用户4076675对问题“”的修改 稍微不同的观点。另见相应的 让我们考虑C项目的经典情况。gcc编译器 能够一步编译和链接程序。我们可以很容易地 用shell脚本描述构建例程: case $1 in build) gcc -o test *.c;; clean) rm -f test;; esac # This script is intentionally very brittle, to keep # the example simple. 然而,描述构建过程

备注这是用户4076675对问题“”的修改 稍微不同的观点。另见相应的

让我们考虑C项目的经典情况。

gcc
编译器 能够一步编译和链接程序。我们可以很容易地 用shell脚本描述构建例程:

case $1 in
    build)  gcc -o test *.c;;
    clean)  rm -f test;;
esac
# This script is intentionally very brittle, to keep
# the example simple.
然而,描述构建过程似乎很惯用 使用
Makefile
,包括编译每个编译的额外步骤 并最终链接这些文件。这个 相应的GNU Makefile将是:

.PHONY: all

SOURCES=$(wildcard *.cpp)
OBJECTS=$(SOURCES:.cpp=.o)

%.o: %.cpp
    g++ -c -o $@ $<

all: default
default: $(OBJECTS)
    g++ -o test $^

clean:
    rm -rf *.o
。假冒:全部
源=$(通配符*.cpp)
对象=$(源:.cpp=.o)
%.o:%.cpp
g++-c-o$@$<
全部:默认值
默认值:$(对象)
g++-o测试$^
清洁:
rm-rf*.o
第二种解决方案可能比简单的shell更复杂 我们以前写的剧本。这也是一个缺点,因为它会使系统混乱 包含目标文件的源目录。那么,我们为什么要描述构建呢 使用makefile而不是shell脚本的过程?在
在前面的例子中,这似乎是一个无用的复杂问题。

在我们编译并链接三个中等大小的 文件,任何方法都可能同样令人满意。我会的 因此,考虑一般情况,但使用的许多好处。 Makefiles仅在较大的项目上才重要。一旦我们学会了 最好的工具,使我们能够掌握复杂的情况下,我们想使用 它也适用于简单的情况

让我强调一下使用
make
而不是简单的 用于编译作业的shell脚本。但首先,我想提出一个建议 无害的观察

shell脚本的过程范式对于编译类作业是错误的 编写Makefile类似于编写一个带有轻微错误的shell脚本 视角的改变。在shell脚本中,我们描述了一个过程 一个问题的解决方案:我们可以开始在中描述整个过程 使用未定义函数的非常抽象的术语,我们对其进行了改进 描述直到我们达到最基本的描述水平, 其中一个过程只是一个普通的shell命令。在Makefile中,我们可以 没有引入任何类似的抽象,但我们关注的是我们需要的文件 想要生产,以及我们如何生产。这很有效,因为 在UNIX中,所有内容都是一个文件,因此每个处理都是 由从输入端读取输入数据的程序完成 文件,进行一些计算,并将结果写入一些输出中 档案

如果我们想计算一些复杂的东西,我们必须使用大量的数据 由程序处理的输入文件,其输出用作 对其他项目的投入,等等,直到我们完成我们的最终计划 包含我们的结果的文件。如果我们把计划翻译成 最后将文件转换为shell脚本中的一组过程,然后 处理的当前状态是隐式的:计划执行者 知道“它在哪里”,因为它正在执行给定的过程, 这隐含地保证了这样或那样的计算是正确的 已经做了,就是说,这样这样这样的中间文件, 已经准备好了。现在,哪些数据描述了“计划执行者在哪里?” 在“

无害观察描述“计划在哪里”的数据 “执行者在”正是一组中介文件 我们已经准备好了,而这正是我们制作的数据 当我们编写Makefiles时显式的

这种无伤大雅的观察实际上就是概念上的差异 在shell脚本和makefile之间,这解释了所有的优点 编译作业和类似作业中外壳脚本的转换文件。 当然,要充分认识到这些优势,我们必须写作 正确的makefile,这对初学者来说可能很难

Make使中断的任务在其所在位置继续变得容易 当我们用Makefile描述编译作业时,我们可以很容易地 中断它,稍后再继续。这是经济衰退的结果 无害的观察。类似的效果只能通过以下方法实现: 在shell脚本中进行了大量的工作,而它只是内置的
make

Make使得使用项目的多个构建变得容易 您观察到Makefiles会将源树与对象混在一起 文件夹。但是makefile实际上可以参数化来存储这些 专用目录中的对象文件。我和 宏的定义和使用

因此,所有对象文件都以
~/obj
结尾,并且不会污染我的 来源。看到这个了吗 更多细节

高级Makefiles允许我们同时拥有多个目录 包含具有不同编译的项目的多个版本 选项。例如,启用了不同的功能或调试 版本等。这也是无害观察的结果 Makefiles实际上是围绕一组中介体来表达的 文件夹。此技术在中进行了说明

使构建并行化变得容易 我们可以轻松地并行构建程序,因为这是一个标准 多种版本的
make
功能。这也是这场战争的后果 无害的观察:因为“计划执行者在哪里”是一个 在Makefile中的显式数据,
make
可以对 信息技术在shell脚本中实现类似效果需要 非常努力

任何版本的
make
的并行模式只有在以下情况下才能正常工作 已正确指定依赖项。这可能是相当困难的 实现起来很复杂,但具有 从字面上说,这是一个问题。它被称为 模式信息技术 使用编译作业的第一个非并行过程来计算 通过监视文件访问的实际依赖关系,并使用此 稍后并行构建中的信息

makefile易于扩展 因为特殊的视角——也就是说,作为另一个结果
MAKEOBJDIR='/usr/home/michael/obj${.CURDIR:S@^/usr/home/michael@@}'