本文首先介绍makefile的语法,之后举一个makefile示例。
文章目录
一、make介绍
由代码生成可执行文件,叫做编译(compile);对编译顺序进行安排,叫做构建(build)。Make是最常用的构建工具,诞生于1977年,主要用于C语言的项目。详细参考《GNU Make手册》、跟我一起写makefile和make命令教程。
二、makefile语法
2.1 通配符
通配符(wildcard)用来指定一组符合条件的文件名。Makefile 的通配符与 Bash 一致,主要有星号(*)、问号(?)和 […] 。比如, *.o 表示所有后缀名为o的文件。
clean:
rm -f *.o
2.2 模式匹配
Make命令允许对文件名,进行类似正则运算的匹配,主要用到的匹配符是%。比如,假定当前目录下有 f1.c 和 f2.c 两个源码文件,需要将它们编译为对应的对象文件。
%.o: %.c
等同于
f1.o: f1.c
f2.o: f2.c
2.3 变量和赋值
2.3.1 变量
要调用变量,需将变量放在 $( ) 之中。
txt = Hello World
test:
@echo $(txt)
2.3.2 赋值
VARIABLE = value #在执行时扩展,允许递归扩展。
VARIABLE := value #在定义时扩展。
VARIABLE ?= value #只有在该变量为空时才设置值。
VARIABLE += value #将值追加到变量的尾端。
2.4 自动变量
$@:指代当前目标。
- $<:指代第一个前置条件。
- $?:指代比目标更新的所有前置条件。
- $^:指代所有前置条件。
- $*:指代匹配符 % 匹配的部分。
- $(@D) 和 ( @ F ) : (@F): (@F):(@D) 和 $(@F) 分别指向 @ 的 目 录 名 和 文 件 名 。 比 如 , @ 的目录名和文件名。比如, @的目录名和文件名。比如,@是 src/input.c,那么 ( @ D ) 的 值 为 s r c , (@D) 的值为 src , (@D)的值为src,(@F) 的值为 input.c。
- $(<D) 和 ( < F ) : (<F): (<F):(<D) 和 $(<F) 分别指向 $< 的目录名和文件名。
2.5 函数
2.5.1 patsubst函数
$(patsubst <pattern>,<replacement>,<text>)
- 名称:模式字符串替换函数。
- 功能:查找
中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式 ,如果匹配的话,则以 替换。这里, 可以包括通配符 % ,表示任意长度的字串。如果 中也包含 % ,那么, 中的这个 % 将是 中的那个 % 所代表的字串。(可以用 \ 来转义,以 % 来表示真实含义的 % 字符) - 返回:函数返回被替换过后的字符串。
- 示例:
$(patsubst %.c,%.o,x.c.c bar.c)
把字串 x.c.c bar.c 符合模式 %.c 的单词替换成 %.o ,返回结果是 x.c.o bar.o
2.5.2 foreach函数
names := a b c d
$(foreach <var>,<list>,<text>)
上面的例子中, $(name) 中的单词会被挨个取出,并存到变量 n 中, $(n).o 每次根据 $(n) 计算出一个值,这些值以空格分隔,最后作为foreach函数的返回,所以, $(files) 的值是 a.o b.o c.o d.o 。
注意,foreach中的 参数是一个临时的局部变量,foreach函数执行完后,参数 的变量将不在作用,其作用域只在foreach函数当中。
2.5.3 notdir函数
$(notdir <names...>)
- 名称:取文件函数——notdir。
- 功能:从文件名序列 中取出非目录部分。非目录部分是指最後一个反斜杠( / )之后的部分。
- 返回:返回文件名序列 的非目录部分。
- 示例: $(notdir src/foo.c hacks) 返回值是 foo.c hacks 。
三、示例
根据上面的内容理解以下的makefile文件:
- 定义了编译器变量CC、LD、OBJCOPY、OBJDUMP ,这有利于跨平台。
- 上面定义了很多变量,方便后面的修改,类似于写C语言程序的宏。
- 变量定义之后的就是编译规则了:有4个目标:从上往下为.bin,.S文件编译出的.o,.c文件编译出的.o,最后一个伪目标clean。
CROSS_COMPILE ?= arm-linux-gnueabihf-
TARGET ?= ap3216c
CC := $(CROSS_COMPILE)gcc
LD := $(CROSS_COMPILE)ld
OBJCOPY := $(CROSS_COMPILE)objcopy
OBJDUMP := $(CROSS_COMPILE)objdump
LIBPATH := -lgcc -L /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/lib/gcc/arm-linux-gnueabihf/4.9.4
INCDIRS := imx6ul \
stdio/include \
bsp/clk \
bsp/led \
bsp/delay \
bsp/beep \
bsp/gpio \
bsp/key \
bsp/exit \
bsp/int \
bsp/epittimer \
bsp/keyfilter \
bsp/uart \
bsp/lcd \
bsp/rtc \
bsp/i2c \
bsp/ap3216c
SRCDIRS := project \
stdio/lib \
bsp/clk \
bsp/led \
bsp/delay \
bsp/beep \
bsp/gpio \
bsp/key \
bsp/exit \
bsp/int \
bsp/epittimer \
bsp/keyfilter \
bsp/uart \
bsp/lcd \
bsp/rtc \
bsp/i2c \
bsp/ap3216c
INCLUDE := $(patsubst %, -I %, $(INCDIRS))
SFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))
CFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))
SFILENDIR := $(notdir $(SFILES))
CFILENDIR := $(notdir $(CFILES))
SOBJS := $(patsubst %, obj/%, $(SFILENDIR:.S=.o))
COBJS := $(patsubst %, obj/%, $(CFILENDIR:.c=.o))
OBJS := $(SOBJS) $(COBJS)
VPATH := $(SRCDIRS)
.PHONY: clean
$(TARGET).bin : $(OBJS)
$(LD) -Timx6ul.lds -o $(TARGET).elf $^ $(LIBPATH)
$(OBJCOPY) -O binary -S $(TARGET).elf $@
$(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis
$(SOBJS) : obj/%.o : %.S
$(CC) -Wall -nostdlib -fno-builtin -c -O2 $(INCLUDE) -o $@ $<
$(COBJS) : obj/%.o : %.c
$(CC) -Wall -Wa,-mimplicit-it=thumb -nostdlib -fno-builtin -c -O2 $(INCLUDE) -o $@ $<
clean:
rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)
四、难点
4.1 对$(CFILENDIR:.c=.o))的理解
把变量CFILENDIR中.c替换成.o。
我们可以替换变量中的共有的部分,其格式是 $(var:a=b) 或是 ${var:a=b} ,其意思是,把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字串。这里的“结尾”意思是“空格”或是“结束符”。
–举例:
foo := a.o b.o c.o
bar := $(foo:%.o=%.c)
这依赖于被替换字串中的有相同的模式,模式中必须包含一个 % 字符,这个例子同样让 $(bar) 变量的值为“a.c b.c c.c”。