介绍

Makefile 的基本规则:

target ... : prerequisites ...
recipe
...
...
  • target:可以是一个 object file(目标文件),也可以是一个可执行文件,还可以是一个标签(label)
  • prerequisites:生成该 target 所依赖的文件和/或 target
  • recipe:该 target 要执行的命令(任意的 shell 命令)

简单来说就是指定了一个依赖关系

输入 make 命令时,是这么工作的:

  1. make 会在当前目录下找名字叫 Makefilemakefile 的文件
  2. 如果找到,它会找文件中的第一个目标文件(target),并把这个文件作为最终的目标文件
  3. 如果该文件不存在,或其所依赖的后面的 .o 文件的文件修改时间要比这个文件新,则执行后面所定义的命令来生成这个文件
  4. 如果所依赖的 .o 文件也不存在,那么 make 会在当前文件中找目标为 .o 文件的依赖性,如果找到则再根据那一个规则生成 .o 文件
  5. 最后用 .o 文件生成可执行文件

在 Makefile 中可以声明变量并使用变量:

OBJ = main.o kbd.o command.o display.o
edit : $(objects)
cc -o edit $(objects)

事实上,make 可以自动推导文件及依赖关系后的命令,如

kbd.o : kbd.c defs.h command.h
cc -c kbd.c

可以简化为:

kbd.o : defs.h command.h

每个 Makefile 都应该在最后写一个清空目标文件的规则,如:

.PHONY : clean
clean :
-rm edit $(objects)

当然,Makefile 可以为任意的文件名,但在使用时必须指定:make -f Make.Solaris

还可以把其他 Makefile 包含进来,如:

include 文件名...

其寻找的位置是:

  1. 当前目录
  2. -I 参数指定的目录
  3. <prefix>/include

书写规则

make 支持三个通配符:*?~

VPATH 变量指定了 make 寻找文件的目录,还有 vpath 功能类似,但是更灵活:

  • vpath <pattern> <directories>:为符合模式 <pattern> 的文件指定搜索目录 <directories>
  • vpath <pattern>:清除符合模式 <pattern> 的文件的搜索目录
  • vpath:清除所有已被设置好了的文件搜索目录

可以连续使用 vpath 语句

因为不生成 clean 文件,故这是一个伪目标,如:

.PHONY : cleanall cleanobj cleandiff

cleanall : cleanobj cleandiff
rm program

cleanobj :
rm *.o

cleandiff :
rm *.diff

Makefile 的规则中的目标可以不止一个,如

bigoutput littleoutput : text.g
generate text.g -$(subst output,,$@) > $@

静态模式可以更加容易地定义多目标的规则:

<targets ...> : <target-pattern> : <prereq-patterns ...>
<commands>
...
  • targets 定义了一系列的目标文件,可以有通配符
  • target-pattern 是指明了 targets 的模式,也就是的目标集模式
  • prereq-patterns 是目标的依赖模式,它对 target-pattern 形成的模式再进行一次依赖目标的定义

大多数的 C/C++ 编译器都支持一个 -M 的选项,即自动找寻源文件中包含的头文件,并生成一个依赖关系

利用这个特性,我们可以为每一个 name.c 的文件都生成一个 name.d 的 Makefile 文件, .d 文件中就存放对应 .c 文件的依赖关系

书写命令

通常,make 会把执行的命令显示到屏幕上,如果不想显示,则加上 @,如:

@echo 正在编译XXX模块......

当然,加上参数 -s 会全面禁止命令的显示

如果要让上一条命令的结果应用在下一条命令上,则应该使用 ; 分割这两条命令,如:

exec:
cd /home/hchen; pwd

make 默认使用的 shell 是 /bin/sh

make 会检测每个命令的返回码,如果失败,则会终止执行当前规则;为了让其忽略出错,可以在命令前加 -,如:

clean:
-rm -f *.o

相应的全局方法是加上参数 -i

可以嵌套执行 Makefile 文件,如总控 Makefile 可以这样写:

subsystem:
cd subdir && $(MAKE)

其作用是执行 subdir 目录下的 Makefile 文件

默认情况下总控中定义的变量不会传递给下级的 Makefile,如果想要传递,可以:

export <variable ...>;

如果要传递所有的变量,则直接使用 export

加上 -w 参数可以看到当前的工作目录

