Title:makefile 学习
0 吐槽
发现自己一年没更新博客了,掏出存货来刷一下存在感 ( ̄▽ ̄)“…
主要原因 是:① 忙于实习;② 懒于撰稿;③ 完美主义越来越强,note回看就修订觉得发不出手、过半个月回看又修订还是发不出手,结果懒得发了… ( ̄▽ ̄)”
先随手发;之后本地修订后,再回来改线上的吧~
1 Basic Concept
1.1 是什么
make是GNU里的一个utility,可实现方便的自动化编译,在unix和linix都可以写;
首先要写一个文本叫 makefile,然后调用命令make
,terminal即会按makefile的内容对工程或源文件进行 re-compile or link;
makefile作用是对bash等命令进行方便地一键重用,故可以用makefile进行compile、link、simulation、执行程序等都可以,反正要执行什么 bash 指令自定义写进去就完事儿了;
makefile中写的内容,表示的是 how、when对什么文件进行什么操作,从而让我们后续可以方便的 “一键remake”。
1.2 OS概念复习
compile 和 link 的概念
- 源码compile(检查语法,有函数声明即可通过)后得到中间文件(windows是
.obj
、UNIX是.o
); - 对中间文件进行打包得到库文件(windows是
.lib
、UNIX是.a
Archive File); - 把它们link(合成一坨一个可执行文件)——链接函数和全局变量等,找不到函数实现就会出现link error——得到可执行文件(windows是
.exe
、UNIX无后缀);
windows下的make
一般默认 make
是Unix OS下;
若是windows下,得看IDE——Delphi是用make,VC++用nmake;
windows下要想make就得下载MinGW编译器了.
1.3 怎么用make
-
①terminal中敲
make
,OS会自动按顺序寻找文件GNUmakefile
、makefile
、Makefile
; -
②也可手动指定执行makefile文件(很少用,一般直接
make
)make -f <...makefile>
1.4 makefile 与 shell
-
makefile和shell的区别
作用:shell是为了重用terminal内的命令服务的,是系统程序员与OS内核的交互接口;makefile是用于工程compile/link用的;makefile内可写shell命令、调用shell脚本,如:
+ ./xxx.sh
;写法区别:
- shell内 =号不允许有空格,makefile内 =号空格无碍;
- shell内开头要写
#!/bin/bash
,后缀名是.sh
; - shell内是自上而下执行,有数组、循环等;makefile内执行shell命令是一行独立一个进程,若想一个进程统一执行,得在末尾用
\
表同一行。 - shell内通配符:
*
;makefile内通配符:%
; - shell内变量用
{}
,命令串用()
;makefile中访问变量用$()
或${}
都行;
-
shell与cshell的区别
linux下提供了很多种shell: (简称sh)、C-Shelll(简称csh)、Korn Shell(简称ksh)和Bourne Again Shell (简称bash)。
- Bourne Shell,文件后缀
.sh
,是Unix的默认shell,也是其他shell的基础;性能好,但用户交互低; - C shell,文件后缀
.csh
,语法类似C,提供了.sh
没有的用户交互功能——命令补全、命令别名等;与.sh
不兼容; - Korn Shell:文件后缀
.ksh
,结合了.sh
和.csh
的优点,并向下兼容了.sh
; - Bourne Again Shell,即bash,是Linux的默认shell,结合了以上
.sh
、.csh
、.ksh
的优点,并向下兼容了.sh
;最强!
- Bourne Shell,文件后缀
2 语法
2.1 基本格式⭐️
目标target: 所需prerequisites
(TAB) command (任意的Shell命令)
label:
(TAB) command...
- 要注意 =号的空格无碍;tab很关键,和python一样;
- 用
make
触发执行makefile后,make会检查 各prerequisite的更新日期,若比target新,就重新执行此句的命令。 - makefile会把第一个target作为编译目标,然后逐层迭代的判断是是否需要更新所需的 prerequisites. 其他没被牵扯到的target或者label,需要自己调用
make label
来单独执行。 - 若target不是实体存在的文件而是个虚名,那它称呼为**label**,label后面的prerequisites不用加文件名,因为target是个label的话压根就没有实体文件给你比日期,写文件也没啥用;
一般label后面的prerequisites也写label;那此时若make target就会先去make prerequisites里的label,嵌套执行,就当成函数好了. .o
文件与自身相关的.h
、.c
文件的prerequisites可省略,因为会自动补充;- 养成好习惯,自己写一个
clean
label,用于删除上一次调用make后产生的所有中间、执行文件,日后用make clean
就能起到复位的作用;clean
label内,务必用rm -rf
而不是rm
,不然报错一句,后续clean都不执行了!
2.2 举个例子Case ⭐️
-
makefile的触发条件
① prerequisite比target要新;
② 或者 prerequisite不存在;
所以,若target的A不是实物,永远不会生成,则此语句每次make都 必会被调用。
B = ? C = ? # B和C是具体的文件,A只是抽象的程序tag无对应实体文件 A: B C B -o C #和A没关系,A永远不会生成,故此语句必执行
也可以有多个抽象的程序块,如下:
all: do1 do2 do3 # 四个label do1: A B C ...balabala... do2: E F ...balabala... do3: ...balabala...
-
makefile中
.PHONY
的学习考虑以上makefile的case,会出现这样的问题:
若在写完makefile后,本地恰巧出现了个文件或文件夹的名称也叫做 A ! 那按makefile的规则,由于A被检测到文件存在,且prerequisite B、C并不是比 A 要新的话,此行makefile就不会执行了!总结:makefile中的target字段本该是无实体文件的抽象label,却因本地存在同名文件/文件夹,使此行makefile无法如意执行。
Solution:在A前面加上
.PHONY
(可以理解为关键字),声明此A就是label、不是实体文件,别去检索当前路径下是否存在同名文件啦!之后,即使makefile目录下有同名文件A,makefile也会按label进行处理此target.B = ? C = ? .PHNONY: A #声明A是phony抽象的 A: B C B -o C #和A没关系,A永远不会生成,故此语句必执行
.PHONY
只是一个声明,不会影响后续label的执行的。
2.3 细节的其他makefile语法
-
target和prerequisite的数量
至少一个target:target是一个东西或事件、自定义函数名。最好加个注释说明一下是啥;
可以0~n个prerequisite:prerequisite是前置文件,可用通配符
*
; -
行注释 用
#
; -
行续写,于行尾 用
/
, 表多行代码用一个进程执行;makefile内写的程序默认一行一个进程执行,故需要将程序串行执行,得自己加
/
;注:注释也可用
/
进行续写;使用/
后会读取“空行”,故下一行别接注释! -
宏定义 Macro define
- 定义: 等号
=
,pi=3.14
, 可嵌套,大小敏感; - 使用:
$(宏名)
- 常用系统宏:
- $@,当前target
- $<,第一个条件的名字
- $?, 所有条件中 比 当前target更newer的 民名字
- $^, 所有条件的名字
- $(MAKE),就是预设的 make命令
- 定义: 等号
-
define
即多行版本的宏定义
define 宏名 ... ... endef
-
include
导入别的makefile(类似导入库),文件名语法可用正则表达和shell.
若多个导入的makefile库内,出现 target冲突,取最后一行定义为准
-
正则表达式,
如:通配符等——
*, [], ?,@
, @好像是让系统回话功能闭嘴,干净。 -
Reference