Makefile使用简介

基本语法

缩进

<TAB>缩进符用于构建目标时需要执行的指令的前导符

<SPACE>空格符可以用于函数或判断排版,但不能用于构建目标的前导符

内置函数

wildcard

遍历符合条件的文件,支持通配符。查找NPC_HOME目录下所有cpp后缀的文件,并返回其列表

1
wildcard $(NPC_HOME)/**/*.cpp

error

错误打印,并退出构建

1
$(error NPC_HOME=$(NPC_HOME) is not a NPC repo)

patsubst

字符串处理函数,字符串替换。将目标字符串的替换,替换为目标格式的字符串。

基本格式:$(patsubst <模式>, <替换>, <文本>)

  • <模式>:指定需要匹配的字符串模式,可以包含通配符 %,表示零个或多个字符。
  • <替换>:指定用来替换匹配到的内容的模式,通配符 % 在替换中保留原始匹配内容。
  • <文本>:要进行匹配和替换的目标文本列表。

riscv32替换为'riscv64'

1
2
# CONFIG_ISA="riscv32"
$(patsubst "%32",'%64',$(CONFIG_ISA))

option

直接使用for option将会循环遍历所有参数,配合case完成解析。示例参考nginx项目nginx-1.22.1/auto/options如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
for option; do
opt="$opt `echo $option | sed -e \"s/\(--[^=]*=\)\(.* .*\)/\1'\2'/\"`"

case "$option" in
-*=*) value=`echo "$option" | sed -e 's/[-_a-zA-Z0-9]*=//'` ;;
*) value="" ;;
esac

case "$option" in
--help) help=yes ;;

--prefix=) NGX_PREFIX="!" ;;
--prefix=*) NGX_PREFIX="$value" ;;
--sbin-path=*) NGX_SBIN_PATH="$value" ;;
# ...
--test-build-devpoll) NGX_TEST_BUILD_DEVPOLL=YES ;;
--test-build-eventport) NGX_TEST_BUILD_EVENTPORT=YES ;;
--test-build-epoll) NGX_TEST_BUILD_EPOLL=YES ;;

*)
echo "$0: error: invalid option \"$option\""
exit 1
;;
esac
done

case

直接使用循环,配合$1shift逐一进行解析。

函数

声明自定义函数

  • 使用变量进行声明
  • 使用define关键字定义

调用自定义函数:使用 $(call 函数名, 参数1, 参数2, ...)

  • 自定义函数的参数用 $1, $2, $3, … 表示。
  • $(call ...) 是用于调用自定义函数的 Makefile 内置函数。

示例1:使用变量将内置函数定义为自定义函数

1
2
3
4
5
CONFIG_ISA="riscv32"
# 变量自定义函数
remove_quote = $(patsubst "%32",%64,$(1))
# 调用自定义函数
GUEST_ISA ?= $(call remove_quote,$(CONFIG_ISA))

示例2:使用define定义多参数处理

注意:函数定义中需要使用空格符缩进

1
2
3
4
5
6
7
8
9
define add_prefix_suffix
$(addprefix $1, $(addsuffix $2, $3))
endef

FILES = main utils lib
RESULT = $(call add_prefix_suffix, src/, .c, $(FILES))

all:
echo $(RESULT)

项目实例

嵌入式处理器r5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
ifneq ($(V),1)
Q := @
else
Q :=
endif

################################以下项目需用户根据需要更改##########################
TARGET := rtos

# 定义路径
TOP_DIR = .
BUILD_DIR := out
OBJ_DIR := $(TOP_DIR)/$(BUILD_DIR)/OBJ
OUT_DIR := $(TOP_DIR)/$(BUILD_DIR)
BUILD := ./out

#链接文件名称和所在路径
LDSCRIPT := ./cortex_r5.ld
LDSCRIPT += -Wl,-Map,$(BUILD)/$(TARGET).map

# libraries
LIBS +=
LIBDIR += $(TOP_DIR)/lib

