Makefile是什么

1,494 阅读3分钟

最近在学习Go语言,看到很多在linux环境中,项目make一下就可以自动运行,产生了好奇。

这里推荐一个大神的博客:www.ruanyifeng.com/blog/2015/0…

Makefile是什么

Makefile文件由一系列规则(rules)构成。一般出现于linux环境中,常见于用C/C++/go项目文件中。用于构建一键化项目。只需要执行make命令。就可以批量运行makefile里面的命令,可以快速启动项目,停止项目,清除环境等等。学会编写makefile和看懂别人书写的makefile可以更加深入的了解项目运行逻辑。

Makefile的格式

<target> : <prerequisites> 
[tab]  <commands>

上面第一行冒号前面的部分,叫做"目标"(target),冒号后面的部分叫做"前置条件"(prerequisites);第二行必须由一个tab键起首,后面跟着"命令"(commands)。

"目标"是必需的,不可省略;"前置条件"和"命令"都是可选的,但是两者之中必须至少存在一个。

每条规则就明确两件事:构建目标的前置条件是什么,以及如何构建。下面就详细讲解,每条规则的这三个组成部分。

目标(target)

目标可以是文件名:

指明Make命令所要构建的对象,比如a.txt 。目标可以是一个文件名,也可以是多个文件名,之间用空格分隔。

目标可以是伪目标:

除了文件名,目标还可以是某个操作的名字,这称为"伪目标"(phony target)。 例如:

clean:
      rm a.txt

clean类似于函数名,当执行make clean的时候。会执行clean下面对应的命令。例子里面就是执行删除文件a.txt的操作。

但是,如果当前目录中,正好有一个文件叫做clean,那么这个命令不会执行。因为Make发现clean文件已经存在,就认为没有必要重新构建了,就不会执行指定的rm命令。

为了避免这种情况,可以明确声明clean是"伪目标",写法如下。

.PHONY: clean
clean:
        rm a.txt

使用.PHONY 可以告诉程序,这是一个伪目标。make就不会去检查是否存在一个叫做clean的文件,而是每次运行都执行对应的命令。像.PHONY这样的内置目标名还有不少,可以查看手册

前置条件

可以理解为要执行次函数,必须先执行:(冒号)后面的函数,也就是前置条件。

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

result.txt: source.txt
    cp source.txt result.txt

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

$ make result.txt
$ make result.txt

上面命令连续执行两次make result.txt。第一次执行会先新建 source.txt,然后再新建 result.txt。第二次执行,Make发现 source.txt 没有变动(时间戳晚于 result.txt),就不会执行任何操作,result.txt 也不会重新生成。

如果需要生成多个文件,往往采用下面的写法

source: file1 file2 file3

上面代码中,source 是一个伪目标,只有三个前置文件,没有任何对应的命令。

make source

执行make source命令后,就会一次性生成 file1,file2,file3 三个文件。这比下面的写法要方便很多。

$ make file1
$ make file2
$ make file3

命令

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

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


.RECIPEPREFIX = >
all:
> echo Hello, world

上面代码用.RECIPEPREFIX指定,大于号(>)替代tab键。所以,每一行命令的起首变成了大于号,而不是tab键。

需要注意的是,每行命令在一个单独的shell中执行。这些Shell之间没有继承关系。

var-lost:
    export foo=bar
    echo "foo=[$$foo]"

面代码执行后(make var-lost),取不到foo的值。因为两行命令在两个不同的进程执行。一个解决办法是将两行命令写在一行,中间用分号分隔。

var-kept:
    export foo=bar; echo "foo=[$$foo]"

另一个解决办法是在换行符前加反斜杠转义。 最后一个方法是加上.ONESHELL:命令。

ONESHELL:
var-kept:
    export foo=bar; 
    echo "foo=[$$foo]"

Makefile 语法

注释

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

# 这是注释
result.txt: source.txt
    # 这是注释
    cp source.txt result.txt # 这也是注释

echo

正常情况下,make会打印每条命令,然后再执行,这就叫做回声(echoing)。

test:
    # 这是测试

执行上面的规则,会得到下面的结果。

$ make test
# 这是测试

在命令的前面加上@,就可以关闭回声。 现在再执行make test,就不会有任何输出。

由于在构建过程中,需要了解当前在执行哪条命令,所以通常只在注释和纯显示的echo命令前面加上@。