1. target ... : prerequisites ...
  2. command
  3. ...
  4. ...

makefile 的核心:

  • prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。

一个示例

  1. edit : main.o kbd.o command.o display.o \
  2. insert.o search.o files.o utils.o
  3. cc -o edit main.o kbd.o command.o display.o \
  4. insert.o search.o files.o utils.o
  5. main.o : main.c defs.h
  6. cc -c main.c
  7. kbd.o : kbd.c defs.h command.h
  8. cc -c kbd.c
  9. command.o : command.c defs.h command.h
  10. cc -c command.c
  11. display.o : display.c defs.h buffer.h
  12. cc -c display.c
  13. insert.o : insert.c defs.h buffer.h
  14. cc -c insert.c
  15. search.o : search.c defs.h buffer.h
  16. cc -c search.c
  17. files.o : files.c defs.h buffer.h command.h
  18. cc -c files.c
  19. utils.o : utils.c defs.h
  20. cc -c utils.c
  21. clean :
  22. rm edit main.o kbd.o command.o display.o \
  23. insert.o search.o files.o utils.o
  • \ 换行
  • 编译 make
  • 清除 make clean
  • 命令以 tab 开头
  • make 不管 command 是否执行成功,只管依赖关系是否完整,否则报错

变量

使用:

  1. objects = main.o kbd.o command.o display.o \
  2. insert.o search.o files.o utils.o
  3. edit : $(objects)
  4. cc -o edit $(objects)
  5. main.o : main.c defs.h
  6. cc -c main.c
  7. kbd.o : kbd.c defs.h command.h
  8. cc -c kbd.c
  9. command.o : command.c defs.h command.h
  10. cc -c command.c
  11. display.o : display.c defs.h buffer.h
  12. cc -c display.c
  13. insert.o : insert.c defs.h buffer.h
  14. cc -c insert.c
  15. search.o : search.c defs.h buffer.h
  16. cc -c search.c
  17. files.o : files.c defs.h buffer.h command.h
  18. cc -c files.c
  19. utils.o : utils.c defs.h
  20. cc -c utils.c
  21. clean :
  22. rm edit $(objects)

自动推导

不必写相同命令:

  1. objects = main.o kbd.o command.o display.o \
  2. insert.o search.o files.o utils.o
  3. edit : $(objects)
  4. cc -o edit $(objects)
  5. # 省略 cc -c xxx
  6. main.o : defs.h
  7. kbd.o : defs.h command.h
  8. command.o : defs.h command.h
  9. display.o : defs.h buffer.h
  10. insert.o : defs.h buffer.h
  11. search.o : defs.h buffer.h
  12. files.o : defs.h buffer.h command.h
  13. utils.o : defs.h
  14. .PHONY : clean
  15. clean :
  16. rm edit $(objects)

清空目标文件的规则

  1. .PHONY : clean
  2. clean :
  3. -rm edit $(objects)
  • - 许某些文件出现问题,但不要管,继续做后面的事
  • -f 可以指定 makefile 文件

引用其它的Makefile

被包含的文件会原模原样的放在当前文件的包含位置:

  1. include <filename>
  2. # 例子
  3. include foo.make *.mk $(bar)
  4. -include <filename>

书写规则

make 默认第一个规则的第一个目标是最终目标.

  1. targets : prerequisites
  2. command
  3. ...
  • make会以UNIX的标准Shell,也就是 /bin/sh 来执行命令

在规则中使用通配符

make 支持三个通配符:

  • *
  • ?
  • ~ 用户的 home 目录

例子:

  1. clean:
  2. rm -f *.o
  3. print: *.c
  4. lpr -p $?
  5. touch print
  6. objects = *.o
  7. # 展开
  8. # objects := $(wildcard *.o)
  9. objects := $(patsubst %.c,%.o,$(wildcard *.c))
  10. foo : $(objects)
  11. cc -o foo $(objects)
  • $? 自动化变量
  • objects 的值就是 *.o, 并没有展开
    • 使用 $(wildcard *.o) 展开

文件搜寻

把一个路径告诉make,让make在自动去找依赖文件.

  • VPATH 环境变量, 当 make 在当前目录下找不到依赖文件时, 到 VPATH 中寻找
  1. VPATH = src:../headers
  • vpath 关键字, 很灵活
  1. vpath <pattern> <directories> # 符合 pattern 的文件的搜索目录
  2. vpath %.h ../headers
  3. vpath <pattern> # 清除符合 pattern 的文件的搜索目录
  4. vpath # 清除已设置的搜索目录
  5. vpath %.c foo:bar # 在 foo, bar 中找 %.c
  6. vpath % blish

