Makefile文件编写快速入门

前言

开始Makefile

Makefile是什么?:分布在Linux程序的源代码中,定义Linux 程序的编译规则,是一种程序构建工具。用户可以在 Makefile 中定义make的行为,来确定模块需要如何编译或重新编译在一起

Makefile有什么作用呢?:Makefile可以定义整个工程的编译规则。一个工程中的源文件不计其数,并且按类型、功能、模块分别放在若干个目录中,如果将这些源文件编译成可执行文件,需要处理非常复杂的文件依赖。而在makefile中,可以定义一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。

Makefile带来什么好处呢?:那就是自动化编译,只需要一个make命令,整个工程完全自动编译,极大提高了软件开发效率,省时省力。

Makefile如何运行?:makefile文件类似于Shell脚本,在其中可以执行操作系统的命令。解释执行makefile中指令需要用到make命令。大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。make在执行时,需要一个命名为Makefile的文件。这个文件告诉make以何种方式编译源代码和链接程序。典型地,可执行文件可由一些.o文件按照一定的顺序生成或者更新。如果在你的工程中已经存在一个着多个正确的Makefile。当对工程中的若干源文件修改以后,需要根据修改来更新可执行文件或者库文件。

  • make会自动根据修改情况完成源文件的对应.o文件的更新、库文件的更新、最终的可执行程序的更新。make通过比较对应文件(规则的目标和依赖,)的最后修改时间,来决定哪些文件需要更新、那些文件不需要更新。
  • 对需要更新的文件make就执行数据库中所记录的相应命令(在make读取Makefile以后会建立一个编译过程的描述数据库。此数据库中记录了所有各个文件之间的相互关系,以及它们的关系描述)来重建它,对于不需要重建的文件make什么也不做。

编译和链接产生的文件

