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 ”
- 变量的使用
- 一般是变量 = 依赖,首先先把变量声明好
- 调用变量时使用 $(OBJ)
- 自动化变量
$< 和 $@称为自动化变量
$@表示目标文件
$<表示依赖文件
makefile自动推导(隐式规则)
一般来说make会自动推导出依赖文件,例如我们的main.o文件就是由main.c得到的,但我们没必要去写:
main.o:main.c defs.h
cc -c main.c
直接用 main.o:defs.h
也只是.o对于.c,其余依赖也要补充
引用其余的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
( 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 键之后),标记为不管命令出不出错都认为是成功的
嵌套执行make
在不同的文件夹中编写不同的makefile,再由总控的makefile进行控制,可以有效的进行模块化的维护
例如我们在subdir下有个makefile,如果我们要在总控makefile中使用:
subsystem:
cd subdir && $(MAKE)
我们的make需要一点参数,所以定义成变量比较合适