Makefile学习

来源:互联网 发布:程序员常用工具 知乎 编辑:程序博客网 时间:2024/05/18 11:37

说明

是选择cmake还是make,这个问题不用纠结。make必须会,自己平时写小程序,一定要会make。而cmake有时候组织一些大工程会很有用,这里不是说make不能做大工程。
make涉及到源文件到目标文件,目标文件到可执行文件,如果这些概念不清楚,先将这些概念性东西理解清楚,更加方便下面的内容。

编写测试文件时小技巧:
如果在vim中编辑Makefile文件,可以在命令行模式下输入 ‘:! make target’ 快速测试,不需要再退出Makefile在shell终端执行make target。

本文档很多东西没有涵盖,也没有做过多的讲解,详细请参考官方make文档。如果已经对Makefile有一点基础,建议直接看实战项目。


Makefile语法规则

    target ... : prerequisites ...        receipe         ...        ...

target是可执行文件或目标文件的名字。(还有一种情况就是伪目标,后面讲)
prerequisites是一个文件,使用这些文件创建最后的目标。
recipe是一个make执行的命令。

注意:每一行的recipe之前一定要有一个tab字符。
说明:Makefile文件名不能更改,除非你指定make读取指定的文件(不建议这种做法)。


简单例子

main : main.o hello.o foo.o    g++ -o main main.o hello.o foo.omain.o : main.cpp hello.h foo.h    g++ -c main.cpphello.o : hello.cpp hello.h    g++ -c hello.cppfoo.o : foo.cpp foo.h    g++ -c foo.cpp.PHONY : cleanclean :     rm main main.o hello.o foo.o

上面逻辑很清楚,用源文件创建出目标文件,然后将所有的目标文件创建出最后的可执行文件。clean是一个伪目标,执行make clean将清除可执行文件main,main.o,hello.h和foo.h。


简单例子升级(使用变量)

使用变量来缩减代码量。

objects = main.o hello.o foo.omain : $(objects)    g++ -o main $(objects)main.o : main.cpp hello.h foo.h    g++ -c main.cpphello.o : hello.cpp hello.h    g++ -c hello.cppfoo.o : foo.cpp foo.h    g++ -c foo.cpp.PHONY : cleanclean :     rm main $(objects)

说明:变量实际上就是进行了内容的替换。
变量不用声明,可以直接使用,使用$(variable)获取变量的值。


简单例子升级(make自动推断)

实际上make可以自动推断recipes,如果你的target是目标文件,prerequisite是源文件,那么可以省去g++ -o xxx.cpp。看下面代码:

objects = main.o hello.o foo.omain : $(objects)    g++ -o main $(objects)main.o : main.cpp hello.h foo.hhello.o : hello.cpp hello.hfoo.o : foo.cpp foo.h.PHONY : cleanclean :     rm main $(objects)

以上代码省略了recipes,让make自动推断,编译出目标文件。

实际上,上面代码还可以省:

objects = main.o hello.o foo.omain : $(objects)    g++ -o main $(objects)$(objects) : hello.h foo.h.PHONY : cleanclean :     rm main $(objects)

上面将上面三条进行合并,这些目标文件并不需要按照顺序,make会根据源代码自动推断,生成目标文件。


Makefile组成

Makefile可以说非常像shell脚本,它里面有显式规则,隐式规则,变量,指令和注释。
下面将粗略讲讲各个组成部分
 显式规则:列出所有的prerequisite和recipes,参考:简单例子。
 隐式规则:用make自动推断,参考:简单例子升级(make自动推断)。
 变量:包括自定义变量和内置变量。
 指令:include,define等指令。include指令可以包含其它Makefile,define指定可以定义变量。
 注释:以 ‘#’ 开头的一行。

提示: 当一行内容特别长,可以使用反斜杠 ‘\’ 来分割一行为两行。
读取其他Makefile可以使用include指令。

recipes中可以使用shell变量,但是有时候并不是直接使用指令。例如创建一个子系统时,通常会使用如下:

substem:    cd subdir && $(MAKE)

这里不能显式使用make,而要使用$(MAKE)。


变量

变量分为自定义变量和内置变量。
自定义变量不需要声明,可以直接使用,使用$(variable)表示variable中的值,内置变量就是make中的一些预留变量,代表一些固定的值,例如$(MAKE)。

可以使用define指令定义一个变量,变量名中不能包含 ‘:’, ‘#’, ‘=’和空格等符号。
变量名大小写敏感,foo和FOO表示不同的变量,可以使用$(foo)${foo}取出变量foo中值。变量能够在target,prerequisite和recipe等地方使用。

记住,变量遵循严格的文本替换。

foo = cprog.o : prog.$(foo)    g$(foo)$(foo) -$(foo) prog.$(foo)

上面代码可以转换为一下内容。

prog.o : prog.c     gcc -c prog.c

变量的赋值

变量名的赋值除了使用 = 外,还可以使用 :=, ::=, ?= 和 +=。
:= 符号用于覆盖变量值并写入新值。

whoami := $(shell whoami)whoami := byyshow:     @echo $(whoami)

使用make show,会输出byy。

说明:可以使用$(shell command)来执行shell命令。

那么问题来了,为什么会同时存在 = 和 := 两个符号?每次赋值不是都可以更新值吗,看看下面例子:

x = fooy = $(x) barx = abcshow:    @echo $(y)

执行make show显示结果: abc bar。

x := fooY := $(x) barx := abcshow:     @echo $(y)

执行make show显示结果:foo bar。

总结::= 符号取决于在makefile中的位置,变量会最后在Makefile中展开,而 “=” 会用变量最后展开的值。

?= 表示条件变量赋值操作符。
如果左边的变量已经定义,那么就将右边的值赋给变量,否则,不会进行赋值操作。

+= 表示在变量原有的情况下,增加一个新的内容,在写Makefile会非常有用。
ojects += another.o

变量值中的替换操作

foo := a.o b.o c.o bar := $(foo:.o=.c)show:    @echo $(bar)

将.o全部替换为.c,显示结果为: a.c b.c c.c

多行变量

define two-lines = echo foo echo $(bar)endef 

如果变量右边的内容太多,可以使用多行变量。上面的代码等同于以下代码:
two-lines = echo foo; echo $(bar)

取消变量定义

使用 undefine 变量名 可以将一个变量变为undefined。

特殊变量

$<  表示第一个prerequisite$^  表示所有的prerequisite$@  表示目标

VPATH指定prerequisite的搜索目录

VPATH = src:../headers...foo.o : foo.c 

如果当前目录没有foo.c,那么会到src目录下去搜foo.c文件是否存在,如果还是没有找到那么会到../headers。

vpath指定搜索路径下的指定类型的文件名

以下是vpath的用法:

    vpath pattern directories        指定搜索目录下匹配的文件名    vpath pattern         清除与pattern相关的搜索路径    vpath          清除前面指定的所有查找路径
示例:vpath %.h ../headers结果:如果当前目录下没有找到.h类型文件,那么会到../headers目录下进行查找。

说明: vpath是指令,在Makefile中,大写的一般是变量,小写一般是指令。


条件判断

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

条件判断的种类:ifeq, ifneq, ifdef, ifndef。


内置函数

字符串操作

$(subst from, to, text)        作用:字符串中子字符串替换,返回替换之后的结果。    例子:$(subst ee, EE, feet on the street)    结果: ‘fEEt on the strEEt’$(patsubst pattern, replacement, text)    作用:字符串模式匹配,进行替换,并返回替换之后的结果。    例子:$(patsubst %.c, %.o, x.c.c bar.c)    结果:‘x.c.o bar.o’上面可以简写: $(objects:.c=.o)$(strip string)    作用:去掉字符串开头和结尾的空格。    去掉字符串开头和结尾的空格可以让代码更加具有健壮性。$(findstring find, in)    作用:在字符串中查找字符串find,如果找到,则返回find,否则返回‘’    例子:$(findstring a,a b c)    结果:返回 ‘a’

还有很多关于文本替换的函数,这里不做过多的介绍。

foreach函数

$(foreach var,list,text)    作用: 遍历list,执行text。

wildcard函数

通配符函数,可以出现在Makefile的任意位置。

小技巧,看下面例子:

$(wildcard *.c)

上面语句可以获取当前目录下所有的.c文件名。
下面语句可以将所有的.c文件变为.o文件。

$(wildcard %.c,%.o,$(wildcard *.c))

$(wildcard pattern…)
wildcard可以在任何地方使用。

文件名函数

$(basename names...)    作用:去掉文件名后缀。    例子:$(basename src/foo.c src-1.0/bar hacks)    结果:src/foo src-1.0/bar hacks$(addprefix prefix,names...)    作用:增加前缀。    例子:$(addprefix src/,foo bar)    结果:src/foo src/bar $(addsuffix suffix,names...)    作用:增加后缀。    例子:$(addsuffix .c,foo bar)    结果:foo.c bar.c

项目实战

看文件目录结构

├── bin├── Makefile├── obj└── src    ├── foo    │   ├── foo.cpp    │   └── foo.h    ├── hello    │   ├── hello.cpp    │   └── hello.h    └── main.cpp

实际项目中,需要分模块将hello和foo当做两个模块。现在如何编写Makefile?

CXXFLAGS = -WallLINK := $(CPP)vpath %.cpp srcvpath %.cpp src/hellovpath %.cpp src/fooOBJ_PATH := objBIN_PATH := binSRC_PATH := src/hello src/foo srcINCS = -I src/hello -I src/fooSRC = $(foreach x,$(SRC_PATH),$(wildcard $(addprefix $(x)/*,.c .cpp)))OBJ = $(addprefix $(OBJ_PATH)/, $(addsuffix .o,$(notdir $(basename $(SRC)))))TARGET = main$(BIN_PATH)/$(TARGET): $(OBJ)    $(CXX) $(CXXFLAGS) -o $@ $^$(OBJ_PATH)/%.o: %.cpp    $(CXX) -c $(CXXFLAGS) $(INCS) -o $@ $<.PHONY: clean clean:    rm -f $(OBJ_PATH)/* $(BIN_PATH)/* 

总结

Makefile中尽量显示出模块化的思想,尽量使用变量带代替重复的内容,不会导致改变一处而全局改变。Makefile的编写中,最重要是当做一门语言来学,学会看错误提示,不断调试。


FAQ

Q: 如何指定目录放指定文件?

A: 在编写Makefile时指定前缀。

    $(OBJ_PATH)/$(TARGET):$(OBJ)        $(CPP) -o $@ @^

参考

[1] gnu make官网:http://www.gnu.org/software/make/manual/make.pdf
[2] Makefile模板 :http://www.oschina.net/code/snippet_113073_38647