计算机 · 2021年8月18日 0

Make

GNU Make官方文档

官网网址

make的作用

从我的理解来看,make的主要作用就是管理大型一点的项目的编译,其优点有:

  • 省去每次输入大量编译命令的麻烦
    当程序规模很小时,比如只有一个源文件时,我们可以直接用一行编译命令来进行编译,但是当项目规模大一点的时候,比如有几十个源文件之类的时候,我们就不可能采用在编译命令里包含几十个源文件的名字这种做法了。当然可以用通配符或者正则表达式来写一个简化了的编译命令(比如gcc *.c -o main),但是如果再考虑到要链接的库,指定头文件所在的目录,设置一些编译选项等,这个项目的编译命令依然会变得长而复杂,因此需要我们把这个编译命令给写下来记在文件里,避免每次都手动输入一长串的编译命令。
  • 避免每次都要编译所有源文件
  • 可以设置不同的编译目标

使用shell脚本也可以达到以上目的,但是这需要大量的shell编程才能做到,所以还是用专门做这个工作的make更加方便。

makefile的简要写法

makefile主要由5部分构成:变量,规则,注释,

规则(Rule)

规则主要描述项目内部的依赖关系,是makefile最核心的部分:

target ... : prerequisites ...; recipe
    recipe
    ...
    ...

上面是一条关于生成target的规则,其含义是当我们想要得到目标target时,我们就得先生成target所依赖的一系列prerequisites,得到prerequisites之后且prerequisites里至少有一项比target更新,那么就会再执行上面列出的recipe这一堆命令。而生成prerequisites的方式是检查他们有没有属于自己的rule,如果有的话就执行他们的rule,如果没有的话,那么这些prerequisites必须是已经存在的文件,否则报错。
在上面的rule中,左边可以有多个target,右边也可以有多个prerequisites,但是为了更容易理解makefile的内容,一般都是在左边写一个target而在右边写多个prerequisites

有序的prerequisites

可以通过管道符号分隔无序和有序的prerequisites

targets : normal-prerequisites | order-only-prerequisites
    recipe
    ...
    ...

指令(Directive)

  • 包含其他makefile
  include filenames...

可以为make命令加上’-I’或者’–include-dir’选项指定搜索要包含的makefile的目录。

变量(Variables)

有些东西(字符串)我们不想多次重复地写,把这些东西赋给变量。

  • 变量命名
  • 变量赋值
    make在运行时分两个阶段,第一个阶段是读取makefile,大概可以理解为预处理的过程,第二个阶段是根据make命令运行规则的阶段。因此变量的赋值也可以分为两个阶段,在前一个阶段被解析(expansion)的称为immediate,在后一个阶段被解析的称为deferred。下面是一个赋值方式的列表:
  • immediate = deferred
  • immediate ?= deferred
  • immediate := immediate
  • immediate ::=immediate
  • immediate += deferred or immediate
    即右边是deferred那么左边也是deferred,右边是immediate左边也是immediate
  • immeidate != immediate 通过define方式赋值的变量遵循与上面列表相同的规则。
    条件指令中的变量都是immediate的,即不能通过条件指令动态修改变量的值。如果一定有这种修改变量值的需求,可以在recipe中用shell语句设法达到这个目的。
  • 对于rule来说,始终遵循以下展开方式: immediate : immediate ; deferred deferredtargetprerequisites都是在第一阶段展开,而recipe在构建目标时展开。

注释(Comments)

  • 一行中以’#’开始的部分为注释。
  • 想要多行注释在行末加上’\’就行。
  • 如果’#’跟在了变量定义或者函数调用的后面,那么它会被当做字面值成为变量定义或者函数调用的一部分,而不被当做注释。
  • recipe里的’#’由运行make的shell负责解释。

运行makefile

makefile的命名

GNU make默认会按顺序搜寻GNUmakefilemakefileMakefile作为输入。据说使用Makefile作为名字比较好,因为在IDE里用大写字母开头容易把文件排在前面,就和README.md一样的道理。

运行

  • 直接在makefile所在目录下输入make命令以运行makefile;
  • 或者通过make -f <your-makefile>来指定要读取的makefile;
  • 或者通过make <target>指定自己要生成的target;make默认选取的target是makefile中第一条规则指定的target

关于make的其他知识

错误处理

  • recipe的命令前面加上-以让make忽略可能的错误。

容易出错的地方

  • 记得recipe前面要用tab键作为separator,而不能用空格。在Vim或者很多IDE、编辑器里都会用空格代替tab键,使得make解析makefile出错。这个tab键也可以替换为其他的键,但一般人应该是没有这个需求的。

Tips

关于规则

  • 书写多行的prerequisites
    如果prerequisites列表太长可以用’\’把列表分隔成多行来写。
  • 隐式规则
    对于name.o这样的target,我们可以省略掉prerequisites里的name.c和相应的recipe,make会自动在其prerequisites里加上name.c,并且加上cc -c的编译命令作为recipe。如果你已经自己写了recipe,那么隐式规则就会被覆盖掉。
  • 覆盖另一个makefile里的规则
    假设我们已经有一个makefile的模板Makefile,我们只需要修改其中的几条规则(比如下面例子中的foo)来形成自己的makefile。为了保留这个makefile模板同时又达到自己的目的,可以在我们自己的makefile里这样写:
  foo:
    frobnicate > foo
  %: force
    @$(MAKE) -f Makefile $@
  force: ;

这样除了foo这个目标的规则使用我们自己定义的规则,其他目标都会使用模板Makefile里的规则。force这个prerequisite存在的意义是保证其他目标都会被重新生成。而给予force空的recipe是防止make利用%: force这条规则来构建force,以免形成循环。