Makefile 实际用例分析(一) ------- 比较通用的一种架构
2014-12-30 16:18
375 查看
这里不再说Makefile的基本知识,如果需要学习,那么请参考: 下载:makefile 中文手册
或者
点击打开链接
或者
跟我一起写Makefile( 陈皓 )
这里说的是一般的实际的一个工程应该怎么去写。
环境:ubuntu 10.04
先看看我的文件的分布情况:
顶层:
然后src中:是所有的源程序以及头文件( 我这里是使用自己的IR树的代码作为实验 )
而build文件夹是为了编译使用的!下面有:
obj文件夹里面放的是编译过程中的.o和.d文件,还有一个subdir.mk的子文件,
用于指示怎么生成.o
obj中:
下面我们从顶层开始慢慢分析:
*******温馨提示:下面的注释是为了方便处理,写在每一条语句后面,其实这样的风格是不好的,所以,如果
你使用了这个makefile,请将注释换行...或者去掉,否则可能编译异常!谢谢记住!
*******
最外层的makefile:
[plain]
view plaincopyprint?
SHELL = /bin/sh # 这个地方是指示使用的shell是sh
EXEC = ir_tree # 最终生成的binary的名称
BUILD_DIR = build # 这个子文件夹,此处也就是我们build文件夹
all: # all在此处是终极目标,这个你应该知道的。一般我们make的时候,第一个目标作为终极目标
@( cd ${BUILD_DIR}; make ) # 这句是进去build文件夹去执行那个makefile
clean: # clean就不说了
@echo 'start clean...'
@($(RM) $(EXEC))
@(cd ${BUILD_DIR}; make clean)
@echo 'Finished!'
@echo ''
现在进入build文件夹,看这个文件夹下面的makefile
[plain]
view plaincopyprint?
SHELL = /bin/sh # 同上
INCLUDE_DIR := # include文件夹,一般我们在引用库的时候,需要将其头文件放在一个include中,然后自己的程序 # 编译的时候需要包含这个include,例如-I$(<span style="font-family: SimHei;">INCLUDE_DIR</span><span style="font-family: SimHei;">)</span>
LIB_DIR := -lm # 引入的库
EXEC = ../ir_tree # 这是一个最终binary名称,这里是将这个可执行放在了上层文件夹中
-include obj/subdir.mk # 这个地方是include了一个子文件
# 这里子文件作用是,为了生成所有的.o文件(当然附带生成.d文件!),生成.o之后,才能回到这一 # 层的makefile进行链接成最终的可执行的操作!具体操作我们稍后再看
all:${EXEC} # 好!这里是这个makefile的第一个目标。即终极目标,所有需要找<span style="font-family: SimHei;">${EXEC}的生成规则!</span>
${EXEC}: ${OBJS} # <span style="font-family: SimHei;">${EXEC}的生成规则,注意这里我们没有看到$(OBJS),那是因为在</span><span style="font-family: SimHei;">obj/subdir.mk中!</span><span style="font-family: SimHei;">
</span> @echo ' Building target: $@ '
gcc -o $@ $(OBJS) $(LIB_DIR) # 这一句就是为了将所有的.o文件 + 引用的库 链接起来,生成最后的$@,也就是$(EX # EC),也就是最后的binary!
@echo 'Finished building target: $@'
@echo ''
clean:
@echo 'start rm objs and deps ...'
$(RM) $(OBJS) \
$(C_DEPS)
@echo 'Finish rm objs and deps ...'
.PHONY: all clean # 伪目标
.SECONDARY:
下面需要看看obj中的subdir.mk的内容了!这个是为了生成所有的.o文件。
同时!请注意:当我们的一个.c或者.h被修改之后,需要重新编译!这一点非常重要!
特别是.h被修改的时候,不能忘记重新编译( 当然,有些时候.h修改,我们不需要编译,这个先暂时不说,后面在讨论!其实,你使用一个make --touch就可以~ )
[plain]
view plaincopyprint?
C_SRCS += \ # 所有的.c文件,当然你喜欢使用wildcard也是可的!
../src/card.c \ # $(<span style="font-family: SimHei;">wildcard ../src/*.c</span><span style="font-family: SimHei;">)</span>
../src/index.c \
../src/node.c \
../src/rect.c \
../src/split_l.c \
../src/test.c
OBJS += \ <span style="font-family: SimHei;"># 所有的.c文件,当然你喜欢使用wildcard也是可的!</span>
./obj/card.o \ # OBJS = $(patsubst %.c,%.o,$(wildcard ../src/*.c))
./obj/index.o \ # 但是你要将src文件目录改成obj的 <span style="font-family: SimHei;">OBJS := $(addprefix "./obj/",$(notdir $(OBJS)))</span>
./obj/node.o \
./obj/rect.o \
./obj/split_l.o \
./obj/test.o
C_DEPS += \ # deps
./obj/card.d \
./obj/index.d \
./obj/node.d \
./obj/rect.d \
./obj/split_l.d \
./obj/test.d
all: $(OBJS) # 注意在这个subdir中,这个是终极目标,也就是所有的objs
obj/%.o: ../src/%.c ./obj/%.d #这里是o文件的依赖规则:注意是.c和.d同时成为依赖,.d文件中是一个目标的所有的依赖文 # 件,包括.c和.h文件,所有一旦.h被修改,这个地方也是可以识别的!
@echo 'start building $< ...'
gcc -O3 -Wall -c -fmessage-length=0 -MMD -MP -MF"$(@:%.o=%.d)" \
-MT"$(@:%.o=%.d)" -o "$@" "$<"
@echo 'Finished building: $< '
@echo ''
-include $(C_DEPS) # 注意:这里将所有的.d文件引入!注意,第一次引入时候,没有.d文件,会寻找.d的生成规则,也就是下面 # 的,这个.d又是依赖.c文件的,所有,一旦一个.c文件中多了一个头文件之类,又可以跟新.d,从而,执行 # 上面的.o生成时候,就能够实时跟新
./obj/%.d: ../src/%.c # 注意:这里为了生成.d
@echo 'start building $(notdir $@)...'
$(CC) $< $(INCLUDE) -MM -MD -o $@
好了,上面所有的都分析完了,然后可以make一下,.、ir_tree 看看效果吧~
之前已经讲了这一篇文章:Makefile实际用例分析(一)-----比较通用的一种架构
现在这篇其实和那个差的不是很多,只是在布局上有些差别(这个makefile也是论坛上一起讨论过的,囧,忘了哪个论坛)
还是先看看基本的文件布局:
介绍:
debug是调试版本的binary文件夹
release是发行版本binary文件夹
src是所有的源文件文件夹、
lib是引用库
include一般是引用库头文件之类,或者其他头文件
obj所有.o文件和.d文件
src中:依然使用之前的那个ir_tree的例子
在lib中有一个外来的引用库,命名为libnew.a,
在include中是这个库的头文件new.h:
我们对于最基本的文件分别已经清楚了,这种架构下只使用了一个makefile,下面我们具体看看:
---------------->>>>>> 我无语了,对csdn格式彻底无语了!这是第三次修改代码格式了,
算了,直接贴成文字吧。。。。。。。。。。。。。。。。。。。。。。。。。。。
********温馨提示:由于csdn格式问题,下面的命令前面的TAB都没有了,所以自己写的时候就得加上!
#################################
# 常见的配置,不多说
#
SHELL=/bin/sh
CC=gcc
MAKE=make
#################################
# 下面定义的是一些目录路径
#
MAKE_DIR=. # 当前文件夹
SRC_DIR=$(MAKE_DIR)/src/ # 源文件夹
OBJ_DIR=$(MAKE_DIR)/obj/ # obj文件夹
LIB_DIR=$(MAKE_DIR)/lib/ # 引用库文件夹
INCLUDE_DIR=$(MAKE_DIR)/include/ # include文件夹
DEBUG_DIR=$(MAKE_DIR)/debug/ # debug文件夹
RELEASE_DIR=$(MAKE_DIR)/release/# release文件夹
EXEC_DIR= # 最终的binary文件夹
#################################
# 下面是include路径和库的路径
#
INCLUDE=-I$(INCLUDE_DIR) -I$(SRC_DIR)
LIB=$(LIB_DIR)/libnew.a -L$(OBJ_DIR) -lm # 引入库,包括自己的库文件( 如果你将libnew.a放到/usr/lib中了, )
# 那么直接-lnew就可以了
#################################
# 下面是可执行名称
#
EXEC=ir_tree
#################################
#
# 注意:当我们下面出现%.c的时候,先在当前文件夹寻找,如果找不到,那么
# 到下面指定的文件夹中寻找!!!
#
# 说白就是:如果依赖文件在本文件夹找不到,那么到下面文件夹寻找!仅仅是依赖文件!
#
vpath %.h $(INCLUDE_DIR)
vpath %.c $(SRC_DIR)
vpath %.o $(OBJ_DIR)
vpath %.d $(OBJ_DIR)
#################################
# 下面是指定SRC OBJ DEP
# 注意:都是不带目录的basename
#
SRC_TMP:=$(wildcard $(SRC_DIR)*.c)
C_SRCS:=$(notdir $(SRC_TMP)) # 源文件
C_OBJS:=$(patsubst %.c,%.o,$(C_SRCS)) # o文件
C_DEPS:=$(patsubst %.c,%.d,$(C_SRCS)) # deps
#################################
# 编译选项!
#
FLAG_DEBUG=-g
CFLAGS=-O2 -Wall -c
# 下面判断是debug还是release
#
DEBUG:=1
ifeq ($(DEBUG),1)
EXEC_DIR:=$(DEBUG_DIR)
CFLAGS:=$(CFLAGS) $(FLAG_DEBUG)
else
EXEC_DIR:=$(RELEASE_DIR)
endif
# 最终binary的名称( 路径+名称 )
#
EXEC:=$(EXEC_DIR)$(EXEC)
################################
# 下面是终极目标all
#
#################################################################
# 关于下面的执行:
#
# 首先-include $(addprefix $(OBJ_DIR),$(C_DEPS))
# 目的是为了将所有的.d文件包含进来,那么.d文件里面是所有的.o的
# 完整的依赖,那么即使.h被修改了,那么也是可以识别编译的!在第一次
# 处理时,没有.d文件,那么需要找.d的生成规则。因为我们include的是
# $(addprefix $(OBJ_DIR),$(C_DEPS)),那么需要找的依赖是$(OBJ_DIR)%.d
# ,那么OK,这个地方必须注意!如果你include的.d是例如card.d,那么
# 规则必须是:%.d: %.c,而不是$(OBJ_DIR)%.d: %.c。好!现在生成.d文件了
# 然后执行all:$(EXEC),那么需要找依赖$(C_OBJS),本文件夹没有,那么到
# vpath %.o $(OBJ_DIR)中寻找!那么可以因为开始.o是不存在(或者过期的),
# 那么需要寻找生成规则:%.o: %.c %.d,OK生成!
# 等所有的.o处理OK,链接成可执行!
#
#################################################################
#
# 重要理解:
# 1: 你有什么样的依赖,那么就是什么样的一个子规则的目标!
# 例如:$(C_OBJS)是不带目录路径的.o的集合,例如a.o b.o c.o
# 那么,我们需要寻找生成他们的规则,那么肯定有一个子伪目标
# 名称是:(%.o:依赖),而不是($(OBJ_DIR)%.o:依赖),所以
# 要理解哦!
#
# 2: 注意vpath用途,当“依赖”在本文件夹下找不到的时候,去指定
# 文件夹寻找!
#
# 3:注意Include是将.d文件中的内容加载到当前文件夹中!那么,如果.d
# 里的是例如:card.o: card.c card.h,那么$(C_OBJS)也应该是不带目录
# 路径的*.o形式!!!
#
all:$(EXEC)
$(EXEC): $(C_OBJS)
@echo 'start building $(notdir $@) ...'
@$(CC) $(addprefix $(OBJ_DIR),$(notdir $^)) $(LIB) -o $@
#
# 注意关系:每次makefile的时候,需要加载.d文件,那么所有的依赖被加进来
# 但是也必须有$(OBJ_DIR)%.d: %.c,这个是为了当我们的.c改变的时候,例如
# 可能心增加一个include,那么可以改变.d文件!那么后面的处理又是连带关系!
#
# 与上一篇说的一样
%.o: %.c %.d
@echo 'start building $(notdir $@)...'
@$(CC) $(CFLAGS) $< $(INCLUDE) -o $(OBJ_DIR)$@
$(OBJ_DIR)%.d: %.c
@echo 'start building $(notdir $@)...'
@$(CC) $< $(INCLUDE) -MM -MD -o $@
# 将所有的d文件引入,作用,为了更新~
-include $(addprefix $(OBJ_DIR),$(C_DEPS))
clean:
@$(RM) $(OBJ_DIR)*
@$(RM) $(EXEC)
@clear
run:
@$(EXEC)
debug:
@echo $(C_OBJS)
@echo $(C_DEPS)
.PHONY:all clean debug run
OK,现在你可以make,然后make run看结果、、、
如果你需要这个工程,同样可以免费下载:ir_tree
前面两篇已经说过了自己怎么去为一个工程写makefile:
第一篇
第二篇
现在这一篇说的是怎么使用GNU的工具去写一个符合开源标准的Makefile呢!
首先我觉你应该参考:
Automake
Autoconf
这里就一步一步来解释:依然使用之前两篇中的例子ir_tree!
还是先看看我的文件布局是怎么样的!
顶层文件夹:相比之前多了Makefile.am和configure.ac文件,还有四个辅助文件( 顾名思义,我就不多说了 ):
AUTHORS、
ChangeLog、
NEWS、
README
然后src文件夹中:相比之前多了一个Makefile.am文件
lib里面:注意,这一次,我是将静态库放在这里一起make出来,而不是之前make好,然后在调用!
所有看lib中内容:
也有一个Makefile.am,是为了生成libnew.a而需要的!
include文件夹中:还是和以前一样
OK,基本结构说完了,然后需要说的是这些新增加的文件的意义!!!
configure.ac文件是为了生成configure文件需要的!
Makefile.am是为了生成makefile.in文件需要的
最后又configure和Makefile.in一起生成Makefile文件!!!!
关于这个自动生成的流程,看下面这个图:来自维基百科
前提,你安装了automake和autoconf
好,我解释一下:
1:
autoscan这是一个命令,需在你的源文件的文件夹下执行,然后会生成一个configure.scan的模板文件,这个模板是configure.ac模板,所以,将configure.scan改名为configure.ac,然后开始修改!注意configure.ac其实就是一些M4
宏,AC开头代表Autoconf使用,AM开头代表Automake使用。
注意:我们不要去畏惧这个文件,虽然很多开源软件中的configure.ac写的比较复杂,但是我们需要时刻认识到一点是:这只不过是一个环境检测文件,检测你的代码版本,检测代码文件是否存在,检测哪些头文件是否存在,检测你需要的库是否存在,检验是否支持这样的类型数据,检测...
所以随便由你写!对于具体的你的需求,然后需要使用什么M4宏来处理!具体的这些宏,你可以去官方网站查看手册!
2:
好了,假如我们已经写好了configure.ac( 后***体会说! ),那么下一步干嘛呢,我们需要aclocal命令执行以下,看看是否需要M4的支持!如果需要会生成:
如果没有生成aclocal.m4,那么也没什么,说明不需要这个而已。
3:
关于autoheader命令,为了一个配置头文件通常是config.h而需要的,如果你不需要,直接省略!不要执行这个命令,但是注意在configure.ac中就不要去检测这个config.h的头文件了!否则报错哦!
4:
好了,下面就是需要自己写非常中的Makefile.am文件了!
Makefile.am到底是什么呢?也不要畏惧,只不过是指示生成什么样的东西,生成.o还是.a,还是一个binary?
然后是怎么生成,其实不需要说怎么生成,只需要将这个文件的依赖文件写出来就OK了!!!其实很easy!
对于Makefile.am文件,一般顶层文件肯定有一个,然后在你的源文件中肯定有一个,用于指示怎么生成最终的binary,然后就是你的库生成文件夹中肯定有一个,为了指示怎么生成.a或者.so的库!
所以Makefile.am文件真的很简单,就是这样指示生成文件而已。。。对于复杂的Makefile.am,是因为考虑了很多条件,但是根本的本质还是一样的!
5:
OK,假如我们已经将所有.am文件生成好了,下面干嘛呢!
下面执行autoconf生成configre文件
6:
然后再使用automake -a生成Makefile.in文件,GOOG!好了!我们基本快要大功告成了!
7:
下面是我们最熟悉最熟悉的:
./configure
make
make install
都是可以执行了~~~~~
上面说的就是总体的一个流程!
################################################################
下面,我们就结合ir_tree的实例一起来简单的写一个!
******* 温馨提示:我下面直接在后面注释习惯不好,可能会编译出错,所有如果你使用了,那么轻删除或者将这些注释换行
>>> configure.ac
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.59) # 这里是工具版本号,不关心
AC_INIT(ir_tree, 1.0, ********@gmail.com) # AC_INIT:指示可执行名称 + 版本号 + BUG-report
AC_CONFIG_SRCDIR([src/test.c]) # AC_CONFIG_SRCDIR:为了检验源文件是否缺失,你可以检测所有的 # 文件,这里为了示意,检查一个
AM_CONFIG_HEADER(config.h) # 这里为了检查头文件config.h,如果你不需要,就不需要检查
AM_INIT_AUTOMAKE(ir_tree,1.0) # 这一句可以不需要,也是指示可执行名称 + 版本号
# Checks for programs. # 下面检验一些系统程序是否存在
AC_PROG_CC # 检验CC(就是c语言编译程序)是否存在
# 如果你是要的C++,那么AC_PROG_CXX,所有你需要什么,到uanfang手册去查找就OK了!
# Checks for libraries. # 检查一些库(包括系统库和你引入的库)
AC_CHECK_LIB([m], [sqrt]) # 这里检查数学库-lm
# 其实AC_CHECK_LIB的完整格式是:AC_CHECK_LIB([lib], [func],[if_exist_action], [not_exist_action])
# 后面的func是你使用到的函数名
AC_PROG_RANLIB # 这里的意义是:当你使用了静态库的时候,需要这一句
# 如果你使用了共享库,那么使用AC_PROG_LIBTOOL
# Checks for header files. # 下面检查一些头文件,只检查你认为需要检查的即可!然后查手册!
#AC_HEADER_STDC
#AC_CHECK_HEADERS([float.h malloc.h stdlib.h])
# Checks for typedefs, structures, and compiler characteristics. # 下面检查一些类型定义是否允许
#AC_C_CONST
# Checks for library functions. # 下面检查一些库函数是否合理
#AC_FUNC_MALLOC # 检查malloc函数
AC_CHECK_FUNCS([pow sqrt]) # 检查pow和sqrt函数
# 哪些你需要的你自己肯定知道,然后查手册,放进了检查就OK了~
# 下面是很重要的!
# 就是最终输出哪些文件,由上面的结构知道,有几个Makefile.am就几个Makefile,那么输出文件自然如下!
#
AC_OUTPUT([
Makefile
src/Makefile
lib/Makefile
])
好了configure.ac,即环境检测文件写好了,那么下面就是一层一层的Makefile.am文件了
>>> 先看顶层的 Makefile.am
特别的简单:
AUTOMAKE_OPTIONS=foreign # 这里是一个automake的选项,foreign ,具体的可以查一下,不是很重要
SUBDIRS=lib src # 注意这个SUBDIRS就很重要的,这里指示所有的子Makefile.am所在的目录,
# 然后执行的时候会一次去执行!注意是:SUBDIRS不是SUBDIR!!!
# 由上面的文件分布可以看到,lib中是生成libnew.a库的Makefile.am,src中是
# 生成最终的可执行需要的Makefile.am。所以有两个子文件!!!
CURRENTPATH=$(shell /bin/pwd) # 指示当前路径
INCLUDES=-I$(CURRENTPATH)/include
# 注意:这个include是为了指示src中头文件之外的其他头文件所在地目录!需要export到后面的子Makefile.am中去!
export INCLUDES # 导出!!
>>> 现在进入lib中看Makefile.am:
AUTOMAKE_OPTIONS=foreign # 不多说
noinst_LIBRARIES=libnew.a # 注意这里是生成库文件,那么就是XXXX_LIBRARIES
# noinst即not install的缩写,所以无需安装这个库,当然你可以指示安装
libnew_a_SOURCES=new.c # 注意前面的命名:‘.’要换成'_',指示源文件!
# 注意这里有一个隐藏条件:就是上层Makefile.am传递下来的INCLUDES,在这里面也是有使用的,不然无法找到所需头文件的!
所以这个Makefile.am基本就是指示需要生成什么,然后这个生成文件的依赖文件是什么,如此而已!!!
>>> 然后在进入src中看看Makefile.am:
bin_PROGRAMS=ir_tree # 这个是最终生成的binary名称:使用bin_PROGRAMS
ir_tree_SOURCES=card.c card.h index.c index.h node.c rect.c split_l.c split_l.h test.c # 指示依赖的源文件
ir_tree_LDADD=$(top_srcdir)/lib/libnew.a # 指示依赖的库文件( 其实是链接的时候需要增加的信息 )
# 注意这里不需要指示-lm,因为我们在configure.ac中已经检查,
# 那么需要的时候,自动添加(其实系统库都会自己添加)
# 解释top_srcdir:就是顶层文件夹目录
ir_tree_LDFLAGS=-D_GNU_SOURCE #这是一个链接FLAG,表示需要用生成.deps依赖文件
DEFS+=-D_GNU_SOURCE
OK,所有的多讲完了,那么最重要的还是需要理解Makefile.am和configure.ac的作用!
不要畏惧,自己想添加什么就添加什么!
OK,
下面你就可以:
执行之前说的那些命令:
aclocal
acheader
autoconf
automake -a
./configure
make
OK... 看看运行结果对不对吧。。。
如果你需要这个工程,同样免费下载:ir-tree
或者
点击打开链接
或者
跟我一起写Makefile( 陈皓 )
这里说的是一般的实际的一个工程应该怎么去写。
环境:ubuntu 10.04
先看看我的文件的分布情况:
顶层:
然后src中:是所有的源程序以及头文件( 我这里是使用自己的IR树的代码作为实验 )
而build文件夹是为了编译使用的!下面有:
obj文件夹里面放的是编译过程中的.o和.d文件,还有一个subdir.mk的子文件,
用于指示怎么生成.o
obj中:
下面我们从顶层开始慢慢分析:
*******温馨提示:下面的注释是为了方便处理,写在每一条语句后面,其实这样的风格是不好的,所以,如果
你使用了这个makefile,请将注释换行...或者去掉,否则可能编译异常!谢谢记住!
*******
最外层的makefile:
[plain]
view plaincopyprint?
SHELL = /bin/sh # 这个地方是指示使用的shell是sh
EXEC = ir_tree # 最终生成的binary的名称
BUILD_DIR = build # 这个子文件夹,此处也就是我们build文件夹
all: # all在此处是终极目标,这个你应该知道的。一般我们make的时候,第一个目标作为终极目标
@( cd ${BUILD_DIR}; make ) # 这句是进去build文件夹去执行那个makefile
clean: # clean就不说了
@echo 'start clean...'
@($(RM) $(EXEC))
@(cd ${BUILD_DIR}; make clean)
@echo 'Finished!'
@echo ''
现在进入build文件夹,看这个文件夹下面的makefile
[plain]
view plaincopyprint?
SHELL = /bin/sh # 同上
INCLUDE_DIR := # include文件夹,一般我们在引用库的时候,需要将其头文件放在一个include中,然后自己的程序 # 编译的时候需要包含这个include,例如-I$(<span style="font-family: SimHei;">INCLUDE_DIR</span><span style="font-family: SimHei;">)</span>
LIB_DIR := -lm # 引入的库
EXEC = ../ir_tree # 这是一个最终binary名称,这里是将这个可执行放在了上层文件夹中
-include obj/subdir.mk # 这个地方是include了一个子文件
# 这里子文件作用是,为了生成所有的.o文件(当然附带生成.d文件!),生成.o之后,才能回到这一 # 层的makefile进行链接成最终的可执行的操作!具体操作我们稍后再看
all:${EXEC} # 好!这里是这个makefile的第一个目标。即终极目标,所有需要找<span style="font-family: SimHei;">${EXEC}的生成规则!</span>
${EXEC}: ${OBJS} # <span style="font-family: SimHei;">${EXEC}的生成规则,注意这里我们没有看到$(OBJS),那是因为在</span><span style="font-family: SimHei;">obj/subdir.mk中!</span><span style="font-family: SimHei;">
</span> @echo ' Building target: $@ '
gcc -o $@ $(OBJS) $(LIB_DIR) # 这一句就是为了将所有的.o文件 + 引用的库 链接起来,生成最后的$@,也就是$(EX # EC),也就是最后的binary!
@echo 'Finished building target: $@'
@echo ''
clean:
@echo 'start rm objs and deps ...'
$(RM) $(OBJS) \
$(C_DEPS)
@echo 'Finish rm objs and deps ...'
.PHONY: all clean # 伪目标
.SECONDARY:
下面需要看看obj中的subdir.mk的内容了!这个是为了生成所有的.o文件。
同时!请注意:当我们的一个.c或者.h被修改之后,需要重新编译!这一点非常重要!
特别是.h被修改的时候,不能忘记重新编译( 当然,有些时候.h修改,我们不需要编译,这个先暂时不说,后面在讨论!其实,你使用一个make --touch就可以~ )
[plain]
view plaincopyprint?
C_SRCS += \ # 所有的.c文件,当然你喜欢使用wildcard也是可的!
../src/card.c \ # $(<span style="font-family: SimHei;">wildcard ../src/*.c</span><span style="font-family: SimHei;">)</span>
../src/index.c \
../src/node.c \
../src/rect.c \
../src/split_l.c \
../src/test.c
OBJS += \ <span style="font-family: SimHei;"># 所有的.c文件,当然你喜欢使用wildcard也是可的!</span>
./obj/card.o \ # OBJS = $(patsubst %.c,%.o,$(wildcard ../src/*.c))
./obj/index.o \ # 但是你要将src文件目录改成obj的 <span style="font-family: SimHei;">OBJS := $(addprefix "./obj/",$(notdir $(OBJS)))</span>
./obj/node.o \
./obj/rect.o \
./obj/split_l.o \
./obj/test.o
C_DEPS += \ # deps
./obj/card.d \
./obj/index.d \
./obj/node.d \
./obj/rect.d \
./obj/split_l.d \
./obj/test.d
all: $(OBJS) # 注意在这个subdir中,这个是终极目标,也就是所有的objs
obj/%.o: ../src/%.c ./obj/%.d #这里是o文件的依赖规则:注意是.c和.d同时成为依赖,.d文件中是一个目标的所有的依赖文 # 件,包括.c和.h文件,所有一旦.h被修改,这个地方也是可以识别的!
@echo 'start building $< ...'
gcc -O3 -Wall -c -fmessage-length=0 -MMD -MP -MF"$(@:%.o=%.d)" \
-MT"$(@:%.o=%.d)" -o "$@" "$<"
@echo 'Finished building: $< '
@echo ''
-include $(C_DEPS) # 注意:这里将所有的.d文件引入!注意,第一次引入时候,没有.d文件,会寻找.d的生成规则,也就是下面 # 的,这个.d又是依赖.c文件的,所有,一旦一个.c文件中多了一个头文件之类,又可以跟新.d,从而,执行 # 上面的.o生成时候,就能够实时跟新
./obj/%.d: ../src/%.c # 注意:这里为了生成.d
@echo 'start building $(notdir $@)...'
$(CC) $< $(INCLUDE) -MM -MD -o $@
好了,上面所有的都分析完了,然后可以make一下,.、ir_tree 看看效果吧~
之前已经讲了这一篇文章:Makefile实际用例分析(一)-----比较通用的一种架构
现在这篇其实和那个差的不是很多,只是在布局上有些差别(这个makefile也是论坛上一起讨论过的,囧,忘了哪个论坛)
还是先看看基本的文件布局:
介绍:
debug是调试版本的binary文件夹
release是发行版本binary文件夹
src是所有的源文件文件夹、
lib是引用库
include一般是引用库头文件之类,或者其他头文件
obj所有.o文件和.d文件
src中:依然使用之前的那个ir_tree的例子
在lib中有一个外来的引用库,命名为libnew.a,
在include中是这个库的头文件new.h:
我们对于最基本的文件分别已经清楚了,这种架构下只使用了一个makefile,下面我们具体看看:
---------------->>>>>> 我无语了,对csdn格式彻底无语了!这是第三次修改代码格式了,
算了,直接贴成文字吧。。。。。。。。。。。。。。。。。。。。。。。。。。。
********温馨提示:由于csdn格式问题,下面的命令前面的TAB都没有了,所以自己写的时候就得加上!
#################################
# 常见的配置,不多说
#
SHELL=/bin/sh
CC=gcc
MAKE=make
#################################
# 下面定义的是一些目录路径
#
MAKE_DIR=. # 当前文件夹
SRC_DIR=$(MAKE_DIR)/src/ # 源文件夹
OBJ_DIR=$(MAKE_DIR)/obj/ # obj文件夹
LIB_DIR=$(MAKE_DIR)/lib/ # 引用库文件夹
INCLUDE_DIR=$(MAKE_DIR)/include/ # include文件夹
DEBUG_DIR=$(MAKE_DIR)/debug/ # debug文件夹
RELEASE_DIR=$(MAKE_DIR)/release/# release文件夹
EXEC_DIR= # 最终的binary文件夹
#################################
# 下面是include路径和库的路径
#
INCLUDE=-I$(INCLUDE_DIR) -I$(SRC_DIR)
LIB=$(LIB_DIR)/libnew.a -L$(OBJ_DIR) -lm # 引入库,包括自己的库文件( 如果你将libnew.a放到/usr/lib中了, )
# 那么直接-lnew就可以了
#################################
# 下面是可执行名称
#
EXEC=ir_tree
#################################
#
# 注意:当我们下面出现%.c的时候,先在当前文件夹寻找,如果找不到,那么
# 到下面指定的文件夹中寻找!!!
#
# 说白就是:如果依赖文件在本文件夹找不到,那么到下面文件夹寻找!仅仅是依赖文件!
#
vpath %.h $(INCLUDE_DIR)
vpath %.c $(SRC_DIR)
vpath %.o $(OBJ_DIR)
vpath %.d $(OBJ_DIR)
#################################
# 下面是指定SRC OBJ DEP
# 注意:都是不带目录的basename
#
SRC_TMP:=$(wildcard $(SRC_DIR)*.c)
C_SRCS:=$(notdir $(SRC_TMP)) # 源文件
C_OBJS:=$(patsubst %.c,%.o,$(C_SRCS)) # o文件
C_DEPS:=$(patsubst %.c,%.d,$(C_SRCS)) # deps
#################################
# 编译选项!
#
FLAG_DEBUG=-g
CFLAGS=-O2 -Wall -c
# 下面判断是debug还是release
#
DEBUG:=1
ifeq ($(DEBUG),1)
EXEC_DIR:=$(DEBUG_DIR)
CFLAGS:=$(CFLAGS) $(FLAG_DEBUG)
else
EXEC_DIR:=$(RELEASE_DIR)
endif
# 最终binary的名称( 路径+名称 )
#
EXEC:=$(EXEC_DIR)$(EXEC)
################################
# 下面是终极目标all
#
#################################################################
# 关于下面的执行:
#
# 首先-include $(addprefix $(OBJ_DIR),$(C_DEPS))
# 目的是为了将所有的.d文件包含进来,那么.d文件里面是所有的.o的
# 完整的依赖,那么即使.h被修改了,那么也是可以识别编译的!在第一次
# 处理时,没有.d文件,那么需要找.d的生成规则。因为我们include的是
# $(addprefix $(OBJ_DIR),$(C_DEPS)),那么需要找的依赖是$(OBJ_DIR)%.d
# ,那么OK,这个地方必须注意!如果你include的.d是例如card.d,那么
# 规则必须是:%.d: %.c,而不是$(OBJ_DIR)%.d: %.c。好!现在生成.d文件了
# 然后执行all:$(EXEC),那么需要找依赖$(C_OBJS),本文件夹没有,那么到
# vpath %.o $(OBJ_DIR)中寻找!那么可以因为开始.o是不存在(或者过期的),
# 那么需要寻找生成规则:%.o: %.c %.d,OK生成!
# 等所有的.o处理OK,链接成可执行!
#
#################################################################
#
# 重要理解:
# 1: 你有什么样的依赖,那么就是什么样的一个子规则的目标!
# 例如:$(C_OBJS)是不带目录路径的.o的集合,例如a.o b.o c.o
# 那么,我们需要寻找生成他们的规则,那么肯定有一个子伪目标
# 名称是:(%.o:依赖),而不是($(OBJ_DIR)%.o:依赖),所以
# 要理解哦!
#
# 2: 注意vpath用途,当“依赖”在本文件夹下找不到的时候,去指定
# 文件夹寻找!
#
# 3:注意Include是将.d文件中的内容加载到当前文件夹中!那么,如果.d
# 里的是例如:card.o: card.c card.h,那么$(C_OBJS)也应该是不带目录
# 路径的*.o形式!!!
#
all:$(EXEC)
$(EXEC): $(C_OBJS)
@echo 'start building $(notdir $@) ...'
@$(CC) $(addprefix $(OBJ_DIR),$(notdir $^)) $(LIB) -o $@
#
# 注意关系:每次makefile的时候,需要加载.d文件,那么所有的依赖被加进来
# 但是也必须有$(OBJ_DIR)%.d: %.c,这个是为了当我们的.c改变的时候,例如
# 可能心增加一个include,那么可以改变.d文件!那么后面的处理又是连带关系!
#
# 与上一篇说的一样
%.o: %.c %.d
@echo 'start building $(notdir $@)...'
@$(CC) $(CFLAGS) $< $(INCLUDE) -o $(OBJ_DIR)$@
$(OBJ_DIR)%.d: %.c
@echo 'start building $(notdir $@)...'
@$(CC) $< $(INCLUDE) -MM -MD -o $@
# 将所有的d文件引入,作用,为了更新~
-include $(addprefix $(OBJ_DIR),$(C_DEPS))
clean:
@$(RM) $(OBJ_DIR)*
@$(RM) $(EXEC)
@clear
run:
@$(EXEC)
debug:
@echo $(C_OBJS)
@echo $(C_DEPS)
.PHONY:all clean debug run
OK,现在你可以make,然后make run看结果、、、
如果你需要这个工程,同样可以免费下载:ir_tree
前面两篇已经说过了自己怎么去为一个工程写makefile:
第一篇
第二篇
现在这一篇说的是怎么使用GNU的工具去写一个符合开源标准的Makefile呢!
首先我觉你应该参考:
Automake
Autoconf
这里就一步一步来解释:依然使用之前两篇中的例子ir_tree!
还是先看看我的文件布局是怎么样的!
顶层文件夹:相比之前多了Makefile.am和configure.ac文件,还有四个辅助文件( 顾名思义,我就不多说了 ):
AUTHORS、
ChangeLog、
NEWS、
README
然后src文件夹中:相比之前多了一个Makefile.am文件
lib里面:注意,这一次,我是将静态库放在这里一起make出来,而不是之前make好,然后在调用!
所有看lib中内容:
也有一个Makefile.am,是为了生成libnew.a而需要的!
include文件夹中:还是和以前一样
OK,基本结构说完了,然后需要说的是这些新增加的文件的意义!!!
configure.ac文件是为了生成configure文件需要的!
Makefile.am是为了生成makefile.in文件需要的
最后又configure和Makefile.in一起生成Makefile文件!!!!
关于这个自动生成的流程,看下面这个图:来自维基百科
前提,你安装了automake和autoconf
好,我解释一下:
1:
autoscan这是一个命令,需在你的源文件的文件夹下执行,然后会生成一个configure.scan的模板文件,这个模板是configure.ac模板,所以,将configure.scan改名为configure.ac,然后开始修改!注意configure.ac其实就是一些M4
宏,AC开头代表Autoconf使用,AM开头代表Automake使用。
注意:我们不要去畏惧这个文件,虽然很多开源软件中的configure.ac写的比较复杂,但是我们需要时刻认识到一点是:这只不过是一个环境检测文件,检测你的代码版本,检测代码文件是否存在,检测哪些头文件是否存在,检测你需要的库是否存在,检验是否支持这样的类型数据,检测...
所以随便由你写!对于具体的你的需求,然后需要使用什么M4宏来处理!具体的这些宏,你可以去官方网站查看手册!
2:
好了,假如我们已经写好了configure.ac( 后***体会说! ),那么下一步干嘛呢,我们需要aclocal命令执行以下,看看是否需要M4的支持!如果需要会生成:
如果没有生成aclocal.m4,那么也没什么,说明不需要这个而已。
3:
关于autoheader命令,为了一个配置头文件通常是config.h而需要的,如果你不需要,直接省略!不要执行这个命令,但是注意在configure.ac中就不要去检测这个config.h的头文件了!否则报错哦!
4:
好了,下面就是需要自己写非常中的Makefile.am文件了!
Makefile.am到底是什么呢?也不要畏惧,只不过是指示生成什么样的东西,生成.o还是.a,还是一个binary?
然后是怎么生成,其实不需要说怎么生成,只需要将这个文件的依赖文件写出来就OK了!!!其实很easy!
对于Makefile.am文件,一般顶层文件肯定有一个,然后在你的源文件中肯定有一个,用于指示怎么生成最终的binary,然后就是你的库生成文件夹中肯定有一个,为了指示怎么生成.a或者.so的库!
所以Makefile.am文件真的很简单,就是这样指示生成文件而已。。。对于复杂的Makefile.am,是因为考虑了很多条件,但是根本的本质还是一样的!
5:
OK,假如我们已经将所有.am文件生成好了,下面干嘛呢!
下面执行autoconf生成configre文件
6:
然后再使用automake -a生成Makefile.in文件,GOOG!好了!我们基本快要大功告成了!
7:
下面是我们最熟悉最熟悉的:
./configure
make
make install
都是可以执行了~~~~~
上面说的就是总体的一个流程!
################################################################
下面,我们就结合ir_tree的实例一起来简单的写一个!
******* 温馨提示:我下面直接在后面注释习惯不好,可能会编译出错,所有如果你使用了,那么轻删除或者将这些注释换行
>>> configure.ac
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.59) # 这里是工具版本号,不关心
AC_INIT(ir_tree, 1.0, ********@gmail.com) # AC_INIT:指示可执行名称 + 版本号 + BUG-report
AC_CONFIG_SRCDIR([src/test.c]) # AC_CONFIG_SRCDIR:为了检验源文件是否缺失,你可以检测所有的 # 文件,这里为了示意,检查一个
AM_CONFIG_HEADER(config.h) # 这里为了检查头文件config.h,如果你不需要,就不需要检查
AM_INIT_AUTOMAKE(ir_tree,1.0) # 这一句可以不需要,也是指示可执行名称 + 版本号
# Checks for programs. # 下面检验一些系统程序是否存在
AC_PROG_CC # 检验CC(就是c语言编译程序)是否存在
# 如果你是要的C++,那么AC_PROG_CXX,所有你需要什么,到uanfang手册去查找就OK了!
# Checks for libraries. # 检查一些库(包括系统库和你引入的库)
AC_CHECK_LIB([m], [sqrt]) # 这里检查数学库-lm
# 其实AC_CHECK_LIB的完整格式是:AC_CHECK_LIB([lib], [func],[if_exist_action], [not_exist_action])
# 后面的func是你使用到的函数名
AC_PROG_RANLIB # 这里的意义是:当你使用了静态库的时候,需要这一句
# 如果你使用了共享库,那么使用AC_PROG_LIBTOOL
# Checks for header files. # 下面检查一些头文件,只检查你认为需要检查的即可!然后查手册!
#AC_HEADER_STDC
#AC_CHECK_HEADERS([float.h malloc.h stdlib.h])
# Checks for typedefs, structures, and compiler characteristics. # 下面检查一些类型定义是否允许
#AC_C_CONST
# Checks for library functions. # 下面检查一些库函数是否合理
#AC_FUNC_MALLOC # 检查malloc函数
AC_CHECK_FUNCS([pow sqrt]) # 检查pow和sqrt函数
# 哪些你需要的你自己肯定知道,然后查手册,放进了检查就OK了~
# 下面是很重要的!
# 就是最终输出哪些文件,由上面的结构知道,有几个Makefile.am就几个Makefile,那么输出文件自然如下!
#
AC_OUTPUT([
Makefile
src/Makefile
lib/Makefile
])
好了configure.ac,即环境检测文件写好了,那么下面就是一层一层的Makefile.am文件了
>>> 先看顶层的 Makefile.am
特别的简单:
AUTOMAKE_OPTIONS=foreign # 这里是一个automake的选项,foreign ,具体的可以查一下,不是很重要
SUBDIRS=lib src # 注意这个SUBDIRS就很重要的,这里指示所有的子Makefile.am所在的目录,
# 然后执行的时候会一次去执行!注意是:SUBDIRS不是SUBDIR!!!
# 由上面的文件分布可以看到,lib中是生成libnew.a库的Makefile.am,src中是
# 生成最终的可执行需要的Makefile.am。所以有两个子文件!!!
CURRENTPATH=$(shell /bin/pwd) # 指示当前路径
INCLUDES=-I$(CURRENTPATH)/include
# 注意:这个include是为了指示src中头文件之外的其他头文件所在地目录!需要export到后面的子Makefile.am中去!
export INCLUDES # 导出!!
>>> 现在进入lib中看Makefile.am:
AUTOMAKE_OPTIONS=foreign # 不多说
noinst_LIBRARIES=libnew.a # 注意这里是生成库文件,那么就是XXXX_LIBRARIES
# noinst即not install的缩写,所以无需安装这个库,当然你可以指示安装
libnew_a_SOURCES=new.c # 注意前面的命名:‘.’要换成'_',指示源文件!
# 注意这里有一个隐藏条件:就是上层Makefile.am传递下来的INCLUDES,在这里面也是有使用的,不然无法找到所需头文件的!
所以这个Makefile.am基本就是指示需要生成什么,然后这个生成文件的依赖文件是什么,如此而已!!!
>>> 然后在进入src中看看Makefile.am:
bin_PROGRAMS=ir_tree # 这个是最终生成的binary名称:使用bin_PROGRAMS
ir_tree_SOURCES=card.c card.h index.c index.h node.c rect.c split_l.c split_l.h test.c # 指示依赖的源文件
ir_tree_LDADD=$(top_srcdir)/lib/libnew.a # 指示依赖的库文件( 其实是链接的时候需要增加的信息 )
# 注意这里不需要指示-lm,因为我们在configure.ac中已经检查,
# 那么需要的时候,自动添加(其实系统库都会自己添加)
# 解释top_srcdir:就是顶层文件夹目录
ir_tree_LDFLAGS=-D_GNU_SOURCE #这是一个链接FLAG,表示需要用生成.deps依赖文件
DEFS+=-D_GNU_SOURCE
OK,所有的多讲完了,那么最重要的还是需要理解Makefile.am和configure.ac的作用!
不要畏惧,自己想添加什么就添加什么!
OK,
下面你就可以:
执行之前说的那些命令:
aclocal
acheader
autoconf
automake -a
./configure
make
OK... 看看运行结果对不对吧。。。
如果你需要这个工程,同样免费下载:ir-tree
相关文章推荐
- Makefile 实际用例分析(一) ------- 比较通用的一种架构
- Makefile 实际用例分析(一) ------- 比较通用的一种架构
- Makefile 实际用例分析(二) ------- 比较通用的一种架构
- Makefile 实际用例分析(二) ------- 比较通用的一种架构
- 规则+工程:一种比较灵巧通用的Makefile写法
- Makefile 实际用例分析(三) ------- 是用GUN automake 处理自己的工程
- 规则+工程:一种比较灵巧通用的Makefile写法
- Makefile 实际用例分析(三) ------- 是用GUN automake 处理自己的工程
- 几种经典的网络服务器架构模型的分析与比较
- 三层架构各层关系分析比较及其约定
- 几种经典的网络服务器架构模型的分析与比较
- Makefile之大型工程项目子目录Makefile的一种通用写法
- 几种经典的网络服务器架构模型的分析与比较
- 几种经典的网络服务器架构模型的分析与比较
- 【分享】通用强大的主数据管理系统(最终分享版本)架构分析及源码下载
- APUE(UNIX环境高级编程)程序架构编译分析(Makefile)
- 比较通用makefile文件写法
- 一个比较通用的makefile,写一些小程序基本够用了
- Makefile之大型工程项目子目录Makefile的一种通用写法
- 三层架构各层关系分析比较及其约定