你这样,一个女人,让我欢喜让我忧... --- 周华健
Makefile 就是一个让人欢喜让人忧的东西,
欢喜呢?使用起来超方便。
忧呢?就是语法比较麻烦,一大堆一大堆的,有时候会繁杂。
当然,了解它之后,或许,忧,会少一点吧。
比如,
“我没事”时,你应该说“我错了”,而不是去玩游戏。
“肚子疼”时,你应该帮她揉揉肚子,而不是“多喝开水”。
这一篇呢,将带你进入一个让你欢喜让你忧的世界-----Makefile。
1、为什么要用 Makefile?
2、Makefile的编写规则。
3、Makefile常用的函数。
1、为什么要用 Makefile?
很久很久以前,人们不用 Makefile,生产一个程序,就只能手动敲命令。
一开始,程序很小很小,只有一个文件,只需要在shell里敲上:
gcc main.c -o main
后来,程序越来越大,有非常多的文件,需要敲入这个:
gcc main.c 1.c 2.c 3.c ... ... ... -o main
每次修改某个文件,都需要重新编译所有文件,要好久好久呀,编译效率非常低下。
有没有办法让提高效率呢?
gcc -c main.c 1.c 2.c 3.c ... ... ... 分别生成 所有的.o文件;
gcc main.o 1.o 2.o 3.o ... ... ... -o main 再把生成的.o文件链接成目标文件main;
如果修改 1.c,只需要 先
gcc -c 1.c 生成 1.o 文件;
gcc main.o 1.o 2.o 3.o ... ... ... -o main 再把刚生成的 1.o,和其他之前生成的.o文件,一起链接成目标文件main
改哪个编哪个,编译效率倒是提高了,不过,敲命令,也够受了,如果改很多文件的话,还要记得,改了哪个,如果编译比较大型的工程,光是敲命令,都得敲到手抽筋,于是,make应运而生。
make机制:make的机制类似于“改哪个编哪个”,make 会自动检查源文件.c和对应的文件.o的最后修改时间,如果某个源文件.c的最后修改时间,比对应的文件.o要新,说明,这个文件修改过,需要重新编译,make就会自动编译对应的文件,再把新编译生成的新的对应文件.o,和之前已生成的.o文件,一起链接成目标文件xxxx。
Ex: 如果 1.c 被更改,make 会自动检查 .c 的修改时间 和 .o 的修改时间,发现,1.c 比 1.o 要新,此时,便自动编译 1.c,再把 1.o 2.o 3.o ... ... ... 一起链接成 目标文件 main
那么,make 怎么知道我要编译哪些文件呢?是只编译1.c 2.c?还是编译全部?或者其它的呢?
这个,就看 Makefile 了,make是个大厨,而 Makefile 就是菜谱,菜谱里写着,菜名,原材料,加工方法等。
2、Makefile的编写规则。
上面说到,make是大厨,Makefile是菜谱,我们就来看一看这菜谱吧。
菜名:原材料
<Tab>加工方法
main: main.o
gcc main.o -o main #gcc前面一定要 <Tab>,一定要,一定要,重要事情说三遍!
main.o: main.c
gcc -c main.c
testString:
echo "shuang!shuang!shuang!"
clean:
rm -rf *.o main
main是我们最终想得到的菜,菜谱如下:
main: main.o
gcc main.o -o main
main,就是菜名,
main.o 就是原材料,
gcc main.o -o main 就是加工方式。
试一下,开始做菜 make
效果同直接在命令行输入:
gcc -c main.c
gcc main.o -o main
我们的加工方式是 gcc main.o -o main,为什么还会有一条 gcc -c main.c呢?
因为我们的原材料,main.o,也是一道菜,是由 gcc -c main.c 加工而成,请看下一条。
上一道菜中的 main.o 这个原材料,它是另一道叫main.o的菜,菜谱如下:
main.o: main.c
gcc -c main.c
main.o,就是菜名,
main.c,就是原材料,
gcc -c main.c,就是加工方式
效果同直接在命令行输入:gcc -c main.c
testString 和 clean 这道菜呢,是试菜和吃光菜,不需要原材料:
testString:
echo "shuang!shuang!shuang!"
意思是试菜时,要喊 爽!爽!爽!
效果同直接在命令行输入:echo "shuang!shuang!shuang!"
clean:
rm -rf *.o main
清理 所有的.o文件 和 main文件
效果同直接在命令行输入:rm -rf *.o main
直接 make 呢?
效果同 make main
总结:
a、当我们执行 make xxx(菜名)时,make会自动执行 xxx(菜名)下的命令(加工方式);
b、当我们执行 make 时,make 会自动执行第一条 xxx:(菜谱上第一个菜名)所对应的命令(加工方式);
c、加工方式前面的<Tab>,必须是退格键,不能是空格或者其它;
3、Makefile变量的声明和赋值。
PS:Makefile里所有变量都是字符串
变量的声明,方法有:=、?=、:=、+=、define endef
变量的使用,方法有:$(var)
var="i am a rich man!"
var?="i am a rich man!"
var:="i am a rich man!"
var+="Good!"
define var
"i am a rich man!"
endef
=、?=、define:延时变量,在使用的时候才确定值
:=:立即变量,在定义的时候就确定值
+=:看前面的,前面的是延时就是延时,前面的是立即就是立即
var1=abc
var2=$(var1)def
var1=ghi
echo $(var2)
输出 ghidef
=、?=、define是延时变量,所以,在 echo $(var2) 时,才确定 var2的值是 $(var1)def,var1的值是 ghi
var1=abc
var2:=$(var1)def
var1=ghi
echo $(var2)
输出 abcdef
:= 是立即变量,所以,在var2:=$(var1)def 时,就已经确定了var2的值是 abcdef
?=只有在变量第一次赋值的时候有效,也就是说,如果前面变量没被赋值,就赋值,如果有赋值,则不执行赋值,跳过。
var1?=abc 或者 var1=abc 或者 var1:=abc
var1?=def
echo $(var1)
输出 abc
var1?=def
echo $(var1)
输出 def
+=字符串连接,在原有的字符串上加上后续内容,不过中间会有空格喔,呵呵哒
var1=abc
var1+=def
echo $(var1)
输出 abc def
4、Makefile常用的函数。
字符串替换和分析函数:
$(subst from,to,text)
在text中使用to替换from.
echo $(subst ee,EE,feeet on the street!) 输出 fEEet on the strEEt!
$(patsubst pattern,repace,text)
在text中寻找符合pattern格式的字,用repace代替它。
$(patsubst %.c,%.o,aa.c.c bb.c) 输出 aa.c.o bb.o
$(strip string) 去掉前导和结尾,并把中间多个空格压缩成一个
$(strip I am a rich man!! ) 输出 I am a rich man!!
$(findstring find,string) 在string中寻找find,有则返回find,无则返回空
$(findstring rich man,I am a rich man!!) 输出 rich man
$(filter pat...,text) 返回在text中“匹配用空格隔开的pat...”的字,去除不匹配的字。
$(filter %h %n,I am a rich-man) 输出 rich-man
$(filter-out pat...,text) 返回在text中“匹配用空格隔开的pat...”的字,去除不匹配的字。
$(filter-out %h %n,I am a rich-man) 输出 I am a
$(sort list...) 去除list...中用空格隔开的重复的单词,并按字母排序,先符号,后数字,再字母,大小写区分
$(sort 1. I am a rich man !man Man a rich) 输出 !man 1. I Man a am man rich
文件名函数:
$(dir dirs...) 抽取每一部分的目录部分,目录从文件名的首字符起到最后一个/结束的字符
$(dir /mnt/src.c hack/ddr/aab.a kko.c) 输出/mnt/ hack/ddr/ ./
/mnt/src.c 的目录是 /mnt/
hack/ddr/aab.a 的目录是 hack/ddr/
kko.c 的目录是 ./
$(notdir names...) 抽取文件名
$(notdir /mnt/src.c hack/ddr/aab.a kko.c) 输出src.c aab.a kko.c
$(suffix names...) 抽取文件名后缀
$(suffix /mnt/src.c hack/ddr/aab.a kko.d rich-man) 输出.c .a .d
$(basename names...) 抽取除后缀外的其他字符
$(basename /mnt/src.c hack/ddr/aab.a kko.d rich-man) 输出/mnt/src hack/ddr/aab kko rich-man
$(addsuffix suffix,name...) 加文件名后缀
$(addsuffix .c,1413 1314 rich-man) 输出1413.c 1314.c rich-man.c
$(addprefix prefix,names...) 加前缀,比如 src/, hack.c bb.c 输出 src/hack.c
$(addprefix rich-,man womem boy girl) 输出 rich-man rich-womem rich-boy rich-girl
$(wildcard *.c) 返回当前文件夹下.c的文件,输出 1.c 2.c main.c
其他函数:
$(foreach var,list,text) 将list和var扩展,再将每个list赋给var,text引用var再进行扩展。
dirs:=a b c d e
files:=$(foreach tmp,$(dirs),$(tmp).txt)
echo $(files)
输出:a.txt b.txt c.txt d.txt e.txt
dir第1次扩展=a, tmp=a
dir第2次扩展=b, tmp=b
…………
最后tmp.txt就是 a.txt b.txt c.txt d.txt e.txt
$(if condition,then,else)
先把condition展开,如果非空,就执行then,如果空,就执行else。
file:=a b c d
$(if $(file),havefile,nothavefile) 返回havefile
#file:=
$(if $(file),havefile,nothavefile) 返回nothavefile
5、其它:
$@:菜名
$^:所有原材料,用空格隔开
$<:第一个原材料
main: main.c 1.c 2.c 3.c
gcc -o main main.c 1.c 2.c 3.c
可以写成:
gcc -o $@ $^
最后,我们在上面例子的 Makefile 文件上,增加 1.c 2.c 3.c,再运用变量,函数,把它改成一个比较通用的Makefile。
src:=$(shell ls *.c) #列出所有.c 文件
ofile:=$(patsubst %c,%o,$(src)) #在列出的.c文件中寻找符合%c格式的字,用repace代替它。
#经过替代,$(ofile)=main.o 1.o 2.o 3.o
main: $(ofile) #菜名main,原材料 main.o 1.o 2.o 3.o
gcc $^ -o $@ # $^,所有原材料,$@,菜名。加工方式:把所有原材料 gcc 成菜名main
#等同于 gcc main.o 1.o 2.o 3.o -o main
%.o: %.c #菜名,%.o的所有菜,原材料,%.o对应所需要的原材料,要分别加工喔。
gcc -c -g -o $@ $< #等同于 gcc -c -g -o 1.o 1.c / gcc -c -g -o 2.o 2.c / gcc -c -g -o 3.o 3.c
#gcc -c -g -o main.o main.c 分别加工成 1.o 2.o 3.o main.o 几道菜。
testString:
echo "shuang!shuang!shuang!"
clean:
rm -rf *.o main
分别试一下。
PS: 命令前面加 @,表示静默编译,也就是不打印命令。
把这句改为:
testString:
@echo "shuang!shuang!shuang!"
再看下,看出区别了么?没有打印 echo "shuang!shuang!shuang"这名了,只打印了 shuang!shuang!shuang!
基本上,这个让人又爱又恨的小妖精 Makefile,也就这样了。