Make 命令教程
最简明的见阮一峰的博客,以下是我整理的写makefile的笔记
从小例子说起
foo.o : foo.c defs.h # foo模块
cc -c -g foo.c
第一条规则中的目标将被确立为最终的目标。
规则说明了
- foo.o依赖于foo.c和defs.h的文件
- 如何生成foo.o这个文件
规则的语法
targets : prerequisites
command
...
targets是文件名,以空格分开,可以使用通配符。
目标可以是一个文件,但也有可能是多个文件。
如果命令太长,你可以使用反斜框(‘\’)换行。
自动找寻源文件中包含的头文件,并生成一个依赖关系
大多数的C/C++编译器都支持一个“-M”的选项,如果你使用GNU的C/C++编译器,你得用“-MM”参数。
- 执行gcc -MM main.c
- 输出main.o : main.c defs.h
gcc为每一个“name.c”的文件都生成一个“name.d”的Makefile文件
于是可以写出[.c]文件和[.d]文件的依赖关系,并让make自动更新[.d]文件,并把其包含在Makefile中,这样,就可以自动化地生成每个文件的依赖关系了。
给出了一个模式规则来产生[.d]文件
%.d: %.c
@set -e; rm -f $@; \
$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
这个规则的意思是:
- 所有的[.d]文件依赖于[.c]文件
- “rm -f $@”的意思是删除所有[.d]文件
- 第二行命令的意思是,为每个依赖文件[.c]文件生成依赖文件
第二行生成的文件有可能是“name.d.12345” - 第三行使用sed命令做替换
- 第四行删除临时文件。
自动找.c文件所含的.h文件,可以参见如下:
http://blog.csdn.net/haoel/article/details/2890
http://wiki.ubuntu.org.cn/%E8%B7%9F%E6%88%91%E4%B8%80%E8%B5%B7%E5%86%99Makefile:MakeFile%E4%BB%8B%E7%BB%8D
使用函数
$(<function> <arguments>)
或者
${<function> <arguments>}
参数间以逗号,分隔,而函数名和参数之间以空格分隔。
例子:
comma:= ,
empty:=
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))
这里的函数subst(替换substitution),把foo里的space用comma替换
格式:$(patsubst <pattern>,<replacement>,<text> )
名称:模式字符串替换函数——patsubst。
功能:查找<text>中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式<pattern>,如果匹配的话,则以<replacement>替换。
这里,<pattern>可以包括通配符“%”,表示任意长度的字串。
如果<replacement>中也包含“%”,那么,<replacement>中的这个“%”将是<pattern>中的那个“%”所代表的字串
例子:
$(patsubst %.c,%.o,x.c.c bar.c)
模式替换函数patsubst(pattern substitution)
把x.c.c bar.c里面的符合%.c模式的,换成%.o模式
所以最后返回的是x.c.o bar.o
例子:
sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
cc $(filter %.c %.s,$(sources)) -o foo
filter函数,保留符合patter的word
上面例子的模式是%.c %.s,最后保留foo.c bar.c baz.s
下面开始为一个简单的项目写makefile吧!
(参考:http://www.cs.colby.edu/maxwell/courses/tutorials/maketutor/ )
假设你的C项目包含如下三个文件:
(1) hellomake.c
#include <hellomake.h>
int main() {
// call a function in another file
myPrintHelloMake();
return(0);
}
(2) hellofunc.c
#include <stdio.h>
#include <hellomake.h>
void myPrintHelloMake(void) {
printf("Hello makefiles!\n");
return;
}
(3) hellomake.h
/*
example include file
*/
void myPrintHelloMake(void);
手动在终端输命令如下可以得到可执行文件hellomake
gcc -o hellomake hellomake.c hellofunc.c -I.
手打这串命令的不方便之处是,当你重新打开终端,得从头(from scratch)再输,此外,如果你只是修改了一个.c文件,你需要重新编译所有文件,浪费时间没效率。
version 1
于是你可以构建第一个版本的自动化makefile
创建一个名为makefile或者Makefile的文件,敲如下命令:
hellomake: hellomake.c hellofunc.c
gcc -o hellomake hellomake.c hellofunc.c -I.
第一行是规则,知道hellomake依赖于哪些文件,
第二行是命令,注意命令前面必须有tab
version 2
CC=gcc
CFLAGS=-I.
hellomake: hellomake.o hellofunc.o
$(CC) -o hellomake hellomake.o hellofunc.o -I.
现在定义了常数CC和CLAGS。
宏CC指定用那个编译器
CFLAGS是flag列表传递给编译命令
通过将目标文件hellomake.o 和 hellofunc.o放在依赖性列表,make知道它需要首先编译.c,然后构建可执行的hellomake。
用上面这种形式的makefile对于大部分小型工程足够了,但遗漏对include文件的依赖性的规定。如果你要修改hellomake.h,make不会重新编译.c文件,即使这些.c文件需要重新编译。所以,我们需要告诉make所有的.c文件依赖于哪些特定的header file。
version 3
CC=gcc
CFLAGS=-I.
DEPS = hellomake.h
%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
hellomake: hellomake.o hellofunc.o
gcc -o hellomake hellomake.o hellofunc.o -I.
这里增加了宏DEPS,定义.c文件依赖的header files。
接着我们定义了应用于所有(注意%符号).o文件的规则,规则是.o文件依赖于所有.c文件以及头文件。
规则然后规定,make需要编译.c文件以产生.o文件。
-c flag(这里flag和option是同义词)指产生obj文件
-o $@指将编译结果放在:左边名字命名的文件里(也即是%.o)
$<是list里面的第一项(也即是 %.c)
- $@——:的左边
- $^ ——:的右边
再举个例子:
all: library.cpp main.cpp
在这种情形下
$@ 指all
$< 指library.cpp
$^ 指library.cpp main.cpp
version 4
CC=gcc
CFLAGS=-I.
DEPS = hellomake.h
OBJ = hellomake.o hellofunc.o
%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
hellomake: $(OBJ)
gcc -o $@ $^ $(CFLAGS)
version 5
如果我们这样组织我们的项目文件,header files在一个include目录,source code在src目录,本地库在一个lib目录,当得到了可执行文件,.o文件也不需要了,如何处理它们呢?
IDIR =../include
CC=gcc
CFLAGS=-I$(IDIR)
ODIR=obj
LDIR =../lib
LIBS=-lm
_DEPS = hellomake.h
DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS))
# hellomake.h整个用../include/hellomake.h替换
_OBJ = hellomake.o hellofunc.o
OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ))
# hellomake.o hellofunc.o分别用obj/hellomake.o和obj/hellofunc.o替换
$(ODIR)/%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
hellomake: $(OBJ)
gcc -o $@ $^ $(CFLAGS) $(LIBS)
.PHONY: clean
clean:
rm -f $(ODIR)/*.o *~ core $(INCDIR)/*~
这个makefile应该放在src目录里面。
这里也包含了clean up你的source和obj目录的规则,如果你敲make clean的话。
注意,本文makefile里面的通配符%和都是表示匹配任意字符,但是%是make层的,是shell层的。
两种口味的变量
上面的例子给出了两种设置变量值的方法,
VAR=VAL VS VAR:=VAL
下面做个简单的说明
递归展开变量
foo = $(bar)
bar = $(ugh)
ugh = Huh?
all:;echo $(foo)
这里foo->bar->ugh
最后在终端打印Huh?
简单展开变量: 使用在之前定义的值,而不会按照引用去递归地找最终的值。
细节请参见GNU make手册
本文是http://www.cs.colby.edu/maxwell/courses/tutorials/maketutor/ 的整理版