Makefile文件 | 编写指南

简介: Makefile文件 | 编写指南

1.一切得从Make谈起


     大家都知道Make这个单词的意思是“制作”的意思,使用Make命令就是要做出某个文件(通常是可执行文件)。例如:要制作出可执行文件a.txt,可以使用下面的命令:


make a.txt

   

但是,如果你真的在Linux的终端下输入上面的这条命令,并不会制作出a.txt文件。相反,你会得到下面这个错误信息:


[njust@njust Make_Tutorials]$ make a.txt
make: *** 没有规则可以创建目标“a.txt”。 停止。


     为什么会出现上面的这个错误呢?因为Make命令本身并不知道怎样才能制作出a.txt,需要有人告诉它如何调用其他命令来完成这个目标。打个比方哈,就像厨房新手做菜时目标是做出来一道硬菜。但是呢,由于是新手还必须得按照菜谱来,第一步该干啥,第二步该干啥.......。因此,Make命令生成可执行文件时,也必须严格按照Makefile文件来进行编译、链接并生成可执行文件,Makefile文件就类似于菜谱,它制定了一系列的规则。因此,学会如何写出一个正确的Makefile文件后,才能愉快地使用Make命令进行编译大型项目代码。


      有的读者可能就会有疑问了,学习Make命令干嘛呢?生成可执行文件直接使用gcc或g++命令进行编译就好啦,为啥还得花时间学习如何编写Makefile文件。是的,在一个小的demo程序中,你的确可以使用gcc/g++命令进行编译生成可执行文件,就像下面的示例一样。但是,如果你恰好要同时编译多个.c的源文件,那么gcc这条编译命令将会敲的很长很长,就很不方便还容易出错。


[njust@njust Make_Tutorials]$ gcc -g demo.c -o demo
[njust@njust Make_Tutorials]$ ./demo
hello world!
[njust@njust Make_Tutorials]$ cat demo.c
#include <stdio.h>
int main()
{
  printf("hello world!\n");
  return 0;
}

   

如果我们使用Make命令进行编译时,先写好Makefile文件,然后执行一条简单的Make命令即可生成可执行文件(见下例所示),大大提高了编译操作的便捷性。


[njust@njust Make_Tutorials]$ ls
demo.c  Makefile
[njust@njust Make_Tutorials]$ cat Makefile 
demo:demo.c
 gcc -g demo.c -o demo
[njust@njust Make_Tutorials]$ make demo
gcc -g demo.c -o demo
[njust@njust Make_Tutorials]$ ls
demo  demo.c  Makefile
[njust@njust Make_Tutorials]$ ./demo 
hello world!


2.Makefile文件的基本格式


    Make命令编译出可执行文件时,所需的构建规则全写在Makefile文件中了。Makefile文件是由一系列规则组成的,每条规则的格式如下


<目标>:<依赖文件>
按一次TAB键<命令>

     上面第一行中冒号前面的为目标,冒号后面的是依赖条件。第二行的开始必须以一个TAB键开头,后面跟着命令。其中,目标是必须写的,不能省略。依赖条件和命令都是可选择不写,但两者中必须至少有一个存在。


(1).目标

一个目标即可构成Makefile文件中的一条规则。目标通常是文件名,表示Make命令所要编译出的对象。目标可以是一个文件名,也可以是多个文件名,多个文件名的话,彼此之间用空格分开。


目标除了可以是文件名外,还可以是某个操作的名字,此时目标也称为“伪目标”。例如,下面的Makefile文件中clear是目标,但它不是文件名,而是一个删除操作的名字,因此也称为伪目标。


clean:
  rm *.o


值得注意的是,假设当前目录下恰好有一个文件的名字也叫clean,那么make clean命令将不会被执行。这是因为Make发现clean文件已经存在,就认为没有必要重新编译啦,因此也就不会执行rm *.o操作。


[njust@njust Make_Tutorials]$ touch hello.o test.o
[njust@njust Make_Tutorials]$ ls
demo.c  hello.o  Makefile  test.o
[njust@njust Make_Tutorials]$ touch clean
[njust@njust Make_Tutorials]$ ls
clean  demo.c  hello.o  Makefile  test.o
[njust@njust Make_Tutorials]$ cat Makefile 
demo:demo.c
 gcc -g demo.c -o demo
clean:
 rm *.o
[njust@njust Make_Tutorials]$ make clean
make: “clean”是最新的。
[njust@njust Make_Tutorials]$ ls
clean  demo.c  hello.o  Makefile  test.o


为了解决上面的问题,可以明确声明clean是伪目标,重新修改Makefile文件,如下所示。声明clean是伪目标后,Make命令将不会去检查当前目录下是否有一个名为clean的文件,而是每次都会执行对应的命令。如果Make命令运行时没有指定目标,默认会执行Makefile文件中的第一个目标。



(2).依赖条件


依赖条件通常是一组文件名,多个文件名之间用空格分开。它指定了目标是否重新编译的判断标准:只要有一个依赖文件不存在或者已更新,目标就需要重新被编译


