一个Makefile的进化(二)
代码分离与通用Makefile
这一篇接着对Makefile进行修改
目的:
- 将代码与生成文件进行分离
- 使得Makefile具有更广泛的通用性
- 使Makefile看起来更牛逼(哈哈哈)
涉及知识:
- makefile的include
- 修改makefile的隐含规则
- makefile中的函数使用
涉及的测试文件:
string@asus:~/Projects/makefile$ tree
.
├── include
│ └── test.h
├── Makefile
└── src
├── Makefile
├── test.c
└── test_fun1.c
主Makefile
CPPFLAGS:=-Iinclude
OUTDIR:=out
SRCDIR:=src
PROJECT:= $(notdir $(SRCDIR))
include $(SRCDIR)/Makefile
OBJS=$(addprefix $(OUTDIR)/,$(OBJ))
$(OUTDIR)/%.o:$(SRCDIR)/%.c
$(CC) -c $(CPPFLAGS) $< -o $@
all:$(OUTDIR) .build.stamp
$(OUTDIR):
mkdir $@
.build.stamp: $(OBJS)
gcc $^ -o $(OUTDIR)/$(PROJECT)
touch $@
clean:
$(RM) .build.stamp
rm -rf $(OUTDIR)
.PHONY : clean
子目录的Makefile
PROJECT=test
OBJ+= test.o test_fun1.o
代码解释:
完成代码与代码的分离需要修改隐含的规则,因为默认的规则会到目标.o对应的目录去寻找.c,为了将其进行分离重新进行规则设计,对应代码中的下面两行
$(OUTDIR)/%.o:$(SRCDIR)/%.c
$(CC) -c $(CPPFLAGS) $< -o $@
单独摘出来应该很容易理解了,这两行的目的就是当有需要OUTDIR目录下的.o的时候就去SRCDIR的目录寻找对应的.c
下面两行代码需要解释一下
OBJS=$(addprefix $(OUTDIR)/,$(OBJ))
PROJECT:= $(notdir $(SRCDIR))
第一行使用了Makefile的一个函数,addprefix,这个函数是增加前缀函数,作用是在$(OBJ)这个变量的每一个字符串前面加上$(OUTDIR),
示例中OUTDIR为:OBJ+= test.o test_fun1.o,
这个函数调用后:OBJS=out/test.o out/test_fun1.o
第二行使用了Makefile的取文件名函数,虽然在这里不是文件名,但是我们利用Makefile的特性获得一个项目名称该行执行后PROJECT=src,虽然在这里看起来没有作用,这样设计是有原因的,后面会讲到
以上Makefile最终会展开成下面的代码
out/%.o:src/%.c
$(CC) -c -Iinclude $< -o $@
all:out .build.stamp
out:
mkdir $@
.build.stamp: out/test.o out/test_fun1.o
gcc $^ -o out/src
touch $@
clean:
$(RM) .build.stamp
rm -rf out
.PHONY : clean
展开以后的Makefile是不是很容易理解了,纯手工展开,实在是太麻烦了,这部分工作还是交给Makefile去做吧
但是展开后的代码只能完成我们本文的第一个目标:代码分离
为什么Makefile要写成那个没有展开的Makefile,写着也有点费力,理解也费力,但是这么写终归是有原因的,下面我们见证以下如何完成本文的第二个目标,第二个目标完成后你就会觉得很多Makefile的写法瞬间理解了
注意一下子目录的Makefile是不是写的很简单,但是该Makefile不能单独使用,需要使用主目录包含后才能起作用,如果我们拥有很多个类似的项目此时就不需要每一个项目写一个麻烦的Makefile,只需要仿照子目录下的那个Makefile写下简单的几行就可以了,下面我们做一下测试
- 首先复制一份src的代码用于测试,复制到serial
- 修改src的复制代码的Makefile,在此只修改PROJECT
- 执行make SRCDIR=serial查看测试结果
serial修改后的Makefile如下
PROJECT=serial
OBJ+= test.o test_fun1.o
执行结果如下
string@asus:~/Projects/makefile$ make SRCDIR=serial
mkdir out
cc -c -Iinclude serial/test.c -o out/test.o
cc -c -Iinclude serial/test_fun1.c -o out/test_fun1.o
gcc out/test.o out/test_fun1.o -o out/serial
touch .build.stamp
请注意一下编译的文件是serial/test.c serial/test_fun1.c这说明了我们编译的是serial目录下的文件生成的目标为serial,在此说明一下:serial下的Makefile中的PROJECT可以不赋值,会得到同样的结果,这时由于在主Makefile下有语句
PROJECT:= (notdir(SRCDIR))
这一句会使用目录SRCDIR目录名作为项目名称赋值给PROJECT
至此本文已经解决完两个目标,至于第三个目标(呵呵),解决完前两个问题第三个还是问题吗 哈哈哈
这个Makefile只作为简单理解Makefile中的一些机制,在linux,openwrt,buildroot,busybox等开源软件中都有大量使用这些规则,如果想更深入的理解Makefile还需要在实际工作中去学习