【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 
相关推荐
漏洞文库-Web安全1 小时前
Linux逆向学习记录
linux·运维·学习·安全·web安全·网络安全·逆向
无奈笑天下1 小时前
【银河麒麟高级服务器部署本地yum源】
linux·运维·服务器·经验分享
dodod20122 小时前
Ubuntu 24.04 LTS 使用清华大学的 Ubuntu 镜像源以加速下载和更新操作
linux·运维·ubuntu
轻颂呀2 小时前
TCP协议
linux·网络·网络协议·tcp/ip
知识分享小能手2 小时前
CentOS Stream 9入门学习教程,从入门到精通,CentOS Stream 9 用户和组管理 —语法详解与实战案例(6)
linux·学习·centos
松涛和鸣2 小时前
25、数据结构:树与二叉树的概念、特性及递归实现
linux·开发语言·网络·数据结构·算法
runfarther3 小时前
mysql_mcp_server部署及应用案例
linux·mysql·centos·mcp
爱吃番茄鼠骗3 小时前
Linux操作系统———信号量
linux
灰勒塔德3 小时前
jetson orin nano super开发指南
linux·服务器·python