Linux【8】-软件管理-1-3-用make进行巨集编译(makefile)

make 的功能是可以简化编译过程里面所下达的指令

一、为什么要用make

先来想像一个案例,假设我的执行档里面包含了四个原始码档案,分别是main.c haha​​.c sin_value.c cos_value.c 这四个档案,这四个档案的目的是:

  • main.c :主要的目的是让使用者输入角度资料与呼叫其他三支副程式;
  • haha.c :输出一堆有的没有的讯息而已;
  • sin_value.c :计算使用者输入的角度(360) sin 数值;
  • cos_value.c :计算使用者输入的角度(360) cos 数值。

由于这四个档案里面包含了相关性,并且还用到数学函式在里面,所以如果你想要让这个程式可以跑,那么就需要这样编译:

# 1.先进行目标档的编译,最终会有四个*.o的档名出现: 
[root@study ~]# gcc -c main.c 
[root@study ~]# gcc -c haha.c 
[ root@study ~]# gcc -c sin_value.c 
[root@study ~]# gcc -c cos_value.c

# 2.再进行连结成为执行档,并加入libm的数学函式,以产生main执行档: 
[root@study ~]# gcc -o main main.o haha.o sin_value.o cos_value.o -lm

# 3.本程式的执行结果,必须输入姓名、360度角的角度值来计算: 
[root@study ~]# ./main 
Please input your name: VBird   <==这里先输入名字 
Please enter the degree angle (ex> 90): 30    <==输入以360度角为主的角度 
Hi, Dear VBird, nice to meet you.     <==这三行为输出的结果喔!
The Sin is: 0.50
The Cos is: 0.87

编译的过程需要进行好多动作啊!而且如果要重新编译,则上述的流程得要重新来一遍,光是找出这些指令就够烦人的了!如果可以的话,能不能一个步骤就给他完成上面所有的动作呢?那就利用make 这个工具吧!先试看看在这个目录下建立一个名为makefile 的档案,内容如下:

# 1.先编辑makefile这个规则档,内容只要作出main这个执行档 
[root@study ~]# vim makefile 
main: main.o haha.o sin_value.o cos_value.o
	gcc -o main main.o haha.o sin_value.o cos_value.o -lm 
#注意:第二行的gcc之前是<tab>按键产生的空格喔!

# 2.尝试使用makefile制订的规则进行编译的行为: 
[root@study ~]# rm -f main *.o    <==先将之前的目标档去除 
[root@study ~]# make
cc -c -o main.o main.c
cc -c -o haha​​.o haha​​.c
cc -c -o sin_value.o sin_value.c
cc -c -o cos_value.o cos_value.c
gcc -o main main.o haha​​.o sin_value.o cos_value.o -lm
# 此时make 会去读取makefile 的内容,并根据内容直接去给他编译相关的档案啰!

