makefile入门
Makefile
shell 中特殊变量:
- $# 是传给脚本的参数个数
- $0 是脚本本身的名字
- $1 是传递给该shell脚本的第一个参数
- $2 是传递给该shell脚本的第二个参数
- $@ 是传给脚本的所有参数的列表
- $* 是以一个单字符串显示所有向脚本传递的参数,与位置变量不同,参数可超过9个
- $$ 是脚本运行的当前进程ID号
- $? 是显示最后命令的退出状态,0表示没有错误,其他表示有错误
targets : prerequisites; command
command # 命令的开始一定要使用Tab键
示例
main:main.o test1.o test2.o # 第一个称为最终目标
gcc main.o test1.o test2.o -o main
main.o:main.c test.h
gcc -c main.c -o main.o
test1.o:test1.c test.h
gcc -c test1.c -o test1.o
test2.o:test2.c test.h
gcc -c test2.c -o test2.o
# 用于清理
.PHONY:clean
clean:
rm -rf *.o test
通配符
- * 匹配0个或者是任意个字符
- ? 匹配任意一个字符
- [] 我们可以指定匹配的字符放在 "[]" 中
- % 也是匹配任意字符
"%.o" 取出所有以 ".o" 结尾的文件,挨个找到对应的 ".c" 文件。依次执行命令。
test:test.o test1.o
gcc -o $@ $^
%.o:%.c
gcc -o $@ $^
注意:使用变量方式时无法展开通配符
OBJ=*.c
test:$(OBJ)
gcc -o $@ $^
会报错“找不到文件 ‘*.c’ ”
使用函数 wildcard
可以展开
OBJ=$(wildcard *.c)
test:$(OBJ)
gcc -o $@ $^
变量定义
定义:VALUE_LIST = one two three
使用:$(VALUE_LIST)
与${VALUE_LIST}
- 简单赋值 ( := ) 常规理解的赋值方式,只对当前语句与之后语句有效。
- 递归赋值 ( = ) 可能影响多个变量,以前、以后的使用都会受到影响。
- 条件赋值 ( ?= ) 变量未定义则赋值,已定义则无效。
- 追加赋值 ( += ) 原变量用空格隔开的方式追加一个新值。
自动化变量
| 变量 | 说明 |
| ---- | ------------------------------------------------------------ |
| $@ | 表示规则的目标文件名。在多目标模式规则中,它代表的是触发规则被执行的文件名。 |
| $% | 当目标文件是一个静态库文件时,代表静态库的一个成员名。 |
| $< | 规则的第一个依赖的文件名 |
| $? | 所有比目标文件更新的依赖文件列表,空格分隔。如果目标文件时静态库文件,代表的是库文件(.o 文件)。 |
| $^ \| 代表的是所有依赖文件列表,使用空格分隔。变量“$^”会去掉重复的依赖文件。 |
| $+ \| 类似“$^”,保留了依赖文件中重复出现的文件。主要用在程序链接时库的交叉引用场合。 |
| $* | 在模式规则和静态模式规则中,代表“茎”。“茎”是目标模式中“%”所代表的部分(当文件名中存在目录时, “茎”也包含目录部分)。 |
示例:
test:test.o test1.o test2.o
gcc -o $@ $^
test.o:test.c test.h
gcc -o $@ $<
test1.o:test1.c test1.h
gcc -o $@ $<
test2.o:test2.c test2.h
gcc -o $@ $<
拓展,不重要:
| 变量名 | 功能 |
| ------------ | ------------------------------------------------------------ |
| $(@D) \| 表示文件的目录部分(不包括斜杠)。如果 "$@" 表示的是 "dir/foo.o" 那么 "$(@D)" 表示的值就是 "dir"。如果 "$@" 不存在斜杠(文件在当前目录下),其值就是 "."。 |
| $(@F) \| 表示的是文件除目录外的部分(实际的文件名)。如果 "$@" 表示的是 "dir/foo.o",那么 "$@F" 表示的值为 "dir"。 |
| $(*D) $(*F) | 分别代表 "茎" 中的目录部分和文件名部分 |
| $(%D) $(%F) | 当以 "archive(member)" 形式静态库为目标时,分别表示库文件成员 "member" 名中的目录部分和文件名部分。踏进对这种新型时的目标有效。 |
| $(<D) $(<F) | 表示第一个依赖文件的目录部分和文件名部分。 |
| $(^D) $(^F) | 分别表示所有依赖文件的目录部分和文件部分。 |
| $(+D) $(+F) | 分别表示所有的依赖文件的目录部分和文件部分。 |
| $(?D) $(?F) | 分别表示更新的依赖文件的目录部分和文件名部分。 |
目标文件搜索
VPATH 是变量,更具体的说是环境变量,Makefile 中的一种特殊变量,使用时需要指定文件的路径;
# VPATH:=src:car
VPATH:=src car # 在当前目录搜寻不到时,到src与car子目录下搜索
test:test.0
gcc -o $@ $^
vpath 是关键字,按照模式搜索,也可以说成是选择搜索。搜索的时候不仅需要加上文件的路径,还需要加上相应限制的条件。
-
vpath PATTERN DIRECTORIES
vpath test.c src:car
在src与car目录下搜索 test.c 文件 -
vpath PATTERN
vpath test.c
清除符合文件 test.c 的搜索目录 -
vpath
清除所有已被设置的 vpath 路径
文件搜索示例
dir
|-Makefile
|
|-include/
| |-list1.h
| |-list2.h
|
|-src/
|-list1.c
|-list2.c
VPATH=src include
main:main.o list1.o list2.o
gcc -o $@ $<
main.o:main.c
gcc -o $@ $^
list1.o:list1.c list1.h
gcc -o $@ $<
list2.o:list2.c list2.h
gcc -o $@ $<
vpath %.c src
vpath %.h include
main:main.o list1.o list2.o
gcc -o $@ $<
main.o:main.c
gcc -o $@ $^
list1.o:list1.c list1.h
gcc -o $@ $<
list2.o:list2.c list2.h
gcc -o $@ $<
隐含规则
谨慎使用,可能出现无法预料的情况。
隐含条件只能省略中间目标文件重建的命令和规则,但是最终目标的命令和规则不能省略。
test:test.o
gcc -o test
test.otest.o:test.c
隐含规则的具体的工作流程:make 执行过程中找到的隐含规则,提供了此目标的基本依赖关系。确定目标的依赖文件和重建目标需要使用的命令行。隐含规则所提供的依赖文件只是一个基本的(在C语言中,通常他们之间的对应关系是:test.o 对应的是 test.c 文件)。当需要增加这个文件的依赖文件的时候要在 Makefile 中使用没有命令行的规则给出。
条件判断
关键字 | 功能 |
---|---|
ifeq | 判断参数是否不相等,相等为 true,不相等为 false。 |
ifneq | 判断参数是否不相等,不相等为 true,相等为 false。 |
ifdef | 判断是否有值,有值为 true,没有值为 false。 |
ifndef | 判断是否有值,没有值为 true,有值为 false。 |
ifeq (ARG1, ARG2)
ifeq 'ARG1' 'ARG2'
ifeq "ARG1" "ARG2"
ifeq "ARG1" 'ARG2'
ifeq 'ARG1' "ARG2"
libs_for_gcc= -lgnu
normal_libs=
ifeq($(CC),gcc)
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif
foo:$(objects)
$(CC) -o foo $(objects) $(libs)
伪目标
总是会被执行的命令
clean:
rm -rf *.o
若当前目录下存在名为clean的文件时,因为没有依赖文件,所以目标总是被认为是最新的。导致shell命令不会执行。
使用 .PHONY
将clean声明为伪目标后无论当前目录下是否有clean文件都会执行,且不会查找隐含关系。
.PHONY:clean
clean:
rm -rf *.o test
.PHONY:all
all:test1 test2 test3
test1:test1.o
gcc -o $@ $^
test2:test2.o
gcc -o $@ $^
test3:test3.o
gcc -o $@ $^
会生成三个可执行文件,当只想生成一个时使用 make test1
函数
函数调用 $(<function> <arguments>)
或者是 ${<function> <arguments>}
字符串函数
-
$(patsubst <pattern>,<replacement>,<text>)
OBJ=$(patsubst %.c,%.o,1.c 2.c 3.c) all: @echo $(OBJ) # 1.o 2.o 3.o
-
$(subst <from>,<to>,<text>)
OBJ=$(subst ee,EE,feet on the street) all: @echo $(OBJ) # fEEt on the strEEt
-
$(strip <string>)
去除两端空格,将内部空格合并为一个OBJ=$(strip a b c) all: @echo $(OBJ) # a b c
-
$(findstring <find>,<in>)
OBJ=$(findstring a,a b c) all: @echo $(OBJ) # a # 如果不存在,返回空
-
$(filter <pattern>,<text>)
OBJ=$(filter %.c %.o,1.c 2.o 3.s) all: @echo $(OBJ) # 1.c 2.o
-
$(filter-out <pattern>,<text>)
OBJ=$(filter-out 1.c 2.o ,1.o 2.c 3.s) all: @echo $(OBJ) # 3.s
-
$(sort <list>)
OBJ=$(sort foo bar foo lost) all: @echo $(OBJ) # bar foo lost
-
$(word <n>,<text>)
OBJ=$(word 2,1.c 2.c 3.c) all: @echo $(OBJ) # 2.c
文件名函数
-
$(dir <names>)
OBJ=$(dir src/foo.c hacks) all: @echo $(OBJ) # src/ ./
-
$(notdir <names>)
OBJ=$(notdir src/foo.c hacks) all: @echo $(OBJ) # foo.c hacks
-
$(suffix <names>)
OBJ=$(suffix src/foo.c hacks) all: @echo $(OBJ) # .c
-
$(basename <names>)
OBJ=$(notdir src/foo.c hacks) all: @echo $(OBJ) # src/foo hacks
-
$(addsuffix <suffix>,<names>)
-
$(addperfix <prefix>,<names>)
-
$(join <list1>,<list2>)
OBJ=$(join src car,abc zxc qwe) all: @echo $(OBJ) # srcabc carzxc qwe
-
$(wildcard PATTERN)
列出当前目录下所有符合模式的 PATTERN 格式的文件名
其他函数
-
$(foreach <var>,<list>,<text>)
把参数
<list>
中的单词逐一取出放到参数<var>
所指定的变量中,然后再执行<text>
所包含的表达式。每一次<text>
会返回一个字符串,循环过程中,<text>
的返所返回的每个字符串会以空格分割,最后当整个循环结束的时候,<text>
所返回的每个字符串所组成的整个字符串(以空格分隔)将会是 foreach 函数的返回值。所以<var>
最好是一个变量名,<list>
可以是一个表达式,而<text>
中一般会只用<var>
这个参数来一次枚举<list>
中的单词。name:=a b c d files:=$(foreach n,$(names),$(n).o) all: @echo $(files) # a.o b.o c.o d.o
-
$(if <condition>,<then-part>)或(if<condition>,<then-part>,<else-part>)
OBJ:=foo.c OBJ:=$(if $(OBJ),$(OBJ),main.c) all: @echo $(OBJ)
-
$(call <expression>,<parm1>,<parm2>,<parm3>,...)
reverse = $(1) $(2) foo = $(call reverse,a,b) all: @echo $(foo) # a b
-
$(origin <variable>)
返回变量从哪里来的
shell 命令相关
-
在命令前加上 @ 就不会显示这一条命令
-
每条命令是一个独立的shell进程,使用 ‘cd’ 切换目录后不会影响后续的语句。应用 ; 分割并放在一行
cd bar;gobble lose >../foo # 或 cd bar; \ gobble lose > ../foo
-
make -j
可以指定并发数,不建议使用
文件包含
include filename1
-include filename1 filename2 # 忽略文件不存在或者是无法创建的错误提示
导入定义的变量或模式规则
嵌套执行
subsystem:
cd subdir && $(MAKE)
# 切换到当前目录下的subdir子目录,并执行makefile
subsystem
$(MAKE) -C subdir
# makefile中有一个名为"CURDIR"的变量,代表工作目录。 使用 "-C" 时切换到指定目录。
使用 export
导入变量
示例
├──Makefile //最外层的Makefile文件,不是目录文件。
├──include //编译的时候需要链接的库文件
│ ├──codec //libui.a 库文件所在的目录
│ ├──db //libdb.a 库文件所在的目录
│ ├──ui //libui.a库文件所在的目录
├──lib //源文件所在的目录,子目录文件中包含Makefile文件
│ ├──codec //编解码器所在的源文件的目录
│ ├──db //数据库源文件所在的目录
│ ├──ui //用户界面源文件所在目录
├──app
│ ├──player
└──doc //这个工程编译说明
lib_codec := lib/codec
lib_db := lib/db
lib_ui := lib/ui
libraries := $(lib_codec) $(lib_db) $(lib_ui)
player := app/player
.PHONY : all $(player) $(libraries)
all : $(player)
$(player) $(libraries) :
$(MAKE) -C $@