本文介绍Makefile的一些基本概念以及简单的用法。本文所用的编译器是Hightec tricore v4.9.1.0。
1 Makefile的作用
简单来说,Makefile的作用是告诉make如何编译和链接程序。我们在Eclipse界面中写好代码后,会点击一下rebuild按钮来将工程重新编译和链接,这背后就是通过Makefile来实现的。本文会结合编译器的帮助文档make.pdf,并且自己编写Makefile,来学习Makefile的基本概念和用法。
2 Makefile的规则
Makefile的规则如下所示:
其中的含义如下:
- target是生成的文件的名称,可以是编译器生成的目标文件,或者链接器生成的可执行文件。另外也可以是执行的动作,例如clean;
- prerequisite是用于生成文件的输入,例如生成目标文件的输入是C文件和头文件,生成可执行文件的输入是目标文件;
- recipe是需要执行的动作,可以理解成命令。recipe前面要加上一个Tab制表符。
知道了以上规则,就可以写一个简单的Makefile了。
3 一个简单的Makefile
3.1 帮助文档中的例子
Hightec的帮助文档中举了个例子来演示Makefile,如下图所示。
该Makefile主要做了一下几件事:
- 将8个源文件编译成目标文件;
- 将8个目标文件链接成一个可执行文件edit;
- 用clean清除掉目标文件和可执行文件。
3.2 例1:一个简单的Makefile
帮助文档中只是个演示,并没有附带源文件供我们练习。因此博主自己写了简单的源文件头文件和Makefile,研究Makefile的执行过程和结果。另外,由于链接过程需要编写复杂的链接脚本,所以在下面的例子中只尝试编译的过程。
1)首先在同一个路径下建立三个文件:hello.c,hello.h,Makefile。
2)在hello.c中写入简单的变量和函数定义,如下所示。
/* hello.c */
#include "hello.h"
int a = 10;
int main(void)
{
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
在hello.h中写入对应的变量和函数声明,如下所示。
/* hello.h */
extern int a;
extern int main(void);
- 1
- 2
- 3
根据上文提到的规则,在Makefile中尝试写入编译hello.c的命令语句,如下所示。
hello.o : hello.c hello.h
tricore-gcc -c hello.c
clean :
rm hello.o
- 1
- 2
- 3
- 4
- 5
Makefile中包含了两个功能:调用tricore-gcc把hello.c编译成hello.o,以及移除hello.o。
3)打开命令提示符,切换到这三个文件所在的路径。然后输入make并回车,如下图所示。
命令窗口会显示tricore-gcc -c hello.c这一行命令语句。然后回到文件夹路径下,看到成功生成了hello.o文件。
4)在窗口中输入make clean并回车,就会执行Makefile中的clean命令,如下图所示。
命令窗口会显示rm hello.o这一行命令语句。然后回到文件夹路径下,之前的hello.o文件就被移除了。clean的功能在软件编译的过程中很常用。在这里,clean不是一个目标文件,而是一个动作。clean的冒号后面没有跟其他的文件,因而被称为伪目标。
5)当路径下已经有了hello.o文件的时候,再去执行make命令会发生什么呢?尝试执行两次make命令,如下图所示。
第二次执行make命令的时候,编译器返回了"make: ‘hello.o’ is up to date"这句话,表示hello.o文件已经是最新的了。这里就要提到一个Makefile的机制,就是如果target文件(冒号前的文件)的时间晚于prerequisite文件(冒号后的文件)的时间,就表示输入文件没有被更新过,所以不会进行重新编译。
4 make是如何执行Makefile的
4.1 默认目标
默认情况下,make从第一个target开始,这个target被称为默认目标。
例如在3.1中所述的帮助文档中的例子。当我们敲下了make指令的时候,会从第一条规则开始,也就是重链接出edit文件。但是在链接之前,make必须首先处理edit文件所依赖的输入文件,也就是.o文件。所以make会先编译所有需要重新编译的源文件,直到.o文件齐全。这有点像是一个金字塔结构,底层的target完成以后才能做顶层的target。
clean伪目标是个例外,因为它不是edit所依赖的输入,需要使用make clean指令来处理clean。
4.2 例2:使用默认目标
把例1中的Makefile进行修改,在编译hello.c之前做一个默认目标project.elf,依赖于输入文件hello.o文件,如下。
project.elf : hello.o
tricore-gcc -o project.elf hello.o
hello.o : hello.c hello.h
tricore-gcc -c hello.c
clean :
rm project.elf hello.o
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
首先运行make clean,将当前路径的hello.o清除掉,只剩下hello.c,hello.h,Makefile三个文件。
然后,运行make。
在路径下就会生成project.elf和hello.o两个文件。
这个Makefile中就是以开头的project.elf作为默认目标。make先检测到默认目标所依赖的hello.o在当前路径中不存在,所以先去以hello.o为目标进行编译。编译完成后再以project.elf为目标去链接。
注意,这里的链接elf文件只是做一个演示,是为了说明默认目标的概念。实际上,链接过程非常复杂,不要被这个例子中的只言片语所误导。
5 使用变量让Makefile更简洁
5.1 变量的简单用法
在3.1帮助文档中的例子中,edit所依赖的.o文件有很多。如果Makefile中重复出现大量相同的字段,会很容易写错。这时就可以用一个变量来指代这一系列.o文件,如下图所示。
然后,在后文用到这一系列.o文件的地方,就可以用$(objects)来替换,如下图所示。
5.2 例3:在Makefile中使用变量
把例1中的Makefile进行修改,将hello.c和hello.h赋值给变量inputfile,然后再后面用到的地方换成$(inputfile),如下。
inputfile = hello.c hello.h
hello.o : $(inputfile)
tricore-gcc -c hello.c
clean :
rm hello.o
- 1
- 2
- 3
- 4
- 5
- 6
- 7
然后打开命令行运行make,效果和例1相同,都能生成hello.o文件。
6 通过make推导规则
6.1 隐式规则
在Makefile文件中可以不用把编译的所有规则写出来,因为make中有个概念叫隐式规则,可以自动推导依赖关系。例如,在make看到了一个.o文件的时候,会自动推导它的源文件是同名的.c文件,就不需要把.c文件也写进去了。所以3.1 帮助文档中的例子也可以写成下面这样。
main.o文件后面就不再写上输入文件main.c了,下面的cc规则也省略了。
6.2 例4:省略源文件
将例1中的Makefile修改,冒号后面的输入文件不带hello.c了,只带一个hello.h,如下。
hello.o : hello.h
tricore-gcc -c hello.c
clean :
rm project.elf hello.o
- 1
- 2
- 3
- 4
- 5
然后打开命令行,用make执行,效果和例1相同。
7 另一种风格的Makefile
由于有了Makefile的隐式规则,就可以不用把源文件清楚地写出来了。因此,诞生了另一种风格的Makefile,如下。
在编译规则中,冒号左边的目标是很多个.o文件,右边是头文件。这样的Makefile也是可以正确地执行的,但是依赖关系非常复杂,写的时候很容易出错。
8 清空路径的规则
上面的例子也说到,可以用make clean来清空路径下的文件。实际应用中最好按照以下方式来写clean。
这里包含了两个要素。首先,.PHONE表示clean是个伪目标,这样就不会把clean和名为clean的文件搞混淆。其次,在rm前面添加一个减号,表示即使rm出现错误也可以使make继续。
注意,诸如clean这样的规则不应放在makefile的开头,因为我们不希望它成为默认目标。
9 总结
本文学习了Makefile的基本概念,并且通过一些实例的操作,熟悉了Makefile的基本用法。
评论记录:
回复评论: