题记:
在此感谢李云的无私分享。看到这篇博客的人搜一下李云的关于makefile的讲解,会受益匪浅。
我对其讲解的有些细节的思考:
1,
.PHONY=clean
CC=gcc
RM=rm
EXE=simple.exe
OBJS=main.o foo.o
$(EXE):$(OBJS)
CC -o $@ $^
%.o:%.c#(这句的意思是,gcc从上面第二句开始执行的。然后发现要需要main.o,再到这里去执行)
CC -o $@ -c $^
clean:
$(RM) $(EXE) $(OBJS)
2,
李云云:“当一个大工程,增加或者减少一个.c文件的话,就需要修改一次makefile的话,工作量很大的。“
现在的项目中也是遇到了李云说的那个问题。不过因为是维护代码,所以变化不是很大的,所以大家都还是比较习惯于修改makefile~~呵呵~~
.PHONY:clean
CC=gcc
RM=rm
EXE=simple.exe
SRCS=$(wildcard *.c)#这句就是自动搜索当前目录下所有的.c文件
OBJS=$(SRCS:.c=.o)#
$(EXE):$(OBJS)
$(CC) -o $@ $^
%.o:%.c
$(CC) -o $@ -c $^
clean:
$(RM) $(EXE) $(OBJS)
3,
编译环境:
complicated目录下:
foo.h foo.c main.c
把生成的.o文件放在objs目录下(这个目录要在makefile中自己建)
把生成的可执行文件放在exes目录下(这个目录要在makefile中自己建)
我自己写了一个makefile,出现了一个错误。这个错误,有助于理解makefile的执行。
.PHONY:all clean
CC = gcc
MKDIR = mkdir
RM = rm
RM_FLAGS = -rf
OBJS_DIR = objs
EXES_DIR = exes
DIRS = $(OBJS_DIR) $(EXES_DIR)
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
OBJS := $(addprefix $(OBJS_DIR)/, $(OBJS))
EXES = complecated.exe
EXES := $(addprefix $(EXES_DIR)/, $(EXES))
all:$(DIRS) $(EXES)
$(DIRS):
$(MKDIR) $@
$(EXES):$(OBJS)
$(CC) -o $@ $^
$(OBJS):$(SRCS)#这个地方错了。会报“gcc -o objs/foo.o -c foo.c main.c"的错误。
$(CC) -o $@ -c $^
clean:
$(RM) $(RM_FLAGS) $(DIRS)
其实错误的原因是对makefile的目标的理解没做对。虽然,$(OBJS)包含了foo.o main.o两个东东,但是在执行
$(OBJS):$(SRCS)
的时候,是执行了2次。第一次目标是foo.o;第二次的目标是main.o.而,我们的$(SRCS)却一直 包含了main.c foo.c两个文件。所以编译八过。
建议:
把
$(OBJS):$(SRCS)
改为
$(OBJS)/%.o:%.c
4,增加对头文件的依赖
对于上面的第3点难道真的没问题了吗?实战的李云说,“我们还没有考虑到:如果头文件变化的话,因为我们的makefile是不依赖头文件的,所以,不会去更新编译的”。
也就是如果你在make之后,改变第3点的头文件(foo.h),再一次的执行make其实还是不会产生编译行为的。
我改善了之后的例子:
.PHONY:all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -fr
CC = gcc
DIR_OBJS = objs
DIR_EXES = exes
DIR_DEPS = deps
EXE = complicated.exe
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
DEPS = $(SRCS:.c=.dep)
DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))
all:$(EXE)
-include $(DEPS)#include要放在all之后才行。不信,你试试
$(DIRS):
$(MKDIR) $@
$(EXE):$(DIR_EXES) $(OBJS)
$(CC) -o $@ $(filter %.o, $^)
$(DIR_OBJS)/%.o:$(DIR_OBJS) %.c
$(CC) -o $@ -c $(filter %.c, $^)
$(DIR_DEPS)/%.dep:$(DIR_DEPS) %.c
@echo "Making $@..."
@set -e;\
$(CC) -E -MM $(filter %.c, $^) > $@.tmp;\
sed 's\(.*\)\.o[:]*,objs/\1.o:,g' <$@.tmp>$@;
clean:
$(RM) $(RMFLAGS) $(DIRS)
解释:
A,李云的例子和我的不同的地方
$(DIR_DEPS)/%.dep:$(DIR_DEPS) %.c
@echo "Making $@..."
@set -e;\
$(RM) $(RMFLAGS) $@.tmp;\
$(CC) -E -MM $(filter %.c, $^) > $@.tmp;\
sed 's\(.*\)\.o[:]*,objs/\1.o:,g' <$@.tmp>$@;\
$(RM) $(RMFLAGS) $@.tmp;
红色两句我删掉了,不然会死循环。我也不晓得原因啦~~
B,
makefile中的“;”和set -e
set -e:只要makefile出错的话就退出不继续执行
;:makefile没执行一条shell语句就开启一个shell,而,“;”保证了命令都是一个shell环境下。(shell环境:比如当前路径)
C,
$(CC) -E -MM $(filter %.c, $^) > $@.tmp;\
-E:表示预编译;
-MM:查看.c文件所需要的非系统的头文件。(-M,包括了系统的头文件)
D,
sed 's\(.*\)\.o[:]*,objs/\1.o:,g' <$@.tmp>$@;
sed语法走一遍:
sed ’s,被替换的词,用于替换的词,g'
E,
-include $(DEPS)
include在make执行之前就包含进来。
-,表示如果文件不存在的话,不要报错
include的行为:查找被包含进来的文件,如果有的 话,就查找是否目标中有更新这个文件的规则。有则更新。我们这里就是用了这点。所以,include是先于all目标先执行的。
include的本质作用,就是把包含进来的文件的内容展开到现在文件中。
所以,这里include的内容
objs/foo.o: foo.c foo.h#main.o一样的东西,就不赘述了
所以,include之后,先去执行all目标
(因为all目标在include之前)标,然后发现需要目标foo.o,就这上面那行。
5,增加对生成头文件的dep文件的依赖。
赘述一下,上面4点是在makefile中增加对头文件的依赖,如果头文件改变的话,也要相应的重新编译。一般情况是没有问题啦~~
为什么说“一般情况”呢?主要是因为dep文件中记录了.o文件对头文件,源文件的依赖。但是如果,我们增加了头文件呢?!
比如:(李云的笔记哈~~我只是拜读者啦~~)
define.h
#ifndef __DEFINE_H
#define __DEFINE_H
#define HELLO "Hello"
#endif
foo.h
#ifndef __FOO_H
#define __FOO_H
#include "define.h"
void foo ();
#endif
foo.c
#include <stdio.h>
#include "foo.h"
void foo ()
{
printf ("%s, this is foo ()!\n", HELLO);
}
main.c
#include "foo.h"
int main ()
{
foo ();
return 0;
}
makefile
.PHONY: all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -fr
CC = gcc
DIR_OBJS = objs
DIR_EXES = exes
DIR_DEPS = deps
DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)
EXE = complicated.exe
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
DEPS = $(SRCS:.c=.dep)
DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))
all: $(EXE)
ifneq ($(MAKECMDGOALS), clean)
-include $(DEPS)
endif
$(DIRS):
$(MKDIR) $@
$(EXE): $(DIR_EXES) $(OBJS)
$(CC) -o $@ $(filter %.o, $^)
$(DIR_OBJS)/%.o: $(DIR_OBJS) %.c
$(CC) -o $@ -c $(filter %.c, $^)
$(DIR_DEPS)/%.dep: $(DIR_DEPS) %.c
@echo "Making $@ ..."
@set -e; \
$(CC) -E -MM $(filter %.c, $^) > $@.tmp ; \
sed 's,\(.*\)\.o[ :]*,objs/\1.o: ,g' < $@.tmp > $@ ;
clean:
$(RM) $(RMFLAGS) $(DIRS)
请一定
只按照我的步骤做,否则,看不到效果。
A,make(保证一定生成成功哈)
cat deps/foo.h
objs/foo.o: foo.c foo.h define.h
B,增加一个other.h和修改define.h头文件
other.h
#ifndef __OTHER_H
#define __OTHER_H
#define HELLO "Hello"
#endif
define.h
#ifndef __DEFINE_H
#define __DEFINE_H
#include "other.h"//这句修改了
#endif
C,再次make(一定不要之前make clean啊~~~)
发现重新编译了foo.o main.o.是吧?
D,修改other.h
#ifndef __OTHER_H
#define __OTHER_H
#define HELLO "Hi"
#endif
E,再次make,没反应了。
你cat deps/foo.h
objs/foo.o: foo.c foo.h define.h
这里你发现没有对other.h的依赖。
所以,我们不仅要对头文件进行依赖,还要对生成头文件的dep文件进行依赖。
sed 's,\(.*\)\.o[ :]*,objs/\1.o: ,g' < $@.tmp > $@ ;
这句话改为:
sed 's,\(.*\)\.o[ :]*,objs/\1.o $@: ,g' < $@.tmp > $@ ;
6,其实我的机器上,上面的makefile总是每次都执行。
目前我只知道生成时间是:foo.o < objs目录 < main.o,所以每次make之后要么foo.o,要么main.o编译。。。呵呵~~这个问题,我看语法是没问题的。可能是shell的相关吧。。。
7,
如果你想体味现实的makefile的话一定要看李云的最后一节。
在此,可能makefile就告一个段落了。
非常感谢李云的分享。李云上的例子我都实践了一遍。问题就是的两个地方,学习过程中有什么疑惑,我们可以交流下。以文会友。