makefile自动依赖和隐式规则测试,顺便探讨下通用的makefile的写法

小石头子子 发布于 2013/04/28 14:24
阅读 1K+
收藏 2

原博文地址:

http://blog.sina.com.cn/s/blog_4868f98601018f9a.html


另有改进版,参考地址:

http://blog.sina.com.cn/s/blog_4868f98601018f9a.html


最近编译工程的时候又用到makefile了。


以前只写过一些很简单的makefile。 形如:
test.o : test.c test.h
gcc  -g -c test.c

之类。 在文件很少的情况下,可以这样用, 但是当文件很多的时候, 这样搞会把人折腾死的。

网上有不少介绍通用makefile的文章: 
比如这篇:
说的还是很详细的。
其中最关键且最华丽丽的句子就是这句:

%.d: %.c

        @set -e; rm -f $@; /

        $(CC) -MM  $< > $@.$$$$; /

        sed 's,/($*/)/.o[ :]*,/1.o $@ : ,g' < $@.$$$$ > $@; /

        rm -f $@.$$$$


我把这些文章介绍的makefile拿到工程中实验了一下,确实好使, 但是产生了三个疑问:
1) 编译.o文件时, -g -Wall等编译选项是怎么传递给gcc的, 因为从makefile中没有看到明显的调用
2) 这些makefile都是针对源码只有 C文件的。 如果源码同时含有cpp文件该怎么办?
3) 上面几句晦涩的话到底是什么意思???
 

带着这几个问题, 做了一个很简单的测试程序:

一)  测试程序
这个测试程序几个文件如下:(名字不用在意, 是之前测试osip留下的余孽)
test_osip.c:
#include <stdio.h>
#include "test_osip.h"

int main(){
test1();
test2();
test3();
test4();
}
test_osip1.c:
#include <stdio.h>

int test1(){
printf("test1\n");
}
test_osip2.c:
#include <stdio.h>

int test2(){
printf("test2\n");
}
test_osip3.cpp:
#include <stdio.h>
extern "C"{
int test3(){
printf("test3\n");
}
}
test_osip4.cpp:
#include <stdio.h>
extern "C"{
int test4(){
printf("test4\n");
}
}
test_osip.h
typedef struct{
int a;
int b;
}T_TEST;

Makefile:
CC=gcc
CFLAGS= -g -Wall
CPPFLAGS= -g
LDFLAGS= -lstdc++ -lpthread
SOURCES_CPP+= \
test_osip3.cpp \
test_osip4.cpp
SOURCES_C+= \
test_osip1.c \
test_osip2.c \
test_osip.c
OBJS_CPP:=$(patsubst %.cpp,%.o, $(SOURCES_CPP))
OBJS_C:= $(patsubst %.c,%.o, $(SOURCES_C))
OBJS:= $(OBJS_CPP) $(OBJS_C)

all: main

#下面这边都是获取依赖关系 ,属于约定俗成的写法
%.d: %.c
rm -f $@;
$(CC) -MM $< > $@.1111;  
sed 's,/($*/)/.o[ :]*,/1.o $@ : ,g' < $@.1111 > $@;  
@echo "$@ $< 77777"
%.d: %.cpp
rm -f $@;
$(CC) -MM $(CFLAGS) $< > $@.1111
sed 's,/($*/)/.o[ :]*,/1.o $@ : ,g' < $@.1111 > $@
@echo "$@ $< 66666"
sinclude $(SOURCES_CPP:.cpp=.d)
sinclude $(SOURCES_C:.c=.d)

main:$(OBJS)
@echo $(SOURCES_CPP:.cpp=.d)
$(CC) $(OBJS) $(LDFLAGS) -o main 

clean:
rm -f *.o main *.d



