雪落的小屋

MakeFile基本语法学习

2022-12-23学习笔记Android编程

工作中经常和 Makefile 打交道,所以花了一些时间快速学习了一下它的常见用法以及使用场景。

基础语法

Makefile是由一系列的单一规则指令组合起来:

目标XX1:依赖文件
<TAB>命令1
<TAB>命令2
 
目标XX2:依赖文件
<TAB>命令1
<TAB>命令2
 
... ...
 
指令1:
    命令1
    命令2
指令2:
    命令1
    命令2
... ...
例子:
Tag:a.o b.o c.o
    gcc -o Tag a.o b.o c.o
a.o: a.c
    gcc -c a.c
b.o: b.c
    gcc -c b.c
c.o: c.c
 
clean:
    rm .o
    rm Tag

变量

= 赋值的变量,其值取决于最后一次赋值。

指令 printecho 前加上 @ 省略回显。

buff = xiaoming
name = $(xiaoming)
buff = lisa
print:
	echo name:$(name)

:= 赋值的变量,值保持不变;

?= 赋值的变量,如果之前已经有值则保持不变,如果没有则赋当前值;

+= 赋值的变量,在后面追加新的值;

模式规则

a.o : a.c
    gcc -c a.c
b.o : b.c
    gcc -c b.c
# 运行模式规则“%”:当目标中重现“%”时,目标中“%”所代表的值决定了依赖文件中的“%”的值
%.o : %.c
    gcc -c $<

伪目标

伪目标主要是为了避免 Makefile 中定义的执行指令和工作目录下的实际文件出现名字冲突。

举例说明:当前目录下如果有一个名为 clean 的文件,执行 make clean 指令,

因为没有依赖文件,所以后续的 rm 指令不会被执行。

解决方法为在 Makefile 中将指令声明为伪目标即可 .PHONY

.PHONY
 
clean:
    rm *.o

函数

1、函数subst:完成字符串替换;

$(subst <from>, <to>, <text>)
 
$(subst aaa, AAA, 3a transform 3A aaa)

将字符串 3a transform 3A aaa 中的 aaa 替换为 AAA 即:3a transform 3A AAA

2、函数 patsubst:完成模式字符串替换;

$(patsubst <pattern>, <replacement>, <text>)
 
$(patsubst %.c, %.o, a.c b.c c.c)

将字符串 a.c b.c c.c 替换为 a.o b.o c.o

如果 text = a.c b.c c.c

那么,$(text: .c = .o) 等同于 $(patsubst %.c, %.o, $(text))

3、函数 dir :获取路径当中的目录;

$(dir <name...>)
 
$(dir </src/a.c>)

提取文件/src/a.c的目录部分/src

4、函数notdir:提取文件名;

$(notdir <name...>)
 
$(notdir <src/a.c>)

提取文件 /src/a.c 的非目录部分 a.c

5、函数 foreach :循环;

6、函数 wildcardwildcard 用来明确表示通配符;

因为在 Makefile 里,变量实质上就是 C/C++ 中的宏,

也就是说,如果一个表达式如 objs = *.o ,则 objs 的值就是 *.o ,而不是表示所有的 .o 文件。

若果要使用通配符,那么就要使用 wildcard 来声明 * 这个符号,使 * 符号具有通配符的功能。

$(foreach <var>, <list>, <text>)
 
SRCDIRS := dira dirb dirc 
$(foreach dir, $(SRCDIRS), $(wildcard $(dir) / *.c))

循环将 SRCDIRS 中的各个目录放进 dir 变量中,调用 wildcard 函数提取 dir 目录下所有 .c 文件

自动化变量

自动化变量描述
$@规则中的目标集合,在模式规则中,如果有多个目标的话,$@表示匹配模式中定义的目标集合
$%当目标是函数库的时候表示规则中的目标成员名,如目标不是函数库文件,那么其值为空
$<依赖文件集合中的第一个文件,如果依赖文件是以模式%定义的,那么$<就是符合模式的一系列的文件的集合
$?所有比目标新的依赖目标的集合,以空格分开
$^所有依赖文件的集合,使用空格分割,如果在依赖文件中有多个重复的文件,%^会去除重复的依赖文件,值保留一份
$+$^类似,但是当依赖文件存在重复的话,不会去除重复的依赖文件
$*这个变量表示目标模式中%以及其之前的部分,如果目标是test/a.test.c目标模式为a.%.c,那么$*就是test/a.test

模板示例

1、原始模板

main.bin:a.o b.o c.o
	arm-linux-gnueabihf-ld  -Txxx.lds -o main.elf a.o b.o c.o
	arm-linux-gnueabihf-objcopy -o binary -s -g main.elf main.bin
	arm-linux-gnueabihf-objdump -D main.elf > main.dis
 
