makefile教程
文章目录
makefile是什么:
makefile是用户自行完成的IDE(integrated development environment集成开发环境)程序,与传统的操作系统下的编译不同,makefile可以通过用户自行安排,决定文件的编译顺序,决定哪些文件需要编译,哪些文件需要重复编译等各种复杂的操作
使用makefile的好处:
可自由编译的好处,能够指定文件编译,将繁杂的编译方法集成到一个makefile文件中,然后使用make进行一键编译,极大的提高了开发效率
关于程序的编译和链接:
关于gcc编译成可执行文件的命令,以及对应命令下生成的文件后缀
命令 | 文件 |
---|---|
gcc -E 预处理(宏展开) | *.i |
gcc -S 编译(生成汇编代码) | *.s |
gcc -c 汇编(生成机器码) | *.o(unix下) |
gcc -o 链接(把大量的.o文件合成生成可执行文件) (主要链接的是函数和全局变量)对于a.out文件的重命名 | 可执行文件 |
- 链接 link
把大量的.o文件合并生成可执行文件
主要链接的就是函数和全局变量,而存放函数和全局变量的地方通常称为库文件 .lib文件
在unix下也叫做.a文件
- 编译
在编译时,编译器只会管你程序的语法是否正确,函数和变量是否已经声明,函数如果没有声明也没关系,只会报警告,而在链接这一步,链接器会在所有的.o文件中寻找这个函数,如果没有找到,就报链接错误码
makefile内核
所有的makefile的实际都是:编译+链接
只要记住编译+链接就行
- 如果这个工程没有被编译过,那么所有的.c文件都要编译并被链接
- 如果这个工程已经编译,但是修改了.c文件,我们需要编译修改的.c文件,并连接目标程序
- 如果这个工程已经编译,但是修改了.h文件,我们需要编译修改的.h文件,并连接目标程序
makefile的使用规则:
一共有五大原则:显示规则、隐式规则、变量定义、文件指示、注释说明
显示规则
目标…:依赖…
命令
…
…
-
目标:可以是个文件(object文件或者是可执行文件),也可以是个标签label
-
依赖:要生成target目标的所需要文件或者是目标
-
命令:就是使用make之后所要执行的命令(shell命令)
当依赖中的文件比目标中的文件要新的话,就必须执行命令中的内容,这就是makefile的核心
其中“ \ ”表示换行符
clean不是一个文件,而是一个动作名,我们要使用clean,就需要make clean。而且clean后面也不需要出现依赖。
所以我们也可以在makefile中定义不用的编译或者与编译无关的命令,比如程序的打包,程序的备份等
怎么使得make时会判断哪些是不需要makefile的命令呢,例如clean这个动作写入makefile时当我们使用make时不会自动执行,其中的原理在哪?
这就谈到make是如何工作的
make是如何工作的
- make会首先在当前文件夹中搜寻makefile或者是Makefile文件
- 读入被include的其他makefile
- 初始化定义的变量
- 为所有的目标建立依赖关系链
- 顺序执行第一个target,并且把target作为最终的目标文件,也就是一切都是为了第一个target
- make会比较依赖和目标的文件时间戳,如果依赖比目标的文件时间新,则会重新生成target文件
- 根据最终目标文件的依赖,首先会查找是否存在这些依赖,如果不存在则会按照是否makefile中存在依赖文件的生成方式,有的话就会执行下去,没有的话就直接退出报错
- 为什么要单独对clean进行make,是因为:
第一个target并没有直接或间接利用clean
clean之后没有存在依赖
如果我们改变了“command.h”,那么,kdb.o、command.o 和 files.o
都会被重编译,并且,edit 会被重新链接。
makefile中使用变量(变量定义)
- 变量的定义
- 就好比C语言中宏的作用,当我们在添加或者修改依赖时,至少要修改三遍以上,所以干脆用个宏,修改一处就可以,一般宏设置为 “OBJ、OBJECT、OBJS ”
直接使用=表示,在变量的值在使用时变量才会递归展开
例如:
VAR1 = foo
VAR2 = $(VAR1) bar
VAR1 = baz
则最终输出VAR2的值为:baz bar而不是foo bar,因为VAR1在VAR2被展开时的值是最后一次赋值的 “baz”。
first = $(second)
second = $(third)
third = forth
all :
echo $(first)
即打印出来的值就是forth
只是简单展开,也就是顺序展开,但是=是递归展开
x := first
y := $(x) second
x := third
则y的值为first second 、 x的值为third
但是不可以倒推比如:
x := $(y) second
y := first
则x的值不能是first second而是second
?= 表示这个变量如果以前被定义过,那这个变量什么也不做,如果以前没有被定义过,那这个变量就赋值为等号后面的值
First ?= bar
变量值的替换
$(var:a=b),意思就是将var变量中所有a替换成为b
eg1:
first := 1.c 2.c 3.c
second := $(first:.c = .o)
eg2:静态模式下的变量替换
first := 1.c 2.c 3.c
second := $(first:%.c = %.o)
把变量的值再当成一个变量
eg:
x = y
y = z
a := $ ($ (x))
$ (x)的值为y,$ (y)的值为z,所以$(a)的值为z
使用+=操作符来给变量追加值
如果变量之前没有定义过,+=就会自动变成=
如果前一次变量的定义赋值是 :=,那么+=就会以 :=作为其赋值符
通常,当我们make时的装有命令行参数的变量在makefile中的赋值就会被忽略。如果我们想在makefile中设置这类参数的值,我们就可以使用 “override”指示符
override < variable> =< value>
override < variable> :=< value>
override < variable> +=< value>
还有一种设置变量值的方法就是使用define,可以去定义多个变量,多个变量分开可以使用换行,结束时使用endef,和命令包的方法一样。如果你的一行开头没有用[tab],就会当成变量,否则就会当成命令
define first
second
third
endef
一般来说,在makefile中定义的变量为全局变量,在整个文件中通用,但是例如 $<这种自动化变量则称为规则型变量
当然我们可以为某个目标自己设置局部变量,他可以与全局变量同名因为此目标只作用于局部
< target…> : < variable assignment>
< target…> : override < variable-assignment>
< variable-assignment>表示赋值表达式:=、+=、?=、:=
pro : CFLAGS = -g
pro : 1.o 2.o 3.o
cc $(CFLAGS) 1.o 2.o 3.o
表示只要在pro这个目标引发的规则下CFLAGS都为-g
我们由目标变量可知,目标也可以成为一个变量。模式变量的好处就是我们可以给定一个模式,然后按照这个模式 我们可以将所有的变量定义在符合这个模式下的目标中
例如make的模式一般至少含有一个%,所以我们可以在%的模式下给所有目标定义目标变量
%.o : CFLAGS = -o
< pattern…> : < variable assignment>
< pattern…> : override < variable-assignment>
- 自动化变量
$@表示目标文件
$ <依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%“)定义的,那么”$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的
$ %仅当目标为函数库文件时,表示规则中的目标成员名,例如 foo.a(bar.o),$%表示foo.a
$?所有比目标新的依赖目标的集合,以空格分开
用于更新补丁操作
例如lib库是由1.o、2.o、3.o、4.o所支持的,但我们会不定时的更新1234的包
这样就可以用这个自动化变量
lib : 1.o 2.o 3.o 4.o
ar r lib $?
$^所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份
$+所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量不会去除重复的依赖目标
$*这个变量表示,目标模式中%(包括%)以及之前的部分
目标是dir/a.foo.b
目标的模式是a.%.b
那么$*表示dir/a.foo
$ (@D)表示 "$ @“的 目录 部 分( 不以斜杠作为结尾 ),如果”$@"值是
“dir/foo.o”,那么"$ (@D)“就是"dir”,而如果"$ @"中没有包含斜杠的话,
其值就是"."(当前目录)。
$ (@F)表示"$ @“的文件部分,如果”$ @“值是"dir/foo.o”,那么"$(@F)"就是
“foo.o”,“$ (@F)“相当于函数”$(notdir $@)”。
“$(*D)” | “$(*F)” |
---|---|
“$(%D)” | “$(%F)” |
“$(<D)” | “$(<F)” |
“$(^D)” | “$(^F)” |
“$(+D)” | “$(+F)” |
“$(?D)” | “$(?F)” |
makefile自动推导(隐式规则)
一般来说make会自动推导出依赖文件,例如我们的main.o文件就是由main.c得到的,但我们没必要去写:
main.o:main.c defs.h
cc -c main.c
直接用 main.o:defs.h
也只是.o对于.c,其余依赖也要补充
“隐式规则”是一种惯例,make会按照这种规则来悄悄运行,隐式规则会使用我们的系统变量,我们可以通过改变系统参数的值来控制隐含规则运行时的参数,比如系统变量“CFLAGS”可以控制编译时的编译器参数
例如:
CFLAGS主要是设置编译器的编译选项,比如警告级别、优化选项、包含路径等
CFLAGS = -wall -O2 -Iinclude
-wall :启用所有警告信息
-O2:启用中级优化
-Iinclude:指定包含文件的搜索路径为include目录
main.o: main.c
gcc -c $< -o $@ $(CFLAGS)
eg:
foo : 1.o 2.o
cc -o foo 1.o 2.o $(CFLAGS) $(LDFLAGS)
进一步省略了以下两部:
1.o : 1.c
cc -c 1.c $(CFLAGS)
2.o : 2.c
cc -c 2.c $(LDFLAGS)
在make的隐含规则库中,每一条隐含规则都在库中有其顺序,越靠前的则是越被经常使用的,所以有时候这也是一个弊端,为啥:
例如 foo.o : foo.p
依赖文件foo.p有时候会变得没有意义,因为如果你存在foo.c文件,C编译器就会自动使用将.c文件的foo编译成.o文件,因为Pascal的规则出现在C的规则之后,系统使用C编译器认为这个任务就完成了,就不会再寻找下一条规则实际上不是你想要de
那我们如何避免呢:
只能写出依赖的规则,避免隐式规则
隐式规则一览
这里包含着所有的预先设置,也就是make内建的隐含规则,当然我们也可以使用make -r或者–no-builtin-rules选项来取消所有的预设置的隐含规则
在这里只写包含有编译各种程序的哪些规则,具体自己查找了解即可:
编译 C 程序的隐含规则
编译 C++程序的隐含规则
编译 Pascal 程序的隐含规则
编译 Fortran/Ratfor 程序的隐含规则
预处理 Fortran/Ratfor 程序的隐含规则
编译 Modula-2 程序的隐含规则
汇编和汇编预处理的隐含规则
链接 Object 文件的隐含规则
Yacc C 程序时的隐含规则
Lex C 程序时的隐含规则
Lex Ratfor 程序时的隐含规则
从 C 程序、Yacc 文件或 Lex 文件创建 Lint 库的隐含规则
隐含规则中使用的变量
在隐含规则中使用的变量要么是之前预先设置的,要么是通过命令行传入的,要么是在环境变量中设置这些值。
例如:如果我改变C程序的隐含规则,make的默认编译命令是cc,我把$(CC)重定义为gcc,并且改变CFLAGS的值,重定义为-g,那么按照C程序的隐含规则,就会按照:
gcc -c -g $(CPPFLAGS)的样子来执行
通过上述的例子,我们可以将$ (CC)认为是于命令相关的变量,可以将$(CFLAGS)认为是与参数相关变量
关于命令的变量《隐含规则》
变量 | 功能 |
---|---|
AR | 函数库打包程序。默认命令是“ar”。 |
AS | 汇编语言编译程序。默认命令是“as”。 |
CC | C 语言编译程序。默认命令是“cc”。 |
CXX | C++语言编译程序。默认命令是“g++”。 |
CO | 从 RCS 文件中扩展文件程序。默认命令是“co”。 |
CPP | C 程序的预处理器(输出是标准输出设备)。默认命令是“$(CC) –E |
FC | Fortran 和 Ratfor 的编译器和预处理程序。默认命令是“f77”。 |
GET | 从 SCCS 文件中扩展文件的程序。默认命令是“get”。 |
LEX | Lex 方法分析器程序(针对于 C 或 Ratfor)。默认命令是“lex”。 |
PC | Pascal 语言编译程序。默认命令是“pc”。 |
YACC | Yacc 文法分析器(针对于 C 程序)。默认命令是“yacc”。 |
YACCR | Yacc 文法分析器(针对于 Ratfor 程序)。默认命令是“yacc –r”。 |
MAKEINFO | 转换 Texinfo 源文件(.texi)到 Info 文件程序。默认命令是“makeinfo”。 |
TEX | 从 TeX 源文件创建 TeX DVI 文件的程序。默认命令是“tex”。 |
TEXI2DVI | 从 Texinfo 源文件创建军 TeX DVI 文件的程序。默认命令是 |
WEAVE | 转换 Web 到 TeX 的程序。默认命令是“weave”。 |
CWEAVE | 转换 C Web 到 TeX 的程序。默认命令是“cweave”。 |
TANGLE | 转换 Web 到 Pascal 语言的程序。默认命令是“tangle”。 |
CTANGLE | 转换 C Web 到 C。默认命令是“ctangle”。 |
RM | 删除文件命令。默认命令是“rm –f”。 |
关于命令参数的变量
变量 | 功能 |
---|---|
ARFLAGS | 函数库打包程序 AR 命令的参数。默认值是“rv”。 |
ASFLAGS | 汇编语言编译器参数。(当明显地调用“.s”或“.S”文件时)。 |
CFLAGS | C 语言编译器参数。 |
CXXFLAGS | C++语言编译器参数。 |
COFLAGS | RCS 命令参数。 |
CPPFLAGS | C 预处理器参数。( C 和 Fortran 编译器也会用到) |
FFLAGS | Fortran 语言编译器参数。 |
GFLAGS | SCCS “get”程序参数。 |
LDFLAGS | 链接器参数。(如:“ld”) |
LFLAGS | Lex 文法分析器参数。 |
PFLAGS | Pascal 语言编译器参数。 |
RFLAGS | Ratfor 程序的 Fortran 编译器参数。 |
YFLAGS | Yacc 文法分析器参数 |
引用其余的makefile(文件指示)
- 使用include关键字
include + Makefilename(路径或者通配符)
路径如果没有设置,将会选择是当前文件夹下的makefile
“如果 make 执行时,有“-I”或“–include-dir”参数,那么 make 就会在这个参数所指定”
这个后面再说
- 环境变量MAKRFILES
如果在当前环境中使用了环境变量MAKEFILES
那么make时会把这个变量MAKEFILES中的值做一个inlcude的动作
但是这个环境变量中的值如果是其他的makefile文件,则会用空格分开
这个变量中的target值在make时也不会起作用,依赖文件发生错误也不会管
通配符的使用
如果要使用一系列的类似的文件,一般使用通配符(* ,?,[…])来概括:
如果文件名中有通配符,我们可以使用 “\ *”来表示真正的“ * ”字符
object = *.o 表示的就是 *.o文件,而没有展开
object = $(wildcard *.o) 就可以得到一个展开后的变量合集
文件搜寻
- 特殊变量 VPATH
VPATH表示目标或者依赖文件的寻找目录
比如:VPATH = src:…/header
表示目标或者依赖文件寻找的目录为src 或者上一层的header目录
其中:表示目录的分隔
- 关键字 vpath
可以指定某个文件在哪个目录下寻找:vpath < pattern模式> < directories>
vpath %.h …/header
表示所有的.h文件都可以在上一层的header目录下找到
vpath %.h
表示符合模式%.h的文件目录
vpath
表示清除所有已被设置好了的文件搜索目录
当然目录的优先级最高的还是当前目录下的
连续使用vpath关键字来指定不同的搜索方式:
vpath %.c first
vpath % second
vpath %.c third
其表示“.c”结尾的文件,先在“first”目录,然后是“second”,最后是“third”目录
这里second比较特殊,什么文件都可以在second中搜索,比较浪费时间
可以通过改写成:
vpath %.c first:third
vpath % second
而上面的语句则表示“.c”结尾的文件,先在“first”目录,然后是“third”目录,最后才是“second”目录
伪目标
什么是伪目标
伪目标不是一个文件,所以无法使用依赖关系,我们必须使用一个特殊的标签来注明,使用时也得使用make+标签来表示
伪目标的使用要求
- 不能与其他目标名重复,我们可以使用.PHONY来指明这是个伪目标
.PHONY:clean
- 总是被执行的
伪目标同样可作为默认目标,就可以放在开头
all : prog1 prog2 prog3
.PHONY : all
由于伪目标总是被执行的,all作为一个伪目标,如果说prog1更新了,或者是prog3更新了,all也会更新
- 伪目标也可以成为依赖
.PHONY : cleanall cleanobj cleaniff
cleanall : cleanobj cleaniff
多目标 涉及到函数后续
有时候多个目标可以共同使用一个依赖,这个叫做多目标
静态模式
更好的服务于多目标的使用
目标集合:目标集模式 依赖目标集模式
targets:%.o :%.c
表示对所有的.o文件进行了二次定义,也就是说去掉了.o的结尾,加上了.c的这个结尾
eg1:
objects = first.o second.o
$(object) : %.o : %.c
$(CC) -c $(CFLAGS) $< -o $@
也就是在objects这个变量中 将所有的.o文件修改成.c文件,所以修改之后我们的依赖目标集就是 first.c second.c
所以 $ <表示依赖目标集,$@表示目标集
eg2:
file = 1.o 2.o 3.elc
$ (filter %.o,$(file)) : %.o : %.c
gcc -c $< -o $@
$(filter %.elc,%(file)) : %.elc : %.el
emacs -f batch-byte-compile $<
filter是个函数,第一个参数是过滤条件,第二个参数过滤源
自动生成依赖性
对于每个.c文件,我们都得清楚包含了哪些头文件,这就大大增加了压力,但GNU编译器存在自动搜索头文件功能,使用 -MM参数
gcc -MM main.c = main.o : main.c defs.h
我们要为每个文件生成.d 文件之后才能自动化生成依赖关系
%.d : %.c
gcc -M $< > @ . @. @.$$$
书写命令
- “@”
我们用“@”字符在命令行前,那么,这个命令将不被 make 显示出来
- “;”
如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令。比如你的第一条命令是 cd 命令,你希望第二条命令得在 cd 之后的基础上运行,那么你就不能把这两条命令写在两行上,而应该把这两条命令写在一行上,用分号分隔。
- “-”
加一个减号“-”(在 Tab 键之后),标记为不管命令出不出错都认为是成功的
嵌套执行make
在不同的文件夹中编写不同的makefile,再由总控的makefile进行控制,可以有效的进行模块化的维护
例如我们在subdir下有个makefile,如果我们要在总控makefile中使用:
subsystem:
cd subdir && $(MAKE)
我们的make需要一点参数,所以定义成变量比较合适
如果你想要总控的makefile中的变量传递到下级的makefile
可以使用:export<variable…>
export variable = value
其中如果定义了MAKEFLAGS和SHELL变量,将总会传递给下级的makefile
make时的命令行的参数就会随着MAKEFLAGS传递到下级makefile
就会在终端打印信息
make:Entering directory ‘/home/hchen/gnu/make’
make:Leaving directory `/home/hchen/gnu/make’
使用make -w可以在总控makefile执行时查看 执行到哪一个下级makefile中
如果你不想要总控的makefile中的变量传递到下级的makefile
可以使用:unexport<variable…>
定义命令包
我们可以单独定义一个新的命令包,使用命令序列define和endef来定义一个命令包,以供后面使用
eg:
define run-mvv 表示命令包的名字
mvv $(firstword $^) 表示第一个命令
mv first.c $@ 表示一个命令的作用
endef
将命令包使用时:
%.d : %.n
$(run-mvv)
使用命令包,就会依次执行各行命令,由于我只定义了一个命令,就执行一个。但是若定义了多个,则依次执行