# 3.在不删除任何档案的情况下,重新执行一次编译的动作: 
[root@study ~]# make
make: `main' is up to date.
# 看到了吧!是否很方便呢!只会进行更新(update) 的动作而已。

或许你会说:『如果我建立一个shell script 来将上面的所有动作都集结在一起,不是具有同样的效果吗?』呵呵!效果当然不一样,以上面的测试为例,我们仅写出main 需要的目标档,结果make 会主动的去判断每个目标档相关的原始码档案,并直接予以编译,最后再直接进行连结的动作!真的是很方便啊!此外,如果我们更动过某些原始码档案,则make 也可以主动的判断哪一个原始码与相关的目标档档案有更新过, 并仅更新该档案,如此一来,将可大大的节省很多编译的时间呢!要知道,某些程式在进行编译的行为时,会消耗很多的 CPU 资源呢!所以说, make 有这些好处:

  • 简化编译时所需要下达的指令;
  • 若在编译完成之后,修改了某个原始码档案,则make 仅会针对被修改了的档案进行编译,其他的 object file 不会被更动;
  • 最后可以依照相依性来更新(update) 执行档。

既然make 有这么多的优点,那么我们当然就得好好的了解一下make 这个令人关心的家伙啦!而make 里面最需要注意的大概就是那个规则档案,也就是makefile 这个档案的语法啦!所以底下我们就针对makefile 的语法来加以介绍啰。

二、makefile 的基本语法与变数

make的语法可是相当的多而复杂的,有兴趣的话可以到GNU ( 注1 )去查阅相关的说明,鸟哥这里仅列出一些基本的规则,重点在于让读者们未来在接触原始码时,不会太紧张啊!好了,基本的makefile规则是这样的:

标的(target): 目标档1 目标档2
<tab> gcc -o 欲建立的执行档目标档1 目标档2

那个标的(target)就是我们想要建立的资讯,而目标档就是具有相关性的object files ,那建立执行档的语法就是以tab按键开头的那一行!特别给他留意喔,『命令列必须要以tab按键作为开头』才行!他的规则基本上是这样的:

  • 在makefile 当中的# 代表注解;
  • tab 需要在命令行(例如gcc 这个编译器指令) 的第一个字元;
  • 标的(target) 与相依档案(就是目标档)之间需以『:』隔开。

同样的,我们以刚刚上一个小节的范例进一步说明,如果我想要有两个以上的执行动作时, 例如下达一个指令就直接清除掉所有的目标档与执行档,该如何制作呢?

# 1.先编辑makefile来建立新的规则,此规则的标的名称为clean : 
[root@study ~]# vi makefile
main: main.o haha​​.o sin_value.o cos_value.o
	gcc -o main main.o haha​​.o sin_value.o cos_value.o -lm
clean:
	rm -f main main.o haha​​.o sin_value.o cos_value.o

# 2.以新的标的(clean)测试看看执行make的结果: 
[root@study ~]# make clean   <==就是这里!透过make以clean为标的
rm -rf main main.o haha​​.o sin_value.o cos_value.o

如此一来,我们的makefile里面就具有至少两个标的,分别是main与clean ,如果我们想要建立main的话,输入『make main』,如果想要清除有的没的,输入『make clean』即可啊!而如果想要先清除目标档再编译main这个程式的话,就可以这样输入:『make clean main』,如下所示:

[root@study ~]# make clean main
rm -rf main main.o haha​​.o sin_value.o cos_value.o
cc -c -o main.o main.c
cc -c -o haha​​.o haha​​.c
cc -c -o sin_value.o sin_value.c
cc -c -o cos_value.o cos_value.c
gcc -o main main.o haha​​.o sin_value.o cos_value.o -lm

这样就很清楚了吧!但是,你是否会觉得,咦!makefile 里面怎么重复的资料这么多啊!没错!所以我们可以再藉由shell script 那时学到的『变数』来更简化makefile 喔:

[root@study ~]# vi makefile 
LIBS = -lm
OBJS = main.o haha​​.o sin_value.o cos_value.o
main: ${OBJS}
        gcc -o main ${OBJS} ${LIBS}
clean:
        rm -f main ${OBJS}

与bash shell script的语法有点不太相同,变数的基本语法为:

  1. 变数与变数内容以『=』隔开,同时两边可以具有空格;
  2. 变数左边不可以有 ,例如上面范例的第一行LIBS 左边不可以是tab;
  3. 变数与变数内容在『=』两边不能具有『:』;
  4. 在习惯上,变数最好是以『大写字母』为主;
  5. 运用变数时,以${变数} 或$(变数) 使用;
  6. 在该shell 的环境变数是可以被套用的,例如提到的CFLAGS 这个变数!
  7. 在指令列模式也可以给予变数。

由于gcc在进行编译的行为时,会主动的去读取CFLAGS这个环境变数,所以,你可以直接在shell定义出这个环境变数,也可以在makefile档案里面去定义,更可以在指令列当中给予这个咚咚呢!例如

[root@study ~]# CFLAGS="-Wall" make clean main 
#这个动作在上make进行编译时,会去取用CFLAGS的变数内容!

也可以这样:

[root@study ~]# vi makefile
LIBS = -lm
OBJS = main.o haha​​.o sin_value.o cos_value.o
CFLAGS = -Wall
main: ${OBJS}
	gcc -o main ${OBJS} ${LIBS}
clean:
	rm -f main ${OBJS}

咦!我可以利用指令列进行环境变数的输入,也可以在档案内直接指定环境变数,那万一这个 CFLAGS 的内容在指令列与makefile 里面并不相同时,以那个方式输入的为主?呵呵!问了个好问题啊!环境变数取用的规则是这样的:

  1. make 指令列后面加上的环境变数为优先;
  2. makefile 里面指定的环境变数第二;
  3. shell 原本具有的环境变数第三。

此外,还有一些特殊的变数需要了解的喔:

$@:代表目前的标的(target)

所以我也可以将makefile 改成:

[root@study ~]# vi makefile
LIBS = -lm
OBJS = main.o haha​​.o sin_value.o cos_value.o
CFLAGS = -Wall
main: ${OBJS}
	gcc -o $@ ${OBJS} ${LIBS}    <==那个$@就是main !
clean:
	rm -f main ${OBJS}

这样是否稍微了解了makefile (也可能是Makefile) 的基本语法?这对于你未来自行修改原始码的编译规则时,是很有帮助的喔!^_^!

参考资料

药企,独角兽,苏州。团队长期招人,感兴趣的都可以发邮件聊聊:tiehan@sina.cn
个人公众号,比较懒,很少更新,可以在上面提问题,如果回复不及时,可发邮件给我: tiehan@sina.cn