前言:
在 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。
建立两个文件
(1)a.c
- #include
-
- int main()
- {
- func_b();
- return 0;
- }
(2)b.c
- #include
-
- 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 个条件:依赖文件比目标文件新,或是目标文件还没生成。

$@ 表示目标
$< 表示第1个依赖文件
$^ 表示所有依赖文件
- test: a.o b.o c.o
- gcc -o test $^
-
- %.o : %.c
- gcc -c -o $@ $<
Makefile
- test: a.o b.o c.o
- gcc -o test $^
-
- %.o : %.c
- gcc -c -o $@ $<
-
- clean:
- rm *.o test
-
- .PHONY: clean
-

简单变量(即时放量):
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 的函数简单地说,就是 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)

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)

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)

从列表中取出每——个值,如果符合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)

- 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)

a.c
-
- #include
-
- void func_b();
- void func_c();
-
-
- int main()
- {
- func_b();
- func_c();
- return 0;
- }
b.c
-
- #include
-
- void func_b()
- {
- printf("This is B\n");
- }
c.c
-
- #include
- #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

c.c
-
- #include
- #include
-
- 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

参考 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放入程序的顶层目录
在各自子目录创建一个空白的Makefile2.确定编译哪些源文件
修改顶层目录和各自子目录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"来彻底清除

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)

-
- EXTRA_CFLAGS := -D DEBUG
- CFLAGS_sub3.o := -D DEBUG_SUB3
-
- obj-y += sub2.o
- obj-y += sub3.o

在 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
情景演绎
