make命令的使用:
1. make 默认在当前目录下找 Makefile, makefile, GNUmakefile(非GNUmake不识别) 来执行,
如果没有这3个文件可以使用 -f filename 或 --file=filename 来指定makefile文档
2. 没有makefile的情况下使用make(当gcc来用); 如有一个 foo.c 文件 可是使用 make foo.o 命令, 对foo.c 进行编译
使用的就是make的隐含规则
make 的命令行参数
-f makefileName 指定makefile文件
-j [整数] 指定make是否并发执行规则, 默认值是1, 表示只有一个任务执行, 不指定整数表示无数个
-t 或--touch 更新目标文件的时间戳, 不会执行命令, make会对目标文件执行shell得touch命令.
-n 或--just-print或--dry-run或--recon 那么make会显示出要执行的命令,但并不真的执行这些命令, 可以在调试Makefile时使用; 可以看到命令的执行顺序
-s 或 --slient 或makefile特殊目标.SILENT 可以禁止所有的命令回显了;
-e 操作系统中的环境变量的值, 在Makefile中覆盖赋值无效!
-q 或--question 不执行命令, 不打印输出信息, 只检查目标(终极目标/指定得目标)是否是最新的, 是返回0 否返回1
-W FILE 或--what-if=FILE 或 --assume-new= FILE 或 --new-file= FILE 该命令需要指定一个文件(FILE), 该文件一般是一个源文件, make会将当前时间作为该文件的时间. 可想而知的后果是该文件的目标文件会重建, 依赖它的也会重建
-o HEADERFILE 让make忽略指定的头文件HEADERFILE的时间戳(只能是.h文件), 也就是告诉依赖这个头文件的目标该头文件没有更新过, 比目标旧;
-k 或 --keep-going 当某个目标执行出错, 还会继续执行后续, 直到最后出现致命错误(无法重建终极目标)
makefile文档的编辑
在编辑其需要对一些特殊字符进行认识和重视:
\ 反斜杠: 换行, 一行书写内容过长不利阅读可以用\来换行, 在\后面不能有任何字符串, 否则会报错
# : 注释, 如果要使用这个字符串, 需要转义 \#
$ : 表示对变量或函数的引用, 如果要使用该字符时 写2个连续的 $$ 即可
$@ : 获取命令行中输入的目标值 如: make fooo ; 此时的$@的值就是 fooo
$? : 表示依赖文件, 在命令中使用可以把依赖文件在命令中展开 如: print: *c ; echo $? # 就是echo *.c
$^ : 该变量表示 通过目录搜索得到的所有依赖文件的完成路径名(目录+文件名列表)
$< : 这个是$^ 中的第一个文件. 使用场景如一个规则要编译一个.c文件使用它 传给cc命令 而不需要把.h文件传给cc
如: foo.o : foo.c x1.h x2.h ; cc -c $< -o $@ 该命令展开后就是 cc -foo.c -o foo.o
$@ : 代表规则的目标(触发一个规则是获取其目标的名称)
% : 所有模式匹配规则, 匹配所有的目标,
$* : 在静态模式下, 自动变量 茎的值; 也就是再 目标/依赖中使用通配符"%"; 匹配到的字符串
$$$$ : 当前进程号
规则命令种的符号:
[tab]制表符 : 一行的一个是制表符说明是一个命令, make 都会把它当一行命令交给shell处理
- : 命令前 或 指示符前使用 - 来忽略命令返回的错误
(make会认为命令执行成功了, 然后执行后面的命令, 否则make会认为出错, 不继续执行了), 但命令本身的错误输出还是照常输出的.
+ : 再命令行前添加, “+”之后的命令都需要被执行。在使用-n - t -q 的选手的时候都不会执行命令, 但带该符号的命令会被执行.
1. makefile 的结构, 包括如下:
1.1 规则: 一个规则就是make 要执行的最小单元.
目标... : 依赖 ...
命令
提示: 这个是规则称为显示规则, 满足3要素, 可以简略的写, 如不写命令/不写.c , make会自动推导
1.2 变量:
objects = main.o GenerateDelphilDpk.o util.o
使用 $(objects)
1.3 指示符 : 各种关键字, 来拓展makefile, 或者条件执行 等等功能:
2.1 使用include 指示符: 包含其他makefile文档
include 指示符make去指定的文件读取内容, 完成后再继续当前的makefile读取, 单独一行书写如下:
语法: include FILENAMES... # FILENAMES 是shell锁支持的文件名(可使用通配符), 可是相对路径也可以是绝对路径
例子: include foo *.mk $(bar) # 意思引入foo文件/ .mk结尾的文件/ 变量中定义的文件名
使用场景: 1. 多个程序, 可是会使用一些公用的 变量定义, 模式规则, 把这些公用的内容写在一个文件中(名称随意), 然后再引入使用
2. 当工具可根据源文件自动生成依赖文件的时候, 可以把这些依赖文件引入导主makefile中, 比直接生成到主文件中要明知很多,
其他版本的make已经使用这样的方式, 4.14 会讲自动产生依赖.
提示:0. include中的文件名没有指明绝对路径, 当前目录下也没有, 会从make命令行参数 -I或-include-dir 指定目录下找
如果指定目录下没有, 或没有指定目录, 会尝试从以下几个目录找 "/usr/gnu/include”、“/usr/local/include”和“/usr/include”。
1.如果include 没找到文件会警告, 但不会中止后续的操作.
当一个规则的依赖规则没有找到才会退出make; 使用-include FILENAMES时,依赖规则没找到, 又不影响终极目标的构建会继续执行.
2. 为了兼容其他的make程序可使用 "sinclude" 来代替 "-include"
3. 导入的makefile 的目标不能和主makfile中冲突!!
2.2 重载另一个makefile
include 载入的makefile和主makefile中的 目标是不能重复的.
在一种特殊场景之下, 如有2给类似的makefile, 很多目标都重复的, 只要个别不重复, 这时又不能使用include,
使用下面的技巧可以不引入另一个makefile的情况下, 使用其的目标
#sample GNUmakefile
foo:
frobnicate > foo
%: force # 之所以给所有模式匹一个依赖, 是因为防止它的命令会被执行, 现在依赖不会被更新,所有也不会被执行这个目标
@$(MAKE) -f Makefile2 $@ # 命令行中的目标参数如果不在makefile中 , 就会被 %所匹配, 这里的命令, 是执行另一个makefile 参数是命令行的参数(目标名)
force: ; # 空命令(命令时空) 防止make为了重建它(使用隐含规则等)是又触发(所有模式匹配%) 该规则 %:force 陷入死死循环。
3. 系统环境变量MAKEFILES
给环境变量赋值, 执行make的时候会导入到当前的makefile中, 执行逻辑和 makefile中的include类似.
提示: 1. 环境变量里的 规则不会成为终极目标!
2. 环境变量里指定了一个文件, 但系统里没有, 不会报错, 比较隐蔽
3. 避免使用该环境变量, 在多级make调用时, 都会加载该变量, 可能会造成混乱, 它的使用场景是仅用于配置一些隐含规则和定义变量
4. makefile 变量 MAKEFILE_LIST
该变量里面放着make读取的多个makefile文件,
案例:
name1 := $(word $(words $(MAKEFILE_LIST)), $(MAKEFILE_LIST)) # $(words $(MAKEFILE_LIST)): $(words xxx) 可以获取 有几个单词
include inc.mk
name2 := $(word $(words $(MAKEFILE_LIST)), $(MAKEFILE_LIST))
name3 := $(word 1, $(MAKEFILE_LIST)) # $(word 1 , list) : 可以从list 中获取第1个单词的值
all :
@echo name1 = $(name1) # 输出 Makefile
@echo name2 = $(name2) # 输出 inc.mk
@echo name3 = $(name3) # 输出 Makefile
@echo $(MAKEFILE_LIST) # 输出 Makefile inc.mk
5. makefile 特殊变量.VARIABLES
GNU make 支持该变量, 它不能被修改; 它的值是在引用点之前 makefile中的所有变量列表
*** 6. makefile文件的重建
make把所有的makefile文件转载后(include, MAKEFILES 中的文件);
make会让这makefile当做目标存在, 然后寻找更新某一个makefile的明确规则/隐含规则; 然后去更新这个makefile文件(如果需要的话)
所有的导入的makefile有任何一个被更新了, make清除本次执行状态, 从新读取一般所有的makefile(这次读完, 它还是会试图去更新所有的makefile)
此时所有载入的makefile再时间戳上看都最新的了, 然后, 开始解析所有的动作.
提示:
运用这种make对makefile重建的机制, 下面自动生成依赖的章节, 会用到.
7. 变量取值(值的展开)
变量值发生的状态
1.赋值时:
IMMEDIATE = DEFERRED # 使用 = 赋值的变量是延迟加载的也就是说, 如果把一个变量赋值给当前遍历, 只要在使用当前遍历被使用时才被展开(获取真正的值)
IMMEDIATE ?= DEFERRED
IMMEDIATE := IMMEDIATE
IMMEDIATE += DEFERRED or IMMEDIATE # 使用 :=定义的变量,运用+= 它是立即展开的,其它情况时都被认为是“延后”展开的变量
define IMMEDIATE
DEFERRED
Endef
2.条件语句
所有使用到条件语句在产生分支的地方,make 程序会根据预设条件将正确地分支展开。
就是说条件分支的展开是“立即”的。其中包括:“ifdef”、“ifeq”、“ifndef”和“ifneq”所确定的所有分支命令。
3. 在目标规则下
IMMEDIATE : IMMEDIATE ; DEFERRED
DEFERRED
小结:
make的执行过程:
1. 读取系统变量MAKEFILES从中获取makefile的定义
2. 读取当前目录下的makefile
3. 读取 include 的文件
4. 查找重建所有已读的makefile文件的规则( 场景: 当一个目标时读取某一个makefile, 之后会从新读所有以读过的makefile, 就是从第一步开始)
5. 开始初始化变量, 有的变量值会立即展开. 类试C语言中的宏, 有的会延迟类似c原因中的变量运行时确认值.
6. 根据"终极目标" 和其他目标的依赖关系, 建立依赖关系链表
7. 执行除"终极目标" 以外的所有目标规则(如果目标文件存在, 根据依赖文件的时间戳,是否不执行)
8. 执行"终极目标" 的规则
规则的执行过程:
1. 比较目标文件和所在的依赖文件时间戳
2. 目标的时间比依赖的文件时间新, 就说明也不做.否则执行这个规则
3. 执行规则就是运行规则下的命令(有可能时隐式的规则命令)
第四章 Makefile的规则
一般以一个目标 多个依赖 加命令 组合成规则, 一般makefile的第一个规则的目标为终极目标, 如果一个规则有多个目标, 那么第一个目标就是终极目标
有2中例外, 以.开通的 和模式规则的目标 他们不是 "终极目标"
1. 语法:
TARGETS : PREREQUISITES
COMMAND
...
或者:
TARGETS : PREREQUISITES ; COMMAND
COMMAND
...
提示: 1. TARGETS 可以有多个文件名 使用空格分割, 文件明可以是通配符
2. COMMAND 命令, 2中写法, 可以在依赖后面 跟一个 ; 分割后 写一个命令 或是 在下一行以[tab]为开头也命令, 命令可以有多行
2. 依赖的类型
分2中依赖类型:
1. 常规依赖, 之前使用的都是常规依赖, 依赖被更新, 那么目标会比重建
2. "order-only"依赖: 使用了这个依赖, 如果依赖被更新了, 目标不会被重建; 使用 在常规依赖后面用"|"符号 分割后 添依赖文件. 语法如下:
TARGETS : NORMAL-PREREQUISITES | ORDER-ONLY-PREREQUISITES
案例:
LIBS = libtest.a # 库文件
foo : foo.c | $(LIBS) # foo目标依赖 一个c文件一个库文件
$(CC) $(CFLAGS) $< -o $@ $(LIBS)
只要 foo.c文件更新后, 那么 foo才会被重建, 而库文件的更新不影响
3. 文件名使用通配符
Makefile中的通配符含义和linux shell中的通配符含义一样. 通配符有 : * ? [...]
在makefile内通配符并不适用于所有地方, 2种场景下使用:
1. 可以在规则的 目标和依赖种使用, make 在读取Makefile时会自动对其进行匹配处理
2. 可以在命令种使用, 通配符的处理交给shell来解释处理了.
提示: 1. 如果一个文件名中有通配符字符需要使用转义符 "\" 如: foo\*bar 表示 "foo*bar" 的文件
2. "~" 这个字符在*unix 系统表示当前用户, 需要注意
3. 通配符只要上面说的2个地方使用, 如果在变量里使用时不起效的
如: obs = *.c 那么就是字符串 "*.c"的字符串
如果像使用 就需要调用wildcard函数来实现,
如: obs = $(wildcard *.o)
缺陷: 因为在变量下不能使用通配符, 所有下下面场景会有烦恼的体验, 更新好的, clean 后make 提示找不到.o文件了
obs = *.o
foo : $(obs) # 当目录下有.o文件时, 执行这个目标没问题, 如果执行过clean后, 没有了.o那么, 这里就出问题了.
cc -o foo $(CFLAGS) $(obs)
案例:
clean:
rm -f *.o
或
print : *.c
lpr -p $? # 自动环变量“$?” 表示依赖文件文件列表
touch print # touch创建空文件或更新文件的修改日期, 这里并不关心, 只是记录下执行的时间
4. 函数 wildcard
该函数参数时一个通配符字符串, 通过字符串找到对应的文件, 然后返回 多个文件会用空格分割
一般用法 : $(wildcard *.c) 找到所有的.c文件
复杂使用 : $(patsubst %.c , %.o , $(wildcard *.c)) : 这使用字符串替换, 讲.c文件后缀替换成.o文件
案例: 一个没有复杂依赖关系的情况, 可以使用下面一个规则编译所有的.c文件, 使用的时隐含规则
objects := $(patsubst %.c,%.o, $(wildcard *.c))
foo : $(objects)
cc -o foo $(objects)
5. 目录搜索
大一点的工程, 会把目标文件(.o文件) 和源码文件分到不同目录下管理, 这样就要让make的目录搜索特性.
1. 一般搜索(变量VPATH)
把文件搜索路径, 给该变量赋值, 多个路径使用 ":" 或空格 分割 如: VPATH = src:../headers
make执行规则的时候, 首先从当前目录下找依赖文件, 然后时 VPATH中的目录按顺序找.
2. 选择性搜索( 关键字 vapth)
它不是变量, 是一个关键字, 和变量类似, 它可能更小颗粒化的之类如某一类文件去某路径中查找, 三种使用方式
1. vapth PATTERN DIRECTORIES
符合PATTERN模式的文件 到DIRECTORIES目录下查找, 如有多个目录使用空格或":"分割
2. vapth PATTERN
清除这个模式的搜索路径
3. vapth
清楚所有使用vapth设置的文件搜索路径
说明:
PATTERN 使用的模式字符是 "%" 类似通配符中的*, 匹配一个或多个字符, 使用如: "%.h" 匹配所有的.h文件
如果没有使用%, 说明是一个文件的全名, 如果要使用这个字符使用\转义
案例:
vpath %.h ../headers
# 在一个规则的依赖中需要某一个.h文件, 如果当前目录(make的启动目录)下灭有,那么就会从../headers目录下搜索头文件, 来看这个文件的时间戳
提示:
1. 案例说的.h文件, 是指makefile中出现的头文件, 对源代码中要使用的.h文件搜索使用 gcc '-l' 选项来知道 参考 gcc 的info文档
2. 如果定义了多个vapth , 也是按顺序找, 如:
vpath %.c foo
vpath % blish
vpath %.c bar # 这里写了3行对.c文件多个目录可以写在一行之上 如: vapth %.c foo blish bar
对.c文件的查找顺序是 foo blish bar ;
5.1 目录搜索的机制
GUN make的默认机制:
如果一个目标在当前工作目录下没有, 需要从vpath知道的目录去搜索时, 2中情况
1. 如果该目标文件不需要重建, 那么该目标文件的路径是有效的(也就是所其它的规则如果依赖它的话, 会使用这个搜索到的目标文件)
2. 如果目标文件需要重建, 这个目标会在工作目录下被重建, 从而这个搜索的目标文件对make是无效的.
依赖文件的完整路径是不会失效(如依赖的.h文件, 也是通过vpath搜索到的). 依赖文件时用这个.
提示: 其他的make 使用简单的机制, 如一个目标文件存在(vpath目录下) 都使用这个vpath目录下的目标文件, 不管是否需要重建.
GUN make也可以如此, 如下:
让gun make 在vpath目录下重建搜索到的目标文件, 而是在工作目录下重建:
使用GPATH变量.
讲vapth下的目录赋值给GPATH, 如此一来, 一个目标文件在vapth下如果需要重建, 就会在此目录下重建, 不会到工作目录下重建了(make启动目录)
案例说明:
只使用vapth时
LIBS = libtest.a
VPATH = src
libtest.a : sum.o memcp.o
$(AR) $(ARFLAGS) $@ $^
该案例有2个目录 prom 主目录, 子目录 src; 如果目标文件libtest.a 在src下,已经有了需要重建的话, 会在prom下生产一个libtest.a 后续make也只会用这个新的
添加变量
GPATH = src
使用了GPATH后, 如子目录 src下有libtest.a需要重建该文件时会更新该src下的libtest.a, 之后make也只会用该文件
5.2 命令行和搜索目录
我们的依赖文件可能是通过搜索路径得到的(并不再工作目录下), 书写的命令可能需要使用到这个路径, 才能保证命令执行成功.
此时需要使用自动变量; 再之后章节(10.5.3)会具体讲自动变量, 西安讲2个了解下有这个事
$^ : 该变量表示 通过目录搜索得到的所有依赖文件的完成路径名(目录+文件名列表)
$< : 这个是$^ 中的第一个文件. 使用场景如一个规则要编译一个.c文件使用它 传给cc命令 而不需要把.h文件传给cc
如: foo.o : foo.c x1.h x2.h ; cc -c $< -o $@ 该命令展开后就是 cc -foo.c -o foo.o
$@ : 代表规则的目标(触发一个规则是获取其目标的名称)
5.3 隐含规则和搜索目录
当定义一个规则, 如下: 隐含规则会直接编译 foo.c文件, 那么使用了搜索目录后, 隐含规则同意会到搜索目录下编译foo.c文件到工作目录下.
foo.o :
隐含规则会用到一些自动变量, 得到foo.c全路径后 生成默认的编译命令.
5.4 库文件和搜索目录
当编译依赖一个库时(静态库/共享库)时, 可以再规则的依赖中添加 -lNAME的依赖文件名, 是一个库的名称, 会被转换为libNAME.so这个文件名
libNAME.so库的文件搜索步骤: 先搜索当前目录 > “VPATH”或者“vpath”指定的搜索目录 > 系统库文件的默认目录如: “/lib”、“/usr/lib”和“PREFIX/lib”(在Linux 系统中为“/usr/local/lib”,其他的系统可能不同)。
如找不到会将-lNAME 展开成 libNAME.a这个文件名根据搜索步骤再查找.
案例: 假设系统里有“/usr/lib/libcurses.a” 文件
foo : foo.c -lcurses
cc $^ -o $@ # 命令展开后 : cc foo.c /usr/lib/libcurses.a -o foo
提示:
-lNAME 这个用法会展开成具体的文件名 如 libNAME.so libNAME.a ; 是通过变量.LIBPATHTERNS 来实现的
.LIBPATTERNS 默认的值是 lib%.so lib%.a ; 这么% 就是替换成NAME来实现的. 这个变量可以清空, 也可以修改
6. Makefile的伪目标
伪目标它不代表一个真正的文件, 可以看作是一个标签, 可以用它来执行一些命令, 执行它的时候make不会使用一些隐含规则
使用的使用需要用到一个特殊目标,如:" .PHONY : 伪目标 .. " ,案例如下:
.PHONY: clean
clean:
rm *.o temp
提示: .PHONY 并不是必须的, 但是如果工作目录下有一个clean文件, make认为这个是一个目标文件, 然后又没有依赖文件, 那么它上面也不做.
所有, 我们要告诉make clean这个目标不是目标文件, 是一个伪目标, 将其放入.PHONY 下行.
另一种使用场景:
在make的并行和递归过程中(执行子目录下的makefile), 是用shell循环 之多个目录下的make, 案例如下:
SUBDIRS = foo bar baz
subdirs:
for dir in $(SUBDIRS); do \
$(MAKE) -C $$dir; \ # 这个$$ 表示使用$字符, 是给shell的 $dir , shell中获取变量值的语法
done
提示: 1. 使用shell循环执行make 的问题是, 如果子目录下的make失败了, 当前make不会停止. shell会执行所有的循环, 造成排除困难
2. shell是一直一个在执行下一个循环, 那么make不能对多个目录并行处理
改进版:
SUBDIRS = foo bar baz # 3个子目录, 也会放如伪目标中
.PHONY: subdirs $(SUBDIRS) # 定义位目标
subdirs: $(SUBDIRS) # 第一个位目标 , 依赖3个位目标
$(SUBDIRS): # 一个规则3个目标
$(MAKE) -C $@ # 伪目标命令: 指定目录下执行make
foo: baz # 给foo 一个依赖baz, 确保baz会在foo前执行.
提示:
1. 并行执行make的makefile对目录的执行顺序需要自己心里又数!
注意:
1. 一般的规则依赖不会使用伪目标, 如果使用了那么每次执行规则, 伪目标的命令都会被执行.
2. 一个伪目标没有被任何目标依赖, 那么要触发它就需要在make命令后面跟这个伪目标名称
3. 伪目标是可以又多个依赖目标或文件.
4. 当一个伪目标依赖另外的伪目标时, 可以理解为调用函数. 案例如下:
.PHONY: cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff # 执行cleanall 会先执行另外2个伪目标的命令, 又能单独使用其他伪目标, 比较灵活
rm program
cleanobj :
rm *.o
cleandiff :
rm *.diff
5. 使用rm 命令删除的时候如果某个文件不存在会报错,导致make中断, 加上参数-f(--force)防止报错, 或这使用-符号忽略命令错误"-rm"
建议用法时使用make的内置隐含变量"RM" 它的定义时: RM = rm -f ; 使用$(RM) 代替rm 逼格也更高超!
伪目标的"终极用法"
当我们需要创建多个可执行程序的时候. 会使用一个all名称的伪目标作为终极目标(放在Makefile文件的第一个目标),
然后让all 依赖多个具体的构建目标, 案例如下:
all : prog1 prog2 prog3 # 又3个依赖
.PHONY : all # 将all 定义为伪目标(这样make不会对它运用隐含规则)
prog1 : prog1.o utils.o # 第一个程序 构建目标
cc -o prog1 prog1.o utils.o
prog2 : prog2.o # 第一个程序 构建目标
cc -o prog2 prog2.o
prog3 : prog3.o sort.o utils.o # 第一个程序 构建目标
cc -o prog3 prog3.o sort.o utils.o
如果要构建某一个程序 只要 make prog1 即可
7. 强制目标 ( 没有命令或依赖的规则)
一个规则没有命令没有依赖, 并且它不是一个存在的文件名, 在规则执行的时候, 该目标会被make认为是最新(认为它被更新过了),
那么这个目标被其他规则依赖的话, 其他目标认为依赖更新过来需要重建(执行命令);
案例:
clean : FORCE # 这里的clean不是伪目标了, 但执行了这个规则的时候, 会检查依赖的更新, 那么这里的FORCE会被认为永远是最新的. 因此会执行命令
rm ($objects)
FORCE:
提示:
该例子可以来替换伪目标的用法, 但还是不建议而且还没伪目标直观, 使用场景是非gun make的make程序没有伪目标概念的时刻可以运用
8. 空目标文件
它是伪目标的变种, 该目标文件存在, 内容不是重点, 关键是使用它来触发一些命令(和伪目标目的类似)
使用也和伪目标一样
案例如:
print: foo.c bar.c
echo $? # 要执行的shell命令
touch print # 第一次执行会生成一个print文件. 后续执行会修改该文件的更新日期.
9. Makefile的特殊目标
一些特殊的名字作为规则目标时, 具有特殊含义, 如.PHONY
.PHONY 所有依赖都时伪目标, 伪目标在make命令行指定此目标时, 目标规则的命令无条件被执行
.SUFFIXES 所有依赖指出一系列后缀规则中需要检查的后缀名(当前make需要处理的后缀) 在10.7 后缀规则讲解
当自己指定某个后缀字符串作为它的依赖后, 改字符串会追加到已有的后缀列表, 如果要清空后缀列表, 就使用一个空的依赖, 再添加依赖,
.SUFFIXES : # 清空后缀列表
.SUFFIXES : .foo # 此时后缀列表就一个后缀
变量 SUFFIXE 存储着这些后缀列表, 但不要通过修改变量的值来设置列表的值, 因该使用 .SUFFIXES 这个伪目标
.DEFAULT 一个文件作为一个依赖的时候, 但自己却不时一个目标, Make 不知道如何重建此文件的规则, 在这种情况下执行 .DEFAULT所指定的命令
.PRECIOUS 它的依赖文件在make过程中会被特殊处理, 当命令在执行过程中被中断, make 不会删除他们; 组后5.5中断make的执行, 和10.4make隐含规则会讲
它的依赖文件可以时一个模式 如: %.o 这样可以保留规则创建的中间过程文件
.INTERMEDIATE 依赖文件在make时被作为中间文件对待(完成构建会删除这些中间文件), 没有任何依赖文件的目标 .INTERMEDIATE 没有意义.
.SECONDARY 依赖文件也是中间过程文件对待, 但完成构建后不会被删除(不像.INTERMEDIATE)
.DELETE_ON_ERROR 如果make执行中报错, 那么会删除已经产出的文件; 参考 5.4命令执行的错误
.IGNORE 它的依赖文件, 在执行命令的时候, 错误会被忽略 , 如果没有依赖, 那么所有的命令执行的时候会忽略命令执行失败的错误 参考 5.4命令执行的错误
.LOW_RESOLUTION_TIME 低分辨率时间戳, 一般都时搞分辨率, 之间比较文件的修改时间的新旧来执行命令. 库文件会被作为它的依赖
.SILENT 依赖目标文件在被构建的时候, 其命令不会打印, 如果没有指定目标, 那么全局的规则的命令被执行都不会回显 参考 9.7 make的命令行选项
.EXPORT_ALL_VARIABLES 它没有依赖 , 它的功能是讲所有的变量传递给子make进程, 参考 5.6 make的递归执行
.NOPARALLEL 它没有依赖 , 所有命令按照串行方式执行; 但在递归调用的字 make 进程中,命令可以并行执
所有隐含规则 作为目标出现时, 被视为特殊目标.
10. 多目标
一个多目标的规则, 可以理解为多个规则的组合;
再命令行中可以使用自动变量 $@ 获取到运行时的目标名称
案例
bigoutput littleoutput : text.g
generate text.g -$(subst output,,$@) > $@ # $(subst 被替换字符串, 替换为字符串, 原始字符串); 函数参考 第八章 make内嵌函数
其等价于:
bigoutput : text.g
generate text.g -big > bigoutput
littleoutput : text.g
generate text.g -little > littleoutput
11. 多规则目标
一个目标 再多个规则的目标中出现的时候, make 内部其实是把多个规则的依赖合并, 然后对该目标执行构建,
需要注意: 1. Makefile对多规则的目标因该只有一个规则才有命令, 这才是正确写法(双冒号规则除外)
2. 错误的做法但不是致命的: 多个规则定义了不同的命令, 那么只有最后一个规则命令会被执行(忽略其他的命令会有错误提示)
例外, 如果以"."开头的目标时, 多个规则命令都会被执行.(为了兼容其他make, 避免使用)
再某些情况下, 需要对相同的目标使用不同的规则中所定义的命令, 需要使用 “双冒号”规则来实现(下面讲)
案例:
objects = foo.o bar.o
foo.o : defs.h
bar.o : defs.h test.h
$(objects) : config.h
提示:1. 对应一个大型项目, 如果某一个.c文件下最加了一个.h文件, 那么 只要再Makefile中追加一个该目标文件的规则即可
2. 使用gcc可以自动生成依赖描述规则 参考 4.14 自动产生依赖 一节
3. 还可以使用变量的方式了将依赖放入变量中. 也可以避免再茫茫规则中修改某一个规则
12. 静态模式
我们使用隐含规则如 .o文件结尾的目标, 会执行gcc -c -o 的编译命令;
静态模式, 在指定的目标列表中, 定义自己的文件类型(如特定后缀, 其实前缀也都行), 来执行自定义的命令. 也就是说它是模式规则的一种子集, 它的模式只作用在指定的目标列表中的目标
语法:
TARGETS ...: TARGET-PATTERN: PREREQ-PATTERNS ...
COMMANDS
...
TAGETS : 一系列目标文件, 可以使用通配符
TAGET-PATTERN : 匹配模式, 从目标中找到某种结果为.o 文件 , 如 %.o 找到.o结尾的文件, 相同后缀的文件, 那些不同的文件名(前缀)被称为茎
PREREQ-PATTERNS : 替换模式, 将匹配到的目标文件 该为某结尾的文件 如 %.c 将匹配到的文件后缀替换为.cc
案例 :
foo.o bar.o : %.o: %.c # 目标文件foo 和 bar 被称为茎
$(CC) -c $(CFLAGS) $< -o $@
展开后如下:
foo.o : foo.c
$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
$(CC) -c $(CFLAGS) bar.c -o bar.o
案例2 :
files = foo.elc bar.o lose.o
$(filter %.o,$(files)): %.o: %.c # $(fiter %.o , ...) 该函数会从源数据集中过滤出以.o结尾的文件返回
$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
emacs -f batch-byte-compile $<
该案例使用函数filter做到了对某个集合分别过滤出不同的文件类型, 转换为不同的依赖文件, 定义不同的命令来构建
案例3 :
bigoutput littleoutput : %output : text.g # 该案例的后缀时一个字符串 output
generate text.g -$* > $@ # $* 展开口时茎的部分 如: big
提示: 通常会被这些不同文件的处理命令会放入到make.rules 文件中, 然后再各个项目下引入这个文件(复用这些编辑好的规则)
小结: 隐含模式可以帮我们做一些时, 但如果一个目标匹配多个隐含模式的规则那么执行最后一个规则的命令;
静态模式可以认为可控的执行指定类型的文件, 执行指定的命令, 而且一个目标文件匹配多个静态模式则会报错!
一般正规项目肯定使用静态模式, 明确某类文件的执行命令.
13 双冒号规则
1. 当一个目标没有依赖的时候, 该目标就算已经存在, 也会被重新执行构建命令
2. 当一个目标出现在多个双冒号规则的目标中时, 那么就看哪个规则的依赖文件被更新了, 然后执行该双冒号规则的命令
提示: 1. 一个不能同时出现在单冒号规则中又出现在双冒号规则中!!!
案例:
Newprog :: foo.c
$(CC) $< -o $@
Newprog :: bar.c
$(CC) $< -o $@
该案例中如果foo.c 被更新, 那么执行展开后的命令: gcc foo.c -o Newprog
如果该案例时普通案例, 就会报错(但会执行 gcc bar.c -o Newprog)
# 使用的linux gun make 4.1 并没有报错, 而且会执行第二条命令, 而windows下 会第一条命令
# 在当前的版本下, 都不能使用双冒号规则
14. 自动产生依赖
gcc 可以通过源代码中的 #include 提取出哪些依赖.h文件, 使用的参数开关是-M; 如下:
gcc -M main.c # 它会在terminal中输出 如: main.o : main.c xxx.h 这样的字符串
提示: 如果使用了系统的头文件如:stdio.h 它也会输出, 其实并不需要, 那么就使用 -MM 参数即可.
一般我们会把对应的依赖重定向到 一个xxx.d以d结尾的文件中, 然后再include到 Makefile文件中.
案例:
%.d: %.c # 根据所有的.c 文件创建对应的.d目标文件.
$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \ # 通过$@(目标名) + 加上进程号($$$$) 生成临时文件
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \ # 生成 $@(目标名)的.d文件;
rm -f $@.$$$$ # 删除带进程号的零时文件
案例提示:
sed 作用: main.o : main.c defs.h # 这一行是 通过CC -MM 生成的; sed 作用是将.d文件加入到目标中
转成:
main.o main.d : main.c defs.h # .d 文件也依赖.c文件了, 这样.c文件添新的头文件, 都会生成新的.d文件
将.d文件引入到Makefile的语句. 如下:
sources = foo.c bar.c # 所有的.c文件
sinclude $(sources:.c=.d) # 变量的高级用法(变量引用置换): 替换.c 替换为.d后缀, 带s的include, 让文件不存在的时候不会报错.
小结: include 导入.d文件(makefile文件)的时候, make运用makefile的重建机制, 会去寻找该makefile的构建规则(案例中的规则 %.d: %.c); 然后看是否需要重建该makefile;
如果依赖文件变化了, 那么就会重建该.d的makefile; 然后make从新读取所有的makefile(并再次尝试更新他们)
第五章 规则的命令
规则的命令有一些shell命令行组成; 命令可以以2种形式出现在规则中: 1. 依赖列表之后用分号隔开跟一个命令 2. 在依赖的第2行以[tab] 开头的命令;
命令行间可以有空行(没有任何字符的行)和注释行会被忽略; 一个以[tab]之后没有任何字符的也算命令交给shell出来.
没有指定shell, 默认使用/bin/sh来处理命令; 因此不同的sh, 可以运行不同的语法.
1. 命令回显
默认情况make会显示正在执行的命令. 如果像显示就使用"@"开头. 典型用法 : @echo 开始编译 XXX 模块.... 这个命令就不会回显到标准输出了.但echo执行的结果会显示
补充: 1. 在make命令行参数使用 -n 或 --just-print 那么make会显示出要执行的命令,但并不真的执行这些命令, 可以在调试Makefile时使用; 可以看到命令的执行顺序
2. 使用 -s 或 --slient 或makefile特殊目标.SILENT 可以禁止所有的命令回显了; 但显然没有 @ 灵活和细颗粒
2. 命令的执行
规则中的多行命令会分别在不同的shell进程中执行, 所有如果第一行命令cd到某目录下, 那么多其他行的命令是无效的!
如果需要将多个命令在同一个个shell中执行, 只需要将命令使用";"分割, 如果命令太多先分行输入那么使用"\" 对行进行分割;
案例:
foo : bar/lose
cd bar; \
gobble lose > ../foo
提示:
make默认使用的shell在变量 SHELL中; linux一般是 /bin/sh; 该变量并不是系统变量SHELL的值; 可以在Makefile中给该变量重新赋值
3. 并发执行命令
GUN make 可以同时执行多条命令, 默认是 串行执行的 一个命令完了执行后面的.
可以通过 make的命令行选项"-j"或"--job"; 选项后面可以跟一个整数表示可以有几个任务并行执行; 不使用该选项就等价于 -j 1
提示:
1. 并行执行对排除无益! 多个命令的输出同时到terminal上, 太凌乱
2. 如果有多个命令使用标准输入流, 那么只要一个命令才能得到输入流; 其他命令得不到输入流而报错; 要么避免使用输入流;和只使用一次
3. 会导致make的递归调用出现问题; 可参考 5.6 make的递归执行 一节。
补充:
1. 当make在执行命令时, 命令执行失败(命令返回非零0值,或被中止); 等同于该目标的命令被中止, make就停止执行而退出; 如果使用了make的命令行选项"-k"或"--keep-going"则会继续执行.
如果一个make被中止(如:ctrl+c) 它会等到那些正在执行命令行的shell的子进程结束之后才真正的退出.
2. 执行make时, 如果系统运行于重负荷状态下, 需要减轻系统执行make时的负荷, 可使用"-l" 或 "--max-load" 选项让make限制运行任务的数量; 该选项接受一个浮点值. 如: -l 2.5 ;
make在启动一项任务前会查看系统负荷, 如果高于设置值(如2.5) 那么make的其他任务完成后再启动一个任务. 默认不限制负荷
4. 命令行的错误
1. 默认情况make在运行规则的命令是会检查命令执行的返回状态, 如命令执行成功(返回0值), 那么执行下一个命令.
2. 当执行的命令执行失败, 那么make会中止当前规则后续命令, 也有可能会中止所有的规则执行.
3. 一些时候命令执行失败, 并不意味着错误, 如创建目录的命令mkdir, 如果目录已经存在那么该命令会执行失败, 对make而言就会终止执行;
忽略命令执行的错误, 让make继续执行的方式:
1. 在命令的前面使用"-"字符, 告诉make忽略该命令的返回值,总是认为命令执行成功了. make会移除"-"后交给shell执行命令.
当然也可以使用命令的一些选项, 避免出错,如 mkdir -p 目录存在也不会报错 ; rm -f 文件不存在不会报错
2. make命令行选项 -i或 --ignore-errors 那么会忽略所有命令的执行错误信息. 避免使用, 大型项目尤其如此. 否则因为忽略命令的错误, 而构建了错误的目标文件, 导致最后的最终程序出现莫名其妙的错误
3. 特殊目标.IGNORE, 当没有依赖的时候, 那么会忽略所有命令的执行错误信息.
4. 不使用忽略命令错误的时候, make 接受到了错误后, 认为目标重建失败, 那么依赖该目标的规则, make也认为没有必要执行, make立即退出返回一个非0状态.表示执行失败.
使用make的命令行选项"-k"或"--keep-going", 来告诉make不立即退出, 继续执行后续命令. 直到无法继续执行命令时才异常退出.
使用场景: 当我们修改了多个文件, 然后使用该选项, 可以让make执行所有的目标文件(.o) 然后到最后链接的时候异常退出, 我们就可以看到那几个修改的文件没有修改好, 一次修复了
提示: 因为构建的时候.o文件可能已经被更新了时间戳, 那么下次再执行是make认为是最新的文件, 但其实是上次构建失败的产物, 因此需要自己先make clean下删除那些错误的文件
可以使用.DELETE_ON_ERROR该目标, 让make自动完成(出错删除对应的目标), 但它可能再其他make中无效, 建议使用clean的方式
5. 中断make的执行
make再执行一个规则的命令的时候, 接受到中断信号(ctrl-c), 那么目标文件构建到一半, make会先删除该文件再真正的中断, 目的就是防止这个不完整的目标文件时间戳比源码文件新而不会被重建.
它删除的是make开始构建的时间和目标文件的新时间比较, 如果比开始时间新就可以删了.
例外:
如果不需要make有这样的行为, 也就是make执行到一半, 但不喜欢它删除该目标文件时, 使用.PRECIOUS目标将对应文件放入其依赖中.
使用场景:
1. 目标的重建时一个原子的不可被中断的过程, 那么没必要删除
2. 目标文件的存在只是用来记录重建时间, 不关心内容的情况
3. 这个目标文件必须一直存在来防止其他麻烦.
6. make的递归执行
在Makefile中使用"make" 作为一个命令来执行本身或其他makefile文件的过程, 叫make递归过程. 在多级子目录的项目中非常有用.
案例:
subsystem:
cd subdir && $(MAKE)
其等价于规则:
subsystem:
$(MAKE) -C subdir # -C 也是进入子目录, 然后执行make
提醒:
make执行的时候 有一个变量CURDIR, 指向当前工作目录, 使用-C 进入子目录执行make, 该变量就是子目录, 如果修改了这个变量, 就不再是工作目录了.
6.1 变量MAKE
递归调用的make命令我们使用了变量而非命令本身,其有2个好处:
1. 如果你有多个make, 它可以保证你使用的make, 和递归使用的是同一个make
2. 当执行make的时候你可能使用了一些make的命令行选项, 希望递归的make也使用这些选项(如:-t, -n, -q等), 就需要使用该变量了
再启动make时的选项会再变量MAKEFLAGS保存, 该变量会传递给递归的make
6.2 变量和递归
在递归调用make的时候, 在主Makefile中的变量不会被传递给子make使用(2个除外:SHELL,MAKEFLAGS);
如果希望主Makefile中的变量可以传递下去就要使用指示符export ; 语法如下:
export VARIABLE ... # 被指明的变量列表都会传递下去, 如果没有跟变量名, 就是所有的变量都传递
如果有一个变量不希望传递使用unexport; 如下
unexport VARIABLE ... # 可以先把所有变量使用export 都传递, 然后使用unexport 指定某几个不用传递
提示:
1. export/unexport 的变量或函数的引用会被立即展开. 也就是说此时的变量值是固定的.
2. 其他的一些export的有效用法
export VARIABLE = value
等效于:
VARIABLE = value # 赋值方式= 可以是 := +=
export VARIABLE
3. export 可以不带变量表示传递所有的变量, 但是在新版本的gun make 是不支持的, 新版本使用的是伪目标 .EXPORT_ALL_VARIABLES ; 该伪目标在旧版本也不会识别所有可以互相兼容
4. unexport 不带变量, 没有任何效果, 必须带变量, 明确指定该变量不传递
5. 在一个文档中有多个export/unexport 到最后一个的时候, 次确定哪些变量是传递或不传递.
补充:
在递归调用可能会用到MAKELEVEL变量; 当我们执行主makefile的时候, 该变量的值是0, 传递到下一级子目录后, 该值是 1 ; 以此类推会自增
使用场景:
一个子目录下有一个makefile, 并不希望在该目录下直接执行make就开始执行构建, 而是希望在主工作目录下先执行后, 再被执行,可以对该变量使用条件测试指令
案例:
ifeq ($(MAKELEVEL),0)
all : msg # 如果为0 , 就依赖msg ;
else
all : other
endif
…
msg:
@echo ”Can not make in this directory!” # msg 就直接输出一个字符串提示, 不能再当前目录下直接运行make
6.3 命令行选项和递归
make 命令的选项 如 -k -s 等都会赋值给MAKEFLAGS这个变量, 它会递归传递给子make, 如果你再make命令后后面跟一个自定义变量, 那么该变量的值也可以通过MAKEFLAGS传递给子make
提示:
1. 并不是所有的命令行选项都会给MAKEFLAGS, 这些例外: -C -f -o 和 -W
2. -j + 整数(如4) : 参数比较特殊; make为了确保所有的作业不超过4个, 主make和子make会通信
3. 如果不希望 MAKEFLAGS 的值传递给子make 有2种方式:
1) subsystem:
cd subdir && $(MAKE) MAKEFLAGS= # 给该变量赋空; 该方法使用了make通过命令行来定义一个变量的特性.
2) 再主make种给变量 MAKEOVRRIDES =值为空; MAKEFLAGS的值其实是从MAKEOVRRIDES种获取的, 如果它清空了那么MAKEFLAGS也就没值了
但不到万不得已不要使用这种方式;
提示: 1. 当Makefile中出现“.POSIX”这个特殊的目标时,在上层Makefile中修改变量“MAKEOVERRIDES”对子make不会产生任何影响
2. 有的系统对变量长度有限制 MAKEFLAGS 超过一定长度的话, 执行过程会 报错如“Arg list too long”
3. 历史版本种有一个变量 MFLAGS 和 MAKEFLAGS类试; 新版本有兼容的. 但它不包含对MAKEOVRRIDES的引用,所有没有命令行选项;
它的值都是以-开头 , 它的用法就是当子make的选项用的如cd subdir && $(MAKE) $(MFLAGS)
4. make执行时, 首先将会对 MAKEFLAGS的值(系统环境中, 或者Makefile中设置的) 进行分析, 如果不是 '-' 开始的值就拆分成 '-'开头并空格分割(如: ks -> -k -s)
如果包括了无效的选项不会提示错误
6.4 -W选项
选项“-w”或者“--print-directory”可以让 make在开始编译一个目录之前和完成此目录的编译之后给出相应的提示信息;如在目录“/u/gnu/make”目录下执行“make -w”,
在开始执行之前我们将看到:
make: Entering directory `/u/gnu/make'.
而在完成之后我们同样将会看到:
make: Leaving directory `/u/gnu/make'.
提示:
-W 会被自动打开, 在主make中使用-C 或 cd 进入一个目录 运行子make时被开启.
使用选项 -s 或 --slient 可以禁止-W ; --no-print-directory 选项也能禁止目录信息的打印
7. 定义命令包(可以立即为c语言的函数或宏)
在Makefile中可能一个规则使用了多行命令, 如多个规则都使用了这些命令, 那么可以帮他们封装成一个命令包, 使用的使用只是对命令包名的应用即可实现相同效果,
案例:
define run-yacc # 关键字 define 开始一个命令包 + 一个名称(在make这个名称和变量命令类试的使用)
yacc $(firstword $^)
mv y.tab.c $@
endef # 关键字 endef 表示一个命令结束
使用:
foo.c : foo.y
$(run-yacc) # 使用和使用变量名类试;
提示:
1. 命令包中使用的变量等, 会在规则执行的时候才会展开, 在规则的命令部分像使用变量一样使用命令包; make看到这个命令包后会像c中的宏一样, 进展展开(字符串替换)
2. 在规则命令部分可能会用到一些前缀控制符(@ , - , + ) ; 命令包前使用控制付如:
foo.c : foo.y
@$(run-yacc) # 如此命令包中的所有命令都不会回显
8. 空命令
是一个规则, 有目标有依赖 有命令(命令是一个空行给shell处理); 目的是为了不让规则触发隐含规则或.DEFAULT中定义的规则.
语法:
target: ;
还要一种就是在换行后使用一个[tab]开头的空命令行; 但看起来不明显(会误以为使用隐含规则的命令)
提示:
1. 不建议给这种规则添加依赖; 可能会重建依赖文件(但为该目标重建依赖没有意义)
使用场景:
之前有案例: 重载另一个makefile
当我们使用符号 "%" 作为目标的时候, make 可以接收任意的目标名称, 然后会被 % 为目标的规则接受.
利用这个规则, 配合空命令(防止死循环); 可以重载另一个makefile文件, 从而使用另一个文件中的规则.但又不需要使用另一个makefile中重复的内容.
第六章 Makefile中的变量
Makefile的变量类试C语言的宏, 对他的设置如变量, 使用的地方如宏, 对字符串的展开. 在其他的make程序变量就是被称为宏
一些特性:
1. 变量 的定义包含了使用 = 或define 定义的 值; 在读取makefile时展开.
2. 变量可以代表 文件名列表, 编译选项列表, 等等.
3. 变量名不包括 : # = 反正变量名的命名最后和编程语言的命名类试即可. 瞎搞会又意想不到的结果
4. 变量名大小写敏感
5. 有些变量名包含了几个特殊字符的, 如: $< $@ $? $* 等是make的自动变量;参考 10.5.3 自动化变量
变量的赋值有 = := define ?= 下面会分别讲到.
1. 变量引用
变量的使用可以有3中方式
1. $(VARIABLE_NAME) 使用$()
2. ${VARIABLE_NAME} 使用${} 和第一种是等价的
3. $@ $P 不使用括号, 对单个字符的变量名有效, 自动变量就是用这种方式; $P 就是引用名为P的变量名
提示:
在命令行使用shell的变量 因为要用到"$" 字符需要使用2个"$$"转义, 才表示命令行种的变量引用, 如下:
@echo $$PATH
2. 两种变量定义(赋值)
GUN make 变量有2种定义方式; 区别在定义的方式使用= 或 := , 展开的时机不同
1. 递归展开式变量
使用 = 或者 指示符 define (命令包); 这种方式的变量是递归方式展开的变量.
它是纯粹的文本替换, 并且是在需要用的时候才展开, 如果的变量值使用了另一个变量, 在用的时候展开,如下:
foo = $(bar) # foo变量的值是bar变量, 但此时bar 并不存在, 但对foo 来说 值是 $(bar)这个字符串而已
bar = $(ugh)
ugh = Huh?
all:
echo $(foo) # 使用变量, foo 首先展开为$(bar) ; 然后bar 再展开为$(ugh) ; 最后ugh 展开为 Huh?字符串替换
再使用foo变量的时候, 整个过程是递归展开的.
优点: 可以先给变量赋值一个为存在的变量, 只要再最终使用的时候才会对引用做展开
缺点: 1. 可能会循环引用, 导致无限递归(死循环), 如下:
CFLAGS = $(CFLAGS) –O
# 间接的循环引用
x = $(y)
y = $(x) $(z)
2. 如果变量种使用了函数, 那么函数再变量展开的地方开始执行, 导致不确定性(就好比函数传参, 却不知道到底传了什么参数)
2. 直接展开式变量
使用:= 定义的变量. 变量被定义的这一刻就对赋值内容展开, 也就是定义的时候, 就确定了值 ,
案例
x := foo
y := $(x) bar
x := later
就等价于:
y := foo bar
x := later
提示:
1. 如果新建一个变量, 使用了一个还没创建的变量, 是不行的. 那怕那个变量在其之后创建了
小结: 在复杂的makefile种建议使用 := 的方式, 这种方式和编程语言的赋值时一样的. 其值立即展开可以避免许多不确定性.
提示: 定义一个空格( 变量定义的时候注释不要一行上)
nullstring :=
space := $(nullstring) #注释
nullstring的值是空的, 而space的值是一个空格. 因为#前有一个空格存在
dir := /foo/bar #XXX
这里的dir结尾的空格也是它的一部分, 原因如上
可以从上面的例子看到, 如果比变量定义的时候同一行的注释之前的内容都是变量的一部分, 如果同行没有注释那么空格将被忽略
"?=" 操作符 : 它是一个条件赋值, 也就是给一个变量赋值的时候, 先判断变量有没有被定义过, 没有就给它赋值, 如果有就什么也不做
案例:
FOO ?= bar
其等价于:
ifeq ($(origin FOO), undefined) # 判读是否被定义过
FOO = bar
endif
3. 变量的高级用法
可以让我们更灵活的使用变量
1. 变量的替换引用
按一定的规律 对变量的值做查找替换,案例如下
foo := a.o b.o c.o
bar := $(foo:.o=.c) #展开后就是: bar = a.c b.c c.c 讲.o字符替换成.c字符
等价方式
foo := a.o b.o c.o
bar := $(foo:%.o=%.c) # 使用了% 符号
函数patsubst 也能实现查找替换 后面会讲到函数部分
2. 变量的嵌套引用
将一个变量的引用, 放入到$()中, 最终引用的变量值, 作为一个字符串传入到$()中, 当引用另一个变量
案例 1: 双层嵌套
x = y
y = z
a := $($(x)) # 首先展开 $(x) 值为y, 放入到$() 中为 $(y) 展开后为z
案例 2: 更复杂的案例, 万变不离其中, 就是把变量的计算结果放入$()再计算
x = variable1
variable2 := Hello
y = $(subst 1,2,$(x))
z = y
a := $($($(z)))
案例 3: 相对复杂的案例, 就是变量名和字面量拼接
a_objects := a.o b.o c.o
1_objects := 1.o 2.o 3.o
sources := $($(a1)_objects:.o=.c) # 根据a1的值, 来决定最后的变量名是a_objects或1_objects , 然后再进行字符串替换
无效案例: 这里嵌套的用法都是针对变量本身的, 用变量获取另一个变量的值, 如果像用函数是不行的.如下:
func = sort
bar = z a c
foo := $( $(func) $(bar) )
#对bar排序, 但内嵌的2个变量展开后是字符串 sort z a c ; 而函数的使用语法是 $(sort z a c) ; 因此无效
# 就算你用变量的值拼接除来 函数的语法也是无效的
另一种使用场景:
定义一个变量的时候也能使用变量的嵌套引用, 使得变量都是动态的.
案例:
dir = foo
$(dir)_sources := $(wildcard $(dir)/*.c)
define $(dir)_print
lpr $($(dir)_sources)
endef
该案例定义了3个变量 dir foo_sources foo_print
警告:
变量的嵌套引用可以避免就避免使用, 超过双层嵌套, 会非常难以理解.
4. 变量取值
1. 可以再make 的命令行选项中直接顶一个变量值, makefile的同名变量值会被命令行的值替代: 如 make foo=123 , foo为自定义变量值
2. 赋值方式或define 为变量赋值
3. 操作系统的环境比哪里, 再make中可以使用所有的系统变量
4. 自动化比哪里, 参考 10.5.3 自动化变量
5. 一些变量具有固定的值。参考 10.3 隐含变量
5. 如何设置变量
使用 = 或:= 来定义2中类型的变量, 定义一个变量需要了解如下细节
1. 变量名中可以包含函数或其他变量的引用, 在make读入makefile的时候确定定义的变量名.
2. 值没有长度限制, 但不排除操作系统或硬件的限制; 在一行上写值的时候, 如果太长可以使用"\"来换行
3. 当引用一个没有定义的变量, 其值默认为空
4. 一些特殊变量, make对他们有固定值, 但我们可以显示修改他们
5. 自动变量, 有2个符号组成的特殊变量, 在不同规则中, 根据规则为他们赋值
6. 使用?= ,来判读一个变量是否已经被定义过 ,如没有就给其赋值.
6. 追加变量值
使用 += 可以给变量追加值, 需要知道的特性如下:
1. 如一个变量没有被定义直接使用 += 定义, 等价于 = 的赋值方式
2. 对 := 的变量追加就是纯粹追加. 最好理解
3. 对 = 的追加, 看似会操作循环引用, 其实不会, make 会对其本身的值先展开, 在追加 , 再赋值, 如下:
FOO = $(includes) -O
FOO += -pg # 其逻辑是 FOO = $(includes) -O -pg ; 这种方式的最追加, 对FOO从新赋值了.
7. override 指示符
使用make命令行选项可以直接定义一个变量, 如果变量在makefile中存在, 其值被命令行中的值替换
如果不想被命令的值替换, 就需要使用 override指示符. 方式如下:
override VARIABLE = VALUE
override VARIABLE := VALUE
override VARIABLE += MORE TEXT
override define VARIABLE
...
endef
提示:
使用了override 的变量, 后续要最佳的话 也要使用override, 要不然最佳的内容不起效
使用场景:
使用它并不是为了真的屏蔽 命令行中定义的变量, 而是可以在命令行中的变量值追加到进来,
如一些编译选项是必须的, 但有时候又希望通过命令行来追加一些选项: 如下
override CFLAGS += -g # 不管如果该变量的值 -g 每次都会有
命令行:
make CFLAGS=-O2 # 命令最佳编译选项, 此时在makefile中的CFLAGS值为 -O2 -g
8. 多行定义 (define的使用)
在命令包时使用过define, 它可以用 = 来写成等价的方式, 所有它是递归展开型的变量, 如下
1. 使用: 使用 define 开始 加一个变量名 换行添加变量值, 末尾行endef 结束
define two-lines
echo foo
echo $(bar)
endef
使用 = 定义方式的等价方式
two-lines = echo foo; echo $(bar)
2. 是递归展开型的变量
3. 可以嵌套应用
4. 值中可以包含 换行符, 空格等特殊字符, 但是以[tab]开头的行, 这行会被当命令行处理, 而不是变量的值
5. 可使用override 在定义是声明比哪里
9. 系统环境变量
操作系统上的所有环境变量对make都是可见的, 对系统环境变量值的修改影响当前构建运行时.
注意点:
1. 在Makefile中定义变量名和系统变量的名称相同会赋值系统的值; 如果不想被覆盖使用make命令行选项 -e
make -e # 系统变量在makefile中的覆盖无效, 始终获取系统环境变量的值
2. make的递归调用中, 系统变量和命令行中定义的变量 都会传递给子make; makefile中的比哪里 需要使用 export指示符才能传递给子make
3. 特殊的系统变量SHELL, 在makefile中不会被使用, make默认给其值赋值为/bin/sh; 对该变量也不建议自己去改
4. make 构建过程中要使用的变量, 最好不要放入系统环境中, 写makefile中也利于切换环境有能构建成功, 并且污染系统变量也会造成不可预知的问题
10. 目标指定变量
在makefile中定义的变量和命令行中定义的变量都会被认为时全局变量(如果要运用到其他makefile需要使用export 对变量声明)
目标变量就是, 它的作用域时针对指定目标的/目标的依赖的构建, 目标依赖和全局变量同名, 那么它看不到全局变量的值.
语法:
TARGET ... : [override] 变量名 = 值 ... # 赋值方式不限于 = := += ?=
注意:
1. 它只适用于目标的规则下的作用域, 不影响全局变量
2. 和全局变量一样, 如果命令行指定了变量名的值, 那么目标变量的值也会被覆盖. 如不想覆盖使用override修饰
3. 如果目标变量名称和系统变量相同, 规则和全局变量一样. 命令行使用-e 选项, 预防系统变量被覆盖
4. 目标变量名称和全局变量名称相同, 但他们时不同的变量, 所以赋值方式(= 或:=)不同不相干.
5. 一个目标指定了一个目标变量, 在目标的规则中有多个依赖, 那么依赖被构建的时候也受这个目标变量印象!!!
myfoo = 999
echo9 : myfoo = 100
echo9 : echo9_1
@echo $(myfoo) # 变量输出 100
echo9_1 :
@echo 目标的依赖,受目标的变量印象, 依赖使用对应的变量读取到了目标的变量而不时全局变量:$(myfoo) # 变量输出 100
11. 模式指定变量
可以说目标指定变量的超集; 模式指定变量值的是使用 模式符号 % 符合其名称的目标都使用改变量
案例 :
%.o : foo = 100 # 匹配所有.o结尾的目标 都用改变量值
abc% : foo = 100 # abc开头的目标 使用改该变量
% : foo = 100 # 这个匹配所有的目标, 类试全局变量.
提示:
1. 限制有3个变量, 全局 / 模式 / 目标 变量; 目标的变量优先级最高, 其次是模式匹配上的变量, 高优先级的变量覆盖高优先级的
1. 这些局部变量, 如果使用了 += 的方式创建局部变量, 它会先读取全局值, 再追加新值(当然对全局变量不影响),
案例:
foo = 1
%.o : foo += 2
a.o : foo = 3
第七章 Makefile条件执行
条件语句可以跟你讲变量的值来控制make执行或忽略Makefile的特定部分. 它只能控制makefile的文件部分, 不能控制shell命令执行过程.
1. 一个例子: 根据不同的编译(gcc)来决定使用哪个库
libs_for_gcc = -lgnu # gnu 的库
normal_libs = # 普通库
foo: $(obkects) # 一个规则
ifeq($(CC), gcc) # 判读CC是不是gcc
$(cc) -o foo $(objects) $(libs_for_gcc)
else
$(cc) -o foo $(objects) $(normal_libs)
endif
说明:
1. ifeq后面跟一个空格(空白符即可), 之后是一个括号, 里面的比较条件用逗号分割, ifeq 格式有多种,下面讲
2. else 条件不满足时才执行的部分
3. endif 结束标记
提示:
1. 条件表达式工作于文本级别, 在make解析Makefile时根据表达式, 保留符号条件的文本行.
2. 条件判断的基本语法
语法:
1. 没有else的语法:
CONDITIONAL-DIRECTIVE
TEXT-IF-TRUE
endif
2. 包含 else
CONDITIONAL-DIRECTIVE
TEXT-IF-TRUE
else
TEXT-IF-FALSE
endif
关键字:
1) ifeq
该关键字是判断相等的. 有2种格式 , 使用括号或引号; 引号部分单双引号, 如下:
ifeq (ARG1, ARG2)
ifeq 'ARG1' 'ARG2' # 变量或这用引号引起来,
ifeq "ARG1" "ARG2"
ifeq "ARG1" 'ARG2'
ifeq 'ARG1' "ARG2"
场景:
1. 判断一个变量的值是否为空(没有任何字符): ifeq ($(foo), )
2. 一个变量前后可能包含空格, 那么我们要去除空格后再比较使用函数$(strip, val), 例子如下:
ifeq ($(strip $(foo), ) # 先对foo的值去除前后空格
2) ifneq
判断不相等, 和ifeq一样的用法
3) ifdef
判断变量是否已经定义, 格式如下:
ifdef VARIABLE-NAME
提示:
1. 如果一个变量定义了, 但没赋值, 那么也返回false
2. 引用的对象为空, 但对变量来说是定义的, 返回true
案例:
bar =
ifdef $(bar) # false
foo = $(bar)
ifdef foo # true
小结: 虽然对变量本身为空ifdef能返回false, 但是用它来判断空值是错误的, 变量应用了一个空值变量, 拿它本身不是空变量了
4) ifndef
和ifdef 相反
3. 标记测试的条件语句
这个是个案例, 使用变量MAKEFLAGS和函数findstring, 对make命令行的选项进行测试.
一个案例代码(一个规则),如下:
archive.a : ....
ifneq ( , $(findstring t, $(MAKEFLAGS))) # 判断make命令选项是否包含 -t选项
+touch archive.a # “+”之后的命令都需要被执行。-t选项不会执行其他命令,他只会对目标命令执行 touch命令
+echo ??? # 在-t的时候, 如果不使用+, 这行命令不会被执行
else
....
endif
第八章 make的内嵌函数
make的函数提供了处理文件名, 变量, 文本, 命令的方法.
1. 函数的调用语法
类试变量的引用使用$, 格式如下:
$(FUNCTION ARGUMENTS) 或者 ${FUNCTION ARGUMENTS} # 可以使用() 或者 {}
说明:
1. FUNCTION是需要调用的函数名
2. ARGUMENTS 参数, 和函数名使用空白符分割, 多参数的时候参数使用逗号分割
3. 以$开头, 使用成对的圆括号或花括号把函数和参数括在其中, 建议使用(), 不要一会() 一会{}; 如: $(sort ${x})不建议这样写
4. 函数中的参数使用变量的时候, 按顺序展开.
5. 函数的参数值, 本身不能是逗号或空格; 如果要使用逗号或空格, 那么定义成变量的值, 在参数中引用该变量,如下
comma:= ,
empty:=
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo)) # 这样我们就实现了“bar”的值是“a,b,c”。
2. 文本处理函数
以下是GNU make的内置文本(字符串)处理函数:
1. $(subst FROM, TO, TEXT) 字符串替换 ; 把TEXT中的FROM 替换为TO ; 返回新字符串
案例:
$(subst ee,EE,feet on the street)
返回: fEEt on the strEEt
2. $(patsubst PATTERN,REPLACEMENT,TEXT) 模式替换函数;
搜索TEXT中以空格为分割的单词, 将符合模式PATTERN替换为REPLACEMENT模式,
参数PATTERN中使用模式通配符%, PATTERN中第一个出现的%才是通配符, 其余的都是%字符串本身, 如果第一个是%字符,那么需要使用"\"转义
案例:
$(patsubst %.c, %.o, x.c.c bar.c)
返回 x.c.o bar.o
提示:
1. 因为变量本身有一些高级用法, 所有这个列子等价的写法:
$(objects:.o=.c)
$(patsubst %.o,%.c,$(objects))
3. $(strip VAL) 去除开头和结尾的空字符. 一般用在判读语句中,对变量进行"清理", 让表达式更可靠健壮
案例:
STR = a b c
LOSTR = $(strip $(STR))
结果是“a b c”。
4. $(findstring FIND,IN) 查找字符串;
拿FIND到IN字符串中查找, 如果找到FIND,就返回, 否则返回空
示例:
$(findstring a,a b c)
$(findstring a,b c)
第一个函数结果是字“a”;第二个值为空字符。
5. $(filter PATTERN... , TEXT) 过滤函数
过滤字符串TEXT中不符合模式PATTERN的单词. 模式字符串一般包含模式字符%; 可以有多个模式(PATTERN), 多个模式使用空格分割
从TEXT返回符合条件的字符串
示例:
sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
cc $(filter %.c %.s,$(sources)) -o foo # 使用返回值(foo.c bar.c baz.s)给cc来编译生成目标“foo”
6. $(filter-out PATTERN...,TEXT) 反过滤函数
filter函数想法操作. 符合模式(PATTERN)字符串的过滤掉
7. $(sort LIST) 排序函数
给LIST中的单词以首字母进行升序排序, 并去除重复的单词
返回没有重复单词的字符串
8. $(word N,TEXT) 取单词函数
从字符串列表TEXT中取出第N个单词( N的下标从1开始)
提示: 如果N大于了TEXT中的个数, 返回空字符串, 如果N为0 (负数一样)出错
示例:
$(word 2, foo bar baz)
返回值为“bar”。
9. $(wordlist S,E,TEXT) 从字符串列表中取出一个子集.
从TEXT中取一个子集; 从S位置开始到E位置结束
提示: S 大于TEXT的内容个数, 返回空
S 大于E 返回空
E 大于TEXT的内容个数, 返回S开始的所有字符串
如果S < 1会报错
示例:
$(wordlist 2, 3, foo bar baz)
返回值为:“bar baz”
10. $(words TEXT) 统计单词数目函数
示例:
$(words, foo bar)
返回值是“2”
返回最后一个单词
$(word $(words TEXT),TEXT)
11. $(firstword NAMES…) 获取第一个单词
示例:
$(firstword foo bar)
返回值为“foo” 等价于 $(word 1, NAMES…)
实用案例:
场景: 源代码文件(.c), 可以会用到一些其他目录下的头文件(.h); 那么我们需要使用gcc的-I参数来指定头文件所在目录;
我们的make可以根据VPATH来搜索文件的所在目录,(VAPTH 多个子目录使用":"分割), 结合函数可以实现一个编译代码, 如下:
代码:
VAPTH = ../includes:src
override CFLAGS += $(patsubst %, -I%, $(subst :, , $(VAPTH)))
展开后: CFLAGS += -Isrc –I../includes
3. 文件名处理函数
1. $(dir NAMES…) 取目录: 获取文件名(NAMES)中的路径部分(目录部分)字符串
如果文件名是一个全路径就很简单, 直接返回最后一个目录分隔符"/"之前的字符串
如果就一个文件名, 没有目录, 会返回"./" 当前目录的字符串
示例:
$(dir src/foo.c hacks)
返回值为“src/ ./”。
2. $(notdir NAMES…) 取文件的名称; 一个带全路径的文件名, 取出他的文件名部分
如果NAMES的文件, 没有带目录路径, 那么直接返回文件名
如果文件时以"/", 结尾就返回空字符
示例:
$(notdir src/foo.c hacks)
返回值为:“foo.c hacks”。
3. $(suffix NAMES…) 取后缀函数
获取最后一个以"."开始的字符串部分. NAMES是多个字符串的列表. 如果没有"." 返回空
示例:
$(suffix src/foo.c src-1.0/bar.c hacks)
返回值为“.c .c”。
4. $(basename NAMES…) 取前缀函数
suffix相反, 从最后一个点"." 之前的字符串. 多个点的情况下也是从最后一个点开始
示例:
$(basename src/foo.c src-1.0/bar.c /home/jack/.font.cache-1 hacks)
返回值为:“src/foo src-1.0/bar /home/jack/.font hacks”。
5. $(addsuffix SUFFIX,NAMES…) 加后缀函数
把后缀SUFFIX字符串添加到NAMES列表中字符串的后缀.
示例:
$(addsuffix .c,foo bar)
返回值为“foo.c bar.c”。
6. $(addprefix PREFIX,NAMES…) 添加前缀函数
和addsuffix函数的反操作, 在字符串的前面添加PREFIX字符串
示例:
$(addprefix src/,foo bar)
返回值为“src/foo src/bar”
7. $(join LIST1,LIST2) 单词链接
LIST1中第一个单词和LIST2中的第一个单词链接, 以此类推.
提示: 如果 一个列表比另一个列表长, 那么多出来的那部分会追加到链接好的字符串列表之后返回.
示例 1:
$(join a b , .c .o)
返回值为:“a.c b.o”。
示例 2:
$(join a b c , .c .o) # 第一个列表多一个c, 什么也不错返回到结果列表中了.
返回值为:“a.c b.o c”
8. $(wildcard PATTERN) 获取当前目录下 匹配模式 匹配上的文件名 函数
列出当前目录下符合模式(PATTERN)格式的文件名
提示: 这个模式使用的是shell的通配符 ?(单字符) *(多字符)
示例:
$(wildcard *.c)
返回值为当前目录下所有.c 源文件列表。
4. foreach 函数(循环)
foreach 和前面的函数不同, 它类试编程语言的for语句
语法:
$(foreach VAR,LIST,TEXT)
说明:
1. 按时先展开变量 VAR , LIST; 其中LIST是被遍历的对象, 从LIST中遍历出的元素赋值给VAR
2. TAXT可以是一个变量或者函数, 它引用了VAR, 展开计算.
3. 返回值是 TEXT的计算结果, 是一个空格分割的列表
案例:
dirs := a b c d
files := $(foreach dir,$(dirs),$(wildcard $(dir)/*))
# 这里遍历对象有4个元素, 会循环展开会 $(wildcard a/*) $(wildcard b/*) $(wildcard c/*) $(wildcard d/*)
# 该例子有给等价的写法:
files := $(wildcard a/* b/* c/* d/*)
提示:
1. TEXT的表达式式可能比较赋值, 我们已将作为一个遍历在函数之外, 进行定义,案例如下:
find_files = $(wildcard $(dir)/*) # TEXT的函数定义到一个变量中
dirs := a b c d
files := $(foreach dir,$(dirs),$(find_files))
案例使用 = 的赋值方式, 所有可以使用为定义的遍历dir;
2. VAR是一个局部遍历, 它和全局遍历有冲突无所谓, 不会冲突. 循环时可见.
3 VAR在TEXT执行的时候直接展开, 它的命令是一个单词, 不要使用其他的字符串(如含空格等)
5. if 函数
作用类试条件语句 ifeq ; 它是一个函数, 书记更简洁一点, 语法如下
函数语法:
$(if CONDITION,THEN-PART[,ELSE-PART])
说明:
1. CONDITION 是一个表达式, 如果值为非空返回 true;
2. THEN-PART 也是一个表达式, 为true是执行该表达式
3. ELSE-PART 表达式, 为false执行, 它不是必须的, 可以忽略
4. 返回值是2个表达式的执行后的返回值.
函数示例:
SUBDIR += $(if $(SRC_DIR) $(SRC_DIR),/home/src)
函数的结果是:如果SRC_DIR变量值不为空,则将变量“SRC_DIR”指定的目录作为一个子目录;否则将目录“/home/src”作为一个子目录。
6. call函数
是一个可以创建定制化参数函数的引用函数. 使用该函数可以实现用户自定义函数的引用.
我们可以将一个变量定义为一个复杂的表达式, 用call调用这表达式, 并传入不同的不同参数, 来实现表达式的展开结果根据不同的参数, 返回不同的值
语法:
$(call VARIABLE,PARAM,PARAM,...)
说明:
1. VARIABLE 是一个变量名(也就是我们的复杂表达式复制给了这个变量名) 变量名中会用一些临时变量,如:$(1), $(2)
2. PARAM 参数列表, 他会转换为$(1) $(2) ... 这些临时变量, 使得VARIABLE表达式可以获取参数值,
3. 返回值就是VARIABLE表达式根据参数代入后展开的计算结果
提示:
1. 函数中的函数中的VARIBLE 是一个变量名, 它不是对变量的引用所以不需要是$
2. 该变量名, 可以是一个计算的变量名, 如: $(a)_foo ;根据变量a的值来决定最终的 变量名
3. 如果VARIBLE变量名使用了内嵌函数名(if, foreach,...) 对参数使用要注意, 不正确的参数产生难以预料的返回值.
4. VARIBLE的变量定义不能直接展开式, 直接展开肯定出错, 因为在变量的值中会使用$(1)...这些值在call调用的使用才产生.
5. call 可以嵌套使用, 如VARIBLE的表达式中使用了call
6. 对于参数的值, 如果包含多余空格, 那么它会保留空格传递给表达式, 是否在表达式中要出来空格,看情况而定
案例1:
reverse = $(2)$(1)
foo = $(call reverse,a,b) # 2个参数 a,b 导入到表达式 $(2)$(1) 后返回 ba
案例2:
pathsearch = $(firstword $(wildcard $(addsuffix /$(1),$(subst :, ,$(PATH))))) # 看是赋值, 从最里面的函数开始向外展开即可得到结果
LS := $(call pathsearch,ls)
案例3:
map = $(foreach a,$(2),$(call $(1),$(a))) # 嵌套使用 call; 这个的call 调用了 map; 这里$(a)是foeach的临时变量a
o = $(call map,origin,o map MAKE)
7. value函数
该函数可以获取变量定义的字符串;
语法:
$(value VARIABLE)
说明:
1. VARIABLE 是一个变量, 不需要使用$对变量进行展开
2. 返回VARIABLE这个变量 定义文本值. 比如 foo = $PATH ; 那么定义文本为 $PATH ,返回这字符串
示例:
FOO = $PATH #
all:
echo $(FOO) # echo ATH
echo $(value FOO) # echo $PATH # FOO在value函数中返回$PATH字符串,这个会被在shell中展开
8. eval函数
可是使用它, 在MAKEFILE中动态的构造一个变化的规则关系(或依赖关系链),
可以对传递给它的字符串 进行语法解析, 通过解析生成: 新的变量,目标,隐含规则,或明确规则等.
提示:
1. 它没有返回值.
2. eval 会对他的参数进行2次展开:
1. 先对参数(字符串)中使用了变量的事情, 对这些变量进行展开, 让其成为字符串的一部分
2. 经过第一次展开完成后, 将该字符串展开到makefile中, 进行语法解析, 使其成为makefile的一部分
3. 对使用字符本身的$字符 需要转义成$$.
例子:
PROGRAMS = server client
server_OBJS = server.o server_priv.o server_access.o
server_LIBS = priv protocol
client_OBJS = client.o client_api.o client_mem.o
client_LIBS = protocol
# Everything after this is generic
.PHONY: all
all: $(PROGRAMS)
# 使用define 定义一个变量, 该变量本身的意义就是当一个文本, 最后使用eval函数讲该函数转化为makefile的一个部分
define PROGRAM_template
$(1): $$($(1)_OBJ) $$($(1)_LIBS:%=-l%)
ALL_OBJS += $$($(1)_OBJS)
endef
# 1.使用forech 遍历变量PROGRAMS
# 2.使用call将变量的结果带入到变量中, 替换PROGRAM_template变量中的$(1)
# 3. call 将PROGRAM_template的值(文本), 变成makefile的一部分.
$(foreach prog,$(PROGRAMS),$(eval $(call PROGRAM_template,$(prog)))) # 这行foreach结合eval的语句展开的结果如下:
#server : $(server_OBJS) –l$(server_LIBS)
#client : $(client_OBJS) –l$(client_LIBS)
$(PROGRAMS): # 这里虽然没有依赖, 但通过上面的动态展开(eval), 接这个规则, 使得它是一个多规则的目标, 执行对于目标时会把多规则合并, 合并后就有依赖了
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
clean:
rm -f $(ALL_OBJS) $(PROGRAMS)
9. origin函数
该函数有固定的返回值, 它检查变量的在哪定义的(或没有定义), 或者说变量的出处
语法:
$(origin VARIABLE) # VARIABLE 时变量的名称
返回值:
1. undefined 变量“VARIABLE”没有被定义。
2. default 内值的变量如 CC RM MAKE
3. environment 操作系统环境变量
4. environment override make在命令行选项使用 -e 后的 操作系统环境变量
5. file 在makefile中定义的变量
6. command line 命令行中定义的
7. override 在 makefile 文件中定义并使用“override”指示符声明, 在命令行定义的变量在makefile中有override了, 也时该返回值
8. automatic 自动化变量
案例:
# 如果 bletch 时一个环境变量, 那么进行从新定义
ifneq "$(findstring environment,$(origin bletch))" ""
bletch = barf, gag, etc.
endif
10 shell函数
将一个命令当参数传递给该函数, 返回值是命令的输出, 如果命令输出多行, 那么make会 去除换行符(\n \n\r) 将对行使用一个空格分割
返回值: 在 shell 环境中的执行结果.
提示:
1. 最好使用直接展开式的方式使用函数给变量赋值, 这样make在解析时就可以将命令执行完毕
案例:
contents := $(shell cat foo) # 将foo文件中的内容赋值给变量
files := $(shell echo *.c) # 将目录下的所有.c文件赋值给变量, 可以使用等价的函数调用$(wildcard *.c)
11. make的控制函数
在make执行过程中通过检查某个错误时, 可以主动调用函数来中止make的执行, 并给出提示信息
1. $(error TEXT...)
功能: 该函数执行的时候会把提示 TEXT... 信息输出给用户, 并退出make的执行.
示例 1:
ifdef ERROR1 # 判断变量是否定义, 没有定义变量就报错退出
$(error error is $(ERROR1))
endif
示例 2:
ERR = $(error found an error!)
.PHONY: err
err: ; $(ERR) # 这个是一个伪目标规则, 主动调用才触发
2. $(warning TEXT…)
和error函数类似, 但它不会让make退出.
第九章 执行make
1. 指定makefile文件
在make命令行选项使用 -f (或 --file 或 --makefile) 指定文件时, make将把指定文件作为要解析的Makefile文件
不指定文件, make在当前目录依次搜索命名为“GNUmakefile”、“makefile”和“Makefile” 这些命名的文件来解析(先找到哪个用哪个)
2. 指定终极目标
第一个非点开头的目标, 为终极目标(多目标的规则中, 第一个目标就是终极目标)
可以在make 命令行参数下指定一个目标, 该目标认为是这次make执行过程终极目标
提示:
1. 在命令行参数里指定一个终极目标的时候, 可以指定多个!!
2. 任何在Makefile中的目标都可以被指定为终极目标.
3. 命令行参数指定目标的时候, 该目标不一定必须在Makefile或者说目录下根本就没有Makefile, 也能指定一个目标只要符合隐含规则即可.
比如: 有一个foo.c 文件, 没有makefile的情况下, 可以使用 make foo 来编译该文件(使用了隐含规则)
4. 与终极目标相关的变量: MAKECMDGOALS , 当在命令行中指定终极目标的时候, 该目标字符串会在该变量中存在,
使用该变量结合自动生成目标依赖makefile, 可以在指定clean目标时避免重建.d文件
案例:
ifneq ($(MAKECMDGOALS),clean) #如果变量的值不是clean时返回true
sources = $(wildcard *.c)
objects = $(patsubst %.c,%.o, $(sources))
all : $(objects)
$(CC) -o myC $(objects)
# 根据.c文件生成依赖规则(makefile, 以.d结尾)
%.d: %.c
$(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
# 将生成的.d makefile文件引入进来.
include $(sources:.c=.d)
endif
一般开源项目的Makefile包含的几个目标, 以下目标是开源项目的一直约定, 如果你也包含了下面的内容显得你更专业
all
顶层目标, 一般默认得终极目标
clean
伪目标定义了一组命令,这些命令的功能是删除所有由 make 创建的文件
mostlyclean
和“clean”伪目标功能相似, 不会全部删除由 make生成的文件。比如说不需要删除某些库文件。
distclean
realclean
clobber
类似于伪目标“clean”,但它们所定义的删除命令所删除的文件更多。
可以包含非 make 创建的文件。例如:编译之前系统的配置文件、链接文件等。
install
将 make 成功创建的可执行文件拷贝到 shell 环境变量“PATH”指定的某个目录。
典型的,应用可执行文件被拷贝到目录“/usr/local/bin”,库文件拷贝到目录“/usr/local/lib”目录下。
print
打印出所有被更改的源文件列表
tar
创建一个 tar 文件(归档文件包)。
shar
创建一个源代码的 shell 文档(shar 文件)。
dist
为源文件创建发布的压缩包,可以使各种压缩方式的发布包。
TAGS
创建当前目录下所有源文件的符号信息(“tags”)文件,这个文件可被 vim 使用。
check
test
对 Makefile 最后生成的文件进行检查。
3. 替代命令得执行
有时候不一定要构建程序, 只是想debug下, 或这看下哪些目标需要更新. 用下面的命令行选项参数可以实现
-n --just-print --dry-run --recon 不执行命令, 只打印命令语句
-t --touch 类试shell下的 touch命令, 更新所有目标文件的时间戳.
-q --question 检查目标是否是最新的(也就是检查有没有依赖比目标新的) 返回0表示目标是最新的. 反正返回1; 不会执行任何命令
-W FILE --what-if= FILE --assume-new= FILE --new-file= FILE 指定一个目标文件FILE, 把当前时间作为该文件的时间戳, 那么该文件会被重建, 依赖也会被重建
-W 的使用技巧技巧/场景:以下三个参数同时使用时可能会出现错误。
1. 和-n 一起使用, 可以看到哪些目标依赖于这个文件
2. 和-t 一起使用, 更新这些依赖文件的时间戳
3. 和-q 一起使用, 由于将当前时间作为指定文件的时间戳,所以 make 的返回状态在没有错误发生时为 1,存在错误时为 2。
可以和“-n”或者“-q”参数配合使用来查看修改所带来的影响(导致那些目标会被重建)。
提示:
参数“-n”、“-t”和“-q”不影响之前带“+”号和包含“$(MAKE)”的命令行的执行
4. 防止特定文件被重建
有时修改了一个文件后,并不希望重建那些依赖该文件的目标, 比如在一个头文件中加入了一个宏定义, 该修改并不对已经编译的程序产生影响(可能对新的程序有用)
因为头文件被修改所有依赖目标都要重新编译, 为了避免重新编译可以如下方式处理:
方式1: 对依赖它的目标文件, 全都执行-t 选项, 更新时间戳; 可以使用 -W 如下:
make -W xx.h -t # 此时依赖xx.h 的目标文件都会被更新时间戳, xx.h 不会被更新
提示: 此时终极目标也被更新了, 可以手动对某一个目标文件执行 touch 命令, 这样再执行make就可以重新编译终极目标了
或者是 使用-W某一个刷新过时间戳的目标文件, 来构建
方式2: 使用命令行选项 –o HEADERFILE 该选项的参数必须是一个头文件(.h)!!!
有多个头文件可以使用 多个 –o HEADERFILE
5. 替换变量定义
就是在命令行中对定义变量, 在变量在Makefile中会被覆盖, 如Makefile的的变量不想被覆盖使用override 关键字
6. 使用make进行编译测试
一次修改对个文件, 可以会又多次错误, 直接使用make遇到错误就会退出, 不能一次知道又多少个文件出错了.
使用 -k 或 --keep-going 当某个目标执行出错, 还会继续执行后续, 直到最后出现致命错误(无法重建终极目标)
这样我们就可以知道有那几个文件需要修改...
7. make 命令行选项
一下信息都可以通过man手册都可以获取
-b -m : 忽略,提供其它版本 make 兼容性。
-B --always-make : 强制重建所有规则的目标,不根据规则的依赖描述决定是否重建目标文件。
-C DIR --directory=DIR : 在读取Makefile之前,进入目录“DIR”,就是切换工作目录到“DIR”之后执行make。存在多个“-C”选项时,make的最终工作目录是第一个目录的相对路径。
如:“make –C / -C etc”等价于“make –C /etc”。一般此选项被用在递归地make调用中。
-d make在执行过程中打印出所有的调试信息(等价于 --debug=a)。包括:make 认为那些文件需要重建;那些文件需要比较它们的最后修改时间、比较的结果;重建目标所要执行的命令;使用的隐含规则等。使用“-d”选项我们可以看到 make 构造依赖关系链、重建目标过程的所有信息,它等效于“—debug=a”.
--debug[=OPTIONS] : make执行时输出调试信息。可以使用“OPTIONS”控制调试信息级别。默认的OPTIONS的值为b ,OPTIONS的可能值为以下这些,首字母有效(all 和 aw等效, a后面在多字符也会被忽略)
a(all) 输出所有类型的调试信息,等效于“-d”选项。
b(basic) 输出基本调试信息。包括:那些目标过期、是否重建成功过期目标文件。
v(verbose) “basic”级别之上的输出信息。包括:解析的 makefile 文件名,不需要重建文件等。此选项目默认打开“basic”级别的调试信息。
i(implicit)输出所有使用到的隐含规则描述。此选项目默认打开“basic”级别的调试信息。
j(jobs)输出所有执行命令的子进程,包括命令执行的 PID 等。
m(makefile)也就是 makefile,输出 make 读取 makefile,更新 makefile,执行 makefile的信息。
-e --environment-overrides : 使用系统环境变量的定义覆盖Makefile中的同名变量定义
-f=FILE --file= FILE --makefile= FILE : 指定“FILE”为make执行的makefile文件。
-h --help : 打印帮助信息
-i --ignore-errors : 执行过程中忽略规则命令执行的错误。
-I DIR --include-dir=DIR : 指定被包含makefile文件的搜索目录。在Makefile中出现“include”另外一个文件时,将在“DIR”目录下搜索(参考 3.3 包含其它makefile文件 一节)。多个“-I”指定目录时,搜索目录按照指定顺序进行。
-j [JOBS] --jobs[=JOBS] : 指定可同时执行的命令数目。在没有指定“-j”参数的情况下,执行的命令数目将是系统允许的最大可能数目。存在多个“-j”参数时,尽最后一个“-j”指定的数目(“JOBS”)有效(参考 5.3 并发执行命令 一节)。
-k --keep-going : 执行命令错误时不终止make的执行,make尽最大可能的执行所有的命令,直到出现致命错误才终止。参考 9.6 测试Makeifle 一节。
-l LOAD --load-average[=LOAD] --max-load[=LOAD] : 告诉make当存在其它任务在执行时,如果系统负荷超过“LOAD”(浮点数表示的,参考 5.3 并发执行命令 一节),不再启动新任务。没有指定“LOAD”的“-I”选项将取消之前“-I”指定的限制。
-n --just-print --dry-run --recon : 只打印出所要执行的命令,但不执行命令。
-o FILE --old-file= FILE --assume-old= FILE : 指定文件“FILE”不需要重建,即使相对于它的依赖已经过期;同时也不重建依赖于此文件任何文件(目标文件)。注意:此参数不会通过变量“MAKEFLAGS”传递给子make进程
-p --print-data-base : 命令执行之前,打印出 make 读取的 Makefile 的所有数据(包括规则和变量的值),同时打印出 make 的版本信息。如果只需要打印这些数据信息(不执行命令)可以使用“make -qp”命令。查看 make 执行前的预设规则和变量,可使用命令“make –p -f /dev/null”。
-q--question : 称为“询问模式”;不运行任何命令,并且无输出。make只是返回一个查询状态。返回状态为 0 表示没有目标需要重建,1 表示存在需要重建的目标,2 表示有错误发生。
-r --no-builtin-rules : 取消所有内嵌的隐含规则,不过你可以在Makefile中使用模式规则来定义规则。同时选项“-r”会取消所有支持后追规则的隐含后缀列表,同样我们也可以在Makefile中使用“.SUFFIXES”定义我们自己的后缀规则。“-r”选项不会取消make内嵌的隐含变量(参考 10.3 隐含变量 一节)。
-R --no-builtin-variabes : 取消 make 内嵌的隐含变量,不过我们可以在 Makefile 中明确定义某些变量。注意,“-R”选项同时打开“-r”选项。因为没有了隐含变量,隐含规则将失去意义(隐含规则是以内嵌的隐含变量为基础的)。
-s --silent --quiet : 取消命令执行过程的打印。参考 5.1 命令回显 一节
-S --no-keep-going --stop : 取消“-k”选项。在递归的make过程中子make通过“MAKEFLAGS”变量继承了上层的命令行选项。我们可以在子make中使用“-S”选项取消上层传递的“-k”选项(参考 5.6 make的递归执行 一节),或者取消系统环境变量“MAKEFLAGS”中的“-k”选项。
-t —touch : 和Linux的touch命令实现功能相同,更新所有目标文件的时间戳到当前系统时间。防止make对所有过时目标文件的重建。
-v --version : 查看 make 版本信息。
-w --print-directory : 在 make 进入一个目录读取 Makefile 之前打印工作目录。这个选项可以帮助我们调试 Makefile,跟踪定位错误。使用“-C”选项时默认打开这个选项。参考本节前半部分“-C”选项的描述。
--no-print-directory : 取消“-w”选项。可以是用在递归的 make 调用过程中,取消“-C”参数的默 认打开“-w”功能。
-W FILE --what-if= FILE --new-file= FILE --assume-file= FILE : 设定文件“FILE”的时间戳为当前时间,但不改变文件实际的最后修改时间。此选项主要是为实现了对所有依赖于文件“FILE”的目标的强制重建。
--warn-undefined-variables : 在发现Makefile中存在对没有定义的变量进行引用时给出告警信息。此功能可以帮助我们调试一个存在多级套嵌变量引用的复杂Makefile。但是:我们建议在书写Makefile时尽量避免超过三级以上的变量套嵌引用。
第十章 make的隐含规则
使用隐含规则可以让makefile更简洁, 如 一个.o的目标文件, 那么make会将后缀替换为.c后对该源文件编译,
make有很多隐含规则. 如从一个.y文件生成对应.c文件,最终生成.o文件
案例:
foo : foo.h # make会洗好澡到foo.c 生成foo的可执行文件
提示:
1. 隐含规则会使用一些内嵌变量, 来给编译器传参, 如: CFLAGS 代表了gcc 编译器编译源文件的编译选项. 改变这个变量就是改变编译选项
2. 隐含规则是不能修改的, 但是我们可以使用模式规则来定义自己的"隐含规则" 或是使用后缀规则(模式规则已经替代后缀规则, 当前保留为了兼容性)
1. 隐含规则的使用
在简单的项目, makefile 都不要明确的目标, 命令, 和规则 ; make会根据目录下的源文件来启用隐含规则, 如下案例
foo : foo.o bar.o
cc -o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)
说明:
1. 终极目标 有2个依赖, 但依赖没有写规则, make会自己使用隐含规则去生成依赖
提示:
1. make 可以编译多种语言的, 如 Pascal ,c++ ; 对于不同的语言使用不同的隐含规则(编译器)
2. 当指定一个.o 的目标, 依赖是一个.p文件(Pascal源文件), 隐含规则不会用Pascal的隐含规则, 如下"
foo.o : foo.p # maked的隐含规则会知道到foo.c 用cc来编译生成foo.o文件, foo.p 并不会影响目标的隐含规则
如果此时情况提示真的要用foo.p来生成目标文件foo.o, 那么就需要自己定制编译命令, 这样隐含规则就无效了.如下:
foo.o: foo.p
pc $< -o $@
3. 如果我们不想让一个目标执行隐含规则, 那么就使用空命令的方式,写法如下
foo : ;
使用隐含规则的简单案例, 案例中没有关于源文件的描述
CUR_DIR = $(shell pwd)
INCS := $(CUR_DIR)/include
CFLAGS := -Wall –I$(INCS)
EXEF := foo bar
.PHONY : all clean
all : $(EXEF) #终极目标, 2个依赖
foo : CFLAGS+=-O2 # 目标变量(可以当局部变量理解)
bar : CFLAGS+=-g
clean :
$(RM) *.o *.d $(EXES)
2. make的隐含规则一览
隐含规则是用后缀规则来实现的(.SUFFIXES) , 如果自定义了某类后缀的规则, 就行不会执行隐含规则.
1. 编译c程序
N.o 有N.c生成,如果该N.c文件有的话, 执行 $(CC) -c $(CPPFLAGS) $(CFLAGS)
2. 编译C++
N.o 有N.cc或者N.C生成, 如果有N.cc或者N.C文件的话, 执行 $(CC) -c $(CPPFLAGS) $(CFLAGS)
3. Pascal
N.o 有N.p 创建, 执行 $(PC) -c $(PFLAGS)
4. 还能编译其他语言: ortran/Ratfor; Modula-2 ; 汇编和需要预处理的汇编程序 ;Yacc C ;Lex C 程序; 还有其他的...
5. 链接单一的目标文件: (生成可执行文件)!!!!
当目标是N 由 N.o 生成; C编译器使用连接器(ld) 执行命令 “$(CC) $(LDFLAGS) N.o $(LOADLIBES) $(LDLIBS)”
如果是多个目标就是: x : y.o z.o 那么隐含规则会编译 y.c z.c 在编译生成 x 可执行文件, 但是一般情况y.c 可以又其他依赖, 这些依赖需要写规则指明!
提示:
每一个隐含规则在创建一个文件时都使用了变量“OUTPUT_OPTION”。make执
行命令时根据命令行参数来决定它的值,当命令行中没有包含“-o”选项时,它的值为:
“-o $@”,否则为空。建议在规则的命令行中明确使用“-o”选项执行输出文件路径。
这是因为在编译一个多目录的工程时,如果我们的Makefile中使用了“VPATH”指定搜
索目录 时,编译后的.o文件或者其它文件会出现在和源文件不同的目录中。在有些系
统的编译器不接受命令行的“-o”参数,而Makefile中包含“VPAT”的情况时,输出文
件可能会出现在错误的目录下。解决这个问题的方式就是将“OUTPUT_OPTION”的
值赋为“;mv $*.o $@”,其功能是将编译完成的.o文件改变为规则中的目标文件。
3. 隐含变量
隐含规则中使用的变量都是预定的, 这些变量称为隐含变量, 这些变量是可修改的, 可以在Makefile中, 命令行选中中或者系统环境变量中.
如果你使用了-R 或 --nobuiltin-variabes 来取消隐含变量的话, 将同时取消隐含规则.
案例:
编译.c 源文件的隐含规则是 : $(CC) -c $(CFLAGS) $(CPPFLAGS)
$(CC)的值默认是cc , 你可以修改如gcc 那么会执行 gcc -c ...
我们也可以对CFLAGS来重新定义, 编译选项.
提示:
对于变量定义后, 如果希望在子目录生效, 别忘记使用关键字 export !!! 否则目录间编译不一致
隐含规则使用的隐含变量可以分为两类:
1. 程序名 , 如 CC 变量 使用cc 或gcc程序名
2. 执行程序使用的参数 , 如 CFLAGS, gcc的编译选项
提示: 程序名变量里是可以包含选项的, 但不建议这样使用.!
3.1 代表命令的变量
AR 函数库打包程序,可创建静态库.a文档。默认是“ar”。 将 .o文件打包成静态库文件
AS 汇编程序。默认是“as”。
CC C编译程序。默认是“cc”。
CXX C++编译程序。默认是“g++”。
CO 从 RCS中提取文件的程序。默认是“co”。
CPP C程序的预处理器(输出是标准输出设备)。默认是“$(CC) -E”。
FC 编译器和预处理Fortran 和 Ratfor 源文件的编译器。默认是“f77”。
GET 从SCCS中提取文件程序。默认是“get”。
LEX 将 Lex 语言转变为 C 或 Ratfo 的程序。默认是“lex”。
PC Pascal语言编译器。默认是“pc”。
YACC Yacc文法分析器(针对于C程序)。默认命令是“yacc”。
YACCR Yacc文法分析器(针对于Ratfor程序)。默认是“yacc -r”。
MAKEINFO 转换Texinfo源文件(.texi)到Info文件程序。默认是“makeinfo”。
TEX 从TeX源文件创建TeX DVI文件的程序。默认是“tex”。
TEXI2DVI 从Texinfo源文件创建TeX DVI 文件的程序。默认是“texi2dvi”。
WEAVE 转换Web到TeX的程序。默认是“weave”。
CWEAVE 转换C Web 到 TeX的程序。默认是“cweave”。
TANGLE 转换Web到Pascal语言的程序。默认是“tangle”。
CTANGLE 转换C Web 到 C。默认是“ctangle”。
RM 删除命令。默认是“rm -f”
3.2 命令参数的变量(如果没有给出默认值表示为空)
ARFLAGS 执行“AR”命令的命令行参数。默认值是“rv”。
ASFLAGS 执行汇编语器“AS”的命令行参数(明确指定“.s”或“.S”文件时)。
CFLAGS 执行“CC”编译器的命令行参数(编译.c源文件的选项)。
CXXFLAGS 执行“g++”编译器的命令行参数(编译.cc源文件的选项)。
COFLAGS 执行“co”的命令行参数(在RCS中提取文件的选项)。
CPPFLAGS 执行C预处理器“cc -E”的命令行参数(C 和 Fortran 编译器会用到)。
FFLAGS Fortran语言编译器“f77”执行的命令行参数(编译Fortran源文件的选项)。
GFLAGS SCCS “get”程序参数。
LDFLAGS 链接器(如:“ld”)参数。
LFLAGS Lex文法分析器参数。
PFLAGS Pascal语言编译器参数。
RFLAGS Ratfor 程序的Fortran 编译器参数。
YFLAGS Yacc文法分析器参数。
4. 隐含规则链
如一个目标文件N.o构建他需要 N.c但是并没有N.c这个文件有一个 N.y文件, 那么make会使用对应的隐含规则对N.y进行编译生成N.c ,再对N.c使用隐含规则, 生成N.o
这个过程就是隐含规则链.
提示:
1. make对所有的中间文件, 默认的在构建完成后对其删除.
2. 在Makefile中没有被指定为目标文件的文件, 被中间过程生成的文件, 就是中间文件会被删除
如: N.y -> N.c -N.o 这个构建过程中 N.c是中间生成的, makefile中也不是一个目标文件, 那么他属于中间文件, 会被删除.
3. 在Makefile中提到文件都不会当中间文件处理!!!
4. 使用特殊目标.INTERMEDIATE , 将makefile中的文件作为他的依赖, make构建完成会被中中间文件删除
5. 使用特殊目标.SECONDARY, 可以指定一些中间文件, 在构建完成后, 不删除他们
6. 一个规则再一个链中只会出现一次, 否则会造成死循环.
7. 规则链逻辑上是2个(或多个过程)的命令, 隐含规则链会可以优化会优化成一个命令, 提供效率,
如构建foo程序, 因该是先编译foo.c生成foo.o 再生成foo可执行文件; 但其实会被优化成一个命令执行编译链接一起生成一个命令
5. 模式规则
类试普通规则,是模式字符 % , 它代表一个多或多个字符, 用来匹配文件名, 如 %.o : %.c
使用了模式规则自定义了隐含规则, 它是替代隐含规则!! 所有有明确规则的时候不会用到它!!
目标中的 % 符号匹配到的字符串称为茎, 依赖中的% 就会使用目标中匹配到的茎(字符串) 来替换对应的依赖文件名!!
提示:
模式字符的展开, 比变量和函数的展开还有完, 在make执行规则的时候展开
5.1 介绍
简单语法:
%.o : %c ; COMMAND ...
可以灵活使用如指定某一个开头的文件 就可以 : foo%.o : foo%.c 就是匹配以foo开头的, 可以灵活应用!!!
提示:
1. 依赖可以不用模式字符 % , 那么该指定的依赖是所有的目标对象的依赖如: dbug.h
2. 模式规则中的 目标也可以有多个, 如下:
%.o %.x : %.c ; COMMAND ...
# make还会把这个多目标的模式规则当一个整体, 命令行中 指定多个目标,时只会执行一个
案例:
%.o %.x : %.c
$(CC) $(CFLAGS) $< -o $@
执行: make foo.o foo.x #只会执行 foo.o , 如果明确的规则都会执行
3. 如果一个文件匹配上多个模式, 那么 它只会执行第一个匹配的模式去执行规则命令
4. make执行会先找明确规则 > 模式规则 > 隐含规则
5.2 模式规则示例
介绍GNU make定义的模式规则(隐含规则)案例.
%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
.o 依赖.c文件创建, %< 代表依赖, $@ 代表目标
% :: RCS/%,v
$(CO) $(COFLAGS) $<
匹配所有的文件 依赖是 RCS目录下.v结尾的文件. 如: RCS目录下有一个N.v 那么会构建N; 双冒号表示最终规则, 依赖的文件不是中间过程文件
%.tab.c %.tab.h: %.y
bison -d $<
多目标的规则
5.3 自动变量
$@ 目标名, 在多目标模式规则中, 它代表当前执行的规则的目标名
$< 规则的依赖中的第一个文件名
$^ 规则的所有依赖文件, 它会去除重复的文件名
$+ 类似 $^ 它不会去除重复文件名, 主要用在程序链接时库的交叉引用场合.
$* 在模式规则或静态模式规则中, 代表"茎" 也就是 目标模式中%匹配到的字符串
提示:
1.如果文件名中有路径名, 那么$*中也会包含路径如: 文件 dir/a.foo.b 目标模式 a.%.b 此时$*值为: dir/a.foo
2.在明确的规则中不存在茎, 但GUN make为了兼容其他make 会让它的值为除后缀的一个字符串, 因该避免使用,
$% 当目标是一个静态库文件时(.a文件-- 时一个压缩包), 它值这个库中的第一个成员名如 foo.a中有2个.o文件 a.o b.o 它指 a.o文件
$? 打包静态库文件的时候使用, 所有比目标文件更新的依赖文件列表,空格分割。如果目标是静态库文件名,代表的是库成员(.o文件)。
使用它可以指定只对更新以后的依赖文件进行操作, 也就是说只有比已有的库文件(.a), 还要新的.o文件才会被打包到.a文件中
打包的规则:
lib : foo.o bar.o
ar r lib.a $?
小结: 以上 $@、$<、$%、$* 代表文件名, 其他的代表 文件名的列表
以下介绍获取文件中目录部分和文件名部分的7个自动变量, 在新版的make中可使用函数dir/notdir来实现等价功能.
$(@D) 文件名中的目录部分,不包含斜杠. 如dir/foo.o 其值为dir, 如果一个文件名没有目录部分,其值为"."表示当前目录, 这和函数dir又区别
$(@F) 实际的文件名, 如 dir/foo.o 其值为foo.o 等级与函数 $(notdir $@)
$(*D) / $(*F) 分别代表目标"茎"中的目录部分和文件名部分
$(%D) / $(%F) 当以静态库为目标时, 如 archive(member) ; 分别代表库文件成员member名中的目录部分和文件名部分.
$(<D) / $(<F) 规则中第一个依赖文件的目录部分和文件名部分
$(^D) / $(^F) 依赖所有依赖文件的目录部分和文件名部分(会去重)
$(+D) / $(+F) 和上一个类似, 但不去重
$(?D) / $(?F) 分别表示被更新的依赖文件的目录部分和文件名部分, 就是一个依赖文件被跟新了, 才会出现在这个变量中
提示:
1.自动变量和普通变量(如: CFLAGS)直接使用 $< 这种形式 其实可以写成$(<) ;
2. GUN make 支持'Sysv make'的自动变量的特性, 允许在规则的依赖列表中使用特殊的变量引用(一般自动化变量只能在命令行中使用).
$$@ $$(@D) $$(@F) 这个3个Sysv 的变量分别 代表 目标的完整文件名, 目录部分, 实际文件名部分.
这个3个变量可以在明确的规则和静态模式规则中的依赖列中使用!
Sysv make和GUN make 的执行路径是不同的. 还有如果要禁止使用这些特性, 在Makefile中使用伪目标".POSIX"
5.4. 模式的匹配
模式规则中的目标模式由 前缀, 后缀和模式字符"%" 组成, 这3个部分可以允许2个同时为空.
去除自己定义的前缀和后缀剩下的就是"茎" (就是%匹配的若干字符)
案例 : t%.o : t%.c 文件 text.c 会被匹配, 其中 est 为茎
提示:
模式匹配的是实际的文件名, 如一个带路径名的文件名如: src/test.c
根据上的模式规则也是可以匹配这个文件的, 他的茎 "src/est" 也就是它首先进行文件的匹配, 然后再把目录字符加上,
案例:
e%t: c%r
此时有一个文件 src/eat , 根据规则提取出a, 目录为src/ ; 会去找依赖 src/car;
5.5 万用规则
没有前缀后缀, 只使用 % 的规则目标, 它可以匹配任何文件, 称为万用规则.
提示: 1. 它会印象make的执行效率, 如果我们Makefile提到了一个源代码文件 foo.c 那么它会使用所有可以使用的隐含规则来重建它, 但其实它只是一个源代码文件.
但整个过程如: 1. 找foo.c.o进行链接并生成foo.c (根本没有foo.c.o这个文件)
2. 为了得到foo.c.o 尝试编译foo.c.c文件(根本没有整个foo.c.c文件)
3. 尝试找foo.c.p 使用Pascal编译器编译 , 但也没这个文件.
4. 还有其他的隐含过程去尝试, 整个执行都会失败, 都是无用功
2. 使用了万用规则, 要考虑的情况要更多更复杂, 来避免效率低下的情况
3. 避免效率问题, 对万用规则进行限制
A. 将万用规则设置为最终规则,定义时使用双冒号规则; 这种规则, 只有依赖文件存在才执行, 依赖文件不能使用隐含规则生成, 这个最终规则没有"链"
B. 定义一个特殊的哑模式规则(类似空命令规则, 但它的目标使用模式字符"%") 如 %.p: ; 没有依赖, 没有命令行
这样如果有.p 文件被makefile提到, 会给这个哑模式规则出来, 不会匹配上万用规则, 而触发所有的隐含规则
5.6 重建内嵌隐含规则
隐含规则是可以被重建的, 只需目标和依赖模式, 和隐含规则的目标和依赖模式一样即可. 然后我们对命令部分自定义,
案例:
%.o : %.c
$(CC) $(CFLAGS) –D__DEBUG__ $< -o $@
取消一个隐含规则, 相同的思路, 只是不指定命令,
%.o : %.s
6. 缺省规则
当一个文件没有找到明确规则和隐含规则来重建它的时候, 它就会使用指定的缺省规则来重建文件.
缺省规则的2种定义方式:
1. 使用 万用规则 来定义一个缺省规则. 如下:
% ::
touch $@
场景: 当一个工程中, 部分源码还没编写, 但makefile已经写好了, 再构建的时候, 用到某个还不存在的.c文件的话, 会用到这个缺省规则来重建一个空的文件
2. 使用 伪目标 .DEFAULT 来指定一个缺省规则, 如下:
.DEFAULT
touch $@ # 和上面的万用规则的缺省规则 是 等价的,
提示: 1. 如果.DEFAULT没有指定命令(空命令)的话, 表示取消一个缺省规则,
2. 缺省规则可以用来实现一个Makefile中重载另一个makefile文件.(重载就是 2个类似的makefile, 使用另一个makefile的差异部分来构建)
.DEFAULT
@$(MAKE) -f Makefile $@ # 这样当一个目标文件没有找到明确规则或隐含规则, 就从另一个makefile中找规则
7. 后缀规则
该规则是旧版本的产物, 已经不建议使用了, 因为它功能单一, 可以使用模式规则来替代!!
在过去对隐含规则的重定义使用的是后缀规则的方式, 现在不提出使用后缀模式, make为了兼容旧的makefile, 还有保留这个特性.
后缀规则分2种类型: 双后缀 和 单后缀.
1.双后缀: 指定目标文件的后缀和依赖文件的后缀, 如下:
.o.c: # 模式规则 等价的方式 %.o : %c
$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<
2. 单后缀: 只指定一个后缀, 它指向的是源文件的后缀. 如下:
.c: # 模式规则 等价的方式 % : %c
$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<
提示:
1. 后缀规则的写法种, 不可以有依赖文件, 如 .o.c : foo.h 那么这个规则不是后缀规则而是".o.c" 这个目标文件的普通规则
2. 没有命令的后缀规则, 没有任何意义, 它会被忽略, 它不会像模式规则那样, 没有命令表示取消该规则.
3. 后缀规则, 需要用到可识别的后缀列表, 依赖一个特殊目标 ".SUFFIXES" ; 因为我们可以通过给这个目标加依赖来扩充已有的后缀列表,如下
.SUFFIXES: .hack .win # 这个可以使用这个2了后缀, 来写对应的后缀规则.
从新定义可识别的后缀列表
.SUFFIXES: # 空依赖意味着: 删除所有已定义的可识别后缀
.SUFFIXES: .c .o .h # 重新定义
提示:
1.命令行选项 -r 或 -no-builtin-rules 也可以清空后缀列表.
2.虽然有变量SUFFIXE 可以读取到后缀列表, 但是修改后缀列表,不用动这个变量, 使用上面伪目标的方式!!!
8. 隐含规则搜索算法
见 p164
第十一章 使用make更新静态库文件
静态库文件也称为"文档文件" 它是一些.o文件的集合. 在*nix中使用工具"ar" 对它进行维护管理(打包,更新).
1. 库成员作为目标
一个静态库通常由多个.o文件组成, 这些成员(.o文件)可独立的被作为一个规则的目标, 库成员作为目标时需要按照如下格式书写:
ARCHIVE(MEMBER)
提示:
1. 这种格式的书写, 只能在规则的目标和依赖中出现
2. 含有这种表达式的规则的命令行只能时"ar"命令 或者 其他可以对库成员进行操作的命令.
案例: 创建库, 并将 hack.o 进入到库中
foolib(hack.o) : hack.o
ar cr foolib hack.o
提示: 该案例规则, 实际上实现了对库的所有成员进行更新, 其过程使用了隐含规则(创建需要的.o文件)
3. 在规则中需要指定多个成员, 有如下2种写法
foolib(hack.o kludge.o)
等价于
foolib(hack.o) foolib(kludge.o)
4. 可以使用shell的通配符"*" 来表示库种的所有成员, 例如: foolib(*.o)
2. 静态库的更新
当把一个库作为目标的时候,如: A(M) ; make可以使用隐含规则来重建成员M的, 当然A(M) 这个库时可以使用隐含规则的命令来重建
提示:
1. 对应库有一个特殊的模式规则"(%)" , 也就是在Makefile中如果提到了库文件, 会被着模式给匹配到.
这个模式的命令行一般是使用ar命令来操作库
2. 特殊的模式规则和其他隐含规则一起可以构成一个隐含规则链. 我们可以直接在 make命令行中指定库目标.如下:
make 'foo.a(bar.o)' # 这个目标需要用单引号, 因为在终端命令行中会对括号作为特殊字符出来. 它会指向如下隐含规则链
cc -c bar.c -o bar.o # 先构建bar.o 使用.c文件
ar r foo.a bar.o # 创建/更新 .a库文件
rm -f bar.o # 删除中间文件bar.o , 使用隐含规则创建的的中间文件会被删除!!!
3. 当.o文件加入库后, 是不带目录信息的. 在makefile中使用A(M)格式的时候, M中式可以带目录的, 写法如下:
foo.a(dir/file.o)
ar r foo.a dir/file.o
对应这个的目标, 自己写的时候可能会需要用到自动化变量 %D %F
2.1 更新静态库的符号索引表
使用ar来创建和维护(更新) 静态库, 当给一个库添加一个.o成员时, ar 可直接将需要增加的.o文件追加到静态库的末尾.
之后, 我们使用库进行连接生成可执行文件时, 链接程序"ld" 却提示错误. 因为在库文件里维护着一个特殊成员名为"__.SYMDEF"
它维护着静态库中所有程序的有效符号(函数名, 变量名). 只将.o添加到.a中并没有更新这个文件, 导致ld使用了旧的信息, 导致报错.
因此, 当一个.o 添加到.a中后, 需要运行下面命令, 更新__SYMDEF这个文档
ranlib ARCHIVEFILE
在makefile中的写法:
libfoo.a : libfoo.a(x.o) libfoo.(y.o) ...
ranlib libfoo.a
提示:
如果使用的是 GUN ar 就不需要执行 ranlib了, GUN ar程序本身会提供更新它的功能.
3. make 静态库的注意事项
在使用 -j 选项 make的并行执行时, 如果有多个ar命令来操作相同的静态库, 将会使静态库损坏, 导致静态库不可用
可以自己添加控制策略来避免多个ar同时运行, 要么放弃并行执行, 或者在未来有新版本的make添加新特性解决这个问题.
4. 静态库的后缀规则
再次强调, 后缀规则已经淘汰, 使用模式规则替换, 单时为了兼容旧的, 需要了解下库的后缀规则, 写法如下:
.c.a :
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $*.o
$(AR) r $@ $*.o
$(RM) $*.o
等价于
(%.o): %.c
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $*.o
$(AR) r $@ $*.o
$(RM) $*.o
提示:
1. 可以看到时.c和.a组合的双后缀模式, 而不是.o.a
2. 有一种情况, 一个.a后缀的文件, 并不是静态库, 但make会当静态库处理
3. 一个双后缀规则, 如 .x.a make会转化为2个模式规则 : (%.o): %.x 和 %.a : %.x ; 也就是通过.x 来生成中间文件, 转换过程参考 10.7后缀规则一节
第十二章 GNU make的特点
第十四章 Makefile的约定
可以使用Automake 工具生成一个遵循约定的Makefile. 也就是在个种*nix上都可以运行(各种*niux的make)
可以了解一些约定, 手写也可以避免一些坑
1. 基本的约定
1.1 所有的Makefile种都包含如下一行
SHELL = /bin/sh
避免该变量在系统变量里对该变量有其他赋值, 虽然GUN make 不会使用系统中的这个变量.
1.2 小心处理后缀和隐含规则, 不同make对可识别的后缀和隐含规则可能不同, 我们可以将可识别后缀限定主是一个好办法,如下
.SUFFIXES:
.SUFFIXES: .c .o
这样对其他的后缀就不识别了.
1.3 小心处理规则中的路径, 当需要处理指定命令的文件时, 因该明确给出路径,
如:
./ 代表当前目录,
$(srcdir) 代表源代码目录, 没有指定明确路径, 以为这时当前目录
1.4 使用GUN make的变量VPATH指定搜索目录时,
对于单个依赖在命令行中尽量使用自动变量, 而不是手写目标名和依赖名
对于多个依赖在命令行中尽可能写上完整路径
提示:
make的过程不因该对源码进行修改或改变目录结构
2. 规则命令行的约定
2.1 规则中的命令是sh可以识别运行的, 而不是csh
2.2 命令行中的命令尽量使用如下几个, 避免使用其他的(不一定所有的系统都会装其他的命令)
cat cmp cp diff echo egrep expr false grep install-info
ln ls mkdir mv pwd rm rmdir sed sleep sort tar test touch true
2.3 在目标“dist”的命令行中可以使用压缩工具“gzip”。
2.4 尽可能使用命令的通用选项, 有的选项在其他系统上可能不支持, 如mkdir -p 在linux上没问题, 其他系统不一定支持-p选项
2.5 在MS-DOS系统上没有符号链接, 所有你要兼容这个系统就尽量避免使用符号链接的命令ln
2.6 对于编译器的工具(命令), 如下几个
ar bison cc flex install ld ldconfig lex
make makeinfo ranlib texi2dvi yacc
可以使用变量来引用这个命令, 因为编译器有很多种类, 换系统换编译环境了, 可以修改变量即可运行makefile了
$(AR) $(BISON) $(CC) $(FLEX) $(INSTALL) $(LD) $(LDCONFIG)$(LEX)
$(MAKE) $(MAKEINFO) $(RANLIB) $(TEXI2DVI) $(YACC)
使用“ranlib”或者“ldconfig”等这些工具时, 可能并不是所有系统都有, 没有得时候可以提示用户, 而不是报错退出执行.
还有几个命令 也可以使用变量来引用: chgrp chmod chown mknod 如: 使用$(CHMOD)来引用
提示: 如果只支持一种操作系统, 那么也不需要支持这些了, 但是还是约定得比较好, 更专业吧...
3. 代表命令变量
书写makefile的时候, 所有的命令尽可能是使用变量来引用, 命令参数亦是如此. 方便修改
如, 定义变量CC=gcc , 规则中使用 $(CC)来引用gcc.
命令行参数一般使用命令标识加FLAGS 来定义一个命令的命令行参数,
如:
c的编译器参数就是CFLAGS
ld的参数 LDFLAGS
预处理的 CCFLAGS
案例:
CFLAGS = -g # 给CFLAGS指定了一个默认值
ALL_CFLAGS = -I $(CFLAGS) # 将CFLAGS作为一个变量的值, 在命令行对CFLAGS重新定义的话, -I 也会有效, 下面规则中使用的编译选项是ALL_CFLAGS变量
.c.o:
$(CC) -c $(CPPFLAGS) $(ALL_CFLAGS) $<
在 Makefile 中实现文件安装的规则, 需要定义变量INSTALL, 代表命令install; 补充: install 类似cp命令, 但复制的时候同时加权限.
同时定义2个变量INSTALL_PROGRAM --- 默认值是$(INSTALL)
INSTALL_DATA --- 默认值是${INSTALL} –m 644
使用这些变量来安装可执行程序或者非可执行程序到指定目录位置, 如下:
$(INSTALL_PROGRAM) foo $(bindir)/foo # 安装可执行文件
$(INSTALL_DATA) libfoo.a $(libdir)/libfoo.a # 安装库文件
4 安装目录变量
在makefile 安装目录也因该是变量来定义.
介绍2个安装文件的根目录, 文件的安装因该安装在他们子目录下(如, 执行文件, 库文件是在不同的目录下, 其他如文档文件)
1. prefix : 默认值为/usr/local
2. exec_prefix : 它的默认值是$(prefix)
具体的安装目录
1. bindir : 可执行程序的安装目录, 通常是/usr/local/bin , 书写的使用因该写成$(exec_prefix)/bin
2. sbindir : 可以在shell中直接调用执行的程序, 这些命令仅对系统管理员有用; 通常为 /usr/local/sbin ; makefile中为: $(exec_prefix)/sbin
3. libexecdir : 安装的程序不是由用户直接使用的, 而是给其他程序调用的可执行程序, 通常为 /usr/local/libexec ; makefile中为: $(exec_prefix)/libexec
数据文件相关的:
1. datadir : 用于安装和机器体系结构无关的只读数据文件(如配置文件). 通常为:/usr/local/share ; makefile中写为:$(prefix)/share
2. sysconfdir : 用于安装从属于特定机器的只读数据文件.(如配置文件, 这些因该是普通的文本文件)
通常为:/usr/local/etc; makefile中写为:$(prefix)/etc
3. sharedstatedir : 用于安装那些可由程序运行时修改的文件,这些文件与体系结构无关. 通常为:/usr/local/com ; makefile中写为:$(prefix)/com
4. localstatedir : 用于安装那些可由程序运行时修改的文件,但这些文件和体系结构相关. 通常为:/usr/local/var ; makefile中写为:$(prefix)/var
5. libdir : 用于存放编译后的目标文件(.o)文件库文件(文档文件或者执行的共享库文件). 通常为:/usr/local/lib ; makefile中写为:$(prefix)/lib
6. infodir : 用于安装软件包的 Info 文件. 通常为:/usr/local/info ; makefile中写为:$(prefix)/info
7. lispdir : 用于安装软件包的 Emacs Lisp 文件的目录 . 通常为:/usr/local/share/emacs/site-lisp ; makefile中写为: $(prefix)/share/emacs/site-lisp
8. includedir : 用于安装用户程序源代码使用“#include”包含的头文件. 通常为:/usr/local/include ; makefile中写为:$(prefix)/include
提示: 只由gcc这个c编译器会在/usr/local/include中搜索头文件, 其他编译器不会主动搜索改目录
1. oldincludedir : 它所指定的目录也同样用于安装头文件,这些头文件用于非gcc的编译器. 通常为: /usr/include ;
makefile在安装头文件时, 需要判断这个变量是否为空,为空就不使用它, 一般安装到/usr/local/include下
Unix 风格的帮助文件需要安装在以下目录中:
1. mandir : 安装帮助文档的顶层目录(不定义有帮助文档). 通常为:/usr/local/man ; makefile中写为:$(prefix)/man
2. man1dir : 用于安装帮助文档的第一节(man 1). 默认值:$(mandir)/man1 ;
3. man3dir : 用于安装帮助文档的第二节(man 2). 默认值:$(mandir)/man2 ;
...
不要将软件的原始文档作为帮助页的内容, 因该编写使用手册, 帮助页时帮助用户在Uniux上方便运行GNU软件, 它时附属的运行程序.
4. manext : 文件名扩展字,它是对安装手册的扩展。以点号(.)开始的十进制数。缺省值为:“.1”
5. man1ext : 帮助文档的第一节(man 1)的文件名扩展字。
6. man2ext : 帮助文档的第二节(man 2)的文件名扩展字。
...
当一个软件包的帮助手册有多个章节时,使用这些变量代替“manext”。(第一节“man1ext”,第二节“man2ext”,第三节“man3ext”……)
另一些需要了解的变量:
1. srcdir :此变量指定的目录是需要编译的源文件所在的目录, 该变量的值在使用“configure”脚本对软件包进行配置时产生的
(使用“Autoconf”工具,应该书写为“srcdir = @srcdir@”)
案例总结:
# 安装的普通目录路径前缀。
# 注意:该目录在开始安装前必须存在
prefix = /usr/local
exec_prefix = $(prefix)
# 放置“gcc ”命令使用的可执行程序
bindir = $(exec_prefix)/bin
# 编译器需要的目录
libexecdir = $(exec_prefix)/libexec
# 软件包的 Info 文件所在目录
infodir = $(prefix)/info
提示:
在Makefile中 实现一个install的伪目标来描述安装这些文件的命令(包括创建子目录, 安装文件到对应的子目录中),
软件发布的时候, 用户可以修改那些变量来改变安装的目录, 不修改就使用默认的值
5. Makefile的标准目标名
一个GNU的软件包的Makefile中, 必须包含以下这些目标:
all
编译整个软件包, 对应makefile的终极目标. 该目标只编译所有的源代码,生产可执行文件.
默认情况下编译和链接的选项时-g, 包含了程序调试信息, 如果不需要调试信息, 可使用strip 去掉可执行程序中的调试符号以减少最终的程序大小.
install
它动作只对最终的可执行程序, 库文件等等文件, 拷贝到安装的目录. 它不改变构建目录, 它要确保安装目录的存在如prfix和exec_prefix指定的目录和子目录存在
安装man文件的命令可使用"-" 忽略安装命令的错误(并不时所有的系统有man文件)
uninstall
删除所有已安装文件——由install创建的文件拷贝, 仅仅是删除安装目录下的文件
install-strip
和目标install的动作类似,但是install-strip指定的命令在安装时对可执行文件进行strip(去掉程序内部的调试信息)。它的定义如下:
install-strip:
$(MAKE) INSTALL_PROGRAM='$(INSTALL_PROGRAM) -s' install
它的操作因该时在install之后, 对在安装目录下的执行文件进行 strip操作. 它不可以对构建目录下的程序操作!!
一般情况没必要strip 可执行程序, 这样出现bug就不能用gdb来debug了
clean
清除当前目录下编译生成的所有文件,这些文件在make过程中产生. 注意当前目录. 其他目录下的不要管
distclean
类似于目标clean,但增加删除当前目录下的的配置文件、build过程产生的文件。
目标“distclean”指定的删除命令应该删除软件包中所有非发布文件。
mostlyclean
类似于目标“clean”,但是可保留一些编译生成的文件,避免在下次编译时对这
些文件重建。例如,对于gcc来说,此目标指定的命令不删除文件“libgcc.a”,因为在绝大多数情况下它都不需要重新编译。
maintainer-clean
定义的命令几乎会删除所有当前目录下能够由Makefile重建的文件。包括目标“distclean”删除的文件、由Bison生成的.c源文件、tags记录
文件、Ifon文件等。但是有一个例外,就是执行“make maintainer-clean”不能删除“configure”这个配置脚本文件,即使“configure”可以由Makefile生成。
因为“configure”是软件包的配置脚本
提示: 该目标一般只要软件包得维护者使用而不是最终用户使用, 所有需要加一些提示如: @echo“该命令用于维护此软件包的用户使用”;@echo“它删除的文件可能需要使用特殊的工具来重建。”
TAGS
此目标所定义的命令完成对该程序的tags记录文件的更新。tags文件通常可被编辑器作为符号记录文件,例如vim,Emacs等。
info
产生必要的Info文档。此目标应该按照如下书写:
info: foo.info
foo.info: foo.texi chap1.texi chap2.texi
$(MAKEINFO) $(srcdir)/foo.texi
必须在Makefile中定义变量“MAKEINFO”,代表命令工具makeinfo,该工具是发布软件Texinfo的一部分。
dvi
为所有的Texinfo文件创建对应的DVI文件。例如:
dvi: foo.dvi
foo.dvi: foo.texi chap1.texi chap2.texi $(TEXI2DVI) $(srcdir)/foo.texi
必须在Makefile中定义变量“TEXI2DVI”。它代表命令工具texi2dvi,该工具是发布软件Texinfo一部分。
规则中也可以没有命令行,这样make程序会自动为它推导对应的命令。
dist
此目标指定的命令创建发布程序的tar文件。创建的tar文件应该是这个软件包的目录,文件名中也可以包含版本号(就是说创建的tar文件在解包之后应该是一个目录)。
例如,发布的gcc 1.40版的tar文件解包的目录为“gcc-1.40”
通常的做法是是创建一个空目录,如使用ln或cp将所需要的文件加入到这个目录中,之后对这个目录使用tar进行打包。打包之后的tar文件使用gzip压缩。
例如: 实际的gcc 1.40版的发布文件叫“gcc-1.40.tar.gz”。
目标“dist”的依赖文件为软件包中所有的非源代码的文件,因此在使用目标进行发布软件打包压缩之前必须保证这些文件是最新的。
check
此目标指定的命令完成所有的自检功能。在执行检查之前,应确保所有程序已经被创建,可以不安装。
为了对它们进行测试,需要实现在程序没有安装的情况下被执行的规则命令。
installcheck
执行安装检查。在执行安装检查之前,确保所有程序已经被创建并且被安装。需要注意的是:安装目录“$(bindir)”是否在搜索路径中。
installdirs
使用目标“installdirs”创建安装目录以及它的子目录在很多场合是非常有用的。
脚本“mkinstalldirs”就是为了实现这个目的而编写的;
Makefile中的规则可以这样书写:
# 确保所有安装目录(例如 $(bindir) )存在,如有必要则创建这些目录
installdirs: mkinstalldirs
$(srcdir)/mkinstalldirs $(bindir) $(datadir) \
$(libdir) $(infodir) \
$(mandir)
或者可以使用变量“DESTDIR”:
# 确保所有安装目录(例如 $(bindir) )存在,如有必要则创建这些目录
installdirs: mkinstalldirs
$(srcdir)/mkinstalldirs \
$(DESTDIR)$(bindir) $(DESTDIR)$(datadir) \
$(DESTDIR)$(libdir) $(DESTDIR)$(infodir) \
$(DESTDIR)$(mandir)
该规则不能更改软件的编译目录,仅仅是创建程序的安装目录。
该规则不能更改软件的编译目录,仅仅是创建程序的安装目录。
6. 安装命令分类
Makefile 书写“install”目标时,需要将其命令分为三类:正常命令、安装前命令和安装后命令。
正常命令 : 是把文件移动到合适的地方,并设置它们的模式。这个过程不修改任何文件,仅仅是把需要安装的文件从软件包中拷贝到安装目录
安装前命令 : 大多数程序不需要安装前命令,但应该在 Makefile 中提供。
安装后命令 : 修改一些配置文件和系统的数据库文件。装前命令在正常命令之前执行,安装后命令在正常命令执行后执行。
安装命令可以运行install-info程序, 它只能在安装命令完成安装软件包的info文档之后才能正确执行.
install目标规则的命令使用变量命令,如下:
install :
$(PRE_INSTALL) # 以下是安装前命令
$(POST_INSTALL) # 以下是安装后命令
$(NORMAL_INSTALL) # 以下是正常命令
这样就可以方便修改具体的安装时的命令, 如在make的命令行中直接给这些变量赋值.
第十五章 第十五章 make 的常见错误信息
make执行过程中所产生错误并不都是致命的, 特别是存在'-'或命令行选项-k执行命令时.
如果一个错误时致命的,那么错误信息的前缀字符串为"***"
书p193 列出了一些错误信息对应的错误原因, 可供参考...
错误的一般类型为: 语法错误, 循环引用, 没有找到目标规则, 变量递归展开死循环(循环引用), 函数调用参数错误, 特殊规则的语法错误, 等。