您的位置:首页 > 其它

自己调试通过的一个通用makefile模板

2013-12-25 08:38 369 查看
这个是从本人的QQ空间转过来的

模板文件夹的下载地址:

http://download.csdn.net/detail/longyue0917/6725889

这个模板是之前公司的一个牛人写的,我这个连门都没入的菜鸟因为没有项目需求,所以一直没有花时间去研究。可惜好景不长,酱油没打几天,就需要我来单挑linux了,网上找了很多模板,都不尽如人意,没办法,只好硬着头皮来啃这个模板了,由于水平有限,有错漏的地方欢迎指出交流

======================================

补记:

MAKE := make -r -R -s

其中-rR参数是使用隐含规则(以免make自做聪明),

-s参数是禁止命令的回显(比如gcc命令输出的 gcc -c -o .... 等字样)

rules.mk中,每条执行前都有

$(make_debug) 字样,这个是可以在common.mk中选择是否显示执行的make命令的,比如:


make[1]:正在离开目录 `/data/mktest/aptt3/lib_bar'
make[1]: 正在进入目录 `/data/mktest/aptt3/lib_foo'

======================================

文件结构:

Makefile

| -- makefiles

| | -- rules.mk / common.mk / arm.mk / i386.mk

| -- third_party

| -- includes

| -- app_test

| -- lib_bar / lib_foo

由于这个make模板的功能很强大,我们就跟着make的运行过程分析,所以文件会交叉:

首先是顶层makefile:

#声明路径和包含

export TOP_DIR := $(realpath .)
include $(TOP_DIR)/makefiles/common.mk

然后一般我们执行的都是make all,所以直接看all依赖:

# 依次运行make_in_list中的make
all:@$(call make_in_list, $(make_list), all)

先看 make_list变量:

make_list := lib_bar

make_list += lib_foo

make_list += app_test

注意主文件锁在的命令要放最后,因为主文件依赖于前面两个目录所产生的库

然后看 make_in_list函数,在common.mk文件中:

# make -r -R -s -C lib_mcu all -j 1

#-C -- 指定路径

#-r/R -- 禁止隐含规则

#-s -- 不显示命令输出

#-j -- 同时运行命令的个数

make_in_list = list="$(1)"; for p in $$list; do $(MAKE) -C $$p $(2) -j $(jobnums); done

展开来就是:

for p in make_list; do make -c p all -j 1; done

不难理解:分别执行make_list路径的make

我们以lib_foo命令为例说明:

由于内容少,我就直接贴完了

#这里好理解,指定命令,包含common.mk


TOP_DIR = $(realpath ../)
include $(TOP_DIR)/makefiles/common.mk

ifeq (arm, $(ARCH))
else ifeq (i386, $(ARCH))
endif

#这里指定本目录索要关联的Lib,注意这里指的是lib_xxx目录,而不是第三方lib,后面会从代码中说明
libs =

#指定本目录生成的lib名,一般和命令名一直
lib_name = foo

#自定义的cflag参数
defines += -DDEBUG

#debug_ar_lib是依赖,实现在下面的rules.mk中
all:debug_ar_lib

include $(TOP_DIR)/makefiles/rules.mk

好了,我们进rules.mk看看

在看实现代码前,我们注意到文件开头有

.init. : $(init_dirs);

这个是由一个include自动调用的:见最后:

ifneq (help, $(findstring help, $(MAKECMDGOALS)))
ifneq (clean, $(findstring clean, $(MAKECMDGOALS)))
-include .init.
endif
endif

这个规则干的事很简单:

# mkdir -p -- 目录不存在则创建
$(init_dirs):
$(make_debug)$(call echo_make_info, 'mkdir', $@)
$(make_debug)mkdir -p $@

而 init_dirs变量则在common.mk中,我就不贴了,就是指定build文件夹来放置编译的依赖文件和中间文件

echo_make_info函数也在common.mk中,用printf来打印输出信息 <用printf的好处是可以格式化数据>

# 打印make信息
echo_make_info = printf " [%s] %-8s -> %-16s %s %s...\n" $(ARCH) $(1) $(lib_name) $(2)

这里我们注意到,格式化是实体是5个,而可变参数却只有4个,因为当其中一个可变参数是用空格隔开的时候,会多占一个%s,最后的'...'不知道是自动扩展%s, 还是装饰,刚想到,还未验证。

#然后看进debug_ar_lib

debug_ar_lib : $(cur_ar_lib) ;

先到commond看cur_ar_lib的定义:

# 根据makefile指定的lib_name合成lib的名字
cur_ar_lib = lib$(lib_name).$(ar_suffix)

ar_suffix ?= $(ARCH).a

其中lib_name就是子目录设置的参数foo,这里就直接合成了libfoo.i386.o

