make笔记

Make命令教程

​ 代码变成可执行文件,叫做编译,先编译这个,还是先编译那个(即编译的顺序安排),叫做构建(build)。make就是最常用的构建(相当于批处理)工具,主要用于C语言的项目,只要某个文件有变化,都要重新构建项目。

Make的概念

​ make的意思就是“制作”,make命令就是要做出某个文件,比如,要做出文件a.txt,就可以执行下面的命令。

1
make a.txt

​ 但是,如果真的输入这个命令,并不会有任何作用,因为make命令本身并不知道如何作出a.txt,需要有人告诉它,如何调用其他命令完成这个目标

​ 比如,假设文件a.txt依赖于b.txt和c.txt,是两个文件连接(cat)的产物,那么,make命令需要知道下面的规则。

1
2
a.txt: b.txt c.txt    #表示a依赖于b和c
cat b.txt c.txt > a.txt # 利用b和c生成a

也就是说,make a.txt这条命令的背后,实际上分为两步:第一步,确认b.txt和c.txt必须已经存在,第二步使用cat命令将这两个文件合并,输出为新文件。

像这样的规则,都写在一个叫做Makefile的文件中,Make命令依赖于这个文件进行构建,可使用命令指定规则的文件名,如下,指定为rules.txt文件作为规则。

1
2
3
make -f rules.txt
# 或者
make --file=rules.txt

Makefile文件的格式

概述

Makefile文件由一系列规则(rules)构成,每条规则的形式如下:

1
2
<target>: <prerequisites> # target叫做目标,后面的叫做前置条件
[tab] <commands> # 第二行必须由一个tab键起首,后面跟着命令

target是必须的,不可省略,前置条件和命令两个必须存在一个。

每条规则实际就是在明确两件事:构建目标的前置条件是什么,以及如何构建

目标(target)

一个目标就构成一条规则,目标通常是文件名,指明Make命令所要构建的对象,比如上文中的a.txt,目标可以是一个文件名,也可以是多个空格分隔的文件名。除了文件名,目标还可以是某个操作的名字,这称为“伪目标”

1
2
clean:
rm *.o

上面代码的目标是clean,它不是文件名,而是一个操作的名字,属于“伪目标”,作用是删除相应的文件。但是,如果当前目录中已经存在一个名为clean的文件,make clean就不会被执行,为避免这种情况,可以明确声明clean是伪目标,写法如下:

1
2
3
.PHONY: clean
clean:
rm *.o temp

因为声明为伪目标后,make就不会去检查是否存在一个叫做clean的文件,而是每次运行都执行对应的命令。

如果make 命令运行时没有指定目标,默认会执行Makefile文件的第一个目标(make)

前置条件(prerequistites)

​ 前置条件通常是一组文件名,之间用空格分隔。它指定了“目标”是否重新构建的判断标准:只要有一个前置文件不存在,或者有过更新(前置文件的last-modification时间戳比目标的时间戳新),“目标”就需要重新构建。

​ 一个例子如下,构建result.txt的前置条件是source.txt,如果当前目录中,source.txt已经存在,那么make result.txt 就可以正常运行,否则,必须再写一条规则来生成source.txt。

1
2
result.txt: source.txt
cp source.txt result.txt

无source的情况生成其如下:

1
2
source.txt:
echo "this is the source" > source.txt

命令(commands)

​ 命令(commands)表示如何更新目标文件,由一行或多行的shell命令组成,它是构建“目标”的具体指令,它的运行结果通常是生成目标文件。

​ 每行命令之前必须有tab键,如果想用其他键,可以用内置变量.RECIPEPREFIX声明。

1
2
3
.RECIPEREFIX = >		#使用>来代替tab
all:
> echo Hello,world #实际使用来代替

需要注意的是,每行命令在一个单独的shell中执行,这些shell之间没有继承关系,即行与行之间的shell变量不能传递,一个例子如下:

1
2
3
var-lost: 
export foo=bar
echo "foo=[$$foo]" #获取不到foo变量的值

改进方法是将其写在一行,中间用分号分隔或者用反斜杠转义。

1
2
3
4
5
var-kept:
export foo=bar; echo "foo=[$$foo]"
var-kept2:
export foo=bar; \
echo "foo=[$$foo]"

还有一个方法是加上.ONSHELL:命令。

1
2
3
4
.ONSHELL
var-kept:
export foo=bar;
echo "foo=[$$foo]"

Makefile文件的语法

注释

井号(#)在Makefile中表示注释

回声(echoing)

正常情况下,make会打印每条命令,然后再执行,这就叫回声(echoing),在命令前加上@,就可以关闭会上。

判断和循环

makefile只用Bash语法,完成判断和循环。

1
2
3
4
5
ifeq ($(CC), gcc)
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif

上面代码判断当前编译器是否是gcc,然后指定不同的库文件。

又一个例子,如下:

1
2
3
4
5
6
7
8
9
10
LIST = one two three
all:
for i in $(LIST); do \
echo $$i; \
done
# 等同于
all:
for i in one two three; do \
echo $$i; \
done

上面的运行结果如下:

1
2
3
one
two
three