伪目标

  1. .PHONY : clean
  2. clean :
  3. rm *.o temp
  • .PHONY 避免伪目标与文件重名

一次性生成所有目标的推荐写法:

  • prog1, 2, 3 之间没有依赖关系, 且最终要生成3个文件, 所以应该告诉 make 最终产物是
  1. all : prog1 prog2 prog3
  2. .PHONY : all
  3. prog1 : prog1.o utils.o
  4. cc -o prog1 prog1.o utils.o
  5. prog2 : prog2.o
  6. cc -o prog2 prog2.o
  7. prog3 : prog3.o sort.o utils.o
  8. cc -o prog3 prog3.o sort.o utils.o

伪目标也可以成为依赖:

  1. .PHONY : cleanall cleanobj cleandiff
  2. cleanall : cleanobj cleandiff
  3. rm program
  4. cleanobj :
  5. rm *.o
  6. cleandiff :
  7. rm *.diff

多目标

  1. bigoutput littleoutput : text.g
  2. generate text.g -$(subst output,,$@) > $@
  3. # 等价于
  4. bigoutput : text.g
  5. generate text.g -big > bigoutput
  6. littleoutput : text.g
  7. generate text.g -little > littleoutput
  • $@ 类似 for 中的 a[i], 指目标中的某一个, 按顺序取出

静态模式

  1. <targets ...> : <target-pattern> : <prereq-patterns ...>
  2. <commands>
  3. ...

有点像是根据 targets 名称倒推 prereq 名称.

例子:

  1. objects = foo.o bar.o
  2. all: $(objects)
  3. $(objects): %.o: %.c
  4. $(CC) -c $(CFLAGS) $< -o $@
  5. # 等价于
  6. foo.o : foo.c
  7. $(CC) -c $(CFLAGS) foo.c -o foo.o
  8. bar.o : bar.c
  9. $(CC) -c $(CFLAGS) bar.c -o bar.o
  • $< 第一个依赖文件

自动生成依赖性

该功能依赖编译器, 编译器将自动分析查找依赖, 输出 makefile 的依赖关系:

  1. cc -M main.c
  2. # makefile
  3. main.o : main.c defs.h

书写命令

  • 默认使用 /bin/sh

显示命令

  • @ 不显示该命令
  • -n--just-print 只显示命令而不执行
  • -s --silent --quiet 不显示所有命令

命令执行

  1. exec:
  2. cd /home/hchen; pwd # 这样才能 cd 到指定目录

命令出错

  • 当命令的状态码不为0时, 说明执行出错, make 会中断执行
  • 使用 - 来忽略命令的错误并继续执行
  1. clean:
  2. -rm -f *.o

其它忽略错误的方法:

  • -i --ignore-errors
  • 规则以 .IGNORE 为目标

命令出错时终止执行:

  • -k --keep-going 如果某规则中的命令出错了,那么就终止该规则的执行,但继续执行其它规则


嵌套执行 make

  1. # 执行子目录的 Makefile
  2. subsystem:
  3. cd subdir && $(MAKE)
  4. # 等价写法
  5. subsystem:
  6. $(MAKE) -C subdir
  • 传递变量到下级Makefile中
  1. export <variable ...>;
  • 不想让某些变量传递到下级Makefile中
  1. unexport <variable ...>;
  • 只写一个 export 将传递所有变量
  1. export

默认总是传递的系统环境变量:

  • SHELL
  • MAKEFLAGS

不往下传递的参数:

  • -C
  • -f
  • -h
  • -o
  • -W

如果你不想往下层传递参数:

  1. subsystem:
  2. cd subdir && $(MAKE) MAKEFLAGS=

在嵌套执行中有用的参数:

  • w --print-directory 打印目前的工作目录
  1. # 进入目录
  2. make: Entering directory `/home/hchen/gnu/make'.
  3. # 离开目录
  4. make: Leaving directory `/home/hchen/gnu/make'

当指定 -C 时, -w 会自动打开. 如果参数中有 -s --no-print-directory, 那么 -w 总是失效.

定义命令包

重用相同命令序列:

  1. define run-yacc
  2. yacc $(firstword $^)
  3. mv y.tab.c $@
  4. endef
  5. # 使用命令包
  6. foo.c : foo.y
  7. $(run-yacc)

使用变量

Makefile 中的变量与 C/C++ 中的宏类似.

变量名规则:

  • 不能含有 : # = 空白字符
  • 大小写敏感
  • 建议使用 MakeFlags 形式

变量的基础

  • 声明时要赋初值
  • 使用变量 $() ${}
  • 使用真实的 $$$

