Makefile系列(二)——通用模板

 

 

Makefile系列(二)——通用模板

小狼@http://blog.csdn.net/xiaolangyangyang

 


        这个模板是之前公司的一个牛人写的,我这个连门都没入的菜鸟因为没有项目需求,所以一直没有花时间去研究。可惜好景不长,酱油没打几天,就需要我来单挑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

 

 


 

参考文献:自己调试通过的一个通用makefile模板

模板下载地址:http://download.csdn.net/detail/longyue0917/6725889

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当编写一个通用Makefile 模板时,可以使用以下内容作为起点: ```makefile # 编译器 CC = gcc # 编译选项 CFLAGS = -Wall -Wextra -std=c99 # 目标文件夹 BUILD_DIR = build # 源文件夹 SRC_DIR = src # 源文件后缀 SRC_EXT = c # 获取所有源文件 SRCS = $(wildcard $(SRC_DIR)/*.$(SRC_EXT)) # 获取所有目标文件 OBJS = $(patsubst $(SRC_DIR)/%.$(SRC_EXT), $(BUILD_DIR)/%.o, $(SRCS)) # 目标可执行文件 TARGET = app # 默认目标 all: $(TARGET) # 编译目标文件 $(BUILD_DIR)/%.o: $(SRC_DIR)/%.$(SRC_EXT) mkdir -p $(BUILD_DIR) $(CC) $(CFLAGS) -c $< -o $@ # 构建可执行文件 $(TARGET): $(OBJS) $(CC) $(CFLAGS) $^ -o $@ # 清理生成的文件 clean: rm -rf $(BUILD_DIR) $(TARGET) .PHONY: all clean ``` 这个模板定义了以下几个变量和规则: - `CC`:编译器的名称,此处为 `gcc`,你可以根据需要修改为其他编译器。 - `CFLAGS`:编译选项,例如 `-Wall` 和 `-Wextra` 是常见的警告选项,`-std=c99` 指定了 C99 标准。 - `BUILD_DIR`:目标文件夹的名称,可以根据需要修改。 - `SRC_DIR`:源文件夹的名称,可以根据需要修改。 - `SRC_EXT`:源文件的扩展名,此处为 `c`,你可以根据需要修改为其他扩展名。 - `SRCS`:所有源文件的列表。 - `OBJS`:所有目标文件的列表。 - `TARGET`:可执行文件的名称。 - `all`:默认目标,依赖于 `$(TARGET)`。 - `$(BUILD_DIR)/%.o`:用于将源文件编译为目标文件的规则。 - `$(TARGET)`:构建可执行文件的规则。 - `clean`:清理生成的文件的规则。 使用该模板时,将源文件放置在 `src` 文件夹中,然后运行 `make` 命令即可编译生成可执行文件。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值