2008年6月4日星期三

如何编写将源代码和编译生成的目标文件.o分离的makefile

概述

本文介绍了一种符合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)
# 请注意上面两行前面是TAB而不是空格
endif

从注释可以了解这个makefile的构造。

  1. 这个makefile其实由两个部分组成,在源文件目录执行第二部分,在目标文件目录执行第一部分
  2. 我们使用了一个宏MAKING_STATUS来说明我们是在源目录还是目标目录
  3. 因为源文件本身也有子目录,所以到目标文件我们希望保持相同的目录关系(否则同名将会造成麻烦),因此需要使用"+@[ -d..."这一段来确保在目标目录下建立相同结构的子目录
  4. make使用的参数-C表示进行make是进入指定的目录工作
  5. make使用的参数-f表示使用制定的makefile

未解决的问题

这个例子不能嵌套,因为makefile指明的宏会继承,也就是如果在第二部分去执行其他同样结构makefile,也会把定义的 SRCDIR、MAKING_STATUS宏传入,造成其他makefile会判断错误 - 当然,只要改写一下这个makefile就可以嵌套了

不支持用一句命令同时编译多个target,比如make debug release

参考文档

前辈的经验:http://make.paulandlesley.org/multi-arch.html

没有评论:

发表评论