二)  执行make之后的结果:
终端输出:
hl@hl-VirtualBox:~/linphone/test_osip_linux$ make 
rm -f test_osip.d;
gcc -MM test_osip.c > test_osip.d.1111;  
sed 's,/(test_osip/)/.o[ :]*,/1.o test_osip.d : ,g' < test_osip.d.1111 > test_osip.d;  
test_osip.d test_osip.c 77777
rm -f test_osip2.d;
gcc -MM test_osip2.c > test_osip2.d.1111;  
sed 's,/(test_osip2/)/.o[ :]*,/1.o test_osip2.d : ,g' < test_osip2.d.1111 > test_osip2.d;  
test_osip2.d test_osip2.c 77777
rm -f test_osip1.d;
gcc -MM test_osip1.c > test_osip1.d.1111;  
sed 's,/(test_osip1/)/.o[ :]*,/1.o test_osip1.d : ,g' < test_osip1.d.1111 > test_osip1.d;  
test_osip1.d test_osip1.c 77777
rm -f test_osip4.d;
gcc -MM -g -Wall test_osip4.cpp > test_osip4.d.1111
sed 's,/(test_osip4/)/.o[ :]*,/1.o test_osip4.d : ,g' < test_osip4.d.1111 > test_osip4.d
test_osip4.d test_osip4.cpp 66666
rm -f test_osip3.d;
gcc -MM -g -Wall test_osip3.cpp > test_osip3.d.1111
sed 's,/(test_osip3/)/.o[ :]*,/1.o test_osip3.d : ,g' < test_osip3.d.1111 > test_osip3.d
test_osip3.d test_osip3.cpp 66666
g++  -g  -c -o test_osip3.o test_osip3.cpp
g++  -g  -c -o test_osip4.o test_osip4.cpp
gcc -g -Wall -g  -c -o test_osip1.o test_osip1.c
test_osip1.c: In function 'test1':
test_osip1.c:5:1: warning: control reaches end of non-void function [-Wreturn-type]
gcc -g -Wall -g  -c -o test_osip2.o test_osip2.c
test_osip2.c: In function 'test2':
test_osip2.c:5:1: warning: control reaches end of non-void function [-Wreturn-type]
gcc -g -Wall -g  -c -o test_osip.o test_osip.c
test_osip.c: In function 'main':
test_osip.c:5:2: warning: implicit declaration of function 'test1' [-Wimplicit-function-declaration]
test_osip.c:6:2: warning: implicit declaration of function 'test2' [-Wimplicit-function-declaration]
test_osip.c:7:2: warning: implicit declaration of function 'test3' [-Wimplicit-function-declaration]
test_osip.c:8:2: warning: implicit declaration of function 'test4' [-Wimplicit-function-declaration]
test_osip.c:9:1: warning: control reaches end of non-void function [-Wreturn-type]
test_osip3.d test_osip4.d
gcc test_osip3.o test_osip4.o test_osip1.o test_osip2.o test_osip.o -lstdc++ -lpthread -o main 


查看所有.d 文件和 .1111文件: 
hl@hl-VirtualBox:~/linphone/test_osip_linux$ cat *.d
test_osip.o: test_osip.c test_osip.h
test_osip1.o: test_osip1.c
test_osip2.o: test_osip2.c
test_osip3.o: test_osip3.cpp
test_osip4.o: test_osip4.cpp
hl@hl-VirtualBox:~/linphone/test_osip_linux$ cat *.1111
test_osip.o: test_osip.c test_osip.h
test_osip1.o: test_osip1.c
test_osip2.o: test_osip2.c
test_osip3.o: test_osip3.cpp
test_osip4.o: test_osip4.cpp
hl@hl-VirtualBox:~/linphone/test_osip_linux$ 

三)  解释说明
为什么会是这样?
我们还要从make的工作步骤说起:
这里推荐一下 《跟我一起写 Makefile》这篇博客,博客地址:
说的非常好

make的工作步骤 
1、读入所有的Makefile。
2、读入被include的其它Makefile。
3、初始化文件中的变量。
4、推导隐晦规则,并分析所有规则。
5、为所有的目标文件创建依赖关系链。
6、根据依赖关系,决定哪些目标要重新生成。
7、执行生成命令。

