Makefle实质是一系列命令的集合,使用Makefile是为了提高效率!
Linux C肯定离不开Makefile,小工程也好大工程也罢,哪里都离不开Makefile;
即使拥有强大IDE也不能像在Windows下编程一样完全不理会run按钮背后的动作!
1 一个简单的Makefile
从源代码到可执行程序,一般要经过预处理,编译,汇编和链接。
如有如下工程,msg.c中定义了一个打印函数,msg.h 中申明了这个打印函数,main.c中使用了这个打印函数。
$ ls main.c msg.c msg.h $ gcc -Wall -c -o main.o main.c $ gcc -Wall -c -o msg.o msg.c$ gcc -Wall -o main.out main.o msg.o $ ls main.out main.c main.o msg.c msg.h msg.o$ ./main.outhello world! |
gcc选项
-Wall 显示所有警告信息
-E 预处理
-S 编译,生成汇编文件
-c 汇编,生成二进制文件
-o 生成指定的文件
用Makefile自动编译工程
Makefile
main.out: main.o msg.o gcc -Wall -o main.out main.o msg.o main.o: main.c msg.h gcc -Wall -c -o main.o main.c msg.o: msg.c msg.h gcc -Wall -c -o msg.o msg.c |
$ make gcc -Wall -c -o main.o main.c gcc -Wall -c -o msg.o msg.c gcc -Wall -o main.out main.o msg.o $ ls main.c main.o main.out Makefile msg.c msg.h msg.o $ ./main.out hello world! |
编译工程由多条编译命令现在只要一个make就能替代了,方便了一些,工程越大,Makefile的优势越加明显!
下面研究下这个Makefile文件
main.out: main.o msg.o gcc -Wall -o main.out main.o msg.o main.o: main.c msg.h gcc -Wall -c -o main.o main.c msg.o: msg.c msg.h gcc -Wall -c -o msg.o msg.c |
此文件可分成三个部分,每个部分由两行组成,可称之为一组规则:
第一行又由两个部分组成,用冒号隔开,冒号前面的是目标,冒号后面的是依赖;
第二行是一条命令;
1.Makefile最基本的三大要素目标,依赖和命令
2.make命令在当前目录找Makefile,找到后,其第一个目标被定为默认目标
3.要生成目标文件,每个依赖文件必须存在
4.依赖条件文件不存在,则查找能生成此依赖文件的规则
5.目标文件不存在,依赖文件存在,则执行命令
6.依赖文件比目标文件的修改日期更新,则执行命令
7.只有目标,没有依赖且目标文件不存在,则执行命令(伪目标除外)
8.命令必须以Tab开头,不能用空格替代
2 Makefile自动处理头文件依赖
main.out: main.o msg.o gcc -Wall -o main.out main.o msg.o main.o: main.c msg.hmsg.o: msg.c msg.h |
$ make cc -c -o main.o main.c cc -c -o msg.o msg.c gcc -Wall -o main.out main.o msg.o $ ./main.out hello world! |
可以看到后面两条规则没有命令,但是make也能运行正常,这是隐含规则自动推导的,
对于上面后两条规则没有命令,make会定义一条模式规则,并执行
%.c: %.o $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@ |
上面规则中添加msg.h是有必要的,因为如果msg.h做了更新,如果此时依赖条件又没包含此头文件,则命令不会被执行,因而其他规则也不会被更新。
但这引发了一个问题,一个.c文件可能包含多个头文件,那么写Makefile时得把这些头文件依次加上,问题是谁会去记一个.c文件包含了哪些头文件呢!
可用gcc的-MM选项完成这个工作
$ gcc msg.c -MM > msg.depend $ cat msg.depend msg.o: msg.c msg.h |
其格式,刚好能代替Makefile中的规则
Makefile改成
all: make depend make main.out main.out: main.o msg.o gcc -Wall -o main.out main.o msg.o depend: gcc msg.c -MM > msg.depend gcc main.c -MM > main.depend -include msg.depend -include main.depend clean: -rm *.out *.o .PHONY: all depend clean |
1.all设成第一个规则,即默认规则
2.make depend,跳至该规则
3.执行depend规则后,生成main.depend和msg.depend文件,把这两个文件include到Makefile中,相当于加了两条规则
min.o: main.c msg.h
msg.o: msg.c msg.h
4.make main.out ,跳至该规则
5.all和depend并非要生成文件的目标,其作用只是为了能执行对应的命令,称之为伪目标
6.如果刚好有与伪目标同名的文件存在,则make认为该规则最新,无需再创建,命令无法执行,所以伪目标用“.PHONY”申明(注意前面有个点),即不管是否有这个文件存在,命令始终执行
7.clean也是伪目标,负责清理工程,make clean可调用
8.include和rm前面加了破折号“-”,表明忽略命令产生的错误,不加,命令产生错误make会中止运行
3 Makefile自动处理文件
到目前为止,该Makefile能较好运行了,可是如果工程较大,文件较多,可能会添加或删除一些文件,那么此时就要相应修改Makefile了,还是有些麻烦。
对Makefile做些改进
FILES = $(wildcard *.c) # 所有.c文件 OBJS = $(FILES:.c=.o) # 所有.c对应的.o文件 all: make depend make main.out main.out: $(OBJS) gcc -Wall -o $@ $^ depend: gcc $(FILES) -MM > MAKEFILE.DEPEND -include MAKEFILE.DEPEND clean: -rm *.out *.o .PHONY: all depend clean |
1.添加两个变量FILES和OBJS,有了这两个变量,工程中有文件要添加和删除就不必修改Makefile了
2.$(wildcard *.c)此函数查找当前目录下所有.c文件
3.$(FILES:.c=.o)将FILES变量中所有.c替换成.o,即所有中间文件
4.main.out规则中使用了变量,则命令中也应该使用变量,$@和$^ 是所谓自动化变量,分别代表目标和所有依赖
5.depend规则中对头文件依赖做了整合
函数
(1)字符串处理
$( subst from, to, text) | 字符串替换,在字符串text 中出现from 的子串都替换为to 子串 如 $(subst ee,EE,feet on the street) , 返回 fEEt on the strEEt |
$( patsubst pattern, replacement, text) | 字符串模式替换,与subst 一样是字符串替换, pattern 可使用通配符“%” ,表示匹配零个或若干个字符, text是用空格相隔的多个字符串组合如$(patsubst %.c,%.o,x.c.c bar.c),返回x.c.o bar.o |
$( var: pattern replacement
| 相当于 $(patsubst pattern replacement var |
$(var:suffix=replacement) | 相当于 $(patsubst%suffix,%replacement,$(var |
$( strip string) | 去掉字符串首尾空格 |
$( findstring find, in) | 在字符串in中查找find字串,找到返回find,否则返回空 |
$( filter pattern..., text) | 返回text中匹配patter的子串, pattern可一个或多个,可使用通配符“%”, text是用空格相隔的多个字符串组合 |
$( filter-out pattern..., text) | 与filter相反,返回不匹配的子串 |
$( sort list) | 排序,list 是用空格相隔的多个字符串组合 |
$( word n, text) | 返回text中的第n个字符串,n从1开始,text是用空格相隔的多个字符串组合 |
$( wordlist s, e, text) | 返回text中第s个到第e个之间的字符串,s从1开始,e从0开始,text是用空格相隔的多个字符串组合 |
$( words text) | 返回text中字符串个数,text是用空格相隔的多个字符串组合 |
$( firstword names...) | 返回names中第一个字符串,names是用空格相隔的多个字符串组合 |
$( lastword names...) | 返回names中最后一个字符串,names是用空格相隔的多个字符串组合 |
(2
)文件名处理
函数 | 说明 |
$( dir names...) | 提取路径如$(dir src/foo.c hacks),返回‘ src/ ./’ |
$( notdir names...) | 提取文件名如$(notdir src/foo.c hacks),返回‘ foo.c hacks’ |
$( suffix names...) | 提取文件后缀如$(suffix src/foo.c src-1.0/bar.c hacks),返回 .c .c |
$( basename names...) | 提取文件路径和文件名,去掉后缀如$(basename src/foo.c src-1.0/bar hacks),返回 src/foo src-1.0/bar hacks |
$( addsuffix suffix, names...) | 添加后缀如$(addsuffix .c,foo bar),返回 foo.c bar.c |
$( addprefix prefix, names...) | 添加前缀如$(addprefix src/,foo bar),返回 src/foo src/bar |
$( join list1, list2) | 连接,如果list1和list2是由空格相隔的多个字符串组合,list1中第一个字符串与list2中第一个字符串连接…,以此类推如$(join a b,.c .o),返回 a.c b.o |
$( wildcard pattern) | 返回匹配pattern的文件,pattern可使用通配符* ? [...]如$(wildcard *.o),返回所有.o文件 |
$( realpath names...) | 返回文件真实路径,文件不存在返回空 |
$( abspath names...) | 返回文件路径,不处理符号链接,文件不一定要存在 |
(3
)条件
函数 | 说明 |
$(if condition, then-part[, else-part]) | |
$(or condition1[, condition2[, condition3...]]) | |
$(and condition1[, condition2[, condition3...]]) |
(4
)
foreach
函数 | 说明 |
$(foreach var,list,text) | 先依次展开list,每次赋值给val,然后val代入text并返回如dirs = b c d,$(foreach dir,$(dirs),$(wildcard $(dir)/*)),返回 $(wildcard a/*) $(wildcard b/*) $(wildcard d/*) |
(5
)shell
函数 | 说明 |
$(shell cmd) | 返回shell命令的执行结果 |
变量
1.
递归变量
用“=”
赋值的变量不会马上展开,而是到最后再依次展开,即如果右值是一个变量,这个变量可在任何地方定义,而不一定在此变量之前定义;这样容易导致无穷递归,但是make
会检测到这种错误
2.
立即变量
用“:=”
赋值的变量会马上展开
3.
条件变量
用“?=”
赋值的变量会判断此变量是否已定义过,如果没定义过则赋值,否则不做任何 事
4.
追加变量
用”+=”
赋值的变量会在其后追加值
5.
自动化变量
$@
目标
$<
第一个依赖
$^
所有依赖
6.引用变量
如$(val), 括号是有必要的
4.Makefile自动处理文件夹
一个工程不在可能所有代码都在一个文件夹下,而是以模块为单位,一个模块单独一个文件夹,如
# 所有头文件路径HEAD_DIRS = $(foreach dir,$(shell ls),$(dir $(wildcard $(dir)/inc/*.h)))INCLUDE_DIRS = $(foreach dir,$(HEAD_DIRS),-I $(dir))# 所有.c文件路径SOURCE_DIRS = $(foreach dir,$(shell ls),$(dir $(wildcard $(dir)/src/*.c)))# 所有.c文件(包括路径)SOURCES = $(wildcard *.c)SOURCES += $(foreach dir,$(shell ls),$(wildcard $(dir)/src/*.c))# 生成.o文件的存放位置OBJ_DIR = obj# 所有.c对应的.o文件(不包括路径)OBJS = $(notdir $(SOURCES:.c=.o)) # 编译选项CC = gccCFLAGS = -Wall $(INCLUDE_DIRS)# 链接选项LD=$(CC)LDFLAGS = -pthread # 最终要生成的文件TARGET = main.out all: -mkdir $(OBJ_DIR) make depend make $(OBJ_DIR)/$(TARGET) # 3.链接$(OBJ_DIR)/$(TARGET): $(OBJS) $(LD) $(LDFLAGS) -o $@ $(OBJ_DIR)/*.o strip --strip-all $@ # 2.编译# 重建模式规则%.o: %.c $(CC) $(CFLAGS) -c -o $(OBJ_DIR)/$@ $< # 1.头文件依赖 depend: $(CC) $(SOURCES) $(CFLAGS) -MM > .DEPEND clean: -rm .DEPEND $(OBJ_DIR)/* # .c和.o文件不在当前目录,加上文件搜索路径vpath %.c $(SOURCE_DIRS)vpath %.o $(OBJ_DIR) -include .DEPEND .PHONY: all exe depend clean |
需要把生成的.o文件存放到一个目录下,所以重建模式规则‘%.o: %.c ’