Linux:自动化构建 - make
make基本概念
make
是一个用于自动化编译和构建过程的工具。它主要用于管理大型软件项目的构建过程,帮助开发者更高效地编译和部署代码,并减少人为错误的发生,这使得软件的编译和部署变得更加自动化和可靠。
其中make
是一个命令,执行该命令需要当前操作目录下有一个名为makefile
或者Makefile
的文件,在makefile
内部编写指令,随后就可以通过make
快速执行一系列的指令了。
当前目录下有一个test.c
文件,需要把该文件编译为可执行文件mybin
。该过程就可以用make
来自动执行。我先编写一个makefile
,然后再讲解,代码如下:
powershell
mybin:test.c
gcc -o mybin test.c
clean:
rm -f mybin
其中我们可以看到两条熟悉的指令:gcc -o mybin test.c
,该指令完成把test.c
文件编译为可执行文件mybin
;rm -f mybin
,该指令完成对mybin
的删除。
有了这个文件后,我们就可以直接输入make
,完成对test.c
的编译:
一开始目录下只有makefile
和test.c
两个文件,执行指令make
后,Linux自动执行了指令gcc -o mybin test.c
,最后目录中就出现了编译好的可执行文件mybin
。
我们也可以输入make clean
,完成对mybin
的删除:
执行make clean
后,Linux自动执行了指令rm -f mybin
,完成了mybin
的删除。
看到其大致是如何执行后,我现在讲解以上代码中各个部分的作用是啥。
powershell
mybin:test.c
gcc -o mybin test.c
mybin:test.c
:这个由两个文件名通过一个:
隔开的整体,叫做依赖关系。左侧的mybin
叫做目标文件,右侧的test.c
叫做依赖文件列表。gcc -o mybin test.c
:这个语句叫做依赖方法。
那么依赖关系
,目标文件
,依赖文件列表
,依赖方法
之间有什么关系呢?
-
目标文件:
- 目标文件是 make 命令要生成的文件,通常是可执行程序、库文件或中间目标文件。
- 每个目标文件都有一条或多条命令来生成它。
- 目标文件可以有多个依赖文件,这些依赖文件共同决定了目标文件的生成过程。
-
依赖文件列表:
- 依赖文件列表描述了目标文件所依赖的输入文件,如源代码文件、头文件、库文件等。
- 依赖文件列表告诉 make 哪些文件的变化会导致目标文件需要重新生成。
- 依赖文件列表可以包含多个文件,用空格分隔。
-
依赖关系:
- 目标文件与其依赖文件之间形成依赖关系。
- 如果依赖文件中任何一个文件发生变化,目标文件就需要重新生成。
- 依赖关系可以形成层次结构,实现文件之间的层次依赖。
-
依赖方法:
- 依赖方法是生成目标文件所需执行的命令。
- 依赖方法通常是 shell 命令,用于编译、链接等操作。
- 依赖方法可以包含多条命令,每条命令用回车分隔。
这里要注意,依赖方法前面必须用一个Tab
键隔开,不可以是四个空格。
再看到后半段:
powershell
clean:
rm -f mybin
该代码中,目标文件是clean
,没有依赖文件列表,依赖关系是删除mybin
的指令。只要执行make clean
就会执行后面的代码rm -f mybin
,从而实现文件的删除。
左侧的目标文件clean
叫做伪目标
,伪目标是 make 中的一种特殊目标,它不对应任何文件,只是用来执行一些命令。常见的伪目标有 all、clean、install 等。
那么现在有一个问题,为什么执行mybin:test.c
直接使用make
就可以,但是make clean
才能执行clean
呢?
当
make
后面不接任何内容时,则在makefile
中从上往下查找,找到第一个依赖关系执行
现有如下makefile
:
powershell
mybin:test.o
gcc -o mybin test.o
test.o:test.s
gcc -c -o test.o test.s
test.s:test.i
gcc -S -o test.s test.i
test.i:test.c
gcc -E -o test.i test.c
clean:
rm -f test.i test.s test.o mybin
该文件完成了一个连续的C语言程序编译链接过程,但是其有一个问题在于,C语言编译过程应该依次生成.i
,.s
,.o
文件,最后生成mybin
。第一条依赖关系执行的时候,就缺少test.o
文件,第二条依赖关系缺少test.s
文件,以此类推,整个编译过程都是反的。那么该makefile
可以执行成功吗?
我们试试:
可以看到,其执行成功了,这是为什么?我只输入了make
指令,按理来说应该只执行第一条依赖关系,为什么除了clean
的所有依赖关系都被执行了?
make
会根据文件的依赖关系进行自动推导,以正确的顺序执行各个依赖关系
比如说一开始mybin
缺少了test.o
,于是make
去makefile
中查找,发现test.o:test.s
的依赖关系可以生成test.o
于是先执行该依赖关系。但是test.o:test.s
的依赖关系缺少test.s
,就再去找生成test.s
的依赖关系,以此类推。
makefile语法
以上我们只简单讲解了一个基本的makefile
例子,来帮助大家理解make
,现在我们来讲解更多的makefile
语法。
首先,在makefile
中,指令内部的目标文件可以用$@
代替,依赖文件列表可以用$^
代替:
powershell
mybin:test.c
gcc -o $@ $^
在这里gcc -o $@ $^
和gcc -o mybin test.c
是一样的。
变量
在makefile
中,是可以定义变量的,只需要用一个等号进行赋值即可:
powershell
bin=mybin
src=test.c
此时bin
的值就是mybin
,src
的值就是test.c
,想要使用这个变量,就在对应的地方以$(变量名)
的形式代替即可,这个变量可以放在任何地方,包括指令内部,依赖关系中等等。
比如下面这样:
powershell
bin=mybin
src=test.c
$(bin):$(src)
gcc -o $(bin) $(src)
clean:
rm -f $(bin)
任何需要test.c
和mybin
的地方,都可以改为$(bin)
和$(src)
。
PHONY
讲解PHONY
前,我们看到一个问题:
现在我们每次make
都会生成一个mybin
文件,如果直接gcc -o mybin test.c
,那么新生成的mybin
文件会覆盖原先的文件。但是make
执行的指令,会进行一次检查,如果源文件test.c
没有进行更新,那么make
就会拒绝再次编译这个文件,因为编译出来的结果是一样的,导致mybin
无法连续编译两次。
也就是上图中我们连续两次make
,第二次拒绝了我们的原因。
PHONY
在Makefile
中是一个非常有用的特殊目标。当make
执行到PHONY
目标时,它会无条件执行该目标下定义的命令,而不会检查是否有同名的文件在。
因此我们就可以对mybin
使用这个PHONY
,让它强制执行命令,不论当前目录下有没有mybin
。
修饰语法:
powershell
.PHONY:xxx
此时xxx
对应的命令就会被强制执行。
makefile
写法:
powershell
.PHONY:mybin
mybin:test.c
gcc -o mybin test.c
此时mybin
每次都会强制执行命令,不论之前是否存在mybin
文件了。
但是这并不是PHONY
的主要用途,其主要用于修饰伪目标。比如伪目标clean
,如果当前目录下刚好有一个叫做clean
的文件,那么clean
作为一个目标文件,就有可能会被make
禁止执行。可是我们的clean
作为一个伪目标,其目的不是生成一个clean
文件,而是执行命令,因此要用PHONY
来强制执行clean
对应的命令:
makefile
.PHONY: clean
clean:
rm -rf *.o
在这里,clean
是一个PHONY
目标,即使当前目录下有一个名为clean
的文件,make clean
也会执行删除操作,而不是试图构建clean
文件。