前言:
在 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放入程序的顶层目录
在各自子目录创建一个空白的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"来彻底清除
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
情景演绎