【Linux】Makefile 基础

Makefile


Ref:Shell 教程


Makefile 定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,Makefile 就像一个 Shell 脚本,其中也可以执行操作系统的命令。

Makefile 带来的好处就是 ------ "自动化编译",一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。make是一个命令工具,是一个解释 Makefile 中指令的命令工具,一般来说,大多数的 IDE 都有这个命令。

本文以 C/C++的源码为基础,介绍 Makefile 的基本用法。


|---|
| |

Makefile 简介


Makefile 文件的基本结构如下图所示:

shell 复制代码
target ... : prerequisites ...
	command
	command
	...

每一部分的作用从其名称中就可以看出来,target 是目标文件,可以是多个;prerequisites 就是要生成 target 所需要的文件或目标,也可以是是多个;command 也就是 make 需要执行的命,它可以是任意的 Shell 命令。

Makefile 文件中描述的其实是一个文件的依赖关系,target 这目标文件依赖于 prerequisites 中的文件,其生成规则定义在 command 中。

prerequisites 中如果有一个以上的文件比 target 文件要新的话,command 所定义的命令就会被执行。这就是 Makefile 的规则,也是 Makefile 中最核心的内容。


示例:

假设我们的工程有 8 个 C 文件,和 3 个头文件,我们要写一个 Makefile 来告诉 make 命令如何编译和链接这几个文件。编译和链接的规则为:

1)如果这个工程没有编译过,那么我们的所有 C 文件都要编译并被链接。

2)如果这个工程的某几个 C 文件被修改,那么我们只编译被修改的 C 文件,并链接目标程序。

3)如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的 C 文件, 并链接目标程序。

针对这样一个需求,其对应的 Makefile 文件如下:

shell 复制代码
edit : main.o kbd.o command.o display.o insert.o search.o files.o utils.o
	cc -o edit main.o kbd.o command.o display.o \
			   insert.o search.o files.o utils.o
main.o : main.c defs.h
	cc -c main.c
kbd.o : kbd.c defs.h command.h
	cc -c kbd.c
command.o : command.c defs.h command.h
	cc -c command.c
display.o : display.c defs.h buffer.h
	cc -c display.c
insert.o : insert.c defs.h buffer.h
	cc -c insert.c
search.o : search.c defs.h buffer.h
	cc -c search.c
files.o : files.c defs.h buffer.h command.h
	cc -c files.c
utils.o : utils.c defs.h
	cc -c utils.c
clean :
	rm edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o

Makefile 文件的工作方式如下:

make命令会在当前目录下找名字叫"Makefile"或"makefile"的文件。

② 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,它会找到"edit"这个文件,并把这个文件作为最终的目标文件。

③ 如果 edit 文件不存在,或是 edit 所依赖的后面的 .o 文件的文件修改时间要比 edit 这个文件新,那么,他就会执行后面所定义的命令来生成 edit 这个文件。

④ 如果 edit 所依赖的 .o 文件也存在,那么 make 会在当前文件中找目标为 .o 文件的依赖性,如果找到则再根据那一个规则生成 .o 文件.(这有点像一个堆栈的过程)

⑤ 当然,你的 C 文件和 H 文件是存在的啦,于是 make 会生成 .o 文件,然后再用 .o 文件生成 make 的终极任务,也就是执行文件 edit 了。

这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。make只管文件的依赖性


Notes:

① 命令行一定要以一个Tab键作为开头

make会比较 targets 文件和 prerequisites 文件的修改日期,如果 prerequisites 文件的日期要比 targets文件的日期要新,或者 target 不存在的话,那么,make 就会执行后续定义的命令。


|---|
| |

Makefile 基础


通常情况下,MakeFile 文件中包含5部分内容:

  • 显式规则。显式规则说明了,如何生成一个或多个的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。
  • 隐晦规则 。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。
  • 变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
  • 文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。
  • 注释 。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用#字符。

Makefile 文件名


默认的情况下,make命令会在当前目录下按顺序找寻文件名为"GNUmakefile"、"makefile"、"Makefile"的文件,推荐使用 Makefile 作为文件名称。

如果使用其他的文件名称,需要使用 -f 选项:

shell 复制代码
make -f Make.Linux

变量


Makefile 中变量的使用方法如下:

shell 复制代码
objects = main.o kbd.o command.o display.o insert.o search.o files.o utils.o

edit : $(objects)
	cc -o edit $(objects)

