makefile教程(一)

Makefile简介:自动化构建工具的力量

Makefile是一种用于自动化构建项目的工具。它定义了一组规则,指定了如何生成目标文件。通过编写Makefile,开发人员可以简化项目的构建过程,减少手动操作的错误,并提高开发效率。

Makefile的基本概念

Makefile由三部分组成:目标(target)、依赖(dependencies)和命令(commands)。

  • 目标:要生成的文件或执行的任务。
  • 依赖:目标文件所依赖的文件或任务。
  • 命令:生成目标文件或执行任务的命令。

以下是一个简单的Makefile示例:

1
2
3
4
5
6
7
8
9
10
all: program

program: main.o utils.o
gcc -o program main.o utils.o

main.o: main.c
gcc -c main.c

utils.o: utils.c
gcc -c utils.c

在这个例子中,all是目标,它依赖于programprogram依赖于main.outils.o。生成main.outils.o的命令分别是编译main.cutils.c

Makefile的工作流程

  1. 定义目标、依赖和命令。在Makefile中,目标通常放在文件的顶部,依赖和命令则放在目标下面。
  2. 运行make命令,指定要生成的目标。例如,运行make all将生成名为all的目标。如果不指定目标,则默认生成名为all的目标。
  3. Makefile检查目标是否已过期。如果目标不存在或依赖文件中的任何一个比目标更新,则目标被视为过期。
  4. 如果目标过期,Makefile会执行与目标相关的命令来生成目标。如果目标没有过期,则不会执行任何操作。
  5. 如果在执行命令时出现错误,Makefile将停止并显示错误消息。如果没有错误,则生成目标文件或执行任务。
  6. 完成后,可以运行其他目标或执行其他操作。

使用Makefile的优点

  1. 自动化:通过编写一次Makefile,可以自动化构建过程,减少手动操作。
  2. 可移植性:Makefile在不同的操作系统和环境中运行,使得构建过程更加一致。
  3. 灵活性:Makefile支持条件语句、变量和函数等高级功能,可以根据项目需求进行定制。

Makefile中的常用函数

Makefile支持自定义函数,这些函数可以用于处理字符串和文件名,以及执行其他任务。

自定义函数的定义格式为:

1
2
3
4
5
define functionName
command1
command2
...
endef

其中,functionName是函数的名称,command1command2等是函数内部的命令。

在函数内部,可以通过命令来操作变量、执行特定任务等。函数内部使用的变量在函数外部是不可见的,只有在函数内部才能访问到这些变量。

下面是一个简单的示例,展示了如何递归遍历目录下指定后缀的文件:

1
2
3
4
5
6
define rwildcard
$(foreach d,$(wildcard $(addsuffix *,$(1))),$(call rwildcard,$(d)/,$(2))$(filter $(subst *,%,$(2)),$(d)))
endef

result := $(call rwildcard,path,*.c)
echo $(result)

在这个例子中,我们定义了一个名为rwildcard的函数,该函数接受两个参数路径和文件后缀返回匹配的文件。然后我们使用$(call functionName,param1,param2,...)语法来调用函数,将参数传递给函数并获取返回值。在这个例子中,$(call rwildcard,path,*.c)将返回后缀为.c的所有文件路径,并存储在变量result中。最后,使用echo命令输出结果。

除了上述的示例外,Makefile还提供了许多预定义的函数来处理文件名、文件路径、环境变量等。这些函数可以与自定义函数一起使用,以便更灵活地处理文件和执行任务。

在Makefile中,函数是用于处理字符串和文件名的强大工具。下面是一些常用的Makefile函数:

1. 字符串函数

  • $(subst from,to,text): 将字符串中的"from"替换为"to"。
  • $(patsubst pattern,replacement,text): 将符合"pattern"的字符串替换为"replacement"。
  • $(strip string): 去掉字符串开头和结尾的空格。
  • $(findstring find,in): 在"in"中查找"find",返回"find"的起始位置。如果未找到,返回空字符串。
  • $(filter pattern...,text): 返回符合"pattern"的字符串列表。
  • $(filter-out pattern...,text): 返回不符合"pattern"的字符串列表。
  • $(sort list): 对字符串列表进行排序。
  • $(word n,text): 返回"text"中的第n个单词。
  • $(wordlist s,e,text): 返回"text"中从第s个到第e个单词的列表。
  • $(words text): 返回"text"中的单词数量。
  • $(firstword names...): 返回第一个非空、非空格字符的单词。
  • $(lastword names...): 返回最后一个非空、非空格字符的单词。

