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 $@
 
                    
                
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号