变量在声明时需要赋初值,使用时在变量名前加上$符号,通常用小括号()或大括号{}将变量名包围。

如果想要使用 Shell 命令的返回值作为变量的值,其定义方法如下:

shell 复制代码
PWD = $(shell pwd)

OS = $(shell uname)

COMPILE_TIME = $(shell date +"%Y-%M-%d %H:%M:%S")

伪目标


在前面的实例中,有这样一个 target:

shell 复制代码
clean :
	rm edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o

clean 这个 target 没有依赖关系,即其冒号后什么也没有,不会自动去找文件的依赖性,也就不会自动执行其后所定义的命令,所以也并不生成 "clean" 这个文件。

这样的 target 称之为 "伪目标,make 并不会自动执行伪目标的内容,只有通过明确的指名这个目标才能让其生效。

shell 复制代码
make clean

所以,伪目标通常用于删除或备份文件等。

"伪目标"的取名不能和文件名重名,不然其就失去了"伪目标"的意义。为了避免和文件重名的这种情况,我们可以使用一个特殊的标记.PHONY来显示地指明一个目标是"伪目标",向make说明,不管是否有这个文件,这个目标就是"伪目标"。

只要有这个声明,不管是否有"clean"文件,要运行"clean"这个目标,只有"make clean"这样。于是整个过程可以这样写:

shell 复制代码
.PHONY: clean
clean:
	rm *.o temp

命令执行


不显示命令

通常,make会把其要执行的命令行在命令执行前输出到屏幕上。当我们用@字符在命令行前,那么,这个命令将不被make显示出来,最具代表性的例子是,我们用这个功能来像屏幕显示一些信息。如:

shell 复制代码
@echo 正在编译XXX模块......

make执行时,会输出"正在编译XXX模块..."字串,但不会输出命令,如果没有@,那么,make将输出:

shell 复制代码
echo 正在编译XXX模块......
正在编译XXX模块......

忽略命令中的错误

每当命令运行完后,make会检测每个命令的返回码,如果命令返回成功,那么make会执行下一条命令,当规则中所有的命令成功返回后,这个规则就算是成 功完成了。如果一个规则中的某个命令出错了(命令退出码非零),那么make就会终止执行当前规则,这将有可能终止所有规则的执行。

有些时候,命令的出错并不表示就是错误的。例如mkdir命令,我们一定需要建立一个目录,如果目录不存在,那么mkdir就成功执行,万事大吉,如果目录存在,那么就出错了。我们之所以使用mkdir的意思就是一定要有这样的一个目录,于是我们就不希望mkdir出错而终止规则的运行。

为了做到这一点,忽略命令的出错,我们可以在 Makefile 的命令行前加一个减号-(在Tab键之后),标记为不管命令出不出错都认为是成功的。如:

shell 复制代码
clean:
	-rm -f *.o

转义 $


在 makefile 中, $ 是一个特殊的符号,主要用于‌引用变量、函数或特殊目标成员‌。

如果需要直接使用 $ 这个符号,需要使用 $ 对其进行转义,如下所示:

shell 复制代码
## Makefile
test:
	echo "Value: $$x"

上述命令的输出为 $x

当 makefile 中使用到 sed 命令的时候,就可能会遇到使用 $$ 的情形:

shell 复制代码
sed -i "s|$$SRC_DIR|${DESIGN_DIR}|g" ${DESIGN_NAME}.f

|---|
| |

指定目录下执行


make 命令是可以在指定目录下寻找 makefile 文件的,如下:

shell 复制代码
make -C <target_dir> [target] -slient

-C 选项的作用是:在执行 Makefile 前先切换到指定目录,再在该目录下查找并执行 Makefile。

【一定是大写 C 】

如果目录中包含空格,用双引号括起来:

shell 复制代码
make -C "dir with spaces"

该操作存在切换目录的过程,默认情况下,make 命令在执行的会输出如下提示信息:

如果不想显示这些信息,可以使用 -sliint 选项:

shell 复制代码
make -C <target_dir> [target] -slient 
相关推荐
倔强的石头1064 分钟前
【Linux指南】动静态库系列(二):从源码复用到目标文件复用:为什么需要把 .o 打包成库
linux·运维·服务器
HLC++12 小时前
Linux的进程间通信
android·linux·服务器
华清远见IT开放实验室13 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
groundhappy16 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕17 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫18 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生18 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.1119 小时前
linux基础IO
linux·运维·服务器
BelongPanda21 小时前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯21 小时前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算