代码的编译过程:
- 无论是c , c++ , php , 等 首先需要将源文件编译成中间代码文件 (Object File) 此过程 ,美其名曰:编译
- windows 下称为 .obj 文件,
- unix 下是 .o 文件
- 编译主要检查语法的正确性
- 大量的中间代码文件 合并成可执行文件 此过程,美其名曰:链接 【也有人叫编排】
- 主要链接函数和全局变量
- 链接需要明显指出中间目标文件名称
ps: 当中间文件太多的时候编译起来就很不方便,通常我们会将其打包,windows 下叫库文件(Library File , .lib 文件),Unix 下 是 Archive File , 也就是 .a 文件
Make 概念
Make 这个词,英语意思为“制作”。 Make 命令直接使用了这个意思,就是要做出某个文件出来。 比如我要做出文件 a.txt ,就可以直接使用
make a.txt
但是,如果你真的输入这条命令,它不会起到任何作用,因为 Make 本身并不知道,如何做出 a.txt 需要有人告诉它怎么去做出a.txt 文件。
用例一:
a.txt 文件依赖于 b.txt 和 c.txt 这两个文件,是后面两个文件内容的集合。那么make 需要知道下面的规则。
a.txt: b.txt c.txt #第一步 cat b.txt c.txt > a.txt #第二步
解析:
第一步:确认 b.txt 和 c.txt 这两个文件必须已经存在。
第二步: 使用cat 命令将这两个文件合并起来了,输出为新文件。
总结:
- 像上面描述的这种规则默认都写在了一个叫 Makefile 的文件中,而我们大名鼎鼎的 make 命令就依赖这个文件进行构建。
- Makefile 也可以写成 makefile
- 如果你实在不想使用makefile 或者 Makefile 来命名你的规则文件,例如使用 aaaa.txt ,你可以使用 make 的 -f 参数来指定 : make -f aaaa.txt
- make 只是一个根据指定Shell 命令进行构建的工具而已,它的规则:
- 你规定要构建哪些文件 【 target】
- 它依赖于哪些源文件 【 prerequisits】
- 当哪些文件由变动是,如何重新构建它 【 commands】
Makefile 文件格式
构建规则都写在了Makefile 文件中,要学会如何Make 命令,就必须先学会如何编写Makefile 文件
概述:
Makefile 文件由一系列规则构成,格式:
<target> :<prerequisites>
<commands>
解析:
target : 目标,必须的
prerequisites: 前置条件,可选
commands: 命令,它前面需要跟着一个空格 , 可选
目标<target>
一个目标(target) 就构成了一个规则,目标通常是文件名,指明make 命令所要构建的对象,比如上文的 a.txt 。 目标可以是一个文件名,也可以是多个文件名,之间用空格隔开。
除了文件名,目标还可以是操作名或任意字母组成的字符串,这类目标称为 “伪目标” (phony target)
上面代码的目标是 bb , 它不是文件名 , 而是一个操作名,属于“伪目标” , 作用是列举根目下的文件或目录
ps : 伪目标,系统是不会去检查bb 文件是否存在的。而是每次执行都执行对应的命令。
但是,如果目录中刚好有个文件就叫bb , 那么bb 也就变成了“目标”了,避免这种情况,可以使用如下命令
.PHONY: bb clean: ls /
通过 ".PHONY" 可以用来指定伪目标,这种内置目标名还有很多,可以查看手册
前置条件 prerequisites
前置条件通常是一组文件名,之间用空格分隔。它指定了“目标” , 是否重现构建的判断标准:只要有一个前置文件不存在或者被更新了,目标就需要重新构建
用例一:
result.txt: source.txt cp source.txt result.txt
解析:
上面的代码中,构建了目标 result.txt , 它的前置条件是 source.txt 已经存在了,那么 make result.txt 可以正常运行,否则必须再写一条规则,来生成 source.txt
用例二:
source.txt: echo " hello world" > source.txt
解析:
上面的代码中,source.txt 后面没有前置条件 , 这意味着这个“source.txt”目标不依赖其它文件,直接运行它的 commands 就好了。只要source.txt 不存在 , 每次调用 make source.txt 它都会生成 source.txt
如果你连续执行两次 make source.txt , 它只会执行第一次就不会再执行了
用例三:
source: a.txt b.txt c.txt
解析:
source 是一个伪目标 , 它只有三个前置条件 , 没有任何的 commands 。作用:一次性创建a.txt , b.txt , c.txt 这三个文件。
命令(commands)
命令就是一堆shell 命令组成的,它是构建“目标” 的具体指令,它的运行结果通常就是生成目标文件。每行命令之前必须有一个tab键,如果要使用其它键,可以使用内置变量 .RECIPEPREFIX 声明
用例一:
.RECIPEPREFIX = > all: > echo Hello , world
解析:
上面的代码就是用 .RECIPEPREFIX 来指定了每行命令开头 是 > 而不再是 tab键了
用例二:
var-lost: export foo=bar echo "foo=[$$foo]"
解析:
commands 中的每条独自换行的命令都是独立分开的,不在同一个进程了,所以数据也是不共享的
第一行的 定义的变量 foo 是不能在 第二行中使用的
解决办法就是:
- 将这两个shell命令合并为一条,中间用 分号 ";" 隔开
- 如果感觉太才了,那么就就可以在 分号 后面加个 反斜杆进行转义 " "
- 感觉加 太扯,那么你可以使用 内置命令 .ONESHELL 来指定
var-kept: export foo=bar; echo "foo=[$$foo]" 或者 var-kept: export foo=bar; echo "foo=[$$foo]" 或者 .ONESHELL var-kept: export foot=bar; echo "foo=[$$foo]"
Makefile 文件的语法
注释 :
在Makefile 文件中 注释符是: #
回声:
正常情况下 , make 会 打印每条命令 , 然后再执行, 这叫做回声(echoing).
如果你想关闭回声,直接在shell 命令行前面加一个 @ 符号就好了
test: # this is test @echo TODO
通配符:
通配符 , 用来指定符合条件的文件名的。 Makefile 的通配符与Bash 一致 , 主要有 * , ? 比如以o 结尾的文件 可以这样 *.o
模式匹配
Make 命令 允许 对文件名 , 进行类似正则运算的匹配 , 主要用到的匹配符是 % , 比如需要找到 当前目录下 有 f1.c 和 f2.c 两个源码文件,需要将它们编译伪对应的对象文件
%.o : %.c 等同于 f1.o :f1.c f2.o :f2.c
变量和赋值符
Makefile 中允许使用 等号 “ = ” 来 自定义变量
bb = Hello World test: @echo $(bb) @echo $$HOME
解析:
变量bb 等于 Hello World , 变量调用是通过 $() 来调用的。
如果你要调用Shell 自带的一些变量,那么你需要在 美元符号前面再加一个美元符号, 因为 Makefile 中会对美元符号进行转义
Makefile 中提供了 4中赋值运算符( = , := , ?= , += ) :
#执行时扩展, 允许递归扩展 key = value #定义是扩展 key := value #只有在该变量为空时才设置值 key ?= value #将值追加到变量的尾端 key += value
内置变量
Make 命令提供了一系列的内置变量,详情见手册
自动变量
Make 命令提供了一些自动变量,他们的值与当前规则有关。
(1)$@ :
代指当前目标,就是Make 命令当前构建的那个目标, 比如 make foo 的 $@ 就值 foo
a.txt b.txt: touch $@ 等同于 a.txt: touch a.txt b.txt: touch b.txt
(2) $<
代指第一个前置条件
a.txt : b.txt c.txt cp $< $@ 等同于 a.txt: b.txt c.txt cp b.txt a.txt
(3) $?
代指比目标更新的所有前置条件 【即:更新的依赖文件】,比如, t1: p1 p2 , p2 的时间戳比t1 新,那么 $? 就代指 p2
(4) $^
代指所有前置条件
(5) $*
代指匹配符 % 匹配成功的部分 , 比如 %匹配 f1.txt 中的 f1 , 那么 $* 就表示 f1
(6) $(@D) 和 $(@F)
$(@D) 和 $(@F) 分别代指: $@ 的目录名和文件名
(7)$(<D) 和 $(<F)
$(<D) 和 $(<F) 分别代指: $< 的目录名和文件名
其它的自动变量请查看手册
用例一:
dest/%.txt: src/%.txt @[ -d dest ] || mkdir dest cp $< $@
解析:
1. 上面的代码就是将 src 目录下的 txt 文件 拷贝到 dest 目录下 。
2. @[-d dest] || mkdir dest : 是说 判断 dest 目录是否存在,不存在则创建之 , 并且关闭回声
3. cp $< $@ : 将 src 中的 txt 拷贝到 dest 目录下
判断和循环
Makefile 使用Bash 语法,完成了 判断和循环
用例一:
ifeq ($(CC),gcc) libs=$(libs_for_gcc) else libs=$(normal_libs) endif
用例二:
LIST = one two three test: for i in $(LIST); do echo $$i; done 等同于 test: for i in one two three; do echo $i; done
函数
Makefile 还可以使用函数 , 它许多内置函数 见手册
格式:
$(function arguments) 或者 ${function arguments}
Makefile 实例
编译 c语言项目
edit: main.o kbd.o command.o display.o cc -o edit main.o kbd.o command.o display.o main: main.c defs.h cc -c main.c kdb.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 cc -c display.c clean: rm edit main.o kbd.o command.o display.o .PHONY: edit clean