继续回rule中:

# make目录中的c,cpp源文件 (ar_objs在common.mk中)
#echo_make_info
-- 打印make信息 ($@ - 源)
$(cur_ar_lib) : $(ar_objs)
$(make_debug)$(call
echo_make_info, 'make', $@) # 打印make信息
$(make_debug)$(AR)
$(arflags) $@ $^ # 创建库
最后一行是用ar将生成的.o文件合成.a库文件

o文件怎么生成的呢,继续看 $(ar_objs)


# 从build目录中滤除主文件main的obj对象,makefile的语法网上很多,这里我就不详细介绍了,baidu一下就出来了

# 因为rule是通用规则,所以主文件Make的时候也会调用这个规则,而main文件是不参加库的合成的,所以这里要从o文件堆中滤除main.o文件。注意,如果主文件不是main.c,那么这里的名字也要做对应的修改
ar_objs := $(filter-out $(build_dir)/main.$(obj_suffix),$(cur_objs))

继续看cur_objs是怎么定义的:


# 编译的结果放入".build"中间目录中 -- 含有编译步骤(rules.mk的开头有对应规则)
cur_objs := $(addprefix $(build_dir)/, $(cur_c_sources:%.c=%.$(obj_suffix)))
cur_objs += $(addprefix $(build_dir)/, $(cur_cpp_sources:%.cpp=%.$(obj_suffix)))可见,obj是从c/cpp的源中提取出来的,然后给加上build_dir前缀,也就是将生成的o文件放入build_dir中



# build的定义:由于.开头的文件会隐藏,所以我去掉了.

# 这里要注意一点,由于这个rule是从子目录中包含进来的,所以路径始终还是在子目录中,所以这里的build是在子目录下创建的


# o文件保存的中间目录
build_dir := build

# 提取c文件
cur_c_sources := $(wildcard *.c)
# 提取cpp文件
cur_cpp_sources := $(wildcard *.cpp)

这个时候初学者一定会问了,rule的最后是将o文件连接为a库文件,cur_objs只是取出了o文件,那么o文件是怎么生成的呢?

o文件的生成就是cur_objs := 这一行中,最后有 %.c=%.$(obj_suffix)

# c文件编译为o的规则
$(build_dir)/%.$(obj_suffix) : %.cpp
$(make_debug)$(call
echo_make_info, 'make', $@)# 显示make信息
$(make_debug)$(CPP)
$(cppflags) $(defines) -c -o $@ $<# g++

# cpp文件编译为o的规则
$(build_dir)/%.$(obj_suffix) : %.c
$(make_debug)$(call
echo_make_info, 'make', $@)
$(make_debug)$(CC)
$(cflags) $(defines) -c -o $@ $<# gcc

从这里就可以看出,子目录make中的defines的作用就是加上定制的cflags,顺便看下默认的cflags:

defines += -DARCH_$(ARCH)
# g++参数
#-std=c++0x
-- 支持c++11标准 (gcc 4.3以上, 4.7以上参数修改为c++11)

cppflags += -Wall -Werror -std=c++0x -g -O0 -I$(TOP_DIR) -I. $(pkg_cflags)
cppflags += -I$(third_party_inc_dir)


# gcc参数
#-Wall
-- 警告当错误处理

#-g
-O0 -- 优化等级

#-I
-- 指定头文件地址

# cflags += -Wall -Werror -g -O0
cflags += -Wall -g -O0 -I$(TOP_DIR) $(pkg_cflags)
cflags += -I$(third_party_inc_dir)

-I指示了系统头文件的查找目录,如果有指定定义的头文件,则也需要添加到这里

如我就做了如下修改:

-I$(top_inc_dir),

top_inc_dir := $(TOP_DIR)/includes

加入了一个公共includes,现在我也明白了linux为什么喜欢把头文件扔一起了

这里有另外一个参数:pkg_cflags,理解起来有点复杂:

这里通过子目录传递下来的lib_name和libs的设置给添加同的参数

如果定义了lib_name,则说明该子目录自己会编译成一个库文件,所以PKG的路径就设置为当前路径


# test -z -- 判断lib_name是否为空
pkg_cflags += $(shell test -z $(lib_name) || (export PKG_CONFIG_PATH=.;pkg-config \
--define-variable=prefix=$(TOP_DIR)/$(dir_name) \
--define-variable=ARCH=$(ARCH) --cflags $(lib_name)))

#这里是lib的参数,指定了PKG搜索lib的路径,由于是本身,所以意义不大
pkg_libs += $(shell test -z $(lib_name) || (export PKG_CONFIG_PATH=.;pkg-config \
--define-variable=prefix=$(TOP_DIR)/$(dir_name) \
--define-variable=ARCH=$(ARCH) --libs $(lib_name)))
# 搜索libs库