变量可以使用在很多地方:

  1. objects = program.o foo.o utils.o
  2. program : $(objects)
  3. cc -o program $(objects)
  4. $(objects) : defs.h
  5. # 就像是字符串替换
  6. foo = c
  7. prog.o : prog.$(foo)
  8. $(foo)$(foo) -$(foo) prog.$(foo)

变量中的变量

使用变量为变量赋值:

  • 等号右侧的变量的定义可以在文件的任何一处, 即右侧变量不一定要已定义好
  1. foo = $(bar)
  2. bar = $(ugh)
  3. ugh = Huh?
  4. all:
  5. echo $(foo)
  • 使用 = 方式的缺点
    • 递归问题
    • 导致 make 运行慢
    • 使得 wildcard 和 shell 发生不可预知的错误

使用 := 操作符:

  • 这种方式只能使用前面已定义的变量
  1. x := foo
  2. y := $(x) bar
  3. x := later

复杂的使用:

  • MAKELEVEL: 记录嵌套使用 make 的层数
  1. ifeq (0,${MAKELEVEL})
  2. cur-dir := $(shell pwd)
  3. whoami := $(shell whoami)
  4. host-type := $(shell arch)
  5. MAKE := ${MAKE} host-type=${host-type} whoami=${whoami}
  6. endif

定义值为空格的变量的方法:

  1. nullstring :=
  2. space := $(nullstring) # end of the line
  3. # 原理是)到#之间存在一个空格, 会将该空格赋给 space
  4. # dir 的值的末尾有四个空格
  5. dir := /foo/bar # directory to put the frobs in

?= 的用法:

  1. FOO ?= bar

如果FOO没有被定义过,那么变量FOO的值就是“bar”,如果FOO先前被定义过,那么这条语将什么也不做,其等价于:

  1. ifeq ($(origin FOO), undefined)
  2. FOO = bar
  3. endif

变量高级用法

变量值替换:

  • 将以 a 字串结尾替换成以 b 字串结尾
  1. $(var:a=b)
  2. # 或
  3. ${var:a=b}
  4. # 例
  5. foo := a.o b.o c.o
  6. bar := $(foo:.o=.c)
  7. # $(bar) 的值为 a.c b.c c.c
  8. # 使用静态模式
  9. foo := a.o b.o c.o
  10. bar := $(foo:%.o=%.c)

把变量的值当成变量:

  1. x = y
  2. y = z
  3. a := $($(x))
  4. # $(a) 的值为 z

结合 “在变量定义中使用变量”:

  1. x = $(y)
  2. y = z
  3. z = Hello
  4. a := $($(x))
  5. # $(a) 的值为 Hello

加上函数:

  • subst 用 2 替换 1, $(subst 1,2,variable1) 的结果是 variable2
  1. x = variable1
  2. variable2 := Hello
  3. y = $(subst 1,2,$(x))
  4. z = y
  5. a := $($($(z)))
  6. # $(a) 的值为 Hello

把变量的值当成变量, 可用于组成变量名:

  1. first_second = Hello
  2. a = first
  3. b = second
  4. all = $($a_$b)

结合变量值替换:

  1. a_objects := a.o b.o c.o
  2. 1_objects := 1.o 2.o 3.o
  3. # $(a1) 的值假设取 a 或 1
  4. sources := $($(a1)_objects:.o=.c)

结合函数, 条件:

  1. ifdef do_sort
  2. func := sort
  3. else
  4. func := strip
  5. endif
  6. bar := a d b g q c
  7. foo := $($(func) $(bar))

“把变量的值再当成变量”这种技术,同样可以用在操作符的左边:

  1. dir = foo
  2. $(dir)_sources := $(wildcard $(dir)/*.c)
  3. define $(dir)_print
  4. lpr $($(dir)_sources)
  5. endef
  6. # 三个变量: dir, foo_sources, foo_print

追加变量值

+=:

  1. objects = main.o foo.o bar.o utils.o
  2. objects += another.o
  3. # 模拟 +=
  4. objects = main.o foo.o bar.o utils.o
  5. objects := $(objects) another.o
  • 如果变量之前没有定义, += 会变成 =
  • 如果变量之前有定义, += 会继承前一次的操作符:
  1. # := 情况
  2. variable := value
  3. variable += more
  4. # 等价于
  5. variable := value
  6. variable := $(variable) more
  7. # = 情况
  8. variable = value
  9. variable += more
  10. # 等价于
  11. variable = value
  12. variable = $(variable) more

override 指示符

如果有变量是通过make的命令行参数设置的,那么Makefile中对这个变量的赋值会被忽略。如果你想在Makefile中设置这类参数的值,那么,你可以使用“override”指示符。其语法是:

  1. override <variable>; = <value>;
  2. override <variable>; := <value>;
  3. override <variable>; += <more text>;
  4. override define foo
  5. bar
  6. endef

多行变量

可以有换行符.

  1. define two-lines
  2. echo foo
  3. echo $(bar)
  4. endef
  • define 的工作方式于 = 相同

如果你用define定义的命令变量中没有以 Tab 键开头,那么make 就不会把其认为是命令。

环境变量

  • Makefile 可以覆盖环境变量 (同名)

目标变量

  1. <target ...> : <variable-assignment>;
  2. <target ...> : overide <variable-assignment>

;可以是前面讲过的各种赋值表达式,如 = 、 := 、 += 或是?= 。第二个语法是针对于make命令行带入的变量,或是系统环境变量。

  1. prog : CFLAGS = -g
  2. prog : prog.o foo.o bar.o
  3. $(CC) $(CFLAGS) prog.o foo.o bar.o
  4. prog.o : prog.c
  5. $(CC) $(CFLAGS) prog.c
  6. foo.o : foo.c
  7. $(CC) $(CFLAGS) foo.c
  8. bar.o : bar.c
  9. $(CC) $(CFLAGS) bar.c

不管全局的 $(CFLAGS) 的值是什么,在prog目标,以及其所引发的所有规则中(prog.o foo.o bar.o的规则), $(CFLAGS) 的值都是 -g

依赖链上的都是局部.

模式变量

把变量定义在符合这种模式的所有目标上。

给所有以 .o 结尾的目标定义目标变量:

  1. %.o : CFLAGS = -O

模式变量的语法:

  1. <pattern ...>; : <variable-assignment>;
  2. <pattern ...>; : override <variable-assignment>;

使用条件判断

  1. libs_for_gcc = -lgnu
  2. normal_libs =
  3. foo: $(objects)
  4. ifeq ($(CC),gcc)
  5. $(CC) -o foo $(objects) $(libs_for_gcc)
  6. else
  7. $(CC) -o foo $(objects) $(normal_libs)
  8. endif

语法

  1. <conditional-directive>
  2. <text-if-true>
  3. endif
  4. <conditional-directive>
  5. <text-if-true>
  6. else
  7. <text-if-false>
  8. endif

条件关键字:

  1. ifeq (<arg1>, <arg2>)
  2. ifeq '<arg1>' '<arg2>'
  3. ifeq "<arg1>" "<arg2>"
  4. ifeq "<arg1>" '<arg2>'
  5. ifeq '<arg1>' "<arg2>"
  6. ifeq ($(strip $(foo)),)
  7. <text-if-empty>
  8. endif
  9. ifneq (<arg1>, <arg2>)
  10. ifneq '<arg1>' '<arg2>'
  11. ifneq "<arg1>" "<arg2>"
  12. ifneq "<arg1>" '<arg2>'
  13. ifneq '<arg1>' "<arg2>"
  14. ifdef <variable-name>
  • ifdef 只是测试一个变量是否有值,其并不会把变量扩展到当前位置
  1. bar =
  2. foo = $(bar)
  3. ifdef foo
  4. frobozz = yes # 这个分支生效
  5. else
  6. frobozz = no
  7. endif
  8. foo =
  9. ifdef foo
  10. frobozz = yes
  11. else
  12. frobozz = no # 这个分支生效
  13. endif
  1. ifndef <variable-name>

使用函数

函数的调用语法

  1. $(<function> <arguments>)
  2. ${<function> <arguments>}
  1. comma:= ,
  2. empty:=
  3. space:= $(empty) $(empty)
  4. foo:= a b c
  5. bar:= $(subst $(space),$(comma),$(foo))
  6. # $(bar) 的值为 a,b,c

函数列表:

make 的运行

make的退出码

  • 0: 成功
  • 1: 出错
  • 2: 使用了 -q 选项, 且 make 使得一些目标不需要更新

指定Makefile

  • -f
  • --file
  • --makefile
  1. make f hchen.mk

可以多次指定 -f, 所有指定的文件都传给 make 执行.

指定目标

all:这个伪目标是所有目标的目标,其功能一般是编译所有的目标。clean:这个伪目标功能是删除所有被make创建的文件。
install:这个伪目标功能是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去。
print:这个伪目标的功能是例出改变过的源文件。
tar:这个伪目标功能是把源程序打包备份。也就是一个tar文件。
dist:这个伪目标功能是创建一个压缩文件,一般是把tar文件压成Z文件。或是gz文件。
TAGS:这个伪目标功能是更新所有的目标,以备完整地重编译使用。
check和test:这两个伪目标一般用来测试makefile的流程。

检查规则

make的参数

隐含规则