复杂多目录的Makefile模板及示例

大约一年多以前,还在移植u-boot,当时参考了u-boot的Makefile,做了一个多目录的Makefile模板。如今,一年过去了,本篇文章还是折腾Makefile模板,本人的水平由此可见一斑。当时的模板没有考虑很多东西,比如将源代码文件及目标文件分离。此次再写一个相对复杂的Makefile模板,自此之后,估计不想再折腾了。 

本文的Makefile跟以往的模板一样,都是参考u-boot的做法(u-boot项目无论代码还是文件组织,都有很多值得学习的地方)。由下列文件组成:

1、配置文件config.mk,主要是编译器、编译选项、头文件路径、目标文件、源文件路径,等等;
2、依赖规则rules.mk,主要是产生depend文件,有了depend文件,当修改了头文件后,包含该头文件的源文件都会被重新编译。
3、主Makefile,或称为总Makefile,此文件调用其它目录的Makefile以及上述文件,以产生库及可执行文件。
4、子目录Makefile,各模块的Makefile,主要是生成静态库。

除子目录Makefile在各个模块目录中外,其它文件都在项目的当前目录。

Makefile模板应用场合及特点缺点说明如下:

1、头文件统一在include目录(或其它自定义的目录),子模块分布于各子目录,该目录的Makefile将模块编译成静态库,主代码(包括main函数及其它的代码)文件在独立目录,该目录的Makefile为总Makefile(或称主Makefile)。
2、修改头文件后,所有依赖该文件者均重新编译。
3、可指定编译后的文件所在目录,如不指定,则在原目录下生成目标文件(.o、.out文件)。指定方式如下:
1)、命令行:$ make O=../build
2)、$ export BUILD_DIR=../build ; make
3)、直接在主Makefile指定目录
4、自动识别编译C、C++文件。

下面分别解释一下各个文件

1、配置文件config.mk

模板如下:

#
# (C) Copyleft 2011
# Late Lee from http://www.latelee.org

# A simple file to specify compier and macros and flags
# and ways to compile .c & .cpp
#

# cross compile...
CROSS_COMPILE = 

CC =  $(CROSS_COMPILE)gcc
CXX =  $(CROSS_COMPILE)g++
AR =  $(CROSS_COMPILE)ar

ARFLAGS = cr
RM = -rm -rf
MAKE = make

CFLAGS =  #-Wall
debug = y

ifeq ( $(debug), y)
CFLAGS += -g
else
CFLAGS += -O2 -s
endif

DEFS = 

CFLAGS +=  $(DEFS)

LDFLAGS =  $(LIBS)

# include path here
INCDIRS =  $(SRCTREE)/./include/

CFLAGS += -I $(INCDIRS)

###############################################################################

ifneq ( $(OBJTREE), $(SRCTREE))
ifeq ( $(CURDIR), $(SRCTREE))
dir :=
else
dir := $(subst  $(SRCTREE)/,, $(CURDIR))
endif

obj := $( if  $(dir), $(OBJTREE)/ $(dir)/, $(OBJTREE)/)
src := $( if  $(dir), $(SRCTREE)/ $(dir)/, $(SRCTREE)/)

$(shell mkdir -p  $(obj))
else
obj :=
src :=
endif

suffix = $(notdir  $(CURDIR))
export suffix

# test of shell script
MKCONFIG    :=  $(SRCTREE)/mkconfig
export MKCONFIG

# export to other Makefile
export CC
export CFLAGS
export INCDIRS
export AR
export ARFLAGS
export RM

###############################################################################

# make all .c or .cpp
$(obj)%.o: %.c
         @echo  "Compling: " $(addsuffix .c, $(basename $(notdir  $@)))
        @ $(CC)  $(CFLAGS) -c  $< -o  $@

$(obj)%.o: %.cpp
         @echo  "Compling: " $(addsuffix .cpp, $(basename $(notdir  $@)))
        @ $(CXX)  $(CFLAGS) -c  $< -o  $@

主要分3部分:

第一部分指定编译器及编译选项,可以选择交叉编译器,头文件路径由INCDIRS 宏指定,这里有的编译选项CFLAGS处理得不太好,比如debug版本及release版本是通过“-g”和“-O2 -s”来区别的,但这里是直接指定,并不能在make时指定。
第二部分指定了源代码目录及目标文件目录,这些目录由主Makefile给出,或者在make时给出。
第三部分是编译c或c++源代码的规则。

实际使用中,此文件需要根据实际情况修改编译器及编译选项。

2、依赖规则rules.mk

内容如下:

#
# (C) Copyleft 2011
# Late Lee from http://www.latelee.org
#
# A simple way to generate depend file(.depend) for .c & .cpp,
# so you change the head file, it will recompile the 
# file(s) which include the head file.
#