#启动文件名称和所在路径
START_FILE_SOURCES += $(TOP_DIR)/boot/FreeRTOS_asm.S
START_FILE_SOURCES += $(TOP_DIR)/boot/boot.S
START_FILE_SOURCES += $(TOP_DIR)/os/freertos/portable/portASM.S

# 头文件路径
C_INCLUDES += -D SAFETY_GMAC
C_INCLUDES += -I $(TOP_DIR)/include
C_INCLUDES += -I $(TOP_DIR)/os/freertos/include
C_INCLUDES += -I $(TOP_DIR)/os/freertos/portable

# APP源文件路径
C_SOURCES :=
# C_SOURCES += $(subst ./,,$(shell find ./ -type f -iname "*.c"))
C_SOURCES += $(TOP_DIR)/app/entry/main.c
C_SOURCES += $(TOP_DIR)/app/base/gic_basic_api.c
C_SOURCES += $(TOP_DIR)/app/base/Uart_PBcfg.c
C_SOURCES += $(TOP_DIR)/app/base/isr_cfg.c
C_SOURCES += $(TOP_DIR)/app/base/Uart.c
C_SOURCES += $(TOP_DIR)/app/base/FreeRTOS_tick_config.c
C_SOURCES += $(TOP_DIR)/app/base/Os_Cfg.c
C_SOURCES += $(TOP_DIR)/app/base/libc_syscall.c
C_SOURCES += $(TOP_DIR)/app/base/Port_Port_Ci.c
C_SOURCES += $(TOP_DIR)/app/base/Port_Cfg.c
C_SOURCES += $(TOP_DIR)/app/base/Port.c
C_SOURCES += $(TOP_DIR)/app/base/Port_PBcfg.c
C_SOURCES += $(TOP_DIR)/app/base/Mcu.c
C_SOURCES += $(TOP_DIR)/app/base/utility_lite.c
C_SOURCES += $(TOP_DIR)/app/base/BswInit.c

C_SOURCES += $(TOP_DIR)/os/freertos/portable/portable_port.c
C_SOURCES += $(TOP_DIR)/os/freertos/portable/MemMang/heap_4.c
C_SOURCES += $(TOP_DIR)/os/freertos/src/queue.c
C_SOURCES += $(TOP_DIR)/os/freertos/src/stream_buffer.c
C_SOURCES += $(TOP_DIR)/os/freertos/src/timers.c
C_SOURCES += $(TOP_DIR)/os/freertos/src/croutine.c
C_SOURCES += $(TOP_DIR)/os/freertos/src/tasks.c
C_SOURCES += $(TOP_DIR)/os/freertos/src/event_groups.c
C_SOURCES += $(TOP_DIR)/os/freertos/src/list.c

# 定义编译标志
GCCFLAGS += -D__SINGLE_DISPLAY_DC__=2
GCCFLAGS += -D__DPTX_CONNECTOR__
GCCFLAGS += -mcpu=cortex-r5 -marm -mfloat-abi=hard -mfpu=vfpv3-d16 -O1 -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -g3
ASFLAGS = $(GCCFLAGS) -x assembler-with-cpp
CCFLAGS = $(GCCFLAGS) $(C_INCLUDES)
LDFLAGS = $(GCCFLAGS) -T$(LDSCRIPT) -L$(LIBDIR) $(LIBS) -nostartfiles -Xlinker --gc-sections --specs=nano.specs -u _printf_float --specs=nosys.specs

# 支持双系统编译,故需选当前系统,0为linux,1为windows
SYS := 1

# 若指定了windows系统,则需确认编译器的路径,若安装时以默认路径安装,则正确
ifeq ($(SYS), 1)
GCC_PATH = "C:/eclipse/Arm_Embedded_Toolchain/arm-none-eabi/bin"
JLINK_PATH = "C:/Program Files (x86)/SEGGER/JLink"
endif