[njust@njust Make_Tutorials]$ cat Makefile 
demo:demo.c
 gcc -g demo.c -o demo
.PHONY:clean
clean:
 rm *.o
[njust@njust Make_Tutorials]$ make clean
rm *.o
[njust@njust Make_Tutorials]$ ls
clean  demo.c  Makefile


下面的示例代码中,编译result.txt的前提条件是source.txt。如果当前目录中,source.txt文件已经存在,则会正常执行make result.txt。否则,必须再写一条规则用于生成source.txt。


[njust@njust Make_Tutorials]$ ls
clean  demo.c  Makefile  source.txt
[njust@njust Make_Tutorials]$ make result.txt
echo "This is a demo!"
This is a demo!
[njust@njust Make_Tutorials]$ cat Makefile 
demo:demo.c
 gcc -g demo.c -o demo
.PHONY:clean
clean:
 rm *.o
result.txt:source.txt
 echo "This is a demo!"


当source.txt文件不存在时,需要修改Makefile文件的内容,如下所示:


[njust@njust Make_Tutorials]$ ls
clean  demo.c  Makefile
[njust@njust Make_Tutorials]$ make result.txt
echo "This is a demo!" > source.txt
cp source.txt result.txt
[njust@njust Make_Tutorials]$ ls
clean  demo.c  Makefile  result.txt  source.txt
[njust@njust Make_Tutorials]$ cat Makefile 
demo:demo.c
 gcc -g demo.c -o demo
.PHONY:clean
clean:
 rm *.o
result.txt:source.txt
 cp source.txt result.txt
source.txt:
 echo "This is a demo!" > source.txt


(3).命令


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


每行命令前必须有一个TAB键,如果想使用其他键进行替换,可以使用内置变量.RECIPEPREFIX进行声明,如下所示。使用.RECIPEPREFIX指定用>来替代默认的TAB键。于是,每行命令的起始变成了>而不是TAB键了。


[njust@njust Make_Tutorials]$ make all
echo Hello,CurryCoder
Hello,CurryCoder
[njust@njust Make_Tutorials]$ cat Makefile 
demo:demo.c
 gcc -g demo.c -o demo
.PHONY:clean
clean:
 rm *.o
result.txt:source.txt
 cp source.txt result.txt
source.txt:
 echo "This is a demo!" > source.txt
.RECIPEPREFIX = >
all:
> echo Hello,CurryCoder

值得注意的是,每行命令在一个独立的shell中执行,这些shell之间没有继承关系,如下例所示。执行make var命令后,取不到foo的值。由于两行命令在两个不同的进程执行。


[njust@njust Make_Tutorials]$ cat Makefile 
demo:demo.c
 gcc -g demo.c -o demo
.PHONY:clean
clean:
 rm *.o
result.txt:source.txt
 cp source.txt result.txt
source.txt:
 echo "This is a demo!" > source.txt
var:
 export foo=bar
 echo "foo=[$$foo]"
[njust@njust Make_Tutorials]$ make var
export foo=bar
echo "foo=[$foo]"
foo=[]


上面的程序执行后的输出结果是foo=[],无法获取foo的值。解决方法一是将两行命令写在同一行中,中间用分号隔开。解决方法二是在换行符前加反斜杠转义。解决方法三是加上.ONESHELL命令。


[njust@njust Make_Tutorials]$ make var
# export foo=bar;echo "foo=[$foo]"
export foo=bar;\
echo "foo=[$foo]"
foo=[bar]
[njust@njust Make_Tutorials]$ vim Ma
[njust@njust Make_Tutorials]$ vim Makefile 
[njust@njust Make_Tutorials]$ make var
# export foo=bar;echo "foo=[$foo]"
# export foo=bar;\
# echo "foo=[$foo]"
export foo=bar;
export "foo=[$foo]"
[njust@njust Make_Tutorials]$ cat Makefile 
demo:demo.c
 gcc -g demo.c -o demo
.PHONY:clean
clean:
 rm *.o
result.txt:source.txt
 cp source.txt result.txt
source.txt:
 echo "This is a demo!" > source.txt
.ONESHELL:
var:
 # export foo=bar;echo "foo=[$$foo]"
 # export foo=bar;\
 # echo "foo=[$$foo]"
 export foo=bar;
 export "foo=[$$foo]"
相关文章
|
7月前
|
Shell Linux C++
Makefile编译实战
Makefile编译实战
94 0
|
7月前
|
编译器 测试技术
如何编写自己的Makefile(1)
如何编写自己的Makefile(1)
34 1
|
IDE 编译器 程序员
编写Makefile
编写Makefile
63 0
|
IDE Linux Shell
【Makefile】简单的Makefile编写
【Makefile】简单的Makefile编写
|
编译器 Shell C语言
Makefile文件 | 进阶指南
Makefile文件 | 进阶指南
381 0
|
Linux C++
利用makefile文件编译c++源文件
makefile文件编译c++ 生成一个so库文件
6329 0