_depend:  $(obj).depend

$(obj).depend:  $(SRCTREE)/config.mk  $(SRC_C)  $(SRC_CPP)
         @rm -f  $@
         @for f  in  $(SRC_C)do \
                g= `echo $$f | sed -e 's/\(.*\)\.\w/\1.o/'`; \
                 $(CC) -MM  $(CFLAGS) -E -MQ  $(obj)$ $g $ $f >>  $@ ; \
         done
         @for f  in  $(SRC_CPP)do \
                g= `echo $$f | sed -e 's/\(.*\)\...\w/\1.o/'`; \
                 $(CC) -MM  $(CFLAGS) -E -MQ  $(obj)$ $g $ $f >>  $@ ; \
         done

该文件主要是产生.depend依赖文件,依赖文件的作用在前面已经说过,此处不再提及。实际使用中,经文件无需修改。

3、主Makefile

内容如下:

#
# (C) Copyleft 2011
# Late Lee from http://www.latelee.org

# A simple Makefile for multi-directory
# Most idea comes from U-boot project
#

# object file directory, eg
# $ make O=../build
# $ export BUILD_DIR=../build ; make
# or modify BUILD_DIR here
ifdef O
ifeq ( "$(origin O)""command line")
BUILD_DIR :=  $(O)
endif
endif

ifneq ( $(BUILD_DIR),)
saved-output :=  $(BUILD_DIR)

# Attempt to create a output directory.
$(shell [ -d  ${BUILD_DIR} ] || mkdir -p  ${BUILD_DIR})

# Verify if it was successful.
BUILD_DIR := $(shell cd  $(BUILD_DIR) && /bin/pwd)
$( if  $(BUILD_DIR),,$(error output directory  "$(saved-output)" does not exist))
endif  # ifneq ($(BUILD_DIR),)

OBJTREE     := $( if  $(BUILD_DIR), $(BUILD_DIR), $(CURDIR))
SRCTREE     :=  $(CURDIR)
TOPDIR      :=  $(SRCTREE)
LNDIR       :=  $(OBJTREE)
export        TOPDIR SRCTREE OBJTREE
export        TOPDIR SRCTREE OBJTREE

include  $(TOPDIR)/config.mk

###############################################################################
# libs
### your libs here
LIBS := 

LIBS := $(addprefix  $(obj), $(LIBS))

LDFLAGS =  $(LIBS)

# source file(s), including c file(s) cpp file(s)
# you can also use $(wildcard *.c), etc.
### main source here
MAIN_DIR =  #main/
SRC_C   := 
SRC_CPP := $(wildcard  $(MAIN_DIR)*.cpp)

$( if  $(MAIN_DIR), $(shell mkdir -p $(addprefix  $(obj), $(MAIN_DIR))),)

# object file(s)
OBJ_C   := $(addprefix  $(obj),$(patsubst %.c,%.o, $(SRC_C)))
OBJ_CPP := $(addprefix  $(obj),$(patsubst %.cpp,%.o, $(SRC_CPP)))

# executable file here
target =  $(obj)a.out

###############################################################################

all: config  $(target)

config:
         @echo  "Generating... hello.h"
        @ $(MKCONFIG)

$(target)$(LIBS) exe

exe:  $(OBJ_CPP) 
         @echo  "Generating executable file..." $(notdir  $(target))
        @ $(CXX)  $(CFLAGS)  $(OBJ_CPP) -o  $(target)  $(LIBS)

$(LIBS):
         $(MAKE) -C $(dir $(subst  $(obj),, $@))

clean:
         @echo  "cleaning..."
        @ $(RM)  $(target)
        @ $(RM) hello.h
         @find  $(OBJTREE) -type f \
                \( -name  'core' -o -name  '*.bak' -o -name  '*~' \
                -o -name  '*.o'  -o -name  '*.a' -o -name  '*.exe' -o -name  '.depend' \) -print \
                | xargs rm -f
distclean:
         @echo  "distcleaning..."
        @ $(RM)  $(target)
        @ $(RM) hello.h
         @find  $(OBJTREE) -type f \
                \( -name  'core' -o -name  '*.bak' -o -name  '*~' \
                -o -name  '*.o'  -o -name  '*.a' -o -name  '*.exe' -o -name  '.depend' \) -print \
                | xargs rm -f
        @ $(RM)  $(obj)

.PHONY: all clean distclean  $(LIBS) exe

该文件也可分为三部分。第一部分指定源代码目录及目标文件目录,第二部分指定了工程所需的静态库以及主代码所在目录,第三部分编译规则,包括make all,make clean,等等命令的规则。实际使用中,需要根据实际情况添加子模块静态库名称、主代码名称。

4、子目录Makefile

内容如下:

