网站链接:跟我一起写Makefile
Mkafile的规则
目标: 依赖
命令
目标:一个目标文件,也可以是一个执行文件,或者一个label。
依赖:生成这个目标的所有的文件
command:该target要执行的命令
Makefile寻找路径:
- 如果这个工程没有编译过,那么我们的所有c文件都要编译并被链接。
- 如果这个工程的某几个c文件被修改,那么我们只编译被修改的c文件,并链接目标程序。
- 如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的c文件,并链接目标程序。
makefile中的使用变量
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
为了简便makefile中的编写,可以利用变量的思路,将
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
通过使用$(objects)
就能使用上述变量
makefile的自动推导
例如:
#include<stdio.h>
int main() {
printf("hello world\n");
return 0;
}
编写Makefile文件:
target = hello
objects = hello.o
$(target):$(objects)
$(objects):
只要make看到一个 .o 文件,它就会自动的把 .c 文件加在依赖关系中
Makefile的文件名
执行make命令时,会在当前目录下寻找:“GNUmakefile”、“makefile”、“Makefile”的文件,推荐使用后两种,也可以通过-f 或者 -file
指定文件名
引用其他的Makefile
可以通过include包含其他的Makefile文件,语法如下:
include <filename>
下面两个语句等价
include foo.make *.mk $(bar)
include foo.make a.mk b.mk c.mk e.mk f.mk
Makefile通配符
make中支持三个通配符:*, ?, ~
波浪号( ~ )字符在文件名中也有比较特殊的用途。如果是 ~/test ,这就表示当前用户的 $HOME 目录下的test目录。
*.c代表所有后缀为c的文件,如果文件名中存在通配符需要进行转义,*
例如:
clean:
cat main.c
rm -f *.o
但是下列例子:
objects = *.o
并不表示所有的.o文件,如果需要将变量展开需要进行如下写法:
objects := $(wildcard *.o)
文件搜寻
在一些大的工程中,有大量的源文件,我们通常的做法是把这许多的源文件分类,并存放在不同的目录中。所以,当make需要去找寻文件的依赖关系时,你可以在文件前加上路径,但最好的方法是把一个路径告诉make,让make在自动去找。
目录之间需要由冒号进行分隔:
VPATH = src:../headers
伪目标
我们只有通过显式地指明这个“目标”才能让其生效。
.PHONY : clean
clean :
rm *.o temp
all : prog1 prog2 prog3
.PHONY : all
prog1 : prog1.o utils.o
cc -o prog1 prog1.o utils.o
prog2 : prog2.o
cc -o prog2 prog2.o
prog3 : prog3.o sort.o utils.o
cc -o prog3 prog3.o sort.o utils.o
上述代码可以通过make自动执行后面的代码
自动生成依赖性¶
大多数的C/C++编译器都支持一个“-M”的选项,即自动找寻源文件中包含的头文件,并生成一个依赖关系。
cc -M main.c
书写命令
make会一按顺序一条一条的执行命令,每条命令的开头必须以 Tab 键开头
显示命令
在进行shell命令前需要添加@,比如echo:
echo 666
输出:
echo 666
666
命令执行
如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令。
exec:
cd /home/hchen
pwd
exec:
cd /home/hchen; pwd
命令出错
有些时候,命令的出错并不表示就是错误的。例如mkdir创建已经存在的文件,需要通过在Makefile的命令行前面加一个减号
clean:
-rm -f *.o
嵌套执行make
在一些大的工程中,我们会把我们不同模块或是不同功能的源文件放在不同的目录中,我们可以在每个目录中都书写一个该目录的Makefile,这有利于让我们的Makefile变得更加地简洁,而不至于把所有的东西全部写在一个Makefile中,这样会很难维护我们的Makefile,这个技术对于我们模块编译和分段编译有着非常大的好处。
subsystem:
cd subdir && $(MAKE)
等价于:
subsystem:
$(MAKE) -C subdir
定义$(MAKE)宏变量的意思是,也许我们的make需要一些参数,所以定义成一个变量比较利于维护。这两个例子的意思都是先进入“subdir”目录,然后执行make命令。
: 表示依赖, ^:表示依赖, :表示依赖,@表示目标
使用变量
变量在声明时需要给予初值,而在使用时,需要给在变量名前加上 $ 符号.
追加变量值
objects = main.o foo.o bar.o utils.o
objects += another.o
于是,我们的 $(objects) 值变成:“main.o foo.o bar.o utils.o another.o”(another.o被追加进去了)
使用条件判断 ifeq ifneq
libs_for_gcc = -lgnu
normal_libs =
foo: $(objects)
ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif
该方法能够判断CC是否为gcc,关键词 ifeq、else
和 endif
,任何一个条件语句的结束都应该以endif结束
关键词 ifdef ifndef
示例:
bar =
foo = $(bar)
ifdef foo
frobozz = yes
else
frobozz = no
endif
使用函数
语法如下所示:
$(<function> <arguments>)
subst
$(subst <from>,<to>,<text>)
名称:字符串替换函数
功能:把字串
返回:函数返回被替换过后的字符串。
comma:= ,
empty:=
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))
$(bar) 的值是 a,b,c 。
patsubst
$(patsubst <pattern>,<replacement>,<text>)
名称:模式字符串替换函数。
功能:查找
返回:函数返回被替换过后的字符串。
示例:
$(patsubst %.c,%.o,x.c.c bar.c)
把字串 x.c.c bar.c
符合模式 %.c
的单词替换成 %.o
,返回结果是 x.c.o bar.o
strip
$(strip <string>)
名称:去空格函数。
功能:去掉 字串中开头和结尾的空字符。
返回:返回被去掉空格的字符串值。
示例:
$(strip a b c )
findstring
$(findstring <find>,<in>)
名称:查找字符串函数
功能:在字串 中查找 字串。
返回:如果找到,那么返回 ,否则返回空字符串。
示例:
$(findstring a,a b c)
$(findstring a,b c)
第一个函数返回 a 字符串,第二个返回空字符串
filter
$(filter <pattern...>,<text>)
名称:过滤函数
功能:以 模式过滤
返回:返回符合模式 的字串。
sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
cc $(filter %.c %.s,$(sources)) -o foo
filter-out 反过滤
sort
$(sort <list>)
名称:排序函数
功能:给字符串 中的单词排序(升序)。
返回:返回排序后的字符串。
示例: $(sort foo bar lose) 返回 bar foo lose 。
备注: sort 函数会去掉 中相同的单词。
word
$(word <n>,<text>)
名称:取单词函数
功能:取字符串
返回:返回字符串
示例: $(word 2, foo bar baz) 返回值是 bar 。
wordlist
$(wordlist <ss>,<e>,<text>)
名称:取单词串函数
功能:从字符串
返回:返回字符串
示例: $(wordlist 2, 3, foo bar baz) 返回值是 bar baz 。
words
$(words <text>)
名称:单词个数统计函数
dir
$(dir <names...>)
名称:取目录函数——dir。
功能:从文件名序列 中取出目录部分。目录部分是指最后一个反斜杠( / )之前的部分。如果没有反斜杠,那么返回 ./ 。
返回:返回文件名序列 的目录部分。
示例: $(dir src/foo.c hacks) 返回值是 src/ ./ 。
notdir 与dir相反
$(notdir <names...>)
suffix
取后缀函数
$(suffix <names...>)
名称:取後缀函数——suffix。
功能:从文件名序列 中取出各个文件名的后缀。
返回:返回文件名序列 的后缀序列,如果文件没有后缀,则返回空字串。
示例: $(suffix src/foo.c src-1.0/bar.c hacks) 返回值是 .c .c。
basename
$(basename <names...>)
名称:取前缀函数——basename。
功能:从文件名序列 中取出各个文件名的前缀部分。
返回:返回文件名序列 的前缀序列,如果文件没有前缀,则返回空字串。
示例: $(basename src/foo.c src-1.0/bar.c hacks) 返回值是 src/foo src-1.0/bar hacks 。
addsuffix
$(addsuffix <suffix>,<names...>)
名称:加后缀函数——addsuffix。
功能:把后缀 加到 中的每个单词后面。
返回:返回加过后缀的文件名序列。
示例: $(addsuffix .c,foo bar) 返回值是 foo.c bar.c 。
addprefix
$(addprefix <prefix>,<names...>)
名称:加前缀函数——addprefix。
功能:把前缀 加到 中的每个单词前面。
返回:返回加过前缀的文件名序列。
示例: $(addprefix src/,foo bar) 返回值是 src/foo src/bar。
join
$(join <list1>,<list2>)
名称:连接函数——join。
功能:把 中的单词对应地加到 的单词后面。如果 的单词个数要比 的多,那么, 中的多出来的单词将保持原样。如果 的单词个数要比 多,那么, 多出来的单词将被复制到 中。
返回:返回连接过后的字符串。
示例: $(join aaa bbb , 111 222 333) 返回值是 aaa111 bbb222 333 。
shell函数
shell函数也不像其它的函数。顾名思义,它的参数应该就是操作系统Shell的命令。它和反引号“`”是相同的功能。这就是说,shell函数把执行操作系统命令后的输出作为函数返回。于是,我们可以用操作系统命令以及字符串处理命令awk,sed等等命令来生成一个变量,如:
contents := $(shell cat foo)
files := $(shell echo *.c)
error
$(error <text ...>)
产生一个致命的错误, <text …> 是错误信息。注意,error函数不会在一被使用就会产生错误信息,所以如果你把其定义在某个变量中,并在后续的脚本中使用这个变量,那么也是可以的。例如:
ifdef ERROR_001
$(error error is $(ERROR_001))
endif
warning
$(warning <text ...>)