# 当子目录有依赖其他库的时候,会设置libs,所以这里就是一个循环,依次加入lib库的搜索路径
pkg_cflags += $(shell list='$(libs)'; for p in $$list; do \
export PKG_CONFIG_PATH=$(TOP_DIR)/lib_$$p;pkg-config \
--define-variable=prefix=$(TOP_DIR)/lib_$$p \
--define-variable=ARCH=$(ARCH) --cflags $$p;done)

# 这里将lib的make参数传入
pkg_libs += $(shell list='$(libs)'; for p in $$list; do \
export PKG_CONFIG_PATH=$(TOP_DIR)/lib_$$p;pkg-config \
--define-variable=prefix=$(TOP_DIR)/lib_$$p \
--define-variable=ARCH=$(ARCH) --libs $$p;done)

关于pkgconfig,自己baidu下吧,我的理解也不是很深,和pkg配合的还有一个pc文件,我也只是仿造已有的进行修改的

最后这里是将lib的标记加入make参数中,注意库的连接是在创建依赖文件d文件的时候进行的

# lib标记,创建依赖文件时使用
libflags += -L$(third_party_lib_dir)
libflags += $(pkg_libs)

这里有一个文件,libflags虽然给出了第三方库的路径,但是如果我程序中要使用第三方库,则目前看来是需要将参数写进libs中,但从上面很明显的看出写入libs会给make加入多余的参数,所以我给libflags加了扩展:

libflags += $(third_party_lib)

然后third_party_lib由子目录的make传入就可以了

刚才大家应该都发现了,在gcc编译的时候,只用了cflags参数,但是没有连接lib,其实lib的连接是自动进行的,在rule文件中用include进行了自动依赖:

# 自动创建依赖文件

auto_deps = 0

ifeq (,$(MAKECMDGOALS))

auto_deps = 1

else ifeq (all, $(findstring all, $(MAKECMDGOALS)))

auto_deps = 1

endif

ifeq (1, $(auto_deps))

-include $(cur_deps)
endif

同样看cur_deps:


# 依赖文件(rules.mk中用include自动调用依赖对象的编译)
cur_deps := $(addprefix $(build_dir)/, $(cur_c_sources:%.c=%.$(dep_suffix)))
cur_deps += $(addprefix $(build_dir)/, $(cur_cpp_sources:%.cpp=%.$(dep_suffix)))

和上面的gcc一样吧,然后看执行:

# 依赖文件创建规则

$(build_dir)/%.$(dep_suffix) : %.cpp

$(make_debug)$(call
echo_make_info, make, $@)


$(make_debug)set
-e; rm -f $@; \


$(CPP) -MM $(cppflags)
$(defines) $(libflags) $< > $@.$$$$; \


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


rm -f $@.$$$$

$(build_dir)/%.$(dep_suffix) : %.c

$(make_debug)$(call
echo_make_info, make, $@)


$(make_debug)set
-e; rm -f $@; \


$(CC) -MM $(cflags)
$(defines) $(libflags) $< > $@.$$$$; \


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

rm -f $@.$$$$

关于set的用法,baidu吧,要解释起来又是一堆文字了

实际上,很多地方的写法都是固定的,这里这是将Make流程解释一下,以便知道如果需要修改,则该改哪里,一些过于绕口的语言,作为初学者来说,可以暂时放过,等以后有时间了再来消化。

=============================================================

以上,就是

$(cur_ar_lib) : $(ar_objs)

的执行过程,

最后:

$(make_debug)$(AR) $(arflags) $@ $^# 创建库好了,子目录的执行过程就分析完了,然后我们看看主文件的makefile有什么不同:

#TOPDIR = ../

include $(TOP_DIR)/makefiles/common.mk

ifeq (arm, $(ARCH))

else ifeq (i386, $(ARCH))

endif

lib_name = app_test

libs += bar foo

main_bin := test_app.$(ARCH).elf

defines += -DDEBUG

#all:$(main_bin) example install

all:$(main_bin)

#$(main_bin): main.cpp $(cur_ar_lib)

$(main_bin): main.cpp $(cur_ar_lib)

$(make_debug)$(call echo_make_info, 'make', $@)

$(make_debug)$(CPP) $(cppflags) -o $@ $^ $(libflags)

.PHONY: $(main_bin)

include $(TOP_DIR)/makefiles/rules.mk

几乎一样,就是libs中指定了主文件所需要的库,libs的处理刚才我们再讲cflags的参数的时候也一起提过了,主要是给pkg用的,然后main_bin指定了生成的应用程序的名字

这里要注意的是,all创建主库的依赖直接用了
$(cur_ar_lib),而不是debug_ar_lib
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: