makefile是用户自行完成的IDE(integrated development environment集成开发环境)程序,与传统的操作系统下的编译不同,makefile可以通过用户自行安排,决定文件的编译顺序,决定哪些文件需要编译,哪些文件需要重复编译等各种复杂的操作
可自由编译的好处,能够指定文件编译,将繁杂的编译方法集成到一个makefile文件中,然后使用make进行一键编译,极大的提高了开发效率
关于gcc编译成可执行文件的命令,以及对应命令下生成的文件后缀
命令 | 文件 |
---|---|
gcc -E 预处理(宏展开) | *.i |
gcc -S 编译(生成汇编代码) | *.s |
gcc -c 汇编(生成机器码) | *.o(unix下) |
gcc -o 链接(把大量的.o文件合成生成可执行文件) (主要链接的是函数和全局变量)对于a.out文件的重命名 | 可执行文件 |
把大量的.o文件合并生成可执行文件
主要链接的就是函数和全局变量,而存放函数和全局变量的地方通常称为库文件 .lib文件
在unix下也叫做.a文件
在编译时,编译器只会管你程序的语法是否正确,函数和变量是否已经声明,函数如果没有声明也没关系,只会报警告,而在链接这一步,链接器会在所有的.o文件中寻找这个函数,如果没有找到,就报链接错误码
所有的makefile的实际都是:编译+链接
只要记住编译+链接就行
一共有五大原则:显示规则、隐式规则、变量定义、文件指示、注释说明
目标…:依赖…
命令
…
…
目标:可以是个文件(object文件或者是可执行文件),也可以是个标签label
依赖:要生成target目标的所需要文件或者是目标
命令:就是使用make之后所要执行的命令(shell命令)
当依赖中的文件比目标中的文件要新的话,就必须执行命令中的内容,这就是makefile的核心
其中“ \ ”表示换行符
clean不是一个文件,而是一个动作名,我们要使用clean,就需要make clean。而且clean后面也不需要出现依赖。
所以我们也可以在makefile中定义不用的编译或者与编译无关的命令,比如程序的打包,程序的备份等
怎么使得make时会判断哪些是不需要makefile的命令呢,例如clean这个动作写入makefile时当我们使用make时不会自动执行,其中的原理在哪?
这就谈到make是如何工作的
第一个target并没有直接或间接利用clean
clean之后没有存在依赖
如果我们改变了“command.h”,那么,kdb.o、command.o 和 files.o
都会被重编译,并且,edit 会被重新链接。
直接使用=表示,在变量的值在使用时变量才会递归展开
例如:
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>
$< 和 $@称为自动化变量
$@表示目标文件
$<表示依赖文件
一般来说make会自动推导出依赖文件,例如我们的main.o文件就是由main.c得到的,但我们没必要去写:
main.o:main.c defs.h
cc -c main.c
直接用 main.o:defs.h
也只是.o对于.c,其余依赖也要补充
include + Makefilename(路径或者通配符)
路径如果没有设置,将会选择是当前文件夹下的makefile
“如果 make 执行时,有“-I”或“–include-dir”参数,那么 make 就会在这个参数所指定”
这个后面再说
如果在当前环境中使用了环境变量MAKEFILES
那么make时会把这个变量MAKEFILES中的值做一个inlcude的动作
但是这个环境变量中的值如果是其他的makefile文件,则会用空格分开
这个变量中的target值在make时也不会起作用,依赖文件发生错误也不会管
如果要使用一系列的类似的文件,一般使用通配符(* ,?,[…])来概括:
如果文件名中有通配符,我们可以使用 “\ *”来表示真正的“ * ”字符
object = *.o 表示的就是 *.o文件,而没有展开
object = $(wildcard *.o) 就可以得到一个展开后的变量合集
VPATH表示目标或者依赖文件的寻找目录
比如:VPATH = src:…/header
表示目标或者依赖文件寻找的目录为src 或者上一层的header目录
其中:表示目录的分隔
可以指定某个文件在哪个目录下寻找: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: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 键之后),标记为不管命令出不出错都认为是成功的
在不同的文件夹中编写不同的makefile,再由总控的makefile进行控制,可以有效的进行模块化的维护
例如我们在subdir下有个makefile,如果我们要在总控makefile中使用:
subsystem:
cd subdir && $(MAKE)
我们的make需要一点参数,所以定义成变量比较合适
如果你想要总控的makefile中的变量传递到下级的makefile
可以使用:export
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
我们可以单独定义一个新的命令包,使用命令序列define和endef来定义一个命令包,以供后面使用
eg:
define run-mvv 表示命令包的名字
mvv $(firstword $^) 表示第一个命令
mv first.c $@ 表示一个命令的作用
endef
将命令包使用时:
%.d : %.n
$(run-mvv)
使用命令包,就会依次执行各行命令,由于我只定义了一个命令,就执行一个。但是若定义了多个,则依次执行