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 会被重新链接。
$< 和 $@称为自动化变量
$@表示目标文件
$<表示依赖文件
一般来说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
( f i l t e r (filter %.o, (filter(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需要一点参数,所以定义成变量比较合适