1:Makefile的作用
Makefile是UNIX下make工具的配置文件,其目的是实现项目的管理,主要包括编译、链接、打包、发布、清除等。Makefile中需要指定make的目标以及完成这些目标需要的规则和依赖关系。关于Makefile的更详细的介绍,可以参考http://blog.csdn.net/ruglcc/article/details/7814546 以及GNU Make 手册。
1.1 GNU make的工作方式
1. 读入所有的Makefile。
2. 读入被include的其它Makefile。
3. 初始化文件中的变量。
4. 推导隐晦规则,并分析所有规则。
5. 为所有的目标文件创建依赖关系链。
6. 执行生成命令。根据依赖关系,决定哪些目标要重新生成。
1.2 Makefile的书写规则
makefile中的规则描述如何生成特定的文件,即规则的目标。规则列出了目标的依赖文件,指定生成或更新目标的命令。规则的次序是不重要的,除非是确定缺省目标:缺省目标是第一个makefile中的第一个规则;
如果第一个规则有多个目标,第一个目标是缺省的。有两个例外:以’.’开头的目标不是缺省目标;模式规则对缺省目标没有影响。通常我们所写的地一个规则是编译整个或makefile中指定的所有程序。基本语法如下:
Targets : prerequisites ...
command
其中第一行指明目标及其依赖关系,第二行是生成这个目标需要执行的命令,必须用Tab开头。也可以书写成 Targets : prerequisites;command ;其可读性较差,一般不推荐。
规则中可以使用通配符,主要包括 '*' '?' '[...]' '~'.同UNIX shell用法。还可以使用Makefile内置函数,以及自动化变量等。
1.2.1 Makefile内置函数
$(subst <from>,<to>,<text> ) 把字串<text>中的<from>字符串替换成<to>
$(patsubst <pattern>,<replacement>,<text> ) 查找<text>中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式<pattern>,如果匹配的话,则以<replacement>替换
$(strip <string> ) 去掉<string>字串中开头和结尾的空字符
$(findstring <find>,<in> ) 在字串<in>中查找<find>字串
$(filter <pattern...>,<text> ) 以<pattern>模式过滤<text>字符串中的单词,保留符合模式<pattern>的单词
$(filter-out <pattern...>,<text> ) 以<pattern>模式过滤<text>字符串中的单词,去除符合模式<pattern>的单词
$(sort <list> ) 给字符串<list>中的单词排序(升序)
$(dir <names...> ) 从文件名序列<names>中取出目录部分。目录部分是指最后一个反斜杠(“/”)之前的部分。如果没有反斜杠,那么返回“./”
$(notdir <names...> ) 从文件名序列<names>中取出非目录部分。非目录部分是指最后一个反斜杠(“/”)之后的部分
$(suffix <names...> ) 从文件名序列<names>中取出各个文件名的后缀
$(basename <names...> ) 从文件名序列<names>中取出各个文件名的前缀部分
$(addsuffix <suffix>,<names...> ) 把后缀<suffix>加到<names>中的每个单词后面
$(addprefix <prefix>,<names...> ) 把前缀<prefix>加到<names>中的每个单词后面
$(join <list1>,<list2> ) 把<list2>中的单词对应地加到<list1>的单词后面。如果<list1>的单词个数要比<list2>的多,那么,<list1>中的多出来的单词将保持原样。
如果<list2>的单词个数要比<list1>多,那么,<list2>多出来的单词将被复制到<list2>中
$(foreach <var>,<list>,<text> ) <span style="white-space:pre"> </span>把参数<list>中的单词逐一取出放到参数<var>所指定的变量中,然后再执行<text>所包含的表达式
$(if <condition>,<then-part> ) 和 $(if <condition>,<then-part>,<else-part> )
$(call <expression>,<parm1>,<parm2>,<parm3>...) 用来创建新的参数化的函数
1.2.2 Makefile自动化变量
$@ 表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于目标中模式定义的集合。$% 仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a(bar.o)",那么,"$%"就是"bar.o","$@"就是"foo.a"。如果目标不是函数库文件(Unix下是[.a],Windows下是[.lib]),那么,其值为空。
$< 依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
$? 所有比目标新的依赖目标的集合。以空格分隔。
$^ 所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。
$+ 这个变量很像"$^",也是所有依赖目标的集合。只是它不去除重复的依赖目标。
$* 这个变量表示目标模式中"%"及其之前的部分。如果目标是"dir/a.foo.b",并且目标的模式是"a.%.b",那么,"$*"的值就是"dir /a.foo"。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么"$*"也就不能被推导出,
但是,如果目标文件的后缀是 make所识别的,那么"$*"就是除了后缀的那一部分。例如:如果目标是"foo.c",因为".c"是make所能识别的后缀名,所以,"$*"的值就是"foo"。
这个特性是GNU make的,很有可能不兼容于其它版本的make,所以,你应该尽量避免使用"$*",除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么"$*"就是空值。
2:Makefile模板分块介绍
假定一个C项目在主目录下有一个sakura的子目录,该项目需要编译成库并且接口API位于主目录的头文件中。完整Makefile见附件,下面分段介绍该Makefile的作用。2.1 常用变量定义
Makefile中可以定义自定义变量,并在之后引用这些变量的值。如下所示,MARCH是指机器位数,根据操作系统选择-m32 或者 -m64。strip
#1 Compiler, tools and options
CC = gcc
CXX = g++
MARCH = -m32
CFLAGS = $(MARCH) -pipe -Wall -W -fPIC
CXXFLAGS = $(MARCH) -pipe -Wall -W -fPIC
DEL_FILE = rm -f
DEL_R = rm -rf
MKDIR = mkdir -p
DEL_DIR = rmdir
MOVE = mv -f
COPY = cp -f
COPY_FILE = cp -f
COPY_DIR = cp -f -R
INSTALL_FILE = install -m 644 -p
INSTALL_PROGRAM = install -m 755 -p
INSTALL_DIR = cp -f -R
SYMLINK = ln -f -s
TAR = tar -cf
COMPRESS = gzip -9f
LINK = gcc
AR = ar cqs
SED = sed
STRIP = strip
2.2 项目相关的选项
如下所示,主要是编译开关,额外的编译选项,链接选项,以及项目的依赖库。
#2 Program specifical options
DEFINES = -DSAKURA
CFLAGS += -std=gnu99 -Wno-pointer-sign -Wno-unused-variable -Wno-unused-parameter $(DEFINES)
LFLAGS = $(MARCH) -shared -Wl,-soname,libsakura.so.1 //指定soname,方便so版本管理。如果编译可执行程序,-shared及-Wl可不需要。
LIBS = -L/usr/local/lib -Xlinker -rpath=/usr/local/lib -lpthread
2.3 编译目标
如下所示,如果编 译可执行程序,TARGETA,TARGET0,TARGET1,TARGET2均可去掉。
#3 Target
TARGET = libsakura.so.1.0.0 //动态链接库
TARGETA = libsakura.a //静态链接库
TARGET0 = libsakura.so
TARGET1 = libsakura.so.1
TARGET2 = libsakura.so.1.0
2.4 工程的头文件路径
如下所示,包括本项目的头文件路径以及第三方SDK的头文件路径。
#4 Header file path
INCDIR = . //当前项目目录
INCDIR += ./sakura //当前项目子目录,依项目而定
INCDIR += /usr/local/include/sakura
INCPATH = $(addprefix -I, $(INCDIR)) //用-I连接路径
2.5 源代码文件路径
此处需指定包含源代码全部目录,目录之间用空格分离。也可通过shell命令遍历源代码主目录获得,或者通过分级Makefile实现,有兴趣者可以自己实现。
#5 Source file path
VPATH = . ./sakura //源代码目录及其子目录,用空格分离
SRC_PATH = $(foreach dir,$(VPATH),$(wildcard $(dir)/*.c)) //遍历源代码目录,获取其中的.c文件
2.6 中间文件及其目录
obj文件以及.d文件(存储obj目标的依赖关系)的生成目录
<pre name="code" class="html">#6 Output dir and files
OBJ_DIR = ./objs
OBJS = $(addprefix $(OBJ_DIR)/,$(subst .c,.o, $(SRC_PATH))) //为每个源代码文件指定一个.o文件
OBJ_PATH = $(addprefix $(OBJ_DIR)/,$(VPATH))
MAKE_OBJECT_DIR := $(shell mkdir -p $(OBJ_DIR) $(OBJ_PATH)) //创建./objs目录,并且和项目保持相同的目录结构</pre>
<pre></pre>
<br>
<span style="white-space:pre"></span>2.7 编译目标<br>
<span style="white-space:pre"></span>make的默认目标<br>
<pre name="code" class="html">#7 Default target
first: all</pre><br>
<span style="white-space:pre"></span>2.8<span style="white-space:pre"> </span>obj文件依赖关系的自动生成<br>
<span style="white-space:pre"></span>使用gcc -MM 自动推导依赖关系并输出到相应的.d文件。<br>
<pre name="code" class="html">#8 Obj dependence
DEPS = $(OBJS:.o=.d)
include $(DEPS)
$(OBJ_DIR)/%.d : %.c
$(CC) -MM $(INCPATH) $< > $@ //使用gcc -MM 自动推导依赖关系并输出到相应的.d文件</pre><br>
<span style="white-space:pre"></span>2.9 obj文件的编译规则<br>
<span style="white-space:pre"></span>由于2.8中生成的.d文件中有对应.o文件的依赖关系,因此此处无需指定依赖关系。<br>
<pre name="code" class="html">#9 Compile rules
$(OBJ_DIR)/%.o :
$(CC) -o $@ -c $< $(INCPATH) $(CFLAGS)</pre><span style="white-space:pre"> </span><br>
<span style="white-space:pre"></span>2.10 编译规则<br>
<span style="white-space:pre"></span>编译目标库并生成符号链接。<br>
<pre name="code" class="html">#10 Build rules
$(TARGET): $(OBJS)
-$(DEL_FILE) $(TARGET) $(TARGET0) $(TARGET1) $(TARGET2)
$(LINK) $(LFLAGS) -o $(TARGET) $(OBJS) $(LIBS)
-ln -s $(TARGET) $(TARGET0)
-ln -s $(TARGET) $(TARGET1)
-ln -s $(TARGET) $(TARGET2)
$(TARGETA): $(OBJS)
-$(DEL_FILE) $(TARGETA)
$(AR) $(TARGETA) $(OBJS)</pre><span style="white-space:pre"> </span><br>
<span style="white-space:pre"></span>2.11 常用目标<br>
<span style="white-space:pre"></span>顾名思义。<br>
<pre name="code" class="html">all: $(TARGET)
static: $(TARGETA)
clean:
-$(DEL_R) $(OBJ_DIR)
distclean:
-$(DEL_R) $(OBJ_DIR)
-$(DEL_FILE) $(TARGET)
-$(DEL_FILE) $(TARGETA)
-$(DEL_FILE) $(TARGET0)
-$(DEL_FILE) $(TARGET1)
-$(DEL_FILE) $(TARGET2)</pre><br>
<span style="white-space:pre"></span>2.12 安装和卸载<br>
<span style="white-space:pre"></span>此处变量一般可通过configure生成,不过自用的私有库,直接指定安装目录即可。<br>
<pre name="code" class="html"># Install header files
INSTALL_INC_FILE = $(wildcard *.h)
INSTALL_INC_PATH = /usr/local/include/libsakura
install_inc:
@test -d $(INSTALL_INC_PATH) || mkdir -p $(INSTALL_INC_PATH)
-$(INSTALL_FILE) $(INSTALL_INC_FILE) $(INSTALL_INC_PATH)
uninstall_inc:
-$(DEL_FILE) -r $(foreach file,$(INSTALL_INC_FILE),$(addprefix $(INSTALL_INC_PATH)/, $(file)))
-$(DEL_DIR) $(INSTALL_INC_PATH)
# Install libs
INSTALL_LIB_PATH = /usr/local/lib/sakura
install_lib: $(TARGET)
@test -d $(INSTALL_LIB_PATH) || mkdir -p $(INSTALL_LIB_PATH)
-$(INSTALL_PROGRAM) $(TARGET) $(INSTALL_LIB_PATH)/$(TARGET)
-$(SYMLINK) $(INSTALL_LIB_PATH)/$(TARGET) $(INSTALL_LIB_PATH)/$(TARGET0)
-$(SYMLINK) $(INSTALL_LIB_PATH)/$(TARGET) $(INSTALL_LIB_PATH)/$(TARGET1)
-$(SYMLINK) $(INSTALL_LIB_PATH)/$(TARGET) $(INSTALL_LIB_PATH)/$(TARGET2)
uninstall_lib:
-$(DEL_FILE) $(INSTALL_LIB_PATH)/$(TARGET)
-$(DEL_FILE) $(INSTALL_LIB_PATH)/$(TARGET0)
-$(DEL_FILE) $(INSTALL_LIB_PATH)/$(TARGET1)
-$(DEL_FILE) $(INSTALL_LIB_PATH)/$(TARGET2)
install: install_inc install_lib
uninstall: uninstall_inc uninstall_lib</pre><br>
<br>