一文详解makefile语法

前言:对于编译单个C文件,我们可以通过gcc直接编译成可执行文件,但是实际的项目工程中往往是由多个C文件组成,这个时候要是再编译,就需要借助makefile这个工具了,本篇文章就介绍一下这个工具的比较详细的语法细节。部分参考一些大佬的文章,仅供学习使用。。。


目录

一,简介

1,概述

2,Makefile规则

 3,makefile编译过程

4,makefile基本格式

 5,makefile三要素

二,常用规则介绍

1,递归扩展变量

2,常见的自动化变量解析

3,常用的编译器宏定义

4,条件语法

4.1 基本的条件判断(ifeq / else)

 4.2 条件判断:ifeq 和 ifneq

 4.3 条件判断:ifdef 和 ifndef

4.4 条件判断:if 与 $(shell)

4.5 多重条件判断

4.6 内联条件判断语法 

4.7 逻辑与和逻辑或的条件判断

$(or , ) 和 $(and , )

4.8 动态评估 Makefile 中的命令或表达式

$(eval )

5,内置函数

5.1  $(call , )

5.2  $(foreach , , )

5.3  $(shell)

5.4  $(wildcard )

5.5  $(patsubst , , )

5.6 $(strip )

5.7 $(addprefix , )

5.8  $(addsuffix , )

5.9 $(filter , )

5.10  $(filter-out , )

5.11  $(sort )

5.12  $(join , )

5.13  $(word , )

5.14  $(words )

5.15  $(realpath )

5.16  $(basename )

5.17  $(dir )

6,常见的特殊变量

6.1 VPATH 变量

6.2  .PHONY 变量

6.3  include 变量

6.4 $(Q) 变量

7,特殊用法

7.1 指定头文件路径

7.2 指定库文件路径

三,简单的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 条件判断:ifeqifneq

  • ifeq:用于判断两个字符串是否相等。如果相等,执行接下来的代码。
  • ifneq:与 ifeq 相反,用于判断两个字符串是否不相等。如果不相等,执行接下来的代码。
ifeq ($(CC), gcc)
    # 如果CC变量为gcc,执行这部分代码
    CFLAGS += -D_GCC
endif

ifneq ($(CC), clang)
    # 如果CC变量不为clang,执行这部分代码
    CFLAGS += -D_NOT_CLANG
endif

 4.3 条件判断:ifdefifndef

  • ifdef:判断一个变量是否已经定义。
  • ifndef:判断一个变量是否未定义。

下面代码解释:

  • ifdef DEBUG:如果 DEBUG 变量已经定义,则添加 -DDEBUGCFLAGS
  • ifndef RELEASE:如果 RELEASE 变量未定义,则添加 -DNO_RELEASECFLAGS
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>)

callMakefile 中的函数调用语法,通常用于调用自定义的函数。它允许你将多个参数传递给一个函数。

# $(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 会在 dir1dir2dir3 中查找依赖文件。

# 在这个例子中,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 的目标总是被认为需要执行其规则,不会受到文件存在与否的影响。

  • 常见场景

    • 用于定义像 cleanallinstall 这样的目标,这些目标不对应实际的文件,而只是用于执行一些操作。

.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

  • file1file2 是要被引入的文件,可以是相对路径或绝对路径。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)

参考文章:

Makefile语法详细总结及示例解析(快速掌握)-优快云博客

Makefile语法详解1-编译链接脚本初始-优快云博客

Makefile入门(超详细一文读懂)-优快云博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是星凡呢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值