目录
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。
那么,make 怎么知道我要编译哪些文件呢?是只编译1.c 2.c?还是编译全部?或者其它的呢?
这个,就看 Makefile 了,make是个大厨,而 Makefile 就是菜谱,菜谱里写着,菜名,原材料,加工方法等。
2、Makefile 是什么样
假如我们有三个源文件。
hellomake.c | hellofunc.c | hellomake.h |
---|---|---|
“#include <hellomake.h> int main() { // call a function in another file myPrintHelloMake(); return(0); }” | “#include <stdio.h> #include <hellomake.h> void myPrintHelloMake(void) { printf(”“Hello makefiles!\n”"); return; }" | “/* example include file */ void myPrintHelloMake(void);” |
我们用命令行编译的话是这样
gcc -o hellomake hellomake.c hellofunc.c
如果写一个简单的makefile的话,是这样,文件名称保存为Makefile。
hellomake: hellomake.c hellofunc.c
gcc -o hellomake hellomake.c hellofunc.c
基本格式要求:
target ... : prerequisites ...
command
....
....
target :
目标文件, 可以是Object File 也可以是可执行文件,还可也是标签Label;
prerequisites:
生成target所需的文件或目标;
command:
make需要执行的命令,可以是任何shell命令。
在linux的shell环境下,直接输入make,则会在当前的目录下查找名为“Makefile”或者“makefile”的文件。如果找到,它会把文件中第一个target作为最终的目标文件。
我们知道,一个程序文件编写完成后, 接下来的编译的流程是(.c .S) – > (.o .a) --> (.bin),但是make工具分析Makefile的过程是相反的。为了生成(.bin) 需要依赖(*.o .a),为了生成(.o .a)间接的又需要(.c .S),可以说(.c *.S)是最初的依赖。
3、Makefile 变量的声明和赋值。
PS:Makefile里所有变量都是字符串
变量的声明,方法有:
=、?=、:=、+=、
define endef
变量的使用方法:$(var)
=、?=、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 的结构
首先来个例子,假如我们有一个可执行文件,名称为edit,它依赖于8个目标文件,这些目标文件又依赖于8个c文件和3个头文件。
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
//每一个目标文件的依赖关系
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
我们在shell命令行输入 make
即可执行这个makefile,
如果输入make clean
执行的同时会把可执行文件edit和所有的目标文件删除。
一个文件如果这么写,显然太复杂、太冗长了,我们可以用变量定义的方式进行简化。
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
$(objects) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h
//这里类似伪代码,指明clean并不是一个文件,而是一个执行语句的缩写
.PHONY : clean
clean :
rm edit $(objects)
5、Makefile 的通配符
为了书写上的便捷,makefile支持很多通配符语法,比如
CC=gcc
CFLAGS=-I.
DEPS = hellomake.h
%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
hellomake: hellomake.o hellofunc.o
$(CC) -o hellomake hellomake.o hellofunc.o
%.o: %.c $(DEPS) 这句定义了一个规则,意思是所有依赖于hellomake.h的.c
文件都编译生成.o
文件。
%在这里称为通配符。
我们还可以看到这种通配符
out.o: src.c src.h
$@
表示 “out.o” (target)
$<
表示 “src.c” (first prerequisite)
$^
表示 “src.c src.h” (all prerequisites)
例如:
CC=gcc
CFLAGS=-I.
DEPS = hellomake.h
%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
意思是把第一个.c 文件编译后的文件作为target文件。
-I
表示在当前路径下操作。
6、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