a.o : a.c
    arm-linux-gnueabihf-gcc -c a.c -o a.o
b.o : b.c
    arm-linux-gnueabihf-gcc -c b.c -o b.o
c.o : c.s
    arm-linux-gnueabihf-gcc -c c.s -o c.o
 
clean:
	rm -rf *.o main.bin main.elf main.dis

2、替换为自动变量和规则模式

objs := a.o b.o c.o
 
main.bin:$(objs)
	arm-linux-gnueabihf-ld  -Txxx.lds  -o main.elf $^               /*(1)*/
	arm-linux-gnueabihf-objcopy -o binary -s -g main.elf $@         /*(2)*/
	arm-linux-gnueabihf-objdump -D main.elf > main.dis              
%.o : %.c
    arm-linux-gnueabihf-gcc -c $< -o $@                             /*(3)*/
%.o : %.s
    arm-linux-gnueabihf-gcc -c $< -o $@ 
clean:
	rm -rf *.o main.bin main.elf main.dis

(1)$^:a.o b.o c.o

(2)$@:main.bin

(3)$<:%.c ; $@:%.o

3、替换为变量

CROSS_COMPILE ?= arm-linux-gnueabihf-
NAME          ?= main
 
CC            := $(CROSS_COMPILE)gcc
LD            := $(CROSS_COMPILE)ld
OBJCOPY       := $(CROSS_COMPILE)objcopy
OBJDUMP       := $(CROSS_COMPILE)objdump
 
OBJS := a.o b.o c.o
 
$(NAME).bin = $(OBJS)
    $(LD) -Txxx.lds -o $(NAME).elf $^
    $(OBJCOPY) -o binary -s -g $(NAME).elf $@
    $(OBJDUMP) -D $(NAME).elf > $(NAME).dis
 
%.o : %.c
    $(CC) -c $< -o $@
%.o : %.s
    $(CC) -c $< -o $@
 
clean:
    rm -rf *.o $(NAME).bin $(NAME).elf $(NAME).dis

4、多文件工程

CROSS_COMPILE ?= arm-linux-gnueabihf-
TARGET        ?= main
 
CC            := $(CROSS_COMPILE)gcc
LD            := $(CROSS_COMPILE)ld
OBJCOPY       := $(CROSS_COMPILE)objcopy
OBJDUMP       := $(CROSS_COMPILE)objdump
 
INCDIRS       := dira \
                 dirb \
                 dirc \
 
SRCDIRS       := dira dirb dirc   
 
INCLUDE       := $(patsubst %, -I %, $(INCDIRS))                              /*(1)*/
 
SFILES        := $(foreach dir, $(SRCDIRS), $(wildcard $(dir) / *.s))         
CFILES        := $(foreach dir, $(SRCDIRS), $(wildcard $(dir) / *.c))         /*(2)*/
 
SFILENDIR     := $(notdir $(SFILES))                                         
CFILENDIR     := $(notdir $(CFILES))                                          /*(3)*/
 
SOBJS         := $(patsubst %, obj/%, $(SFILENDIR:.s=.o))                     
COBJS         := $(patsubst %, obj/%, $(CFILENDIR:.c=.o))                     /*(4)*/
OBJS          := $(SOBJS) $(COBJS)                                            /*(5)*/
 
VPATH         := $(SRCDIRS)                                                   /*(6)*/
 
.PHONY: clean
 
$(TARGET).bin : $(OBJS)
    $(LD) -Txxx.lds -o $(TARGET).elf $^
    $(OBJCOPY) -o binary -s %(TARGET) $@
    $(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis
 
$(SOBJS) : obj/%.o : %.s
    $(CC) -Wall -nostdlib -c -o2 $(INCLUDE) -o $@ $<
$(COBJS) : obj/%.o : %.c
    $(CC) -Wall -nostdlib -c -o2 $(INCLUDE) -o $@ $<   
 
clean:
    rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)

(1):INCLUDE := -I dira -I dirb -I dirc

将字符串目录前加 -IMakefile 语法要求头文件目录需加 -I

(2):CFILES := dira/a.c dirb/b.c

SRCDIRS 各个目录下的 c 文件提取出来

(3):CFILENDIR := a.c b.c

提取 CFILES 中的文件名,省略路径

(4):COBJS := obj/a.o obj/b.o

将原目录下各个 c 文件 s 文件编译为 .o 文件,并将其放置 obj 目录下。

(5):OBJS = obj/a.o obj/b.o obj/c.o

整合 SOBJSCOBJS

(6):指定编译时查询目录