2. 文件名函数

  • $(dir names...): 返回文件名中的目录部分。
  • $(notdir names...): 返回文件名中的非目录部分。
  • $(suffix names...): 返回文件名中的后缀部分。
  • $(basename names...): 返回文件名中的基本名称部分(去掉后缀)。
  • $(addsuffix suffix,names...): 将后缀"suffix"添加到文件名"names"上。
  • $(addprefix prefix,names...): 将前缀"prefix"添加到文件名"names"上。
  • $(join list1,list2): 将两个列表中的对应项连接起来,并以空格分隔。
  • $(wildcard pattern): 返回符合"pattern"的文件名列表。
  • $(realpath names...): 返回文件名的绝对路径。
  • $(abspath names...): 返回文件名的绝对路径(如果文件存在)。
  • $(call variable,param,param,...): 使用参数扩展变量。

makefile通用模板

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
# /**************************************
# * @Author : Retuze
# * @Date : 2023-09-24 03:50:23
# * @LastEditors : Retuze
# * @LastEditTime : 2023-09-25 18:45:48
# * @Description :
# */*************************************

# Application name
BUILD_EXE = OpenGL.exe
# Output directory
BUILD_OUTPUT = build
# Source directory
SOURCES =
# Add a search directory for header files other than the $(SOURCES) directory
INCLUDES = -Idepends/include
# Add a search directory for libraries
LD_DIRS = -Ldepends
# Add required libraries
LD_LIBS = -lglfw3 -lopengl32 -lgdi32


# 预定义的一些函数

# brief : 打印编译消息
# eg : $(call print,compiling,file)
ifeq ($(OS),Windows_NT)
print=echo $(1) $(notdir $(2))...
else
print=printf "$(1) $(notdir $(2))...\n"
endif

# brief : 递归遍历指定后缀的文件
# eg : $(call rwildcard,path,suffix)
rwildcard = $(foreach d,$(wildcard $(addsuffix *,$(1))),$(call rwildcard,$(d)/,$(2))$(filter $(subst *,%,$(2)),$(d)))

# brief : 删除文件或者文件夹
# eg : $(RM) file
ifeq ($(OS),Windows_NT)
RM = del /Q
else
RM = rm -rf
endif

# brief : 是否打开调试输出信息(默认关闭)
ifeq ($(Debug),0)
at =
else
at = @
endif

# 编译命令
CPP = $(CROSS_COMPILE)g++
CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)ld
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
READELF = $(CROSS_COMPILE)readelf
STRIP = $(CROSS_COMPILE)strip

export CPP CC LD AR NM OBJCOPY OBJDUMP READELF STRIP

ifndef SOURCES
SOURCES := ./
endif

ifeq ($(strip $(BUILD_OUTPUT)), )
OBJ_FOLDER =
else
OBJ_FOLDER = $(strip $(BUILD_OUTPUT))/
endif

INCLUDES += $(sort $(foreach d,$(call rwildcard,$(SOURCES),*.h),$(dir -I$d)))
C_SOURCES = $(sort $(call rwildcard,$(SOURCES),*.c))
CPP_SOURCES = $(sort $(call rwildcard,$(SOURCES),*.cpp))
ASM_SOURCES = $(sort $(call rwildcard,$(SOURCES),*.s))
ASM_SOURCES += $(sort $(call rwildcard,$(SOURCES),*.S))

OBJECTS = $(addprefix $(OBJ_FOLDER),$(notdir $(patsubst %.c,%.o,$(C_SOURCES))))
OBJECTS += $(addprefix $(OBJ_FOLDER),$(notdir $(patsubst %.cpp,%.o,$(CPP_SOURCES))))
OBJECTS += $(addprefix $(OBJ_FOLDER),$(notdir $(patsubst %.s,%.o,$(ASM_SOURCES))))
OBJECTS += $(addprefix $(OBJ_FOLDER),$(notdir $(patsubst %.S,%.o,$(ASM_SOURCES))))

DEPENDS = $(OBJECTS:.o=.d)

# 宏定义
CC_SYMBOLS =
ASM_SYMBOLS =

# Flags
COMMON_FLAGS = -g -O2

C_FLAGS =
CPP_FLAGS =
ASM_FLAGS =

CFLAGS = $(strip $(COMMON_FLAGS) $(C_FLAGS) $(INCLUDES) $(CC_SYMBOLS) -c -MMD -MP)
CPPFLAGS = $(strip $(COMMON_FLAGS) $(CPP_FLAGS) $(INCLUDES) $(CC_SYMBOLS) -c -MMD -MP)
ASFLAGS = $(strip $(COMMON_FLAGS) $(ASM_FLAGS) $(INCLUDES) $(ASM_SYMBOLS) -c)

VPATH := $(SOURCES)
VPATH += $(sort $(dir $(C_SOURCES)))
VPATH += $(sort $(dir $(CPP_SOURCES)))
VPATH += $(sort $(dir $(ASM_SOURCES)))
VPATH += $(sort $(dir $(INCLUDES)))

PHONY := __all__
__all__: $(BUILD_EXE) check
$(at)$(call print,successful build,$<)

# Compile C sources.
$(OBJ_FOLDER)%.o : %.c
$(at)$(call print,compiling,$<)
$(at)$(CC) $(CFLAGS) $< -o $@

# Compile C++ sources.
$(OBJ_FOLDER)%.o : %.cpp
$(at)$(call print,compiling,$<)
$(at)$(CPP) $(CPPFLAGS) $< -o $@

# Compile ASM sources.
$(OBJ_FOLDER)%.o : %.s
$(at)$(call print,assebmling,$<)
$(at)$(CPP) $(ASMFLAGS) $< -o $@

# Compile ASM sources.
$(OBJ_FOLDER)%.o : %.S
$(at)$(call print,assebmling,$<)
$(at)$(CPP) $(ASMFLAGS) $< -o $@

$(BUILD_OUTPUT):
ifeq ($(OS),Windows_NT)
$(at)-mkdir $(BUILD_OUTPUT)
else
$(at)$(shell mkdir $(BUILD_OUTPUT) 2>/dev/null)
endif

# Build executable
$(BUILD_EXE) : $(BUILD_OUTPUT) $(OBJECTS)
$(at)$(call print,linking,$@)
$(at)$(CPP) $(LD_DIRS) -o $(OBJ_FOLDER)$@ $(OBJECTS) $(LD_LIBS) $(LD_OPTIONS)

-include $(DEPENDS)

PHONY += clean
clean:
ifdef BUILD_OUTPUT
$(at)-$(RM) $(subst /,\\,$(OBJ_FOLDER))
else
$(at)-$(RM) $(subst /,\\,$(OBJECTS)) $(subst /,\\,$(DEPENDS)) $(subst /,\\,$(BUILD_EXE))
endif

PHONY += show
show:
@echo "PHONY=$(PHONY)"
@echo "BUILD_OUTPUT = $(BUILD_OUTPUT)"
@echo "OBJ_FOLDER = $(OBJ_FOLDER)"
@echo "SOURCES = $(SOURCES)"
@echo "C_SOURCES = $(C_SOURCES)"
@echo "CPP_SOURCES = $(CPP_SOURCES)"
@echo "ASM_SOURCES = $(ASM_SOURCES)"
@echo "OBJECTS = $(OBJECTS)"
@echo "DEPENDS = $(DEPENDS)"
@echo "INCLUDES = $(INCLUDES)"
@echo "CFLAGS = $(CFLAGS)"
@echo "CPPFLAGS = $(CPPFLAGS)"
@echo "ASMFLAGS = $(ASMFLAGS)"
@echo "LD_DIRS = $(LD_DIRS)"
@echo "LD_LIBS = $(LD_LIBS)"
@echo "LD_OPTIONS = $(LD_OPTIONS)"

PHONY += check
check:
$(if $(subst /,\\,$(filter-out $(OBJECTS) $(DEPENDS),$(sort $(call rwildcard,$(BUILD_OUTPUT),*.o)) $(sort $(call rwildcard,$(BUILD_OUTPUT),*.d)))),$(at)-$(RM) $(subst /,\\,$(filter-out $(OBJECTS) $(DEPENDS),$(sort $(call rwildcard,$(BUILD_OUTPUT),*.o)) $(sort $(call rwildcard,$(BUILD_OUTPUT),*.d)))))

.PHONY: $(PHONY)

makefile教程(一)
https://retuze.github.io/sun/9ae7dfcb.html
作者
Retuze
发布于
2023年9月26日
许可协议