前言:
在 Linux 中使用 make 命令来编译程序,特别是大程序;而 make 命令所执 行的动作依赖于 Makefile 文件。最简单的 Makefile 文件: hello: hello.c 、gcc -o hello hello.c 、clean:、 rm -f hello 将上述 4 行存为 Makefile 文件,放入 01_hello 目录下,然后直接执行 make 命令即可编译程序,执行 “make clean”即可清除编译出来的结果。 make 命令根据文件更新的时间戳来决定哪些文件需要重新编译,这使得可以避免编译已经编译过的、没有变化的程序,可以大大提高编译效率。目前我们只需要会使用Makefile即可,等后面学到驱动更底层的知识时候再详细认识Makefile。
一、Makefile 规则与示例
规则
建立两个文件
(1)a.c
#include <stdio.h> int main() { func_b(); return 0; }
(2)b.c
#include <stdio.h> void func_b() { printf("This is B\n"); }
(3)Makefile
test:a.o b.o gcc -o test a.o b.o a.o : a.c gcc -c -o a.o a.c b.o : b.c gcc -c -o b.o b.c
book@100ask:~/source/04_2018_Makefile/001_test_app$ gcc -o test a.c b.c
这样编译会全部编译步骤进行一下,如果每次这样编译会大大降低效率,应该改哪步进行编译哪步,这样才能更好的提高编译效率,所以我们就要学习makefile的规则,一个简单的 Makefile 文件包含一系列的“规则”,其样式如下:
目标(target)… : 依赖(prerequiries)…
命令(command)
如果“依赖文件”比“目标文件”更加新,那么执行“命令”来重新生成“目 标文件”。
命令被执行的 2 个条件:依赖文件比目标文件新,或是目标文件还没生成。
二、Makefile的语法
1.通配符: %.o
$@ 表示目标
$< 表示第1个依赖文件
$^ 表示所有依赖文件
test: a.o b.o c.o gcc -o test $^ %.o : %.c gcc -c -o $@ $<
2.假想目标:.PHONY
Makefile
test: a.o b.o c.o gcc -o test $^ %.o : %.c gcc -c -o $@ $< clean: rm *.o test .PHONY: clean
3.即时变量、延时变量,export
简单变量(即时放量):
A := xxx # A的值即刻确定,在定义时即确定
B = xxx # B的值使用到时才确定
:= #即时变量
= #延时变量
?= #延时变量,如果是第1次定义才起效,如果在前面该变量已定义则忽略这句
+= #附加,它是即时变量还是延时变量取决于前面的定义
Makefile
A := $(C) B = $(C) C = abc #D = 100ask D ?= weidongshan all: @echo A = $(A) @echo B = $(B) @echo D = $(D) C += 123
三、Makefile 的函数
$(foreach var,list,text)
简单地说,就是 for each var in list, change it to text。
对 list 中的每一个元素,取出来赋给 var,然后把 var 改为 text 所描述 的形式。
A = a b c B = $(foreach f, $(A), $(f).o) all: @echo B = $(B)
$(wildcard pattern)
pattern 所列出的文件是否存在,把存在的文件都列出来。
$(filter pattern. ..,text) #在text中取出符合patten格式的值
$(filter-out pattern.. . ,text) #在text中取出不符合patten格式的值
C = a b c d/ D = $(filter %/, $(C)) E = $(filter-out %/, $(C)) all: @echo D = $(D) @echo E = $(E)
$(wildcard pattern)
pattern定义了文件名的格式,wildcard取出其中存在的文件
files = $(wildcard *.c) #列出符合后缀是.c的文件都有哪些 all: @echo files = $(files)
files2 = a.c b.c c.c d.c e.c files3 = $(wildcard $(files2)) all: @echo files = $(files) @echo files3 = $(files3)
$(patsubst pattern,replacement,$(files2))
从列表中取出每——个值,如果符合pattern,则替换为replacement
files2 = a.c b.c c.c d.c e.c files3 = $(wildcard $(files2)) dep_files = $(patsubst %.c,%.d,$(files2)) #将files2中.c后缀文件替换为.d后缀文件 all: @echo dep_files = $(dep_files)
所有Makefile 函数代码
A = a b c B = $(foreach f, $(A), $(f).o) C = a b c d/ D = $(filter %/, $(C)) E = $(filter-out %/, $(C)) files = $(wildcard *.c) files2 = a.c b.c c.c d.c e.c abc files3 = $(wildcard $(files2)) dep_files = $(patsubst %.c,%.d,$(files2)) all: @echo B = $(B) @echo D = $(D) @echo E = $(E) @echo files = $(files) @echo files3 = $(files3) @echo dep_files = $(dep_files)
四、Makefile实例
1.支持头文件依赖
a.c
#include <stdio.h> void func_b(); void func_c(); int main() { func_b(); func_c(); return 0; }
b.c
#include <stdio.h> void func_b() { printf("This is B\n"); }
c.c
#include <stdio.h> #include "c.h" void func_c() { printf("This is C = %d\n", C); }
c.h
#define C 4
gcc -M c.c //打印出依赖
gcc -M -MF c.d c.c //把依赖写入文件c.d
gcc -c -o c.o c.c -MD -MF c.d //编译c.o,把依赖写入文件c.d
Makefile
objs = a.o b.o c.o dep_files := $(patsubst %,.%.d, $(objs)) dep_files := $(wildcard $(dep_files)) test: $(objs) gcc -o test $^ ifneq ($(dep_files),) #将依赖文件添加进去 include $(dep_files) endif %.o : %.c gcc -c -o $@ $< -MD -MF .$@.d #自动生成依赖文件 clean: rm *.o test distclean: rm $(dep_files) .PHONY: clean
2.添加CFLAGS
c.c
#include <stdio.h> #include <c.h> void func_c() { printf("This is C = %d\n", C); }
CFLAGS = -Werror -I.include #-Werror将所有的警告变为错误
#-I.include执行当前目录下的include文件
%.o : %.c
gcc $(CFLAGS) -c -o $@ $< -MD -MF .$@.d #自动生成依赖文件
Makefile
objs = a.o b.o c.o dep_files := $(patsubst %,.%.d, $(objs)) dep_files := $(wildcard $(dep_files)) CFLAGS = -Werror -I.include #-Werror将所有的警告变为错误 #-I.include执行当前目录下的include文件 test: $(objs) gcc -o test $^ ifneq ($(dep_files),) #将依赖文件添加进去 include $(dep_files) endif %.o : %.c gcc $(CFLAGS) -c -o $@ $< -MD -MF .$@.d #自动生成依赖文件 clean: rm *.o test distclean: rm $(dep_files) .PHONY: clean
五、Makefile的使用
参考 Linux 内核的 Makefile 编写了一个通用的 Makefile,它可以用来 编译应用程序:
1.支持多个目录、多层目录、多个文件;
2.支持给所有文件设置编译选项;
3.支持给某个目录设置编译选项;
4.支持给某个文件单独设置编译选项;
5.简单、好用。
说明:
本程序的Makefile分为3类:
1. 顶层目录的Makefile
2. 顶层目录的Makefile.build
3. 各级子目录的Makefile
一、各级子目录的Makefile:
它最简单,形式如下:
EXTRA_CFLAGS :=
CFLAGS_file.o :=
obj-y += file.o
obj-y += subdir/
"obj-y += file.o" 表示把当前目录下的file.c编进程序里,
"obj-y += subdir/" 表示要进入subdir这个子目录下去寻找文件来编进程序里,是哪些文件由subdir目录下的Makefile决定。
"EXTRA_CFLAGS", 它给当前目录下的所有文件(不含其下的子目录)设置额外的编译选项, 可以不设置
"CFLAGS_xxx.o", 它给当前目录下的xxx.c设置它自己的编译选项, 可以不设置
注意:
1. "subdir/"中的斜杠"/"不可省略
2. 顶层Makefile中的CFLAGS在编译任意一个.c文件时都会使用
3. CFLAGS EXTRA_CFLAGS CFLAGS_xxx.o 三者组成xxx.c的编译选项
二、顶层目录的Makefile:
它除了定义obj-y来指定根目录下要编进程序去的文件、子目录外,
主要是定义工具链前缀CROSS_COMPILE,
定义编译参数CFLAGS,
定义链接参数LDFLAGS,
这些参数就是文件中用export导出的各变量。
三、顶层目录的Makefile.build:
这是最复杂的部分,它的功能就是把某个目录及它的所有子目录中、需要编进程序去的文件都编译出来,打包为built-in.o
四、怎么使用这套Makefile:
1.把顶层Makefile, Makefile.build放入程序的顶层目录
在各自子目录创建一个空白的Makefile
2.确定编译哪些源文件
修改顶层目录和各自子目录Makefile的obj-y :
obj-y += xxx.o
obj-y += yyy/这表示要编译当前目录下的xxx.c, 要编译当前目录下的yyy子目录
3. 确定编译选项、链接选项
修改顶层目录Makefile的CFLAGS,这是编译所有.c文件时都要用的编译选项;
修改顶层目录Makefile的LDFLAGS,这是链接最后的应用程序时的链接选项;
修改各自子目录下的Makefile:
"EXTRA_CFLAGS", 它给当前目录下的所有文件(不含其下的子目录)设置额外的编译选项, 可以不设置
"CFLAGS_xxx.o", 它给当前目录下的xxx.c设置它自己的编译选项, 可以不设置
4. 使用哪个编译器?
修改顶层目录Makefile的CROSS_COMPILE, 用来指定工具链的前缀(比如arm-linux-)
5. 确定应用程序的名字:
修改顶层目录Makefile的TARGET, 这是用来指定编译出来的程序的名字
6. 执行"make"来编译,执行"make clean"来清除,执行"make distclean"来彻底清除
1.顶层目录
Makefile
CROSS_COMPILE = AS = $(CROSS_COMPILE)as LD = $(CROSS_COMPILE)ld CC = $(CROSS_COMPILE)gcc CPP = $(CC) -E AR = $(CROSS_COMPILE)ar NM = $(CROSS_COMPILE)nm STRIP = $(CROSS_COMPILE)strip OBJCOPY = $(CROSS_COMPILE)objcopy OBJDUMP = $(CROSS_COMPILE)objdump export AS LD CC CPP AR NM export STRIP OBJCOPY OBJDUMP CFLAGS := -Wall -O2 -g CFLAGS += -I $(shell pwd)/include LDFLAGS := export CFLAGS LDFLAGS TOPDIR := $(shell pwd) export TOPDIR TARGET := test obj-y += main.o obj-y += sub.o obj-y += a/ all : start_recursive_build $(TARGET) @echo $(TARGET) has been built! start_recursive_build: make -C ./ -f $(TOPDIR)/Makefile.build $(TARGET) : start_recursive_build $(CC) -o $(TARGET) built-in.o $(LDFLAGS) clean: rm -f $(shell find -name "*.o") rm -f $(TARGET) distclean: rm -f $(shell find -name "*.o") rm -f $(shell find -name "*.d") rm -f $(TARGET)
Makefile.build
PHONY := __build __build: obj-y := subdir-y := EXTRA_CFLAGS := include Makefile # obj-y := a.o b.o c/ d/ # $(filter %/, $(obj-y)) : c/ d/ # __subdir-y : c d # subdir-y : c d __subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y))) subdir-y += $(__subdir-y) # c/built-in.o d/built-in.o subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o) # a.o b.o cur_objs := $(filter-out %/, $(obj-y)) dep_files := $(foreach f,$(cur_objs),.$(f).d) dep_files := $(wildcard $(dep_files)) ifneq ($(dep_files),) include $(dep_files) endif PHONY += $(subdir-y) __build : $(subdir-y) built-in.o $(subdir-y): make -C $@ -f $(TOPDIR)/Makefile.build built-in.o : $(subdir-y) $(cur_objs) $(LD) -r -o $@ $(cur_objs) $(subdir_objs) dep_file = .$@.d %.o : %.c $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $< .PHONY : $(PHONY)
2.子目录
EXTRA_CFLAGS := -D DEBUG CFLAGS_sub3.o := -D DEBUG_SUB3 obj-y += sub2.o obj-y += sub3.o
六、通用 Makefile 的解析
通用 Makefile 的设计思想
在 Makefile 文件中确定要编译的文件、目录,比如:
obj-y += main.o obj-y += a/
“Makefile”文件总是被“Makefile.build”包含的。
在 Makefile.build 中设置编译规则,有 3 条编译规则:
怎么编译子目录? 进入子目录编译:
$(subdir-y): make -C $@ -f $(TOPDIR)/Makefile.build
怎么编译当前目录中的文件?
%.o : %.c $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<
当前目录下的.o 和子目录下的 built-in.o 要打包起来:
built-in.o : $(cur_objs) $(subdir_objs) $(LD) -r -o $@ $^
顶层 Makefile 中把顶层目录的 built-in.o 链接成 APP:
$(TARGET) : built-in.o $(CC) $(LDFLAGS) -o $(TARGET) built-in.o
情景演绎