###################################用户修改结束###################################
# 编译器定义
PREFIX = arm-none-eabi-
ifdef GCC_PATH
SUFFIX = .exe
GDB := arm-none-eabi-gdb
SZ := arm-none-eabi-size
CC := arm-none-eabi-gcc
AS := arm-none-eabi-as
LD := arm-none-eabi-ld
AR := arm-none-eabi-ar
OBJCOPY := arm-none-eabi-objcopy
else
CC := $(PREFIX)gcc
SZ := $(PREFIX)size
OBJCOPY := $(PREFIX)objcopy
GDB := $(PREFIX)gdb
endif
BIN := $(OBJCOPY) -O binary -S
HEX := $(OBJCOPY) -O ihex

# Jlink定义,用于支持一键下载和gdb仿真
ifdef JLINK_PATH
SUFFIX = .exe
JLINKEXE := $(JLINK_PATH)/JLink$(SUFFIX)
JLINKGDBSERVER := $(JLINK_PATH)/JLinkGDBServer$(SUFFIX)
else
JLINKEXE := JLinkExe
JLINKGDBSERVER := JLinkGDBServer
endif

#################### CFLAGS config Start ##########################
#搜索所有的h文件,并输出携带-I的.h文件路径
#C_INCLUDES := $(addprefix -I,$(subst ./,,$(sort $(dir $(shell find ./ -type f -iname "*.h")))))

#################### CFLAGS config End ##########################
#链接指令集-specs=nosys.specs
#是否开启优化掉未使用的函数和符号

#制作启动文件依赖Obj,输出去掉路径的.o文件,可兼容.s和.S
START_FILE_OBJ = $(addsuffix .o, $(basename $(notdir $(START_FILE_SOURCES))))
OBJECTS = $(addprefix $(BUILD)/obj/, $(START_FILE_OBJ))
#搜索所有的c文件,制作所有的.c文件依赖Obj
#C_SOURCES = $(subst ./,,$(shell find ./ -type f -iname "*.c"))

OBJECTS += $(addprefix $(BUILD)/obj/, $(notdir $(C_SOURCES:%.c=%.o)))
#PS:去掉终极目标的原始路径前缀并添加输出文件夹路径前缀(改变了依赖文件的路径前缀,需要重新指定搜索路径)
#指定makefile搜索文件的路径(假如终极目标的依赖文件不携带.c文件所在的路径,
#且不指定搜索路径,makefile会报错没有规则制定目标)
vpath %.c $(sort $(dir $(C_SOURCES))) #取出路径并去重和排序(以首字母为单位)
vpath %.s $(dir $(START_FILE_SOURCES))
vpath %.S $(dir $(START_FILE_SOURCES))

#指定为伪目标跳过隐含规则搜索,提升makefile的性能,并防止make时携带的参数与实际文件重名的问题
.PHONY:all clean printf JLinkGDBServer debug download reset commit
all : $(BUILD)/$(TARGET).elf $(BUILD)/$(TARGET).bin $(BUILD)/$(TARGET).hex

#编译启动文件 备用参数:#-x assembler-with-cpp
$(BUILD)/obj/%.o : %.S Makefile | $(BUILD)/obj
$(Q)echo "$(CC) $(ASFLAGS) -c $< -o $@"
$(Q)$(CC) $(ASFLAGS) -c $< -o $@

#编译工程
$(BUILD)/obj/%.o : %.c Makefile | $(BUILD)/obj
$(Q)echo "$(CC) -c $(CCFLAGS) $(BUILD)/obj/$(notdir $(<:.c=.lst)) -c $< -o $@"
$(Q)$(CC) -c $(CCFLAGS) -Wa,-a,-ad,-alms=$(BUILD)/obj/$(notdir $(<:.c=.lst)) -c $< -o $@

#链接所有的.o生成.elf文件
$(BUILD)/$(TARGET).elf : $(OBJECTS) Makefile | $(BUILD)
$(Q)echo "$(CC) $(OBJECTS) $(LDFLAGS) -o $@"
$(Q)$(CC) $(OBJECTS) $(LDFLAGS) -o $@
$(Q)echo "make $@: "
$(Q)$(SZ) $@

