Makefile学习
目录
1.1 什么是Makefile
相信在Linux系统中经常会用到make这个命令来编译程序,而执行make命令所依赖的文件便是Makefile文件,make命令通过Makefile文件编写的内容对程序进行编译。make命令根据文件更新的时间戳来决定哪些文件需要重新编译这可以避免编译已经编译过的,没有改变的文件,从而提升编译效率。
1.2 Makefile的规则与示例
一个简单的 Makefile 文件包含一系列的“规则”,其样式如下:
目标(target)…: 依赖()…
<tab>命令(command)
当“依赖文件”比“目标文件”更加新时,或者目标文件还没有生成时,就会执行“命令”
下面就是一个简单的Makefile文件:
hello: hello.c
gcc -o hello hello.c
clean:
rm -f hello
在代码所在的文件夹下放入此Makefile文件,并执行make操作,便会生成目标文件hello,如下
所示:
hello.c内容为:
#include <stdio.h>
int main()
{
printf("hello world\n");
return 0;
}
以上述为例,介绍一下详细规则:
二、Makefile的基本语法
2.1 通配符
当一个目标文件所依赖的依赖文件有很多时,需要写很多条规则,因此可以使用通配符只需要写一行来代替多行的规则。
首先举一个没有使用通配符的例子:共有两个.c文件,分别为a.c,b.c
a.c:
#include <stdio.h>
int main()
{
func_b();
return 0;
}
b.c:
#include <stdio.h>
void func_b()
{
printf("This is B\n");
}
Makefile:
test:a.o b.o
gcc -o test a.o b.o
a.o : a.c
gcc -c -o a.o a.c
b.o : b.c
gcc -c -o b.o b.c
上述例子中的目标文件“test”有两个依赖“a.o”,“b.o”,不难看出,生成a.o,b.o的规则有很多相似之处,因此可以使用通配符来改进Makefile。改进后的Makefile如下:
test:a.o b.o
gcc -o test $^
%.o : %.c
gcc -c -o $@ $<
其中用到的通配符的解释:
%.o:表示所有.o文件
%.c:表示所有.c文件
$@:表示目标
$<:表示第一个依赖文件
$^:表示所有依赖文件
2.2 假想目标
当我们需要清除文件时,可以在Makefile中添加如下代码:
test:a.o b.o
gcc -o test $^
%.o : %.c
gcc -c -o $@ $<
clean:
rm *.o test
在shell中输入make clean即可清除文件:
上面的写法其实存在问题,当前目录下没有clean文件时,使用make clean即可清除文件,但是当当前目录中存在clean文件时,再执行make clean便会出现下面的问题:
出现上面提示的原因是,一个规则能够被执行需要满足两个条件中的一个:
(1)目标文件不存在
(2)依赖文件比目标文件新
这里,clean没有依赖文件,并且clean文件在当前目录已经存在,因此不会执行make clean的操作。为了解决这个问题,需要用到关键字PHONY,其具体用法如下:
test:a.o b.o
gcc -o test $^
%.o : %.c
gcc -c -o $@ $<
.PHONY : clean # 把clean定义为假象目标。他就不会判断名为“clean”的文件是否存在
clean:
rm *.o test
将clean定义为假想目标,便可执行在当前目录已经存在clean文件的情况下执行make clean操作:
2.3 变量
在Makefile中有两种变量:
(1)即时变量(简单变量)
A:=XXX,对于即时变量使用 “:=” 表示,它的值在定义的时候已经被确定了
(2)延时变量()
B=XXX,对于延时变量使用“=”表示,它只有在使用到的时候才确定,在定义时并没有
: = # 即时变量= # 延时变量? = # 延时变量 , 如果是第 1 次定义才起效 , 如果在前面该变量已定义则忽略这句\+ = # 附加 , 它是即时变量还是延时变量取决于前面的定义? = : # 如果这个变量在前面已经被定义了,这句话就会不会起效果
例子:
A := $(C) #即时变量
b = $(C) #延时变量
C = ABC
C +=123
D = hello
D ?= hello world # 延时变量,如果是第一次定义才有效,如果在前面该变量已经定义则忽略这句
E := $(C)
all:
@echo A = $(A) #@表示不打印echo
@echo b = $(b)
@echo D = $(D)
@echo E = $(E)
运行结果:
分析:
A = ,由于A被定义为即时变量,在开始执行的时候C的值为空,所以A的值也为空。
b = ABC 123,b被定义为延时变量,执行make时,会解析Makefile中所用的变量,先执行C = ABC,再执行C +=123,最终 C = ABC 123,因此b = ABC 123。
D = hello,D ?= hello world,执行时会判断前面是否已经定义D,在本例中前面已经定义D = hello了,因此D ?= hello world便没有执行,依然输出D = hello
E =ABC 123,这里E依然为即时变量,但是与A不同的是,执行到这里,C已经被赋值为ABC 123,因此E = $(C) =ABC 123。
三、Makefile的常用函数
函数调用的格式为:
$(function arguments)
这里function是函数名字,arguments是函数的参数。参数和函数名之 间是用空格或 Tab 隔开,如果有多个参数,它们之间用逗号隔开。这些空格和逗号不是参数值的一部分。接下来介绍一些常用的函数。
3.1 字符串替换和分析函数
3.1.1 subst
$(subst from,to,text)
在文本“text”中将“from”用“to”替换
例:
A = $(subst c,o,a.c b.c)
all:
@echo subst = $(A)
运行结果,将a.c b.c 中的c字符替换为o字符:
3.1.2 patsubst
$(patsubst pattern ,replacement,text)
将“text”中符合pattern的格式的字符用“replacement”替换,“pattern”和“replacement”可以使用通配符。
例:
files = a.c b.c c.c abc
dep_files1 = $(patsubst %.c,%.o,$(files))
all:
@echo dep_files1 = $(dep_files1)
运行结果:将以.c结尾的字符替换为.o
3.1.3 strip
$(strip string)
去掉前导和结尾空格,并将中间的多个空格压缩为单个空格。
例:
dep = $(strip a d e f )
all:
@echo dep = $(dep)
运行结果,将a d e f ,中多余的空格去除:
3.1.4 findstring
$(findstring find,in)
在字符串“in”中搜寻“find” ,若找到则返回“find”,否则返回空
例:
dep1 = $(findstring a,a b c)
dep2 = $(findstring a,b c)
all:
@echo $(dep1)
@echo $(dep2)
运行结果,dep1 为a,dep为空:
3.1.5 filter/filterout
$(filter pattern ,text)
在text中取出符合patten格式的值
$(filter-out pattern...,text)
在text中取出不符合patten格式的值
例:
# filter函数,$(filter pattern...,text) 在text中取出符合patten格式的值
# filter-out函数,$(filter-out pattern...,text) 在text中取出不符合patten格式的值
A = a b c d/
B = $(filter %/,$(A))
C = $(filter-out %/,$(A))
all:
@echo B = $(B)
@echo C = $(C)
运行结果,B为以/结尾的d/,C为出去/d的其他字符:
3.1.6 sort
$(sort list)
将“list”中的字符按字母顺序排序,并去除重复的字,输出由单个空格隔开的字的列表
例:
dep = $(sort appel cat bat egg)
all:
@echo $(dep)
运行结果:
3.2 文件名函数
3.2.1 dir/notdir
$(dir names...)
抽取“names”中的每一个文件名的路径部分。文件名的路径部分包括从文件名的首字符到最后一个斜杠之前的一切字符。
$(notdir names...)
抽取‘names...’中每一个文件名中除路径部分外一切字符(真正的文件名)。
例:
dep1 = $(dir src/hello.c hello.c)
dep2 = $(notdir src/hello.c hello.c)
all:
@echo dep1 = $(dep1)
@echo dep2 = $(dep2)
运行结果:
3.2.2 suffix
$(suffix names...)
抽取“names”中每一个文件名的后缀
例:
dep = $(suffix src/foo.c src-1.0/bar.c hacks.o)
all:
@echo dep = $(dep)
运行结果:
3.2.3 basename
$(basename names...)
抽取‘names...’中每一个文件名中除后缀外一切字符。
例:
dep = $(basename src/foo.c src-1.0/bar hacks.o)
all:
@echo dep = $(dep)
运行结果:
3.2.4 addsuffix/addprefix
$(addsuffix suffix,names...)
dep = $(addsuffix .c,cat dog)
all:
@echo $(dep)
运行结果:
$(addprefix prefix,names...)
例:
dep = $(addprefix src/,cat dog)
all:
@echo $(dep)
运行结果:
3.2.5 wildcard
$(wildcard pattern)
#$(wildcard pattern) pattern定义了文件名的格式, wildcard会在当前目录下取出其中存在的文件。
files = $(wildcard *.c)
all:
@echo files = $(files)
运行结果:
3.3 其他函数
3.3.1 foreach
$(foreach var ,list,text)
将list中遍历的每一个字都赋值给var,然后text引用变量var对其中保存的字进行扩展,因此text每次扩展都不相同。
例:
dirs = a b c d
files = $(foreach dir,$(dirs),$(wildcard $(dir)/*))
all:
@echo files = $(files)
该例中files保存的是当前路径下的a/,b/,c/,d/目录下所有的文件列表,a/,b/,c/,d/路径下的文件如下图所示:
执行Makefile后显示内容为:
3.3.2 if
$(if condition,then-part[,else-part])
例:
# SUBDIR = $(B)
SUBDIR := $(B)
B = a/
sub_dir = $(if $(SUBDIR),$(SUBDIR),/home/src)
all:
@echo sub_dir = $(sub_dir)
当SUBDIR为即时变量时,SUBDIR没有被即时赋值,因此该变量保存的内容为空,经过if语句判断后sub_dir输出的内容应该为/home/src
当SUBDIR为延时变量时,SUBDIR被即时赋值为a/,经过if语句判断后sub_dir输出的内容应该为a/:
3.3.3 origin
$(origin variable)
a = a/
ifdef a
ifeq ("$(origin a)", "file")
dir := $(a)
endif
endif
all:
@echo $(origin a)
@echo $(dir)
如果a变量被定义,且a变量是在Makefile中被定义的,则dir = a,结果输出file和a/
当a变量是在命令行中被定义时,输出为command line 和空:
3.3.4 shell
$(shell command arguments)
c_src := $(shell ls)
all:
@echo c_src = $(c_src)
显示当前目录下所有的文件