【香橙派系列教程】(二十二) Makefile入门

【二十二】Makefile入门

1.编译工具及构建工具介绍

在之前 ,都是直接使用gcc对代码进行编译,这对简单的工程是可以的,但当我们遇到复杂的工程时,每次用gcc等编译工具去操作就会显得很低效。因此make工具就出现了, make的出现是为了解决手动编译和链接大型工程的问题,它可以避免重复的工作,提高效率,保证正确性。make工具就根据makefile中的命令进行编译和链接的。但是当工程非常大的时候,手写makefile也是非常麻烦的,如果换了个平台makefile又要重新修改,因此更高级的一些构建系统或者工具工具像cmake、qmake、ninja和auto make就出现了,它们可以根据一些配置文件来自动化编译和链接软件项目。

image-20240804191506517

  • cmake是一个跨平台的构建系统,它可以根据CMakeLists.txt中的指令来生成不同平台和工具的工程文件,例如Makefile、Visual
  • Studio解决方案、Ninja文件等。cmake可以支持多种语言和多种架构,它还提供了一些高级功能,如测试、打包、安装等。
  • qmake是一个用于Qt项目的构建系统,它可以根据.pro或.pri中的指令来生成Makefile或其他形式的工程文件。
  • ninja是一个小巧而快速的构建工具,它可以根据ninja.build中的规则来执行编译和链接命令。ninja主要关注于性能和效率,它可以利用多核处理器和并行处理来加速构建过程。ninja通常不需要用户直接编写配置文件,而是由其他构建系统(如cmake)来生成
  • auto make是一个用于生成Makefile.in文件的工具,Makefile.in是一种用于auto conf的配置文件格式,auto conf是一个用于生成
  • configure脚本的工具。configure脚本是一个用于检测系统环境并生成最终的Makefile文件的脚本Makefile.am是一种用于auto
  • make的配置文件格式,它包含了一些指令和变量,用于定义程序或库的源文件、目标文件、依赖关系和编译选项等。
  • make是一个经典而通用的构建工具,它可以根据Makefile中的规则来执行编译和链接命令。make可以支持多种平台和工具,它还提供了一些高级功能,如条件判断、函数调用、模式匹配。

2.Makefile的简单讲解

1. 编译的四个阶段

回顾下编译的四个过程:预处理(Pre-Processing)、编译(Compiling)、汇编 (Assembliang)、链接(Linking)

image-20240804193254351

2. Makefile的规则

安装make指令sudo apt install make

关于命名:必须是makefile,大小写无所谓

image-20240804195302204

a. 基本规则

跟python很像,缩进很严格

target ... : prerequisites ...
<tab缩进>command
<tab缩进>...
<tab缩进>...
  • target 也就是一个目标文件,可以是 Object File,也可以是执行文件。还可以是一个标签( Label),对于标签这种特性,在后续的讲“伪目标”中会有叙述。
  • prerequisites 就是要生成那个 target 所需要的文件或是目标。
  • command 也就是 make 需要执行的任意shell命令。

Makefile一个示例:

debug:
	@echo "hello world"

image-20240804194909868

我们要编译下面这个最简单的例子:

hello.c

#include <stdio.h>

int main(int argc, char *argv[])
{
	printf("hello\n");
	return 0;
}

Makefile修改如下:

@让程序默默执行,不在控制台显示

debug:
	@echo "hello world"
	
test:
	gcc -o hello hello.c
        
clean:
	@rm hello

image-20240804201735994

如果不想在前面看到执行的过程rm hello,可以在前面加上@

image-20240804201922578

b. 伪目标

如果一个目标和一个实际文件同名,那么make会认为该目标已经是最新的,不需要重新生成,也不会执行其命令。通过将目标声明为伪目标.PHONY: debug,可以避免这种情况,强制执行其命令

debug:
	@echo "hello world"
	
test:
	gcc -o test test.c
        
clean:
	@rm hello
        
.PHONY: clean

这种情况我们就要引入伪目标了,加入.PHONY: clean

image-20240804202515373

image-20240804202824460

c. 变量赋值和预定义变量

Makefile中的变量赋值运算符有四种:分别是=、:=、?=和+=, $符号表示取变量的值,当变量名多于一个字符时,使用"( )":

  • = 表示延迟展开赋值,即变量的值是在使用时才确定,可能会受到后面的赋值影响。

例如,VAR_A = A,VAR_B = $(VAR_A) B,VAR_A = AA,那么最后VAR_B的值是AA B,而不是A B。

  • := 表示直接赋值,即变量的值是在定义时就确定,不会受到后面的赋值影响。

例如,VAR_A := A, VAR_B := $(VAR_A) B,VAR_A := AA,那么最后VAR_B的值是A B,而不是AA B。

  • ?=表示条件赋值,即只有当变量没有被赋值时,才使用等号后面的值作为变量的值。

例如,VAR ?= new_value,如果VAR在之前没有被赋值,那么VAR的值就为new_value,否则保持原来的值不变。

  • += 表示追加赋值,即将等号后面的值追加到变量原来的值之后,形成一个新的值。

例如,VAR += new_value,如果VAR在之前没有被赋值,那么VAR的值就为new_value,如果VAR在之前被赋值为old_value,那么VAR的值就为old_value new_value

$符的其他用法

$^ 表示所有的依赖文件

$@ 表示生成的目标文件

$< 代表第一个依赖文件

program: main.o lib.o
        gcc -o $@ $^

在这个例子中,$@program: main.o lib.o 规则中被用来代表 program,这是规则的目标。当执行 make program 时,gcc -o $@ $^ 命令会被展开为 gcc -o program main.o lib.o,其中 $@ 被替换为 program$^ 被替换为所有的依赖项 main.o lib.o

自动变量 $@ 非常有用,因为它允许你在编写Makefile时不必重复目标名称,并且可以更容易地编写通用的构建规则。

d.注释和换行符

采用#进行一行注释

采用\作为续行符

image-20240804212511717

image-20240804212952178

image-20240804213035482

e. 变量的替换引用

语法格式是:

#表示把变量var的值中的a后缀替换成b后缀。
$(var:a=b)
或
${var:a=b}

例如:把变量src的值中的.c后缀替换成.o后缀,赋值给变量obj,

src := a.c b.c c.c
obj := $(src:c=o)

结果是:

obj := a.o b.o c.o

举例:

# 这是一个Makefile的注释
TARGET = hello #TARGET延迟赋值hello
CC := gcc #CC立即赋值gcc
CC += -g #CC追加赋值-g, gcc -g表示添加调试信息,可用于gdb的调试

SRC = hello.c
OBJ = $(SRC:.c=.o) #变量的替换引用,把hello.c的.c替换成.o

debug :
	@echo "hello world"
	echo $(SRC)
	echo $(OBJ)
	
$(TARGET): $(SRC)	#替换完等价于hello:hello.c
	$(CC) -o $@ $<
# $(CC) -o ${TARGET} hello.c

compile: $(TARGET)

clean:
	@rm hello hello.o -r	#-r表示递归去删除
	
.PHONY: clean compile

image-20240804223114071

f.常见函数

Makefile函数的基本格式是:$ (<function> <arguments>)或者是 ${<function> <arguments>},其中,是函数名,是函数的参数,参数之间要用逗号分隔开,参数和函数名之间使用空格分开。调用函数的时候要使用字符“$”,后面可以跟小括号或者大括号。

1.wildcard 通配符
Makefile中的wildcard 是一个函数,用于扩展通配符,返回与通配符匹配的文件列表。通配符是一种特殊的字符,可以表示多个文件名或目录名,常见的通配符有 * 和 ?,分别表示任意长度的任意字符和单个任意字符。格式如下:

$(wildcard argments)

比如*.c 表示所有以 .c 结尾的文件名,a?.txt 表示所有以 a 开头,中间有一个任意字符,以 .txt 结尾的文件名。例如:

SRC = $(wildcard src/*.c)

表示查找并返回src目录下所有的.c文件, *表示通配符, 匹配一个或者多个任意字符

image-20240805093131043

2.shell

$(shell <cmd> <args>)

cmd: 执行命令名称
args:参数列表
返回值: 返回命令执行结果

例如:

SRC = $(shell find . -name *.c)

表示查找当前目录及子目录下的所有.c文件结尾的代码源文件。

SRC = $(shell find . -name *.c)

SRC = $(wildcard src/*.c) 输出的结果一样

3.patsubst替换函数:

$(patsubst pattern,replacement,text)

pattern: 是一个包含通配符 % 的模式,表示匹配任意长度的任意字符

replacement: 是一个替换字符串,也可以包含 %,表示用 pattern 中匹配的字符替换。

text: 是一个要处理的文本,可以包含多个以空格分隔的单词。

返回值:patsubst 函数会在 text 中找到所有符合 pattern 的单词,并用 replacement 替换它们,然后返回替换后的文本。

例如,如果有一个变量 src,它的值是:

src = a.c b.c c.c

想把它的值中的所有 .c 后缀替换成 .o 后缀,可以这样写:

obj = $(patsubst %.c,%.o,$(src))

这样,obj 的值就是:

obj = a.o b.o c.o

4.subst替换函数

$(subst from,to,text)

from: 是要被替换的字符或单词

to: 是替换后的字符或单词

text: 是要处理的字符串。

返回值:subst 函数会在 text 中找到所有的 from,并用 to 替换它们,然后返回替换后的字符串。

例如:

$(subst ee,EE,feet on the street)

返回:

fEEt on the strEEt

区别:

patsubst替换函数一般是配合通配符使用的,一般替换不了整段字符里的单字符,但是subst可以。

综合举例,测试工程代码目录:

test@test:~/makefiletest$ tree
.
├── Makefile
└── src
└── test.c

Makefile内容:

CC = gcc
CC += -g
SRC := $(shell find . -name *.c)
TARGET := $(patsubst %.c, %,$(subst src,obj, $(SRC)))

debug:
	@echo "hello world"
    echo $(SRC)
    echo $(TARGET)
$(TARGET): $(SRC)
    mkdir -p obj
    $(CC) -o $@ $<
compile: $(TARGET)
clean:
	@rm obj -r

.PHONY: clean compile

test.c内容:

#include <stdio.h>
int main()
{
    printf("hello world\n");
    return 0;
}

执行:

make compile

生成obj/test:

pg@pg-Default-string:~/makefiletest$ tree -a
.
├── Makefile
├── obj
│ └── test
└── src
	└── test.c

5.dir函数

$(dir NAMES...)

dir 函数是一个用于从文件名序列中提取目录部分的函数
优化Makefile内容:

CC = gcc
CC += -g
SRC := $(shell find . -name *.c)
TARGET := $(patsubst %.c, %,$(subst src,obj, $(SRC)))

debug:
	@echo "hello world"
	echo $(SRC)
	echo $(TARGET)
	
$(TARGET): $(SRC)
	mkdir -p $(dir $(TARGET))
	$(CC) -o $@ $<
	
compile: $(TARGET)
clean:
	@rm $(dir $(TARGET)) -r

.PHONY: clean compile

6.suffix函数

$(suffix <names...>)

功能:从文件名序列中取出各个文件名的后缀。
返回值:返回文件名序列的后缀序列,如果文件没有后缀,则返回空字串。
例如:

$(suffix src/foo.c src-1.0/bar.c hacks)

返回:

.c .c (空字符串)

7.basename函数

格式:

$(basename <names...>)

功能:从文件名序列中取出各个文件名的前缀部分。
返回值:返回文件名序列的前缀序列,如果文件没有前缀,则返回空字串。
例如:

$(basename src/foo.c src-1.0/bar.c hacks)

返回:

src/foo src-1.0/bar hacks

8.addsuffix函数

$(addsuffix <suffix>,<names...>)

功能:把后缀加到每个单词后面。
返回:返回加过后缀的文件名序列。
例如:

$(addsuffix .c,foo bar)

返回值:

foo.c bar.c”

9.addprefix函数

功能:把前缀加到中的每个单词后面。
返回值:返回加过前缀的文件名序列。
例如:

$(addprefix src/,foo bar)

返回值:

src/foo src/bar

10.foreach函数

当我们需要链接到很多库的时候可以用到

foreach 的基本语法如下:

$(foreach var,list,text)
  • var:在循环中使用的变量名。
  • list:一个以空格分隔的字符串列表,foreach 将遍历这个列表。
  • text:对于列表中的每个元素,foreach 将执行 text 中的命令或操作。

这里有一个简单的例子,演示如何使用 foreach

# 定义一个变量,包含一系列的目标
TARGETS := a b c

# 使用 foreach 来为每个目标生成一个对象文件
objects: $(foreach target,$(TARGETS),$(target).o)

在这个例子中,foreach 将遍历 TARGETS 变量中定义的列表 a b c,并且对于列表中的每个元素,它都会生成一个目标(例如 a.ob.oc.o)。

foreach 可以与Makefile中的其他函数一起使用,例如 $(eval) 来动态创建规则:

TARGETS := a b c

$(foreach target,$(TARGETS),\
    $(eval $(target).o: $(target).c)\
    $(eval $(RM) $(target).o)\
    $(eval $(CC) -o $(target).o -c $(target).c)\
)

在这个例子中,foreach 用于为每个目标生成编译规则,eval 用于立即执行生成的规则定义。

请注意,foreach 的使用可能会使Makefile变得复杂,因此应当谨慎使用,尤其是在大型项目中。在可能的情况下,尽量使用Makefile的模式规则(pattern rules)来简化规则的定义。

g.条件判断语言

Makefile条件判断有下面几种:

ifeq/ifneq语句:

ifeq语句 : 判断参数 是否相等,相等为 true, 否则是 false.

ifeq (arg1, arg2)
	#arg1 arg2 相等执行这里的语句
else
	#arg1 arg2 不相等执行这里的语句
endif

ifneq语句:判断参数 是否不等,不等为 true, 否则为 false.

ifneq (arg1, arg2)
	#arg1 arg2 不相等执行这里的语句
else
	#arg1 arg2 相等执行这里的语句
endif

ifdef/ifndef语句

ifdef 语句: 判断参数 是否有值 ,有值为 true, 否则是 false
ifndef : 判断参数 是否没有值 ,没有值为 true, 否则为 false.

ifdef:

ifdef var
	#如果定义了var,执行这里的内容
else
	#如果没定义var,执行这里的内容
endif

ifndef:

infdef var
	#如果没定义var,执行这里的内容
else
	#如果定义var,执行这里的内容
endif
  • 19
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

橘猫.exe

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

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

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

打赏作者

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

抵扣说明:

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

余额充值