前言

第七次编译失败时,控制台的报错信息在黑暗中格外刺眼。项目从三个文件扩展到二十多个模块后,手工输入gcc命令已成噩梦:稍不留神漏掉某个依赖,程序便在运行时崩溃。上周生产环境的校验事故,正是因修改头文件后忘记重新编译关联模块——这种隐藏在手工流程中的“定时炸弹”,让开发效率与代码质量同步滑坡。当光标再次移向历史命令时,瞥见邻座屏幕上几行简洁的Makefile规则:

obj := sensor.o network.o data.o  
iot_gateway: $(obj)  
    gcc $^ -o $@ -lpaho-mqtt3cs  
%.o: src/%.c  
    gcc -c $< -Iinclude  

它像一张精准的依赖地图,让机器自动追踪文件变更、按需构建。本文将从Makefile基础概述到语法规则讲起,拆解如何用Makefile将碎片化编译转化为可维护的自动化流程,让构建系统从负担变为基石。


一、makefile概述

1. makefile脚本

Makefile 是一个用于自动化构建和管理软件项目的配置文件,通常与 make命令配合使用。它定义了项目的编译规则、依赖关系以及构建步骤,能够高效地处理代码的编译和链接过程。

2. make指令

make包括我们之前所讲的Linux命令本质上都是可执行文件,我们可以通过which make来查找相关指令可执行文件的路径:
在这里插入图片描述
当我们的项目足够大包含的源文件足够多的时候,使用gcc命令进行编译需要把所有源文件作为参数加到指令后面,这样命令显得很臃肿,也很麻烦。而make这个可执行文件可以自动寻找makefile、Makefile、GNUmakefile脚本文件中的任何一个,根据里面提前写好的指令来编译文件。
使用makefile进行编译不仅简化了编译指令而且还可以节省编译时间。这是因为我们再使用makefile编译时,系统会首先检查哪些参与编译的文件发生了修改,makefile会只编译这些修改过的文件,没有修改过的文件会直接使用,不会再进行二次编译,相比下gcc命令会对所有文件进行编译。


二、Makefile语法规则

makefile作为脚本文件,有着极其严苛的语法规则,其本质是一组目标-依赖-命令三元组,它的语法规则如下:

目标:依赖文件列表
	命令列表

其中目标代表生成的东西,依赖文件表示生成目标所需的东西,下面的命令列表则指示的是如何去做。
例如:

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

以上就表示通过gcc main.c fun,c -o main指令,依赖main.c、fun.c文件生成可执行文件main。

在这里插入图片描述在上图中,我们可以看到,当我们在命令行执行make时,会执行下面的命令列表中的指令。

注意事项:
- make指令虽然自动寻找makefile、Makefile、GNUmakefile脚本文件中的任何一个,但是可以通过make -f 指定文件名的方式来选择文件(不建议)。
- 执行make命令后默认执行makefile脚本文件中的第一个目标-依赖-命令三元组,而不会执行后面的脚本。如果想要执行其他的指令可以通过make 目标的方式。
我们以下图为例:
在这里插入图片描述
上图这个makefile文件有两组目标-依赖-命令三元组,make默认执行第一个目标,当我们想执行其他目标时需要指定目标名,即make 目标名

此外,当目标的依赖文件列表不存在时,makefile才会继续向下寻找依赖文件是否为其他目标,如果是则会执行目标命令,我们来看以下例子:

main:main.o fun.o
	gcc -o main main.o fun.o
main.o:main.c
	gcc -c main.c
fun.o:fun.c
	gcc -c fun.c
clean:
	rm -f main

此时我们的makefile脚本定义了三个目标,而main.ofun.o为第一个目标的依赖,这时我们执行make看看会发生什么:
在这里插入图片描述
如果当前目录下没有依赖所需的.o文件时,makefile会向下寻找,执行main目标所需的依赖项指令,上图可以看到执行的具体指令及顺序;此时删掉main可执行文件,再次运行make命令,可以看到这时只执行了gcc -o main main.o fun.o,这是因为此时.o文件已经存在。

三、Makefile的变量

1.自定义变量

makefile中的变量类似C语言中宏,我们可以将它看作替换。以下为变量的定义和使用:

定义格式:变量名=变量值
取变量值:$变量名

变量名的命名规则

  • 变量是大小写敏感的
  • 变量一般都在makefile文件的头部定义
  • 变量几乎可以在makefile的任何地方使用

例如:

CC=gcc #将gcc赋给CC
EXEC=main #定义可执行文件名
$(EXEC):main.o fun.o
	$(CC) main.o fun.o -o $(EXEC) #gcc main.o fun.o -o main 
main.o:main.c
	$(CC) -c main.c -o main.o #gcc -c main.c -o main.o
fun.o:fun.c
	$(CC) -c fun.c -o fun.o  #gcc -c fun.c -o fun.o
clean:
	rm $(EXEC) *.o

2.系统环境变量

make工具会拷贝系统的环境变量,并将其设置为makefile的变量,在makefile中可以直接读取或修改拷贝后的变量。
在命令行中输入env可以查看系统环境变量:
在这里插入图片描述

3.预定义变量

预定义变量是makefile中提取定义好的变量,以下为常见的预定义变量:

变量名 描述
$@ 目标名
$< 依赖文件列表中的第一个文件
$^ 依赖文件列表中除去重复文件的部分
AR 归档维护程序的程序名,默认值为 ar
ARFLAGS 归档维护程序的选项
AS 汇编程序的名称,默认值为 as
ASFLAGS 汇编程序的选项
CC C编译器的名称,默认值为 cc
CFLAGS C编译器的选项
CPP C预编译器的名称,默认值为$(CC) -E
CPPFLAGS C预编译的选项
CXX C++编译器的名称,默认值为 g++
CXXFLAGS C++编译器的选项

在了解了预定义变量后,我们上面的makefile脚本就可以改成如下所示:

CC=gcc #将gcc赋给CC
EXEC=main #定义可执行文件名
$(EXEC):main.o fun.o
	$(CC) $^ -o $@ #$(CC) main.o fun.o -o $(EXEC)
main.o:main.c
	$(CC) -c $< -o $@ #$(CC) -c main.c -o main.o
fun.o:fun.c
	$(CC) -c $< -o $@ #$(CC) -c fun.c -o fun.o
clean:
	rm $(EXEC) *.o

此外在makefile中%表示通配符,我们还可以进行如下简化:

CC=gcc #将gcc赋给CC
EXEC=main #定义可执行文件名
OBJ=main.o fun.o
$(EXEC):$(OBJ)
	$(CC) $^ -o $@ #$(CC) main.o fun.o -o $(EXEC)
%.o:%.c
	$(CC) -c $< -o $@ #$(CC) -c %.c -o %.o
clean:
	rm $(EXEC) *.o

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