本文介绍了一种符合gmake要求的高级makefile编写方法。
背景介绍
必须注意到,makefile对于.o目标有一个特殊的依赖,即依赖于相同目录下 的.c或.cpp源文件。这样,如果简单的书写makefile,比如让工程目标依赖于若干的.o,那么这些.o在编译时将输出到和源代码相同的目录下, 即不方便管理,同时也会有有一个问题,那就是不能同时编译多种配置,比如编译debug以后,必须clean才能编译release。
当然,我们可以通过逐个.o显示的书写依赖规则,但是这一般需要使用一些工具来完成(否则工程浩大)。如果想只是简单的书写一个makefile就来达到目标,则需要使用一些特别的手段。
实现方案
在介绍这个方案之前,需要先说明几点。
因为原始的make功能较弱,所以这里介绍的makefile需要用gmake支持
我们当然希望能够写一个放之四海皆准的makefile,然而,由于make实在过于古老以至于我们无法简单的做到这一点。要么选择可移植的makefile生成工具,要么选用可移植的make软件,我们选用了流行的gmake。
这并不是唯一的实现方法
还有其他的方法,比如将源代码复制到另外一个目录;采用工具生成makefile;我们采用了使用VPATH的方法,但是采用VPATH也有几种实现方法,这里只是介绍了其中的一种。
好,现在让我们看看如何来做。
什么是VPATH
参考本文http://make.paulandlesley.org/vpath.html。
简单的说,VPATH可以指明源代码所在的搜索路径,让.o不一定依赖于相同目录下的.c/.cpp源文件,而可以依赖VPATH指明的路径集合中的某一个。我们可以认为,make默认VPATH为.,即当前路径。
一个最简单的实现
我们假设有一个源文件夹src,下有一个a.c,其功能是输出"hello world!"我们希望把它编译成a.out,中间文件a.o放到中文文件夹中,并且能根据配置debug/release分别放到obj/debug、obj/release下。
我们可以这么编写makefile(请注意所有规则后续的命令前面不是空格而是跳格 - TAB):
# Makefile of a.out
ifeq (IN_INT_DIR,$(MAKING_STATUS))
# Already enter target directory, do make
# Set source directoy
VPATH=$(SRCDIR)
# All configurations
debug : a.out
release : a.out
# Specifiy all obejcts
OBJS = a.o
# Rule a.out
a.out : $(OBJS)
gcc lt; -o $@
# 请注意上面两行前面是TAB而不是空格
# Implicity rules
# make the file from $(SRCDIR)
# If the obj's directory is not created, create it
# -d means check exists
# || means when not exists (got false of the -d) do ...
# $(dir $@) means fetch the path of the output target
.c.o :
+@[ -d $(dir $@) ] || mkdir -p $(dir $@)
gcc -o $@ -c lt;
# 请注意上面两行前面是TAB而不是空格
else
# In source directory now, enter target directory & make by the first part of makefile
# Specify intermeidate directory ../obj/$(CONFIGURATION)
INTDIR = $(CURDIR)/../obj/$(MAKECMDGOALS)
# 1. Select target directory
# 2. Using this makefile again (select the part by input MACRO: MAKING_STATUS)
# 3. Specify the SRCDIR (current directory)
# 4. Specify the making status (already in target dir)
# 5. Send the makeing goals to following operation
MAKETARGET = $(MAKE) --no-print-directory \
-C $(INTDIR) \
-f $(CURDIR)/makefile \
SRCDIR=$(CURDIR) \
INTDIR=$(INTDIR) \
MAKING_STATUS=IN_INT_DIR \
$(MAKECMDGOALS)
# Configrations
debug : target
release : target
# Common target
target :
@mkdir -p $(INTDIR)
@$(MAKETARGET)
# make the file from $(SRCDIR)
# If the obj's directory is not created, create it
# -d means check exists
# || means when not exists (got false of the -d) do ...
# $(dir $@) means fetch the path of the output target
.c.o :
+@[ -d $(dir $@) ] || mkdir -p $(dir $@)
gcc -o $@ -c lt;
# 请注意上面两行前面是TAB而不是空格
else
# In source directory now, enter target directory & make by the first part of makefile
# Specify intermeidate directory ../obj/$(CONFIGURATION)
INTDIR = $(CURDIR)/../obj/$(MAKECMDGOALS)
# 1. Select target directory
# 2. Using this makefile again (select the part by input MACRO: MAKING_STATUS)
# 3. Specify the SRCDIR (current directory)
# 4. Specify the making status (already in target dir)
# 5. Send the makeing goals to following operation
MAKETARGET = $(MAKE) --no-print-directory \
-C $(INTDIR) \
-f $(CURDIR)/makefile \
SRCDIR=$(CURDIR) \
INTDIR=$(INTDIR) \
MAKING_STATUS=IN_INT_DIR \
$(MAKECMDGOALS)
# Configrations
debug : target
release : target
# Common target
target :
@mkdir -p $(INTDIR)
@$(MAKETARGET)
# 请注意上面两行前面是TAB而不是空格
endif
从注释可以了解这个makefile的构造。
endif
从注释可以了解这个makefile的构造。
- 这个makefile其实由两个部分组成,在源文件目录执行第二部分,在目标文件目录执行第一部分
- 我们使用了一个宏MAKING_STATUS来说明我们是在源目录还是目标目录
- 因为源文件本身也有子目录,所以到目标文件我们希望保持相同的目录关系(否则同名将会造成麻烦),因此需要使用"+@[ -d..."这一段来确保在目标目录下建立相同结构的子目录
- make使用的参数-C表示进行make是进入指定的目录工作
- make使用的参数-f表示使用制定的makefile
未解决的问题
这个例子不能嵌套,因为makefile指明的宏会继承,也就是如果在第二部分去执行其他同样结构makefile,也会把定义的 SRCDIR、MAKING_STATUS宏传入,造成其他makefile会判断错误 - 当然,只要改写一下这个makefile就可以嵌套了
不支持用一句命令同时编译多个target,比如make debug release
没有评论:
发表评论