#
# (C) Copyleft 2011
# Late Lee from http://www.latelee.org

# A simple Makefile for lib(libxxx.a)
# You just need to change library name and source file(s)
#

include  $(TOPDIR)/config.mk

# here you can set your own flags
CFLAGS +=  # -D_A_

# static library name
LIB =   # $(obj)libfoo.a

# source file(s), including c file(s) cpp file(s)
# you can also use $(wildcard *.c), etc.
SRC_C   :=  # foo.c
SRC_CPP := 

# object file(s)
OBJ_C   := $(addprefix  $(obj),$(patsubst %.c,%.o, $(SRC_C)))
OBJ_CPP := $(addprefix  $(obj),$(patsubst %.cpp,%.o, $(SRC_CPP)))

all:  $(obj).depend  $(LIB)

$(LIB)$(OBJ_C)  $(OBJ_CPP)
         @echo  "Generating static library: " $(notdir  $@)
        @ $(AR)  $(ARFLAGS)  $@  $^

clean:
         @echo  "Cleaning..."
        @ $(RM)  $(OBJ_C)  $(OBJ_CPP)  $(LIB) *.bak *~ *.d *.o  $(obj).depend

.PHONY: all clean

#########################################################################

include  $(TOPDIR)/rules.mk

sinclude  $(obj).depend

#########################################################################

子目录的Makefile比较简单,因为编译配置及depend文件都在其它文件中,这里直接包含进来即可。
实际使用中,需要修改的地方有:
1、配置文件config.mk,指定头文件路径,编译选项,额外库,等等。
2、主Makefile,指定各子模块的库名称及生成可执行文件名称
3、子Makefile,指定静态库名称即可,每一个模块目录都需要一个Makefile。

文中的Makefile模板,远远不如IDE方便,因此,用与不用,则仁者见仁了。

本文语法着色由迟思堂工作室提供高亮工具手工完成。

2014.04.30补充:

本文所涉及的Makefile模块及示例仓库请参考下面地址:

https://github.com/latelee/Makefile_templet

如果有git客户端,请直接clone出工程:

https://github.com/latelee/Makefile_templet.git

 

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
好的,下面是一个多文件的 makefile 示例: ```makefile # 定义编译器 CC = g++ # 定义编译参数 CFLAGS = -c -Wall # 定义链接参数 LFLAGS = -Wall # 定义源文件目录 SRC_DIR = src # 定义头文件目录 INC_DIR = include # 定义目标文件目录 OBJ_DIR = obj # 定义可执行文件目录 BIN_DIR = bin # 定义源文件列表 SRCS = $(wildcard $(SRC_DIR)/*.cpp) # 定义头文件列表 INCS = $(wildcard $(INC_DIR)/*.h) # 定义目标文件列表 OBJS = $(patsubst $(SRC_DIR)/%.cpp,$(OBJ_DIR)/%.o,$(SRCS)) # 定义可执行文件名 TARGET = $(BIN_DIR)/app # 编译规则 $(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp $(INCS) $(CC) $(CFLAGS) -I$(INC_DIR) $< -o $@ # 链接规则 $(TARGET): $(OBJS) $(CC) $(LFLAGS) $(OBJS) -o $@ # 清理规则 clean: rm -rf $(OBJ_DIR)/*.o $(TARGET) ``` 在这个示例中,我们假设有一个包含源文件和头文件的 `src` 和 `include` 目录,以及一个存放目标文件的 `obj` 目录和存放可执行文件的 `bin` 目录。我们希望将所有的源文件编译成目标文件,再将所有的目标文件链接成可执行文件。 makefile 中的变量 `CC`、`CFLAGS` 和 `LFLAGS` 分别定义了编译器、编译参数和链接参数,我们可以根据需要进行修改。 makefile 中的变量 `SRC_DIR`、`INC_DIR`、`OBJ_DIR` 和 `BIN_DIR` 分别定义了源文件目录、头文件目录、目标文件目录和可执行文件目录,我们也可以根据需要进行修改。 makefile 中的变量 `SRCS`、`INCS` 和 `OBJS` 分别定义了源文件列表、头文件列表和目标文件列表,其中 `$(wildcard)` 函数可以获取指定目录下的所有文件,`$(patsubst)` 函数可以将一个文件名模式映射到另一个文件名模式,具体可以参考 GNU make 的文档。 makefile 中的规则 `$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp $(INCS)` 定义了编译规则,表示将源文件编译成目标文件,其中 `-I$(INC_DIR)` 表示将头文件目录添加到编译器的搜索路径中。 makefile 中的规则 `$(TARGET): $(OBJS)` 定义了链接规则,表示将所有的目标文件链接成可执行文件。 makefile 中的规则 `clean` 定义了清理规则,表示删除所有的目标文件和可执行文件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值