对于我们这个makefile,其处理步骤是:
1) make看见 sinclude $(SOURCES_CPP:.cpp=.d)和  sinclude $(SOURCES_C:.c=.d) 这两句之后, 就会include这些.d 文件,但是很明显, 这些文件还没有生成出来,于是make就会去找这些文件的生成规则。
$(SOURCES_CPP:.cpp=.d) 这句的的意思是把 SOURCES_CPP这个变量里面 所有的.cpp替换成.d,也就是
test_osip3.d test_osip4.d
2) make 发现有两个规则 %d : %c  以及 %d :%cpp 可以生成这些.d 文件。 于是他就去执行这些规则中的语句, 所以我们可以看见输出:
rm -f test_osip.d;
gcc -MM test_osip.c > test_osip.d.1111;  
sed 's,/(test_osip/)/.o[ :]*,/1.o test_osip.d : ,g' < test_osip.d.1111 > test_osip.d;  
test_osip.d test_osip.c 77777
解释下这几句话的意思:  首先删除.d文件, 接着把 test_osip.c 的依赖关系写入到  test_osip.d.1111文件中,其次用sed 规整  test_osip.d.1111 文件,并把规整的结果写入到  test_osip.d 文件中,最后是echo的结果,可以很清楚的看到:$@是待生成文件    $<是“原料”文件   ,这两个自动化变量在makefile中经常用到
3).d文件生成完毕之后,就会被include进来, 这样一来,相当于makefile就多了几行这样的语句
test_osip.o: test_osip.c test_osip.h
test_osip1.o: test_osip1.c
test_osip2.o: test_osip2.c
test_osip3.o: test_osip3.cpp
test_osip4.o: test_osip4.cpp
如此一来, .o文件的依赖关系也出来了。
4) 接着make就开始去寻找依赖关系了,  首先他发现生成all 需要main ;生成main 需要OBJS , 而OBJS就是那些个.o文件, 它就会去生成.o
不过紧接着问题就来了, 我们看到这些.o文件只有依赖文件而没有生成方式,make是怎么生成他们的?
这就是make的隐式规则了:  他会去找  CC  CFLAGS   CPPFLAGS 这些变量。所以我们看到
g++   -g  -c -o test_osip3.o test_osip3.cpp
g++   -g  -c -o test_osip4.o test_osip4.cpp
gcc  -g -Wall  -g  -c -o test_osip1.o test_osip1.c
可见对于cpp代码 ,他会用CPPFLAGS ,而对于c代码,会用 CPPFLAGS and CFLAGS  
5) 最后链接生成可执行文件

四  同时支持C和C++的通用makefile文件
既然我们已经了解了makefile的脾气, 也就可以写出通用的makefile了:

CC=gcc
CFLAGS= -g -Wall
CPPFLAGS= -g
LDFLAGS= -lstdc++ -lpthread
SOURCES_CPP+= \
test_osip3.cpp \
test_osip4.cpp
SOURCES_C+= \
test_osip1.c \
test_osip2.c \
test_osip.c
OBJS_CPP:=$(patsubst %.cpp,%.o, $(SOURCES_CPP))
OBJS_C:= $(patsubst %.c,%.o, $(SOURCES_C))
OBJS:= $(OBJS_CPP) $(OBJS_C)

all: main

#下面这边都是获取依赖关系 ,属于约定俗成的写法
#注意rm 那一行最后不要加\  . 不然的话这一句出错会导致后面的不执行
%.d: %.c
@rm -f $@;
@$(CC) -MM $< > $@.1111; \
sed 's,/($*/)/.o[ :]*,/1.o $@ : ,g' < $@.1111 > $@;  \
rm -f $@.1111

%.d: %.cpp
@rm -f $@;
@$(CC) -MM $< > $@.1111; \
sed 's,/($*/)/.o[ :]*,/1.o $@ : ,g' < $@.1111 > $@;  \
rm -f $@.1111
sinclude $(SOURCES_CPP:.cpp=.d)
sinclude $(SOURCES_C:.c=.d)

main:$(OBJS)
$(CC) $(OBJS) $(LDFLAGS) -o main 

clean:
rm -f *.o main *.d

加载中
返回顶部
顶部