技术栈系列基础篇2-Makefile

2022-09-21 17:14:52 浏览数 (1)

什么是Makefile

Makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,文件之间有哪些依赖等。Makefile有自己的书写格式、关键字、函数。像C 语言有自己的格式、关键字和函数一样。而且在Makefile中可以使用系统shell所提供的任何命令来完成想要的工作。Makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率

Makefile里有什么

Makefile里包含了:显示规则、隐晦规则、变量定义、文件指示和注释

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

Makefile的文件名

Makefile的文件名有两种方式:

  • 默认的文件名 Makefile 或makefile
  • 指定文件名 例如 Make.Linux,Make.Mac等
代码语言:txt复制
# 默认的文件名,Makefile、makefike
> make
# 指定文件名,Make.Linux
> make -f Make.linux

Makefile规则

显式规则

代码语言:txt复制
target ... : prerequisites ...
    command
  • target: 需要生成目标,或者是标签
  • prerequisites:生成目标所依赖的列表
  • command:shell指令,<font color=red>格式为Tab开头</font>

Makefile规则的target 和prerequisites 存在依赖关系,target是依赖于prerequisites;Makefile 存在自动推导的能力;不断向上<font color=red>自动推导</font>;

参考示例:

代码语言:txt复制
test : test1.o test2.o
	cc -o test test1.o test2.o
	
test1.o : test1.c test1.h
	cc -c test1.c
	
test2.o : test2.c test2.h
	cc -c test2.c
	
clean :
	rm test test1.o test2.o

Makefile会进行自动推到,层层依赖、推导关系如下,

  • test 依赖于 test1.o 、test2.o
  • test1.o 依赖于 test1.c 、test1.h
  • test2.o 依赖于 test2.c 、test2.h

makefile会把所有依赖关系列举出来,执行make命令的时候,会根据依赖关系自动编译

隐晦规则

每个.o文件的依赖文件默认会有同名的.c文件,比如有一个target是test.o,那么test.c默认就是test.O的依赖文件,这个是makefile的隐晦规则,是make会自动推导出来的

make的工作方式

在默认命名的情况下,输入make命令做了什么?

  • make命令会找当前工作路径下的Makefile 或makefile文件
  • 找到文件,会寻找文件中,<font color=red>第一个目标文件(target)</font>,参考上面的示例就是”test“文件,并作为最终目标文件
  • 如果test文件不存在,就会根据test文件所依赖的.o文件,根据.o文件来生成test文件
  • 如果.o文件不存在,make会在makefile文件中,找到目标为.o文件,根据.o文件的依赖性,生成.o文件

make的依赖性,会自动推到一层层的依赖关系,最终编译出最终的目标。

makefile 变量

变量基础

例如:

代码语言:txt复制
# 声明一个变量
objects = test1.o test2.o 
		test3.o
# 使用变量
test: $(objects)
	cc -o test $(objects)
  • 变量的引用可以使用 ${变量名} 或 $(变量名) 中括号或括号都可以
  • Makefile 中的变量的使用其实非常的简单,因为它并没有像其它语言那样定义变量的时候需要使用数据类型。变量的名称可以由大小写字母、阿拉伯数字和下划线构成。<font color=red>等号左右的空白符没有明确的要求</font>,因为在执行 make 的时候多余的空白符会被自动的删除。至于值列表,既可以是零项,又可以是一项或者是多项。

变量的基本赋值

  • 简单赋值 ( := ) 编程语言中常规理解的赋值方式,<font color=red>只对当前语句的变量有效。</font>
  • 递归赋值 ( = ) 赋值语句可能影响多个变量,所有目标变量相关的其他变量都受影响。
  • 条件赋值 ( ?= ) 如果变量未定义,则使用符号中的值定义变量。如果该变量已经赋值,则该赋值语句无效。
  • 追加赋值 ( = ) 原变量用空格隔开的方式追加一个新值。

变量中的变量

在定义变量的值时,我们可以使用其它变量来构造变量的值,在Makefile中有两种方式来在用变量定义变量的值。

例如:

代码语言:txt复制
x := foo
y := $(x) 

追加变量值

代码语言:txt复制
objects = main.o foo.o bar.o utils.o
objects  = another.o

于是,我们的$(objects)值变成:“main.o foo.o bar.o utils.o another.o”(another.o被追加进去了)

如果变量之前没有定义过,那么,“ =”会自动变成“=”,如果前面有变量定义,那么“ =”会继承于前次操作的赋值符。如果前一次的是“:=”,那么“ =”会以“:=”作为其赋值符,如:

变量高级用法

变量值替换

<font color=red size=5> (var:a = b) 或{var:a = b }</font>

把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字串。这里的“结尾”意思是“空格”或是“结束符还是看一个示例吧:

代码语言:txt复制
foo := a.o b.o c.o
bar := $(foo:.o=.c)
# 把“$(foo)”中所有以“.o”字串“结尾”全部替换成“.c”,最终bar的值是a.c b.c c.c

把变量值再当成变量

代码语言:txt复制
x = y
y = z
a := $($(x))
# 在这个例子中,$(x)的值是“y”,所以$($(x))就是$(y),于是$(a)的值就是“z”

makefile注释

Makefile中只有行注释,注释使用”#“ 字符 例如:

代码语言:txt复制
# 这是makefile的注释

引用其他的Makefile

在Makefile使用include关键字可以把别的Makefile包含进来,make命令开始时,会把找寻include所指出的其它Makefile,并把其内容安置在当前的位置。这很像C语言的#include,被包含的文件会原模原样的放在当前文件的包含位置。include的语法是:

代码语言:txt复制
include <filename>
# 在include前面可以有一些空字符,但是绝不能是[Tab]键开始
# filename可以是当前操作系统Shell的文件模式(可以保含路径和通配符)

-include <filename>
# 无论include过程中出现什么错误,都不要报错继续执行。上面那条指令若是找不到include的目标文件,会报错

伪目标

代码语言:txt复制
clean:
    rm *.o temp

<font color=red size=4>伪目标不会自动被执行,只能显式地调用执行。</font>但是上面伪目标的写法有一个缺陷,若是当前目录下存在有一个文件名为"clean",那么根据我们的规则,command将不会被执行,因为目标已经存在了,为了解决这个问题,我们可以使用一个特殊的标记“.PHONY”来显示地指明一个目标是“伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”

代码语言:txt复制
.PHONY : clean
clean:
    rm *.o temp

通过.PHONY,无论是否存在“clean”文件,我们的command都将会被执行了

常见伪目标

伪目标

描述

all

这个伪目标是所有目标的目标,其功能一般是编译所有的目标。

clean

这个伪目标功能是删除所有被make创建的文件。

install

这个伪目标功能是删除所有被make创建的文件。

print

这个伪目标的功能是例出改变过的源文件。

tar

这个伪目标功能是把源程序打包备份。也就是一个tar文件。

dist

这个伪目标功能是创建一个压缩文件,一般是把tar文件压成Z文件。或是gz文件。

TAGS

这个伪目标功能是更新所有的目标,以备完整地重编译使用。

check 和test

这两个伪目标一般用来测试makefile的流程。

常见问题

1. @echo,命令不回显

  • @echo,make会把其要执行的命令行在命令执行前输出到屏幕上。当我们用“@”字符在命令行前,那么,这个命令将不被make显示出来

2. 立即赋值(:=) 和延迟赋值(=)

  • :=: 强制按先后顺序执行,立即赋值。
  • =:赋值的结果会等到整个路径执行完再决定,后面的会覆盖前面的,延迟赋值。

示例:

代码语言:txt复制
$ cat Makefile

a = foo
b1 := $(a) bar
b2 = $(a) bar
a = xyz

all:
	@echo b1=$(b1)
	@echo b2=$(b2)

$ make
b1=foo bar
b2=xyz bar

3. 在 Makefile 表达式中使用逗号和空格变量

逗号和空格是 Makefile 表达式中的特殊符号

参考文档:

https://juejin.cn/post/6844904001872330760#heading-4

https://blog.csdn.net/weixin_38391755/article/details/80380786/

0 人点赞