前言:对于编译单个C文件,我们可以通过gcc直接编译成可执行文件,但是实际的项目工程中往往是由多个C文件组成,这个时候要是再编译,就需要借助makefile这个工具了,本篇文章就介绍一下这个工具的比较详细的语法细节。部分参考一些大佬的文章,仅供学习使用。。。
目录
一,简介
1,概述
makefile可以简单的认为是一个工程文件的编译规则,描述了整个工程的自动编译和链接的规则。
2,Makefile规则
(1)显式规则
显式规则说明了,如何生成一个或多的的目标文件。这是由 Makefile 的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。
(2)隐晦规则
由于我们的 make 命名有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写 Makefile,这是由 make 命令所支持的。
(3) 变量的定义
在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点像C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
(4)文件指示
其包括了三个部分,一个是在一个 Makefile 中引用另一个 Makefile,就像C语言中的 include 一样;另一个是指根据某些情况指定 Makefile 中的有效部分,就像C语言中的预编译 #if 一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。
(5)注释
Makefile 中只有行注释,和 UNIX 的 Shell 脚本一样,其注释是用“#”字符,这个就像 C/C++ 中的“//”一样。如果你要在你的 Makefile 中使用“#”字符,可以用反斜框进行转义,如:“#”
3,makefile编译过程
不同厂家的make可能会稍有不同,并且语法上也有区别,不过基本思想都差不多,主要还是落在目标依赖上,最广泛使用的是GNUmake。
GNU Make 是一个用于 自动化构建项目 的工具,通常用于管理大型项目的 编译、链接 和 生成文件 的过程。它通过解析 Makefile 文件中的规则,自动化完成源代码到目标文件的编译过程。
在嵌入式开发、C/C++ 项目以及 Linux 系统中,GNU Make 是非常常用的构建工具
4,makefile基本格式
targets: prerequisites
command
//targets:规则的目标,可以是 Object File(一般称它为中间文件),也可以是可执行文件,还可以是一个标签;
//prerequisites:是我们的依赖文件,要生成 targets 需要的文件或者是目标。可以是多个,也可以是没有;
//command:make 需要执行的命令(任意的 shell 命令)。可以有多条命令,每一条命令占一行
5,makefile三要素
目标 ... : 依赖 ...
命令1
命令2
. . .
上面的三个要素之间的关系可以用一个比喻来理解:我的目标是要去某一个城市(生成一个目标文件),这个城市离我有很远的距离(需要依赖的文件),这个时候我就要依赖交通工具了,在去往目的地的过程需要很多动作,比如开车或者付费乘坐公共交通工具(这些相当于众多的命令)。
伪目标:为了避免与文件名重名,如下所示,我们常采用伪目标的形式将clean修饰一下,目的是即使有一个同名的文件名,他也会执行makefile中的clean命令
clean :
@rm -rf *.o
.PHONY :clean bebug #可以同时声明多个伪目标文件
二,常用规则介绍
1,递归扩展变量
内核中常用的Makefile例子:
//例如: fs/ext2/Makefile
obj-$(CONFIG_EXT2_FS) += ext2.o
ext2-y := balloc.o dir.o file.o ialloc.o inode.o ioctl.o namei.o super.o symlink.o
ext2-$(CONFIG_EXT2_FS_XATTR) += xattr.o xattr_user.o xattr_trusted.o
2,常见的自动化变量解析
举个例子:
# Makefile内容
%.o:%.c
gcc -o $@ $^
OBJ=$(wildcard *.c)
test:$(OBJ)
gcc -o $@ $^
# 当用通配符赋值给变量时需要用wildcard通配符函数,通过它可以得到我们所需的文件,这个函数类似我们在 Windows 或Linux命令行中的“*”
3,常用的编译器宏定义
举个例子:
# 这行表示将 -DDEBUG 编译标志添加到编译过程中。这是针对内核模块或内核代码的构建,通常用于启用调试功能
ccflags-y := -DDEBUG
# 类似于上一行,这行表示将 -DVERBOSE_DEBUG 编译标志添加到编译过程中
ccflags-y := -DVERBOSE_DEBUG
# 指定了一个 目标文件(object file)
opps-objs :=oops_test.o
# 这一行定义了模块的构建目标文件
obj-make := opps.o
# 这行添加了 -g 标志到 KBUILD_CFLAGS 中
KBUILD_CFLAGS +=-g
# 定了使用的编译器为GCC
CC = gcc
# 定义了 CCFLAGS 编译器标志
CCFLAGS = -D_DEBUG -g -m486
# 定义了一个规则,表示如何从源文件生成目标文件
test.o: test.c test.h
# 生成 test.o 的具体编译命令
$(CC) -c $(CCFLAGS) test.c
4,条件语法
4.1 基本的条件判断(ifeq / else
)
解释一下上面代码:
ifeq ($(CC), gcc)
:判断CC
变量是否等于gcc
,如果为真,则执行ifeq
下的部分。else
:如果条件不成立,执行else
部分。endif
:结束条件判断
ifeq ($(CC), gcc)
CFLAGS += -D_GCC
else
CFLAGS += -D_NON_GCC
endif
4.2 条件判断:ifeq
和 ifneq
ifeq
:用于判断两个字符串是否相等。如果相等,执行接下来的代码。ifneq
:与ifeq
相反,用于判断两个字符串是否不相等。如果不相等,执行接下来的代码。
ifeq ($(CC), gcc)
# 如果CC变量为gcc,执行这部分代码
CFLAGS += -D_GCC
endif
ifneq ($(CC), clang)
# 如果CC变量不为clang,执行这部分代码
CFLAGS += -D_NOT_CLANG
endif
4.3 条件判断:ifdef
和 ifndef
ifdef
:判断一个变量是否已经定义。ifndef
:判断一个变量是否未定义。下面代码解释:
ifdef DEBUG
:如果DEBUG
变量已经定义,则添加-DDEBUG
到CFLAGS
。ifndef RELEASE
:如果RELEASE
变量未定义,则添加-DNO_RELEASE
到CFLAGS
ifdef DEBUG
CFLAGS += -DDEBUG
else
CFLAGS += -DNO_DEBUG
endif
# 或者
ifndef RELEASE
CFLAGS += -DNO_RELEASE
endif
4.4 条件判断:if
与 $(shell)
解释:
$(shell uname)
会执行uname
命令,返回当前操作系统的名称,判断当前操作系统是 Linux 还是 macOS,并设置相应的编译选项
SYSTEM := $(shell uname)
ifeq ($(SYSTEM), Linux)
CFLAGS += -DLINUX
endif
ifeq ($(SYSTEM), Darwin)
CFLAGS += -DMACOS
endif
4.5 多重条件判断
Makefile
允许多个条件判断。通过嵌套ifeq
/ifneq
或使用多个if
来处理更复杂的情况解释:
- 首先判断
CC
是否为gcc
,如果为真,再判断ARCH
是否为x86
,如果是则添加-m32
编译选项
ifeq ($(CC), gcc)
ifeq ($(ARCH), x86)
CFLAGS += -m32
endif
endif
4.6 内联条件判断语法
Makefile
还支持使用内联条件判断语法$(if <condition>, <then-part>, <else-part>)
来实现条件判断。这对于简单的条件判断非常有用。解释:
- 如果
CC
变量已定义,则RESULT
被赋值为$(CC)
;如果未定义,则RESULT
被赋值为gcc
RESULT := $(if $(CC), $(CC), gcc)
4.7 逻辑与和逻辑或的条件判断
$(or <condition1>, <condition2>)
和$(and <condition1>, <condition2>)
Makefile
还提供了$(or ...)
和$(and ...)
函数,用于逻辑与和逻辑或的条件判断。解释:
$(or <condition1>, <condition2>)
:如果<condition1>
为真,返回<condition1>
;否则返回<condition2>
。$(and <condition1>, <condition2>)
:如果<condition1>
和<condition2>
都为真,则返回<condition2>
,否则返回空值。
RESULT := $(or $(CC), gcc) # 如果CC没有定义,则使用gcc
4.8 动态评估 Makefile
中的命令或表达式
$(eval <makefile command>)
$(eval ...)
是一个特殊的函数,可以用于动态评估Makefile
中的命令或表达式解释:
$(eval FOO = bar)
会在当前的Makefile
环境中动态定义或修改变量FOO
的值为bar
。
$(eval FOO = bar)
5,内置函数
Makefile
提供了丰富的内置函数,可以帮助你进行各种字符串操作、文件查找、模式匹配和条件判断等操作。理解这些函数的用法,可以让你编写更简洁、灵活的 Makefile
,提高构建系统的效率
5.1 $(call <function>, <args>)
call
是Makefile
中的函数调用语法,通常用于调用自定义的函数。它允许你将多个参数传递给一个函数。
# $(call MYFUNC, one two three four) 调用了自定义的 MYFUNC 函数,传入的参数是 one, two, three, four。
# MYFUNC 函数的定义将获取第一个单词和第 2 到第 3 个单词,所以结果将是 one two three
MYFUNC = $(firstword $(1)) $(wordlist 2,3, $(1))
MYRESULT = $(call MYFUNC, one two three four)
5.2 $(foreach <var>, <list>, <commands>)
foreach
用于遍历一个列表,并对列表中的每个元素执行指定的命令。
# $(foreach file, $(FILES), $(file).o) 将遍历 FILES 列表,并为每个元素生成相应的目标文件(即 .o 文件)。如果 FILES 包含 file1.txt file2.txt file3.txt,则 ALL_OBJECTS 会被设置为 file1.txt.o file2.txt.o file3.txt.o
FILES := file1.txt file2.txt file3.txt
ALL_OBJECTS := $(foreach file, $(FILES), $(file).o)
5.3 $(shell <command>)
shell
用于执行一个外部命令,并返回其输出结果。
# $(shell uname) 执行 uname 命令,并返回系统的名称(例如 Linux 或 Darwin)
SYSTEM := $(shell uname)
5.4 $(wildcard <pattern>)
wildcard
用于返回与给定模式匹配的文件列表。通常用于列出某一目录下的文件
# $(wildcard src/*.c) 返回 src 目录下所有 .c 文件的列表,并将其赋值给 SOURCES 变量。
SOURCES := $(wildcard src/*.c)
5.5 $(patsubst <pattern>, <replacement>, <text>)
patsubst
用于在文本中查找匹配的模式,并将其替换为指定的内容。
# $(patsubst %.c, %.o, $(SOURCES)) 将 SOURCES 中所有的 .c 文件名替换为 .o,返回一个新的文件名列表
OBJS := $(patsubst %.c, %.o, $(SOURCES))
5.6 $(strip <text>)
strip
用于删除文本中的多余空格
CLEANED := $(strip $(MYVAR))
# $(strip $(MYVAR)) 将删除 MYVAR 变量中的前导空格和尾部空格。
5.7 $(addprefix <prefix>, <names>)
addprefix
用于为每个给定的名字添加一个前缀
FILES_WITH_PATH := $(addprefix /home/user/, $(FILES))
# $(addprefix /home/user/, $(FILES)) 会将 /home/user/ 作为前缀添加到 FILES 列表中的每个文件前。假设 FILES 包含 file1.txt file2.txt,则 FILES_WITH_PATH 将会是 /home/user/file1.txt /home/user/file2.txt。
5.8 $(addsuffix <suffix>, <names>)
addsuffix
用于为每个给定的名字添加一个后缀
FILES_WITH_SUFFIX := $(addsuffix .c, $(NAMES))
# $(addsuffix .c, $(NAMES)) 会为 NAMES 列表中的每个元素添加 .c 后缀。
5.9 $(filter <pattern>, <text>)
filter
用于从给定文本中筛选出匹配的部分。
SOURCES := $(filter %.c, $(FILES))
# $(filter %.c, $(FILES)) 将从 FILES 中筛选出所有以 .c 结尾的文件,并返回这些文件
5.10 $(filter-out <pattern>, <text>)
filter-out
用于从给定文本中排除匹配的部分。
NON_C_FILES := $(filter-out %.c, $(FILES))
# $(filter-out %.c, $(FILES)) 会从 FILES 中排除所有以 .c 结尾的文件,返回剩下的文件。
5.11 $(sort <list>)
sort
用于对列表进行排序。
SORTED_FILES := $(sort $(FILES))
# $(sort $(FILES)) 会将 FILES 列表中的文件名按字母顺序排序。
5.12 $(join <list1>, <list2>)
join
用于连接两个列表
COMBINED := $(join $(list1), $(list2))
# $(join $(list1), $(list2)) 会将两个列表 list1 和 list2 连接成一个新的列表
5.13 $(word <n>, <text>)
word
用于从文本中提取第n
个单词
WORD := $(word 2, $(TEXT))
# $(word 2, $(TEXT)) 提取文本 TEXT 中的第二个单词。
5.14 $(words <text>)
words
用于返回文本中单词的数量。
WORD_COUNT := $(words $(TEXT))
# $(words $(TEXT)) 返回文本 TEXT 中单词的个数
5.15 $(realpath <file>)
realpath
用于返回文件的绝对路径
ABSOLUTE_PATH := $(realpath $(FILE))
# $(realpath $(FILE)) 会返回 FILE 的绝对路径。
5.16 $(basename <files>)
basename
用于返回文件名部分(去掉路径和扩展名)。
BASENAME := $(basename $(FILES))
# $(basename $(FILES)) 会返回 FILES 中每个文件名的基础部分(去掉路径和扩展名)
5.17 $(dir <files>)
dir
用于返回文件路径部分
DIRECTORY := $(dir $(FILES))
# $(dir $(FILES)) 会返回 FILES 中每个文件的路径部分
6,常见的特殊变量
6.1 VPATH
变量
-
用途:
VPATH
变量用于指定查找依赖文件(如源文件、头文件等)的路径列表。它允许Make
在指定的目录中搜索源文件或其他文件,而不必将所有文件都放在同一目录下。 -
默认行为:
VPATH
会影响Make
查找源文件、目标文件和其他依赖文件的位置。
语法:
VPATH = dir1 dir2 dir3
Make
会在dir1
、dir2
和dir3
中查找依赖文件。
# 在这个例子中,VPATH 被设置为 src 和 include 目录,这意味着 Make 会在这两个目录中查找 main.c 和 util.c 文件,而不需要将这些源文件直接放在当前目录下
VPATH = src include
OBJ = main.o util.o
all: $(OBJ)
main.o: main.c
gcc -c $< -o $@
util.o: util.c
gcc -c $< -o $@
6.2 .PHONY
变量
-
用途:
.PHONY
变量用于声明某些目标是伪目标,即它们不代表文件名,Make
会忽略与这些目标同名的文件。因此,.PHONY
的目标总是被认为需要执行其规则,不会受到文件存在与否的影响。 -
常见场景:
- 用于定义像
clean
、all
、install
这样的目标,这些目标不对应实际的文件,而只是用于执行一些操作。
- 用于定义像
.PHONY: target1 target2
.PHONY: all clean install
all: program
program: program.o main.o
gcc -o $@ $^
clean:
rm -f *.o program
# .PHONY: clean 表示 clean 是一个伪目标,不是一个实际的文件。即使当前目录下存在名为 clean 的文件,Make 也会执行 clean 规则。
6.3 include
变量
-
用途:
include
语句用于在Makefile
中引入其他的Makefile
文件。通过include
可以将多个Makefile
组合起来,便于模块化和复用。
语法:
include file1 file2
file1
和file2
是要被引入的文件,可以是相对路径或绝对路径。Make
会在指定的文件中查找并执行其中的规则、变量等。
include config.mk
all: program
program:
gcc -o program main.c
# include config.mk 会将 config.mk 文件中的内容包含到当前的 Makefile 中。通过这种方式,可以将配置或常用规则模块化到单独的文件中。
6.4 $(Q)
变量
-
用途:
$(Q)
是一个常用的Makefile
变量,用于控制命令的输出。当$(Q)
被定义时,它通常用于静默或格式化命令的输出,使得Makefile
执行时的输出更加清晰或简洁。 -
典型场景:通常用于开发时使输出更加简洁,或者在生成最终版本时禁用命令输出。
$(Q)
变量通常和一个条件一起使用,如果需要静默模式,$(Q)
可以为空或者设置为@
,否则可以定义为其他值来显示详细的命令。
Q = @
all:
$(Q)echo "Building the project..."
$(Q)gcc -o program main.c
# $(Q) 在此被设置为 @,意味着在执行 Makefile 中的命令时,Make 会将命令的输出抑制(即不打印 echo 和 gcc 的命令行),而仅显示结果或错误信息。如果将 Q 设置为空,则会打印所有命令。
# 另一种用法
Q =
all:
$(Q)gcc -o program main.c
# 这里 Q 被设置为空,因此 Make 会显示 gcc -o program main.c 命令及其输出。
7,特殊用法
7.1 指定头文件路径
一般都是通过"-I"(大写i)来指定,假设头文件在:/home/develop/include
# 将该目录添加到头文件搜索路径中
#在Makefile中则可以这样写:
CFLAGS=-I/home/develop/include
# 然后在编译的时候,引用CFLAGS即可,如下
yourapp:*.c
gcc $(CFLAGS) -o yourapp
7.2 指定库文件路径
与上面指定头文件类似只不过使用的是"-L"来指定
LDFLAGS=-L/usr/lib -L/path/to/your/lib
# 告诉链接器要链接哪些库文件,使用"-l"(小写L)如下:
LIBS = -lpthread -liconv
三,简单的Makefile实例
编译可执行程序\静态库\动态库
这个示例是看的其他博主举的例子,感觉不错就放上来了
VERSION =
#编译链如gcc或其他编译链
CC =arm-linux-gnueabihf-gcc
AR =ar
#rcs或rv
ARFLAGS =rv
DEBUG =-g
#CFLAGS =-Wall
#源文件
SOURCES =$(wildcard *.c)
#需要指定的头文件
INCLUDES =-I./include
#需要链接的库文件
LIB_NAMES = -lm -ldl -lpthread
#库文件路径
LIB_PATH =-L./
OBJ =$(patsubst %.c, %.o, $(SOURCES))
STATIC_LIB = libmylib.a
SHARED_LIB = libmylib.so
TARGET =test
# 可执行程序
$(TARGET):$(OBJ)
$(CC) $(OBJ) $(LIB_PATH) $(LIB_NAMES) -o $(TARGET)$(VERSION)
@rm -rf $(OBJ)
# 编译静态库
$(STATIC_LIB): mylib.o
$(CC) $(ARFLAGS) $(STATIC_LIB) $(OBJ)
@rm -rf $(OBJ)
# 编译动态库
$(SHARED_LIB): mylib.o
$(CC) -shared -fPIC -o $(SHARED_LIB) $(OBJ)
@rm -rf $(OBJ)
%.o: %.c
$(CC) $(DEBUG) -c $(CFLAGS) $< -o $@
.PHONY:clean
clean:
@echo "Remove linked and compiled files......"
rm -rf $(OBJ) $(TARGET)$(VERSION)
参考文章: