在Linux(unix)环境下使用GNU的make工具能够比较容易的构建一个属于你自己的工程,整个工程的编译只需要一个命令就可以完成编译、连接以至于最后的执行。
make是一个命令工具,它解释Makefile中的指令(应该说是规则)。在Makefile文件中描述了整个工程所有文件的编译顺序、编译规则。Makefile有自己的书写格式、关键字、函数。像C语言有自己的格式、关键字和函数一样。而且在Makefile中可以使用系统shell所提供的任何命令来完成想要的工作。
Makefile(在其它的系统上可能是另外的文件名)在绝大多数的IDE开发环境中都在使用,已经成为一种工程的编译方法。
1 基础
首先需要了解常见的编译器选项:以
gcc
编译器为例:-c
,仅编译不链接;-I
,指定头文件路径(include);-L
,指定依赖库路径;-l
,指定链接库的名字,如libc.a
这一名称的库可用-lc
来引用;-o
,指定输出;-O
,优化的4级别(0,1,2,3,常用-O2
);-g
,产生调试信息;-static
,禁用动态库;-share
,生成一个共享目标文件,他可以和其他目标文件连接产生可执行文件.只有部分系统支持该选项;-D
,定义比较重要的宏,如-DDEBUG=1
,可配合程序中类似#if DEBUG
语句使用;-w
,关闭编译时警告;-Wall
,编译后显示所有警告;
- 简单规则:
target ... : prerequisites ... #recipe前面一定要是一个Tab,不是空格 recipe clean: recipe
简单例子:
main.c
、defs.h
、func.c
,其中两个.c
文件都依赖defs.h
#反斜线(用于换行)后不要加空格 Name: main.o \ func.o #这里用空格就行 gcc -o main.o func.o # 命令前是Tab main.o: main.c defs.h gcc -c main.c func.o: func.c defs.h gcc -c func.c clean: #这个target没有依赖 rm Name main.o func.o
变量的使用
objects= main.o func.o Name: $(objects) gcc -o ${objects} #小括号和花括号都行,但必须要加
一条默认规则: 一个
N.o
文件默认依赖N.c
,可以不用写出,故上面例子简化成:Name: main.o \ func.o #这里用空格就行 gcc -o main.o func.o # 命令前是Tab #书写建议:单目标、多依赖 main.o: defs.h func.o: defs.h .PHONY:clean #将clean声明为伪目标,避免存在名为clean的文件 clean: -rm Name main.o func.o # '-':表示忽略rm的执行错误
2 进阶
include FILENAME
: 暂停当前Makefile读取,完成指定文件读取后再继续# VPATH, 指定依赖文件的搜索路径 VPATH=../src:../headers # 使用空格或冒号可将多个目录分开 include ../src/Makefile
通配符(如
*.c
)的使用,限于规则的目标、依赖和命令中变量定义中应使用
wildcard
展开通配符# :=表示不能使用后面定义的变量 OBJS:=$(wildcard *.o) # 高级用法,先获取所有.c文件,再将变量中的.c后缀替换为.o OBJS:=$(patsubst %.c,%.o,$(wildcard *.c))
?=
操作符,表示只有在之前没有赋值的情况下才会对此变量进行赋值定义变量时注释和定义要分行写,否则注释前的空格会被作为变量值的一部分
dir := /foo/bar # directory to ... # 变量dir的值是"/foo/bar "(后面有四个空格)
变量的替换引用
# 表示替换foo中所有以.o结尾的字为.c结尾 bar:=$(foo:.o=.c)
$^
代表所有通过目录搜索得到的依赖文件的完整路径名(目录 + 一般文件名)列表;$@
代表规则的目标名;$<
代表规则中通过目录搜索得到的依赖文件列表的第一个依赖文件链接静态库、共享库
# 表示生成目标时需要链接libcurses.a或libcurses.so文件 foo : foo.c -lcurses cc $^ -o $@
命令回显:make执行命令时会默认将其输出到shell
# 用@字符后不会回显命令,但仍会输出命令运行结果 @echo 开始编译...
命令的执行:每一行的命令执行相互独立,除非用
\
将一行分两行写命令前加
-
字符,表示即使命令执行失败,make仍继续执行make中的条件语句
ifeq ($(CC),gcc) libs=$(libs_for_gcc) else libs=$(normal_libs) endif foo: $(objects) $(CC) -o foo $(objects) $(libs)
make中的循环语句
dirs:=a b c d files := $(foreach dir, $(dirs) , $(wildcard $(dir)/*)) #等价于: files := $(wildcard a/* b/* c/* d/*)
shell函数的使用
files:=$(shell echo *.c) # 变量“ files”赋值为当前目录下所有.c文件的列表(文件名之间使用空格分割),等效于$(wildcard *.c)
3 深入
自动找寻源文件中包含的头文件,并生成文件的依赖关系
gcc -M main.c # 不考虑标准库头文件时,用-MM参数
自动生成每一个
.c
文件对应的.d
文件(包含相应依赖文件描述)%.d: %.cpp # 1. 使用编译器自自动生成依赖文件($<)的头文件的依赖关系,并输出成为一个临时文件 # 2. 使用sed处理第二行已产生的那个临时文件并生成此规则的目标文件。(将.d加入到规则的目标中) # 3. 删除临时文件 $(CXX) -MM $(CXXFLAGS) $< > $@.$$$$; \ sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \ rm -f $@.$$$$
.d
文件的处理(接着上面一条)SRCS = $(wildcard ./*.cpp) dependence:=$(SRCS:.cpp=.d) sinclude $(dependence) # sinclude相当于 -include,(和其它版本make兼容),表示无论include过程中出现什么错误,都不要报错继续执行。
order-only依赖使用
LIBS=libtest.a foo: foo.c|$(LIB) $(CC) $(CFLAGS) $< -o $@ $(LIBS) # 如果foo已存在(前提),若libtest.a被修改,将不会重建foo
filter
分类函数使用files = foo.elc bar.o lose.o $(filter %.o,$(files)): %.o: %.c $(CC) -c $(CFLAGS) $< -o $@ $(filter %.elc,$(files)): %.elc: %.el emacs -f batch-byte-compile $<
没卵用的小技巧
var := one$\ word # 上面的内容等价于: var := one$ word # '$ '($后面有一个空格): a variable named ' ' # 又等价于: var := oneword
一个例子
#一个实用的makefile,能自动编译当前目录下所有.c/.cpp源文件,支持二者混合编译 #并且当某个.c/.cpp、.h或依赖的源文件被修改后,仅重编涉及到的源文件,未涉及的不编译 #详解文档:http://blog.csdn.net/huyansoft/article/details/8924624 #author:胡彦 2013-5-21 #---------------------------------------------------------- #编译工具用g++,以同时支持C和C++程序,以及二者的混合编译 CC=g++ #使用$(winldcard *.c)来获取工作目录下的所有.c文件的列表 #sources:=main.cpp command.c #变量sources得到当前目录下待编译的.c/.cpp文件的列表,两次调用winldcard、结果连在一起即可 sources:=$(wildcard *.c) $(wildcard *.cpp) #变量objects得到待生成的.o文件的列表,把sources中每个文件的扩展名换成.o即可。这里两次调用patsubst函数,第1次把sources中所有.cpp换成.o,第2次把第1次结果里所有.c换成.o objects:=$(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(sources))) #变量dependence得到待生成的.d文件的列表,把objects中每个扩展名.o换成.d。也可写成$(patsubst %.o,%.d,$(objects)) dependence:=$(objects:.o=.d) #---------------------------------------------------------- #当$(objects)列表里所有文件都生成后,便可调用这里的 $(CC) $^ -o $@ 命令生成最终目标all了 #把all定义成第1个规则,使得可以把make all命令简写成make all: $(objects) $(CC) $(CPPFLAGS) $^ -o $@ @./$@ #编译后立即执行 #这段使用make的模式规则,指示如何由.c文件生成.o,即对每个.c文件,调用gcc -c XX.c -o XX.o命令生成对应的.o文件 #如果不写这段也可以,因为make的隐含规则可以起到同样的效果 %.o: %.c $(CC) $(CPPFLAGS) -c $< -o $@ #同上,指示如何由.cpp生成.o,可省略 %.o: %.cpp $(CC) $(CPPFLAGS) -c $< -o $@ #---------------------------------------------------------- include $(dependence) #注意该句要放在终极目标all的规则之后,否则.d文件里的规则会被误当作终极规则了 #因为这4行命令要多次凋用,定义成命令包以简化书写 define gen_dep set -e; rm -f $@; \ $(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \ sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \ rm -f $@.$$$$ endef #指示如何由.c生成其依赖规则文件.d #这段使用make的模式规则,指示对每个.c文件,如何生成其依赖规则文件.d,调用上面的命令包即可 %.d: %.c $(gen_dep) #同上,指示对每个.cpp,如何生成其依赖规则文件.d %.d: %.cpp $(gen_dep) #---------------------------------------------------------- #清除所有临时文件(所有.o和.d)。之所以把clean定义成伪目标,是因为这个目标并不对应实际的文件 .PHONY: clean clean: #.$$已在每次使用后立即删除。-f参数表示被删文件不存在时不报错 rm -f all $(objects) $(dependence) echo: #调试时显示一些变量的值 @echo sources=$(sources) @echo objects=$(objects) @echo dependence=$(dependence) @echo CPPFLAGS=$(CPPFLAGS)