# Makefile # Basic rules ``` 每条规则由 "目标,依赖,命令" 组成 TARGET : PREREQUISITES COMMAND ... ... ``` make 根据规则的依赖关系,决定是否执行规则所定义的命令 Makefile中的内容: 1. 显式规则 描述了如果更新目标文件,需要明确的给出目标文件,依赖文件,命令. (有些规则没有命令,这样的规则只是描述了文件的依赖关系) 2. 隐含规则 由make自动推导出来的规则, make根据目标文件名,自动得到目标的依赖文件, 并使用默认的命令来更新目标文件. 3. 变量定义 4. 提示符 include指示符: include FILENAMES 告诉make停止读取当前的Makefile, 先读取include指定的文件,可以使用通配符来指定FILENAMES 如果include指定的文件不存在,make将会提示错误信息, 在include指示符前面加上"-",可以忽略错误提示. -include FILENAMES 5. 注释 “#”后面的内容被认为是注释, 如果第行首的第一个字符是"#", 那么此行是注释行. 终极目标: Makefile中的第一条规则定义的目标是Makefile的"终极目标", make命令默认执行"终极目标". 如果第一条规则中有多个目标,那么多个目标中的第一个目标被认为是make的终极目标 有两种情况例外: 1. 目标名以点号"."开始,并且其后不存在斜线"/" 2. 模式规则的目标 例 Makefile for Project bar ``` foo.o : foo.c defs.h cc -c -g foo.c ``` 其中foo.o是目标文件, foo.c defs.h是依赖文件, `cc -c -g foo.c`是规则的命令 如何确定目标文件是否过期(需要重建目标文件) 目标文件不存在或者目标文件的时间戳比依赖文件中的任何一个"老" Makefile中的`$`有特殊含义(表示变量或者函数定义) 通配符只能用在 "目标,依赖, 命令" 中, 其他地方不能直接使用通配符, 可以通过函数来实现(例如wildcard) # Searching Directories for Prerequisites 在一个较大的工程中,一般会将源代码和二进制文件放在不同的目录。 可以使用make的目录搜索功能自动搜索依赖文件。 例: project的目录结构如下: ``` . ├── foo ├── inc │   ├── comm.h │   └── foo.h ├── Makefile ├── obj │   ├── event.o │   ├── foo.o │   ├── message.o │   └── utils.o └── src ├── event.c ├── foo.c ├── message.c └── utils.c ``` Makefile: ``` TARGET=foo SRC_DIR=./src INC_DIR=./inc OBJ_DIR=./obj SRCS:=$(foreach dir, $(SRC_DIR), $(notdir $(wildcard $(dir)/*.c))) OBJS=$(SRCS:%.c=$(OBJ_DIR)/%.o) INC_FLAGS := -I$(INC_DIR) .PHONY:all clean VPATH=$(SRC_DIR) #vpath %.c $(SRC_DIR) $(OBJ_DIR)/%.o : %.c @if [ ! -d $(OBJ_DIR) ]; then mkdir -p $(OBJ_DIR); fi $(CC) -c -o $@ $^ all : $(OBJS) @echo "VPATH=$(VPATH)" @echo ".LIBPATTERNS=$(.LIBPATTERNS)" $(CC) -o $(TARGET) $(OBJS) clean: @rm -f $(OBJS) @rm -rf $(OBJ_DIR) ``` ## VPATH VPATH是一个make变量,可以指定搜索目录。 make use VPATH as a search list for both prerequisities and targets of rules 定义VPATH变量时, 使用空格或者冒号将多个搜索目录分开.例如: VPATH = src:../headers 如果目标或者依赖中的文件在当前目录不存在,那么make将根据VPATH中定义的顺序进行搜索。 上面例子中中定义的VPATH=./src `obj/foo.o : foo.c` 被解释为 `obj/foo.o : ./src/foo.c` ## vpath vpath和VPATH类似,但更灵活。 和VPATH不同的是,vpath不是一个变量,而是make的一个关键字, 可以通过模式匹配,为不同类型的文件指定不同的搜索目录。 vpath有3种使用形式: 1. vpath pattern directories 为所有符合模式pattern的文件指定搜索目录,多个目录使用空格或冒号隔开 2. vpath pattern 清除为pattern指定的搜索目录 3. vpath 清除之前用vpath设置的所有搜索目录 vpath的pattern是一个包含`%`的字符串. 例如 `%.h`匹配所有以`.h`结尾的文件 例如上面例子中的 `vpath %.c $(SRC_DIR)` 表示在SRC_DIR指定的目录中搜索`.c`文件 和VPATH一样, vpath也是在当前目录下不能找到匹配文件时,才按照vpath指定的directories顺序搜索。 在Makefile中如果存在多个连续的vpath语句使用了相同的"pattern", make就对这些vpath一个一个处理。 搜索目录的顺序由vpath语句在Makefile中出现的先后顺序决定。 例如: ``` vpath %.c foo:bar vpath % blish ``` 搜索`.c`文件的顺序为 "foo", "bar", "blish" ## Static Pattern Rules 静态模式规则: 规则存在多个目标,并且不同的目标可以根据目标的文件名来自动构造出依赖文件. 静态模式的语法: ``` targets ...: target-pattern : prereq-patterns ... recipe ... ``` target-pattern和prereq-patterns说明了如何为没一个目标文件生成依赖文件。 从目标名字中取出一部分字符串(称为stem), 使用stem代替依赖模式中相应的部分来产生对应目标的依赖文件。 上面例子中的Makefile还使用了静态模式: `$(OBJ_DIR)/%.o : %.c` 这条规则表示所有.o文件的依赖文件为对应的.c文件, 例如foo.o 对应的依赖文件为foo.c, stem为foo 在Commands中可以使用自动化变量`$*`来表示stem # Phony Targets 伪目标并不表示真正的文件名,在执行make时,可以指定这个目标,来执行其所在规则定义的命令。 使用伪目标的原因: 1. 避免目标名和工作目录下的实际文件名相同,出现冲突 2. 提高make时的效率 例如: ``` clean: rm *.o temp ``` 如果当前目录下有一个文件的名字为clean, 由于这个规则没有任何依赖, 所以目标被认为是最新的,命令就得不到执行。 将clean声明为伪目标可以解决这个问题。 例如: `.PHONY : clean` 当一个目标被声明为伪目标后,make将不会查找隐含规则来创建它,这样也可以提高make的执行效率 伪目标的另外一种使用场合是用于make的并行和递归执行 例如: ``` SUBDIRS := foo bar baz subdirs : for dir in $(SUBDIRS); do \ $(MAKE) -C $$dir; \ done ``` 在一个规则中使用for循环对多个目录进行make 这种方式存在的问题: 1. 当某一个子目录make失败时,会继续对其他目录进行make. 在最后很难定位是在哪个地方发生的错误 2. 这种shell循环的方式没有用到make对目录的并行处理功能, 因为只有一条规则. 使用伪目标可以解决上面两个问题 ``` SUBDIRS := foo bar baz .PHONY:subdirs $(SUBDIRS) subdirs : $(SUBDIRS) $(SUBDIRS): $(MAKE) -C $@; foo:baz ``` 上面有一个没有命令行的规则 `foo:baz`, 表示foo依赖于baz, 用于限制子目录的make顺序,必须先make baz目录,然后才能make foo目录 一般情况下,一个伪目标不能作为另外一个目标的依赖, 因为当一个目标文件的依赖包含伪目标时,每一次执行这个规则时,伪目标所定义的规则都会被执行。 # Generate Prerequisites Automatically gcc 的 `-M`选项可以通过查找源文件中的`#include` 自动生成依赖关系 如果main.c中只包含了头文件 "defs.h" 那么执行gcc -M main.c, 其输出如下: main.o : main.c defs.h -M的输出结果包括标准库的头文件, 使用-MM选项可以去掉标准库的头文件 推荐为每一个源文件生成一个描述其依赖关系的的makefile文件, 对于一个源文件"NAME.c" 对应的这个makefile文件为 "NAME.d" 采用这种方式,只有源文件再修改后,才会重新生成对应的依赖文件。 对"Makefile for Project bar"进行改造一下,自动生产依赖文件 ``` TARGET=bar SRC_DIR=./src INC_DIR=./inc OBJ_DIR=./obj DEPEND_DIR=.dpend SRCS:=$(foreach dir, $(SRC_DIR), $(notdir $(wildcard $(dir)/*.c))) OBJS=$(SRCS:%.c=$(OBJ_DIR)/%.o) DEPEND_FILES=$(SRCS:%.c=$(DEPEND_DIR)/%.d) INC_FLAGS := -I$(INC_DIR) CFLAGS := -Wall -g CFLAGS += $(INC_FLAGS) .PHONY:all clean #VPATH=$(SRC_DIR) vpath %.c $(SRC_DIR) $(OBJ_DIR)/%.o : %.c @echo ----------------- @if [ ! -d $(OBJ_DIR) ]; then mkdir -p $(OBJ_DIR); fi $(CC) $(CFLAGS) -c -o $@ $< all : $(OBJS) @echo "VPATH=$(VPATH)" @echo ".LIBPATTERNS=$(.LIBPATTERNS)" $(CC) -o $(TARGET) $(OBJS) clean: @rm -f $(TARGET) @rm -f $(OBJS) @rm -rf $(OBJ_DIR) @rm -f *.o @rm -rf $(DEPEND_DIR) sinclude $(DEPEND_FILES) $(DEPEND_DIR)/%.d : %.c @if [ ! -d $(DEPEND_DIR) ]; then mkdir -p $(DEPEND_DIR); fi @$(CC) $(CFLAGS) -MM $< > $@.$$$$; \ sed 's,\($*\)\.o[ :]*,$(OBJ_DIR)/\1.o $@ :,g' < $@.$$$$ > $@; \ rm -f $@.$$$$ ``` 说明: 1. `sinclude` 是 `-include` 的另一种写法, 如果include要包含的某些文件不存在,或者无法被创建,make会继续执行。 只有真正由于不能完成终极目标的重建时才会提示错误,并退出。 2. make在试图读取include包含的`*.d`文件时,发现这些文件不存在,会试图创建他们。 3. 定义依赖文件的生成规则又用到了静态模式规则 说明如何生成依赖文件 例如gcc -MM src/bar.c 生成的依赖为"bar.o: src/bar.c inc/foo.h" 使用sed命令替换为"./obj/bar.o .dpend/bar.d : src/bar/c inc/foo.h" 因为将bar.d也加入到bar.o相同的依赖里面,所以只要bar.o的任何依赖文件发生变化后,都会导致bar.d被重建 4. sed命令中的`$$$$`表示当前进程号,用于生成临时文件。 注意: sed命令和下一行rm命令一定要用续行符连起来,如何分开为两行,那么rm命令和sed将会是两个不同的进程, `导致$@.$$$$指的不是同一个文件` 5. sed命令替换是还到了`$*`这个自动化变量(stem),在静态模式规则中表示被匹配到的字符串, 例如"bar" 多规则目标: 一个文件可以作为多个规则的目标(但是重建此目标的命令只能出现在其中一个规则中) 以这个目标为规则的所有依赖文件将会合并成此目标的依赖文件列表 上面的例子中就用到了多规则目标,因为`gcc -MM的 src/bar/c` 生成的依赖 `bar.o : src/bar.c inc/foo.h` 被替换为 `./obj/bar.o .dpend/bar.d : src/bar.c inc/foo.h` 由于之前的静态模式规则定义了`./obj/bar.o`的依赖为 `src/bar/c` 所以依赖合并后, `obj/bar.o`的依赖为 `src/bar.c src/bar/c inc/foo.h` # Writing Recipes in Rules 规则的命令由一些shell命令行组成。 make在执行命令行之前会把要执行的命令行输出到stdout, 这种行为称为回显 如果规则的命令行以`@`开头, 则make在执行前不会回显这个命令 如果使用make -n 或者 "--just-print" 或者 "--dry-run", 那么make执行时只显示要执行的命令,而不会执行这些命令。 其中包括`@`开头的命令也会被显示出来, 这个选项对调试Makefile非常有用. 命令的执行: 1. 如果是多行命令,那么每个命令将在一个独立的子shell进程中被执行. 2. 如果同一行里面写了多条命令,那么这几条命令在同一个shell进程中被执行. 命令执行的错误处理 规则的命令在运行结束后, make会检测命令执行的返回状态. 如果返回成功,就启动另外一个子shell来执行下一条命令 如果某一个命令出错,make会停止执行后续的命令 在命令前加一个减号`-`, 来告诉make忽略此命令的执行失败. ## Recursive use of make make的递归过程指的是: 在Makefile中使用make作为一个命令来执行本身或者其他Makefile文件的过程 递归调用在存在有多级子目录的项目中非常有用. 例如: ``` subsystem: cd subdir && $(MAKE) 等价于 subsystem: $(MAKE) -C subdir ``` 变量CURDIR表示make的工作目录, 在使用`-C` 进入子目录后,CURDIR会被重新赋值 在make的递归调用时,最好使用$(MAKE)来代替make, 这样做的好处是: "当使用一个其他版本的make程序时, 可以保证最上层使用的make程序和子目录执行的make程序一致" 执行make时,MAKE变量为make make的命令行选项参数被通过一个变量“MAKEFLAGS”传递给子目录下的make程序。 有几个特殊的命令行参数除外, `"-C" "-f" "-o" "-W"` 不会被赋值给MAKEFLAGS # Variables in Makefile 变量的引用方式为 `$(VARIABLE_NAME)` 当引用一个没有定义的变量时,make默认它的值为空。 如果要引入一个shell中的变量,需要使用`$${VAR}`. 示例如下: ``` $(SUBDIRS): @echo "MAKELEVEL=$(MAKELEVEL)" @for var in 1 2 3 4; do\ echo "var = $${var}"; \ done $(MAKE) -C $@; ``` ## Automatic variables *自动化变量* | auto var | descriptions | | ---- | ---- | | `$@` | 规则的目标文件名, 在多目标模式规则中,表示触发规则执行的那个目标的文件名. | | `$<` | 规则的第一个依赖文件名 | | `$?` | 所有比目标文件新的依赖文件列表,空格分隔. | | `$^` | 规则的所有依赖文件列表, `$^`会去掉重复的依赖文件列表 | | `$*` | 在模式规则和静态模式规则中,表示"茎"(stem), 即目标模式%所代表的部分 | | `$(@D)` | 表示目标中的目录部分,如果`$@`是`dir/foo.o`, 那么`$(@D)`表示dir | | `$(@F)` | 表示目标中的文件部分,如果`$@`是`dir/foo.o`, 那么`$(@F)`表示foo.o | ## Assign to a variable *变量的定义方式* 1. 递归展开式变量 通过`= 或者 define` 定义的变量。在引用的时候是严格的文本替换过程 例如: ``` foo=$(bar) bar=$(ugh) ugh=UGH all : ; @echo "foo=$(foo)" ``` 执行的时候将会打印foo=UGH 替换过程是这样的: `$(foo) 被替换为$(bar), $(bar)被替换为$(ugh), 最后$(ugh)被替换为UGH` 变量名两边的空格和`=`之后的空格在make处理的被忽略. 这种定义方式的缺点: 可能由于变量的递归定义而导致make陷入到无限的变量展开过程,不过make会阻止这种情况发生,打印出错误信息并退出 如果在这种定义方式中使用了函数,那么其中的函数会在变量被引用是被执行, 降低make的执行效率 2. 直接展开式变量 使用`:=`定义 变量值中对其他变量或者函数的引用在定义变量时被展开 这种形式的定义,不能实现对其后定义的变量的引用 例如: ``` CFLAGS := $(inc_dirs) -O inc_dirs := -Ifoo -Ibar ``` 由于变量inc_dirs的定义在CFLAGS之后,所以CFLAGS的值是`-O` 3. 条件赋值 使用`?=`给变量赋值 只有此变量之前没有被赋值的情况下才会对这个变量赋值 例如: `foo ?= bar` 如果foo之前已经被定义过了,那么foo的值就不会被改变为bar *变量的替换引用* 将变量的后缀字符(串)使用指定的字符(串)替换 格式: `$(VAR:A=B)` 例如: ``` foo := a.o b.o c.o bar := $(foo:.o=.c) ``` 也可以使用模式字符 `%`, 例如: ``` foo := a.o b.o c.o bar := $(foo:%.o=%.c) ``` *追加变量值* 例如: ``` objects := main.o foo.o bar.o objects += another.o ``` 如果被追加值的变量之前没有被定义,那么 `+=` 会自动变成 `=`, 此变量就被定义为一个递归展开式变量. 如果之前存在该变量的定义,那么保持之前的变量风格不变。 # Functions make的函数提供了处理文件名,变量,文本和命令的方法。 函数的调用语法: $(function arguments) 1. function是需要调用的函数名,必须是make的内嵌函数。 如果是用户自己定义的函数,需要使用make的call函数来间接调用。 2. 函数名和参数之间使用空格分隔; 如果有多个参数,参数之间使用逗号 `,` 分开。 ## Functions for string substitution an analysis ### subst `$(subst FROM,TO,TEXT)` 字符串替换函数, 把字符串TEXT中的FROM替换为TO 返回值: 被替换后的新字符串 ### patsubst `$(patsubst PATTERN, REPLACEMENT, TEXT)` 模式替换函数,搜索TEXT中以空格分开的单词, 将符合模式"PATTERN"的字符串替换为"REPLACEMENT" 1. PATTERN中可以使用模式通配符`%`来代表一个单词中的若干个字符。 2. 如果"REPLACEMENT"中也包含一个`%`, 那么"REPLACEMENT"中的`%`将是"PATTERN"中的那个`%`所代表的字符串。 3. 在"PATTERN"和"REPLACEMENT"中只有地一个`%`被作为模式字符处理,之后出现`%`表示普通字符。 返回值: 被替换后的新字符串 示例: `$(patsubst %.c,%o,x.c.c bar.c)` 把字符串"x.c.c bar.c"中以.c结尾的字符替换为.o结尾的字符, 结果为 "x.c.o bar.o" 变量的替换引用`$(VAR:PATTERN=REPLACEMENT)` 是一个简化版的patsubst在变量引用过程的实现。 ### strip `$(strip STRING)` 去空字符函数 去掉字符串开头和结尾的空字符,并将其中多个连续空字符合并为一个空字符。 空字符包括空格,Tab等不可显示的字符 示例: ``` str= a b c str1=$(strip $(str)) ``` str1为"a b c" ### findstring `$(findstring FIND,IN)` 在字符串IN中查找字串FIND 返回值: 如果IN中存在FIND, 则返回FIND, 否则返回空。 示例: ``` $(findstring a,a b c) 结果为"a" $(findstring a,b c) 结果为空 ``` ### filter `$(filter PATTERN..., TEXT)` 过滤掉字符串TEXT中所有不符合PATTERN的单词,保留所有符合此模式的单词。 可以使用多个模式,模式中需要包含模式字符`%`, 存在多个模式时,表达式之间使用空格分隔。 返回值: TEXT中所有符合PATTERN的的字串,不同模式的结果使用空格分隔。 示例: ``` sources:=foo.c bar.c baz.S ugh.h foo:$(sources) $(CC) $(filter %.c %.S,$(sources)) -o foo ``` ### filter-out `$(filter-out PATTERN...,TEXT)` 和filter功能相反 ### sort `$(sort LIST)` 对字符串LIST中的单词排序(按首字母升序排列), 并去掉重复的单词 示例: ``` $(sort foo bar lose foo) 结果为 "bar foo lose" ``` ## Function for file names ### dir `$(dir NAMES...)` 从文件名序列中取出文件名的目录部分(最后一个`/`之前的部分) 示例: ``` $(dir src/foo.c hacks) 结果为"src/ ./" ``` ### notdir `$(notdir NAMES...)` 取文件名, 从文件名序列中取出非目录部分 示例: ``` $(dir src/foo.c hacks) 结果为"foo.c hacks" ``` ### suffix `$(suffix NAMES...)` 取后缀, 从文件名序列中取出个文件名的后缀(最后一个点`.`开始的部分, 包含点号) 如果文件名中不包含点号,则为空 示例: ``` $(suffix src/foo.c src-1.0/bar.c hacks) 结果为".c .c" ``` ### basename `$(basename NAMES...)` 取前缀, 从文件名序列中取出文件名的前缀(最后一个点之前的部分) 如果文件名中没有`.`, 那么文件名保持不变 如果文件名中存在多个点号`.`, 那么取最后一个点号之前的部分 示例: `$(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" ### wildcard `$(wildcard PATTERN)` 获取匹配模式的文件名 PATTERN使用shell可识别的通配符,包括`?`(单字符) `*`(多字符) 示例: ``` $(wildcard *.c) 返回当前目录下所有.c文件的列表 ``` ## function for debug 在Makefile中添加调试信息 可以在Makefile中的任意位置使用,而echo命令只能在target中使用 ### info `$(info TEXT)` 打印TEXT信息,也可以打印变量 示例: ``` $(info A top-level warning) $(info CURDIR=$(CURDIR)) ``` ### warning `$(warning TEXT)` 打印TEXT信息,也可以打印变量 和info的区别是, warning可以打印在makefile中的行号 示例: ``` $(warning A top-level warning) $(warning CURDIR=$(CURDIR)) ``` ### error `$(error TEXT)` 打印TEXT信息,也可以打印变量 打印信息后,会停止当前makefile的编译 ## foreach `$(foreach VAR, LIST, TEXT)` 把LIST中使用空格分隔的单词依次取出赋值给变量VAR, 然后执行TEXT表达式 如果在TEXT中存在对VAR的引用,那么VAR在TEXT中,每次会得到不同的值,因为TEXT的变量是在执行时才被展开 示例: ``` dirs := a b c d files := $(foreach dir, $(dirs), $(wildcard $(dir)/*.c)) ``` TEXT的表达式为 `$(wildcard $(dir)/*.c)` 表达式第一次被执行时将展开为 `$(wildcard a/*.c)`, 第二次被执行时将展开为 `$(wildcard b/*.c)` ... 当TEXT比较复杂时,也可以通过定义一个中间变量,在TEXT表达式中引用这个变量, 由于TEXT将在执行时才展开变量,所以该变量需要定义为递归展开式变量. 示例: ``` find_files=$(wildcard $(dir)/*.c) dirs := a b c d files := $(foreach dir, $(dirs), $(find_files) ``` ## origin `$(origin variable)` 获取变量variable是哪里来,variable为变量的名字,而不是引用 所以变量名前没有`$`字符. 1. 如果从来没有定义过变量V, 则origin函数返回"undefined" 2. 如果变量为环境变量,则origin函数返回“enviroment” 3. 如果变量是默认定义的,则origin函数返回“default” 4. 如果变量是为定义在Makefile文件中,那么origin函数返回"file" 5. 如果变量由命令行传入,那么返回“command line” 6. 如果变量被重新定义过,则origin函数返回"override" 7. 如果变量是自动化变量,则origin函数返回“automatic” 例如: Makefile中的内容如下: ``` OBJS=dummy.o override SHELL=/bin/sh all: @echo "V: $(origin V)" @echo "USER: $(origin USER)" @echo "CC: $(origin CC)" @echo "OBJS: $(origin OBJS)" @echo "ARCH: $(origin ARCH)" @echo "SHELL: $(origin SHELL)" @echo "@: $(origin @)" ``` ``` $ make ARCH=arm V: undefined USER: environment CC: default OBJS: file ARCH: command line SHELL: override @: automatic ```