我们可以为相同的命令序列定义一个变量:

define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef

使用变量

定义变量的方法:

foo = $(bar)
bar = $(ugh)
ugh = Huh?

注意到上面那种定义顺序是合法的,当然还有另一种方法避免了这种情况:

x := foo
y := $(x) bar

一种定义一个空格的变量的方法:

nullstring :=
space := $(nullstring) # 通过注释指定了一个空格

?= 表示如果没有定义过,则定义;反之,则什么都不做

$(var:a=b):把变量 var 中所有结尾的 a 替换成 b。这里的“结尾”意思是“空格”或是“结束符”。

可以使用 += 给变量追加值

如果有变量是 make 的命令行参数设置的,则 Makefile 中对这个变量的赋值会被忽略,如果想设置,可以在前面加 override

之前定义的变量都是“全局变量”,也可以为某个目标设置局部变量,其作用范围只在这条规则以及连带规则中:

<target ...> : <variable-assignment>;

还支持模式变量,即可以把变量定义在所有符合该模式的目标上:

<pattern ...>; : <variable-assignment>;

使用条件判断

ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif

除了 ifeq 以外,还有 ifneqifdefifndef

使用函数

函数调用类似于变量使用:

$(<function> <arguments>)

字符串替换函数 subst

$(subst <from>,<to>,<text>)

把字串 <text> 中的 <from> 字符串替换成 <to>

还有相应的模式版本 patsubst

$(patsubst <pattern>,<replacement>,<text>)

去空格函数 strip

$(strip <string>)

去掉 <string> 字串中开头和结尾的空字符

查找字符串函数 findstring

$(findstring <find>,<in>)

sort 排序(升序),注意同时会去重

取单词串函数 wordlist

$(wordlist <start>,<end>,<text>)
名称 功能 名称 功能
filter 过滤 filter-out 反过滤
word 单词个数统计 firstword 首单词
dir 取目录 notdir 取非目录
suffix 取后缀 basename 取前缀
addsuffix 加后缀 addprefix 加前缀

连接函数 join

$(join <list1>,<list2>)

功能:把 <list2> 中的单词对应地加到 <list1> 的单词后面

用来做循环的 foreach

$(foreach <var>,<list>,<text>)

把参数 <list> 中的单词逐一取出放到参数 <var> 所指定的变量中,然后再执行 <text> 所包含的表达式

if 函数

$(if <condition>,<then-part>)

call 可以用来创建新的参数化:

reverse =  $(2) $(1)
foo = $(call reverse,a,b)

origin 告诉你变量是从哪里来的

shell 函数参数是 shell 命令,和反引号功能一致,注意其会影响性能

还有控制函数:

$(error <text ...>)
$(warning <text ...>)

make 的运行

make 的时候可以指定目标

隐含规则

make 中有一些隐含规则,不必写出来,例如编译 C 语言的:

<n>.o 的目标的依赖目标会自动推导为 <n>.c ,并且其生成命令是 $(CC) –c $(CPPFLAGS) $(CFLAGS)

隐含规则使用的变量:

  • AR:函数库打包程序,默认命令是 ar
  • AS:汇编语言编译程序,默认命令是 as
  • CC:C 语言编译程序,默认命令是 cc
  • CXX:C++语言编译程序,默认命令是 g++

相关命令的参数:

  • CFLAGS : C 语言编译器参数
  • CXXFLAGS : C++语言编译器参数
  • CPPFLAGS : C 预处理器参数
  • LDFLAGS : 链接器参数

模式规则:使用 % 通配

自动化变量及其说明:

  • $@:表示规则中的目标文件集。在模式规则中,如果有多个目标,那么, $@ 就是匹配于目标中模式定义的集合
  • $%:仅当目标是函数库文件中,表示规则中的目标成员名
  • $<:依赖目标中的第一个目标名字。如果依赖目标是以模式(即 % )定义的,那么 $< 将是符合模式的一系列的文件集。注意,其是一个一个取出来的
  • $?:所有比目标新的依赖目标的集合
  • $^:所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那么这个变量会去除重复的依赖目标,只保留一份
  • $+:这个变量很像 $^ ,也是所有依赖目标的集合。只是它不去除重复的依赖目标

使用 make 更新函数库文件

一个函数库文件由多个文件组成,可以用如下格式指定函数库文件及其组成:

archive(member)