$(BUILD)/%.hex: $(BUILD)/%.elf | $(BUILD)
$(HEX) $< $@

$(BUILD)/%.bin: $(BUILD)/%.elf | $(BUILD)
$(BIN) $< $@
# arm-none-eabi-objdump -D -S $(BUILD)/${TARGET}.elf > $(BUILD)/${TARGET}.dump

$(BUILD)/obj:
$(Q)mkdir -p $@
$(Q)echo "mkdir $@"

#用于检查链接脚本和启动文件是否存在,不存在则报错误
$(START_FILE_SOURCES):
$(Q)echo ERROR: The startup file does not exist or has the wrong path !;\
exit 1
$(LDSCRIPT):
$(Q)echo ERROR: The link file does not exist or has the wrong path !;\
exit 2

clean:
$(RM) -rf $(BUILD)

-include $(wildcard $(BUILD)/obj/*.d)

支持NFS启动的qemu

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# QEMU		:= qemu-system-aarch64
QEMU := QEMU_AUDIO_DRV=none qemu-system-arm
Machine := -M vexpress-a9
# CPU := -cpu cortex-a9
CPU := -smp 1
Memory := -m 512M
Kernel := -kernel linux-6.1.73/arch/arm/boot/zImage
Dtb := -dtb linux-6.1.73/arch/arm/boot/dts/vexpress-v2p-ca9.dtb
SDCard := -sd images/sd_rootfs.ext4
# Network := -net nic -net user
Network := -nic user

# try to generate a unique GDB port
GDBPORT = $(shell expr `id -u` % 5000 + 25000)
# QEMU's gdb stub command line changed in 0.11
QEMUGDB = $(shell if $(QEMU) -help | grep -q '^-gdb'; \
then echo "-gdb tcp::$(GDBPORT)"; \
else echo "-s -p $(GDBPORT)"; fi)

all:
@echo "all done"

qemu-gdb:
@echo "*** Now run 'gdb' in another window." 1>&2
${QEMU} \
${Machine} \
${CPU} \
${Memory} \
${Kernel} \
${Dtb} \
${SDCard} \
${Network} \
-nographic \
-append "root=/dev/mmcblk0 rw console=ttyAMA0" -S -s

run:
@echo "host 10.0.2.2"
@echo "guest 10.0.2.15"
${QEMU} \
${Machine} \
${CPU} \
${Memory} \
${Kernel} \
${Dtb} \
${SDCard} \
${Network} \
-nographic \
-append "root=/dev/mmcblk0 rw console=ttyAMA0"

run-nfs:
@echo "host 10.0.2.2"
@echo "guest 10.0.2.15"
${QEMU} \
${Machine} \
${CPU} \
${Memory} \
${Kernel} \
${Dtb} \
${Network} \
-nographic \
-append "console=ttyAMA0,115200 noinitrd init=/linuxrc loglevel=8 nfsroot=10.0.2.2:/home/johnny/big-proj/os-dev/arm_rootfs/rootfs_v6.x,v3,nolock rw ip=10.0.2.15:10.0.2.2:10.0.2.1:255.255.255.0::eth0:off rootwait"

gui:
@echo "host 10.0.2.2"
@echo "guest 10.0.2.15"
${QEMU} \
${Machine} \
${CPU} \
${Memory} \
${Kernel} \
${Dtb} \
${SDCard} \
${Network} \
-display sdl -serial mon:stdio -append "root=/dev/mmcblk0 rw console=tty0"

# -net nic -net user
# -netdev user,id=network0 -device e1000,netdev=network0,mac=52:54:00:12:34:56
# qemu-system-arm -M vexpress-a9 \
-cpu cortex-a9 -m 256 -kernel ~/share/linux-4.4.157/object/arch/arm/boot/zImage -dtb ~/share/linux-4.4.157/object/arch/arm/boot/dts/vexpress-v2p-ca9.dtb -append "root=/dev/mmcblk0 rw console=tty0" -sd rootfs.ext3

附录

参考文章

跟我一起写Makefile