我们已经知道Makefile是一种程序构建工具,那么它在处理源文件的过程中肯定会遇到各种编译和链接产生的文件,或者是编译和链接需要的文件。编译时,编译器需要的是语法的正确,函数与变量的声明的正确。链接时,主要是链接函数和全局变量。下面笔者对相关的文件进行说明(以c++为例):

  • 源文件:.cpp.hpp
  • 源文件编译为中间代码文件: Windows下是.obj文件,UNIX下是 .o 文件,即Object File。一般每个源文件都应该对应于一个中间目标文件( .o 文件或.obj 文件)。
  • 链接器需要中间目标文件( .o 文件或.obj 文件)来链接我们的应用程序。
  • 大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便。所以,我们要给中间目标文件打个包,在Windows下这种包叫“库文件”(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是.a 文件。
  • 经过编译链接后产生了可执行文件:Windows下是.exe文件,UNIX下是 .out 文件。

文件编译规则

以C语言文件举例子,当使用make工具进行编译时,工程中以下几种文件在执行make时将会被编译(重新编译):

  1. 所有的源文件没有被编译过,则对各个C源文件进行编译并进行链接,生成最后的可执行程序;
  2. 每一个在上次执行make之后修改过的C源代码文件在本次执行make时将会被重新编译;
  3. 头文件在上一次执行make之后被修改。则所有包含此头文件的C源文件在本次执行make时将会被重新编译。

后两种情况是make只将修改过的C源文件重新编译生成.o文件,对于没有修改的文件不进行任何工作。重新编译过程中,任何一个源文件的修改将产生新的对应的.o文件,新的.o文件将和以前的已经存在、此次没有重新编译的.o文件重新连接生成最后的可执行程序。

第一个实例

分析一个简单的Makefile,它对一个包含8个C源代码和三个头文件的工程进行编译和链接。

#sample Makefile
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

说明:

  • 上述文件作用:在完成了这个Maekfile以后;需要创建可执行程序“edit”,所要做的就是在包含此Makefile的目录(当然也在代码所在的目录)下输入命令“make”。
  • 文件清除:删除已经此目录下之前使用“make”生成的文件(包括那些中间过程的.o文件),也只需要输入命令“make clean”就可以了。
  • 在这个Makefile中,我们的目标(target)就是可执行文件“edit”和那些.o文件(main.o,kbd.o….);
  • 依赖(prerequisites)就是冒号后面的那些 .c 文件和 .h文件。
  • 所有的.o文件既是依赖(相对于可执行程序edit)又是目标(相对于.c和.h文件)。
  • 命令包括“cc –c maic.c”、“cc –c kbd.c”……

注意:

  • 可以将一个较长行使用反斜线(\)来分解为多行。但需要注意:反斜线之后不能有空格(这也是大家最容易犯的错误,错误比较隐蔽)
  • 命令行必需以[Tab]键开始,以和Makefile其他行区别。就是说所有的命令行必需以[Tab] 字符开始,但并不是所有的以[Tab]键出现行都是命令行。但make程序会把出现在第一条规则之后的所有以[Tab]字符开始的行都作为命令行来处理。

目标/依赖/命令行

在描述依赖关系行之下通常就是规则的命令行(存在一些些规则没有命令行),命令行定义了规则的动作(如何根据依赖文件来更新目标文件)。(记住:make程序本身并不关心命令是如何工作的,对目标文件的更新需要你在规则描述中提供正确的命令。“make”程序所做的就是当目标程序需要更新时执行规则所定义的命令)。

目标“clean”不是一个文件,它仅仅代表执行一个动作的标识。

  • 正常情况下,不需要执行这个规则所定义的动作,因此目标“clean”没有出现在其它任何规则的依赖列表中。因此在执行make时,它所指定的动作不会被执行。除非在执行make时明确地指定它。
  • 而且目标“clean”没有任何依赖文件,它只有一个目的,就是通过这个目标名来执行它所定义的命令。Makefile中把那些没有任何依赖只有执行动作的目标称为“伪目标”(phony targets)。需要执行“clean”目标所定义的命令,可在shell下输入:make clean。

make如何工作

默认的情况下,make执行的是Makefile中的第一个规则,此规则的第一个目标称之为“最终目的”或者“终极目标”(就是一个Makefile最终需要更新或者创建的目标。

当输入“make”命令以后。make读取当前目录下的Makefile文件,并将Makefile文件中的第一个目标作为其执行的“终极目标”,(我们的例子中是edit)开始处理第一个规则(终极目标所在的规则)。

我们简单总结一下:

  • 对于一个Makefile文件,“make”首先解析终极目标所在的规则(上节例子中的第一个规则),根据其依赖文件(例子中第一个规则的8个.o文件)依次(按照依赖文件列表从左到右的顺序)寻找创建这些依赖文件的规则。

  • 首先为第一个依赖文件(main.o)寻找创建规则,如果第一个依赖文件依赖于其它文件(main.c、defs.h),则同样为这个依赖文件寻找创建规则(创建main.c和defs.h的规则,通常源文件和头文件已经存在,也不存在重建它们的规则)……,直到为所有的依赖文件找到合适的创建规则。

  • 之后make从最后一个规则(上例目标为main.o的规则)回退开始执行,最终完成终极目标的第一个依赖文件的创建和更新。之后对第二个、第三个、第四个……终极目标的依赖文件执行同样的过程(上例的的顺序是“main.o”、“kbd.o”、“command.o”……)。

  • 创建或者更新每一个规则依赖文件的过程都是这样的一个过程(类似于c语言中的递归过程)。对于任意一个规则执行的过程都是按照依赖文件列表顺序,对于规则中的每一个依赖文件,使用同样方式(按照同样的过程)去重建它,在完成对所有依赖文件的重建之后,最后一步才是重建此规则的目标。

  • 更新(或者创建)终极目标的过程中,如果任何一个规则执行出现错误make就立即报错并退出。整个过程make只是负责执行规则,而对具体规则所描述的依赖关系的正确性、规则所定义的命令的正确性不做任何判断。就是说,一个规则的依赖关系是否正确、描述重建目标的规则命令行是否正确,make不做任何错误检查。

指定变量

还是上边的例子,我们来看一下终极目标“edit”所在的规则:

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

观察一下出现什么问题?在这个规则中.o文件列表出现了两次;第一次:作为目标“edit”的依赖文件列表。第二次:规则命令行中作为“cc”的参数列表。
这样做所带来的问题是:如果我们需要为目标“edit”增加一个的依赖文件,我们就需要在两个地方添加(依赖文件列表和规则的命令中)。添加时可能在“edit”的依赖列表中加入了、但却忘记了给命令行中添加,或者相反。这就给后期的维护和修改带来了很多不方便,添加或修改时出现遗漏。
有什么解决办法呢:在实际工作中大家都比较认同的方法是,使用一个变量“objects”、“OBJECTS”、“objs”、“OBJS”、“obj”或者“OBJ”来作为所有的.o文件的列表的替代。在使用到这些文件列表的地方,使用此变量来代替。

在上例的Makefile中我们可以添加这样一行:

objects = main.o kbd.o command.o display.o \
	insert.o search.o files.o utils.o

在定义了此变量后,我们就可以在需要使用这些.o文件列表的地方使用“$(objects)”来表示它

objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
	cc -o edit $(objects)
…….
…….
clean :
	rm edit $(objects)

Makefile - 宏

make 允许使用类似变量的宏。宏在 Makefile 中使用 名称 =值的形式来定义。例如

ROOT_DIR= $(shell pwd)

特殊宏

有一些预定义的特殊宏

  • $@ 要创建的文件的名称。
  • $? 是被更改的对应文件名。
hello: main.cpp hello.cpp factorial.cpp
    $(CC) $(CFLAGS) $? $(LDFLAGS) -o $@
#上下这两部分语句是等价的
hello: main.cpp hello.cpp factorial.cpp
    $(CC) $(CFLAGS) $@.cpp $(LDFLAGS) -o $@

说明:

  • 变量CFLAGS是编译.c文件时gcc的编译选项,可以在Makefile中给它指定明确的值、也可以使用隐含的定义值。
  • 这里要创建的文件是hello,所以$@指的是hello
  • $? 或者 $@.cpp 则代表所有已修改源文件。
    常见的隐式规则是用于构建 .cpp(源文件)之外的 .o(对象)文件。

注意:

  • $(CC) $(CFLAGS) $? $(LDFLAGS) -o $@ 这样的操作行应该在行首输入一个制表符 (\t) ,否则 make 会报错。

同时默认规则中还有另外两个特殊的宏。分别是

  • $< 触发操作的相关文件的名称。
  • $* 由目标文件和依赖文件共享的前缀。
.cpp.o:
    $(CC) $(CFLAGS) -c $<
或者 −
.cpp.o:
    $(CC) $(CFLAGS) -c $*.c

常规宏

有各种默认的宏。可以通过 make -p命令看到他们。大多数的常规宏都是见名知意。
这些预定义变量(即隐式规则中使用的宏)分为两类:

  • 作为程序名称的宏(如CC)
  • 包含程序参数的宏(如CFLAGS)

下表是一些用作 Makefile 内置规则中程序名称的常用变量表。(比如指定使用什么程序进行编译)

变量名(制定程序)作用
CC编译 C 语言文件的程序;默认是 cc
CXX编译 C++ 文件的程序; 默认是 g++
CPP运行 C 预处理器并输出到当前输出流的程序; 默认是 $(CC) -E
RM删除文件的命令;默认是 rm -f

下表是一个变量表,其值是上述程序的附加参数。除非另有说明,否则所有这些变量的默认值都是空字符串。

变量名(指定参数)
CFLAGS传递给 C 编译器的额外 flag。GCC编译选项CFLAGS
CXXFLAGS传递给 C 编译器 的额外 flag。
CPPFLAGSExtra flags to give to the C preprocessor and programs, which use it (such as C and Fortran compilers).
LDFLAGSGCC链接选项LDFLAGS参数 Extra flags to give to compilers when they are supposed to invoke the linker, ld.

注 - 你可以使用-R --no-builtin-variables 选项取消隐式规则使用的所有变量。

你也可以在命令行中定义宏,如下所示

make CPP = /home/courses/cop4530/spring02

自动化变量

假如你需要书写一个将.c文件编译到.o文件的模式规则,那么你该如何为gcc书写正确的源文件名?当然了,不能使用任何具体的文件名,因为在每一次执行模式规则时源文件名都是不一样的。为了解决这个问题,就需要使用“自动化变量”,自动化变量的取值是根据具体所执行的规则来决定的,取决于所执行规则的目标和依赖文件名。

简单来说,我们用模式规则来描述做菜这个动作,但是用什么原料,和做出什么菜我们都不知道,所以为了描述这个动作的过程,我们需要一些自动化变量来指代。

常见的自动化变量:

  • $<:规则的第一个依赖文件名。如果是一个目标文件使用隐含规则来重建,则它代表由隐含规则加入的第一个依赖文件。
  • $?:所有比目标文件更新的依赖文件列表,空格分割。如果目标是静态库文件名,代表的是库成员(.o文件)。
  • $<:规则的第一个依赖文件名。如果是一个目标文件使用隐含规则来重建,则它代表由隐含规则加入的第一个依赖文件。

自动推导规则

在使用make编译.c源文件时,编译.c源文件规则的命令可以不用明确给出。这是因为make本身存在一个默认的规则,能够自动完成对.c文件的编译并生成对应的.o文件。它执行命令“cc -c”来编译.c源文件。在Makefile中我们只需要给出需要重建的目标文件名(一个.o文件),make会自动为这个.o文件寻找合适的依赖文件(对应的.c文件。对应是指:文件名除后缀外,其余都相同的两个文件),而且使用正确的命令来重建这个目标文件。对于上边的例子,此默认规则就使用命令“cc -c main.c -o main.o”来创建文件“main.o”。对一个目标文件是“N.o”,依赖文件是“N.c”的规则,完全可以省略其规则的命令行,而由make自身决定使用默认命令。此默认规则称为make的隐含规则。

在书写Makefile时,我们就可以省略掉描述.c文件和.o依赖关系的规则,而只需要给出那些特定的规则描述(.o目标所需要的.h文件)。因此上边的例子就可以以更加简单的方式书写,我们同样使用变量“objects”。Makefile内容如下:

# sample Makefile
objects = main.o kbd.o command.o display.o \
	insert.o search.o files.o utils.o

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

main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h

.PHONY : clean
clean :
	rm edit $(objects)

这种格式的Makefile更接近于我们实际应用。make的隐含规则在实际工程的make中会经常使用,它使得编译过程变得方便。几乎在所有的Makefile中都用到了make的隐含规则,make的隐含规则是非常重要的一个概念。

清除工作目录过程文件

规则除了完成源代码编译之外,也可以完成其它任务。例如:前边提到的为了实现清除当前目录中编译过程中产生的临时文件(edit和哪些.o文件)的规则:

clean :
	rm edit $(objects)

在实际应用时,我们把这个规则写成如下稍微复杂一些的样子。以防止出现始料未及的情况。

.PHONY : clean
clean :
	-rm edit $(objects)

这两个实现有两点不同:

  1. 通过.PHONY特殊目标将“clean”目标声明为伪目标。避免当磁盘上存在一个名为“clean”文件时,目标“clean”所在规则的命令无法执行。
  2. 在命令行之前使用-,意思是忽略命令“rm”的执行错误。

Makefile 注释符

  • 如果某行的第一个非空字符为 #,则此行会被 make 解释为注释行(命令行除外,如果 Tab 字符之后使用 # 字符,则会被 make 解释为命令行)。

  • 注释行的结尾如果存在反斜线(\),则下一行也被作为注释行。

本文涉及的术语

  • Makefile: 用于定义Linux 程序的编译规则。
  • make命令: 用于解释执行Makefile文件。·

参考资料

[1] Makefile 基础教程
[2] 跟我一起写Makefile
[3] GNU make中文手册ver - 3.8 翻译整理:徐海兵
[4] 跟我一起写 Makefile(一)
[5]Makefile 编译与链接选项及CFLAGS与LDFLAGS示例说明

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值