分类: 其他平台
2015-03-17 14:41:20
在Makefile中我们经常看到 = := ?= +=这几个赋值运算符,那么他们有什么区别呢?我们来做个简单的实验
新建一个Makefile,内容为:
ifdef DEFINE_VRE
VRE = “Hello World!”
else
endif
ifeq ($(OPT),define)
VRE ?= “Hello World! First!”
endif
ifeq ($(OPT),add)
VRE += “Kelly!”
endif
ifeq ($(OPT),recover)
VRE := “Hello World! Again!”
endif
all:
@echo $(VRE)
敲入以下make命令:
make DEFINE_VRE=true OPT=define 输出:Hello World!
make DEFINE_VRE=true OPT=add 输出:Hello World! Kelly!
make DEFINE_VRE=true OPT=recover 输出:Hello World! Again!
make DEFINE_VRE= OPT=define 输出:Hello World! First!
make DEFINE_VRE= OPT=add 输出:Kelly!
make DEFINE_VRE= OPT=recover 输出:Hello World! Again!
从上面的结果中我们可以清楚的看到他们的区别了
= 是最基本的赋值
:= 是覆盖之前的值
?= 是如果没有被赋值过就赋予等号后面的值,因此常用赋值默认值
+= 是添加等号后面的值
之前一直纠结makefile中“=”和“:=”的区别到底有什么区别,因为给变量赋值时,两个符号都在使用。网上搜了一下,有人给出了解答,但是本人愚钝,看不懂什么意思。几寻无果之下,也就放下了。今天看一篇博客,无意中发现作者对于这个问题做了很好的解答。解决问题之余不免感叹,有时候给个例子不就清楚了吗?为什么非要说得那么学术呢。^_^
1、“=”
make会将整个makefile展开后,再决定变量的值。也就是说,变量的值将会是整个makefile中最后被指定的值。看例子:
x = foo
y = $(x) bar
x = xyz
在上例中,y的值将会是 xyz bar ,而不是 foo bar 。
2、“:=”
“:=”表示变量的值决定于它在makefile中的位置,而不是整个makefile展开后的最终值。
x := foo
y := $(x) bar
x := xyz
在上例中,y的值将会是 foo bar ,而不是 xyz bar 了。
$? :表示比目标的还要新的那些依赖文件列表
$@ :扩展成当前规则的目的文件名。
$< :扩展成当前规则依靠列表中的第一个依靠文件名。
$^: 扩展成当前规则整个依靠列表。
$* :去掉后缀的当前目标名。例如,若当前目标是pro.o,则$*表示pro。
$*: 这个变量表示目标模式中"%"及其之前的部分。如果目标是"dir/a.foo.b",并且目标的模式是"a.%.b",那么,"$*"的值就是"dir/a.foo"。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么"$*"也就不能被推导出,但是,如果目标文件的后缀是make所识别的,那么"$*"就是除了后缀的那一部分。例如:如果目标是"foo.c",因为".c"是make所能识别的后缀名,所以,"$*"的值就是"foo"。这个特性是GNU make的,很有可能不兼容于其它版本的make,所以,你应该尽量避免使用"$*",除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么"$*"就是空值。
支持特别符号:"%":匹配零或多个字符,如 %.c 表示所有以 .c 结尾的文件
make/makefile中的加号+,减号-和at号@的含义
【make中命令行前面加上减号】
就是,忽略当前此行命令执行时候所遇到的错误。
而如果不忽略,make在执行命令的时候,如果遇到error,会退出执行的,加上减号的目的,是即便此行命令执行中出错,比如删除一个不存在的文件等,那么也不要管,继续执行make。
【make中命令行前面加上at符号@】
就是,在make执行时候,输出的信息中,不要显示此行命令。
而正常情况下,make执行过程中,都是会显示其所执行的任何的命令的。如果你不想要显示某行的命令,那么就在其前面加上@符号即可。
【make中命令行前面加上加号+】
对于命令行前面加上加号+的含义,目前还是不是很清楚。
比如:
archclean:
@$(MAKEBOOT) clean
或者
checkbin:
@ /bin/ture
@ true
$(MAKEBOOT) 是变量 MAKEBOOT 的值,
@表示在make时不输出make的信息(类似Windows下的echo off)。
$%: 仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a (bar.o)",那么,"$%"就是"bar.o","$@"就是"foo.a"。如果目标不是函数库文件(Unix下是[.a],Windows下是[.lib]),那么,其值为空。
$<: 依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
变量名 | 说明 |
---|---|
AR | 静态库打包命令的名字,缺省值是ar。 |
ARFLAGS | 静态库打包命令的选项,缺省值是rv。 |
AS | 汇编器的名字,缺省值是as。 |
ASFLAGS | 汇编器的选项,没有定义。 |
CC | C编译器的名字,缺省值是cc。 |
CFLAGS | C编译器的选项,没有定义。 |
CXX | C++编译器的名字,缺省值是g++。 |
CXXFLAGS | C++编译器的选项,没有定义。 |
CPP | C预处理器的名字,缺省值是$(CC) -E。 |
CPPFLAGS | C预处理器的选项,没有定义。 |
LD | 链接器的名字,缺省值是ld。 |
LDFLAGS | 链接器的选项,没有定义。 |
TARGET | _ARCH 和目标平台相关的命令行选项,没有定义。 |
OUTPUT | _OPTION 输出的命令行选项,缺省值是-o $@。 |
LINK.o | 把.o文件链接在一起的命令行,缺省值是$(CC) $(LDFLAGS) $(TARGET_ARCH)。 |
LINK.c | 把.c文件链接在一起的命令行,缺省值是$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)。 |
LINK.cc | 把.cc文件(C++源文件)链接在一起的命令行,缺省值是$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)。 |
COMPILE.c | 编译.c文件的命令行,缺省值是$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c。 |
COMPILE.cc | 编译.cc文件的命令行,缺省值是$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c。 |
RM | 删除命令的名字,缺省值是rm -f。 |
函数调用,很像变量的使用,也是以“$”来标识的,其语法如下:
$(
这里,
还是来看一个示例:
comma:= ,empty:=space:= $(empty) $(empty)foo:= a b cbar:= $(subst $(space),$(comma),$(foo))
在这个示例中,$(comma)的值是一个逗号。$(space)使用了$(empty)定义了一个空格,$(foo)的值是“a b c”,$(bar)的定义用,调用了函数“subst”,这是一个替换函数,这个函数有三个参数,第一个参数是被替换字串,第二个参数是替换字串,第三个参数是替换操作作用的字串。这个函数也就是把$(foo)中的空格替换成逗号,所以$(bar)的值是“a,b,c”。
$(subst
$(patsubst
备注: 这和我们前面“变量章节”说过的相关知识有点相似。
如:
$(var:
而 $(var:
例如有: objects = foo.o bar.o baz.o
那么, $(objects:.o.c)= 和 $(patsubst %.o,%.c,$(objects)) 是一样的。
$(strip
$(findstring
$(findstring a,a b c)
$(findstring a,b c)
第一个函数返回“a”字符串,第二个返回“”字符串(空字符串)
$(filter
sources := foo.c bar.c baz.s ugh.h
foo: $(sources)cc $(filter %.c %.s,$(sources)) -o foo
`$(filter %.c %.s,$(sources))` 返回的值是“foo.c bar.c baz.s”。
$(filter-out
objects=main1.o foo.o main2.o bar.o
mains=main1.o main2.o
`$(filter-out $(mains),$(objects))` 返回值是“foo.o bar.o”。
$(sort )
$(word
$(wordlist ,
$(words
$(firstword
以上,是所有的字符串操作函数,如果搭配混合使用,可以完成比较复杂的功能。
这里,举一个现实中应用的例子。我们知道,make 使用“VPATH”变量来指定“依赖文件”的搜索路径。于是,我们可以利用这个搜索路径来指定编译器对头文件的搜索路径参数 CFLAGS ,如:
override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH)))
如果我们的“$(VPATH)”值是“src:../headers”,那么 $(patsubst %,-I%,$(subst :, ,$(VPATH))) 将返回 -Isrc -I../headers ,这正是 cc 或 gcc 搜索头文件路径的参数。
下面我们要介绍的函数主要是处理文件名的。每个函数的参数字符串都会被当做一个或是一系列的文件名来对待。
$(dir
$(notdir
$(suffix
$(basename
$(addsuffix
$(addprefix
$(join
foreach 函数和别的函数非常的不一样。因为这个函数是用来做循环用的,Makefile中的 foreach 函数几乎是仿照于 Unix 标准 Shell(/bin/sh)中的 for 语句,或是 C-Shell (/bin/csh)中的 foreach 语句而构建的。它的语法是:
$(foreach ,,
这个函数的意思是,把参数中的单词逐一取出放到参数所指定的变量中,然后再执行
所以,最好是一个变量名,可以是一个表达式,而
中的单词。举个例子:
names := a b c dfiles := $(foreach n,$(names),$(n).o)
上面的例子中,$(name)中的单词会被挨个取出,并存到变量“n”中,“$(n).o”每次根据“$(n)”计算出一个值,这些值以空格分隔,最后作为 foreach 函数的返回,所以,$(files)的值是“a.o b.o c.o d.o”。
注意,foreach 中的参数是一个临时的局部变量,foreach 函数执行完后,参数的变量将不在作用,其作用域只在 foreach 函数当中。
if 函数很像 GNU 的 make 所支持的条件语句——ifeq,if 函数的语法是:
$(if
或是:
$(if
可见,if 函数可以包含“else”部分,或是不含。即 if 函数的参数可以是两个,也可以是三个。
而 if 函数的返回值是,如果
所以,
call 函数是唯一一个可以用来 创建新的参数化的函数 。你可以写一个非常复杂的表达式,这个表达式中,你可以定义许多参数,然后你可以用 call 函数来向这个表达式传递参数。其语法是:
$(call
当 make 执行这个函数时,
reverse = $(1) $(2)foo = $(call reverse,a,b)
那么,foo 的值就是“a b”。当然,参数的次序是可以自定义的,不一定是顺序的,如:
reverse = $(2) $(1)foo = $(call reverse,a,b)
此时的 foo 的值就是“b a”。
origin 函数不像其它的函数,他并不操作变量的值,他只是告诉你你的这个变量是哪里来的。
其语法是:
$(origin
注意,
“automatic” 如果
这些信息对于我们编写 Makefile 是非常有用的,例如,假设我们有一个 Makefile 其包了一个定义文件 Make.def,在 Make.def 中定义了一个变量“bletch”,而我们的环境中也有一个环境变量“bletch”,此时,我们想判断一下,如果变量来源于环境,那么我们就把之重定义了,如果来源于 Make.def 或是命令行等非环境的,那么我们就不重新定义它。于是,在我们的 Makefile 中,BEGIN:
ifdef bletch ifeq “$(origin bletch)” “environment” bletch = barf, gag, etc. endif endif
当然,你也许会说,使用 override 关键字不就可以重新定义环境中的变量了吗?为什么需要使用这样的步骤?是的,我们用 override 是可以达到这样的效果,可是 override 过于粗暴,它同时会把从命令行定义的变量也覆盖了,而我们只想重新定义环境传来的,而不想重新定义命令行传来的。
shell 函数也不像其它的函数。顾名思义,它的参数应该就是操作系统 Shell 的命令。它和反引号 ` 是相同的功能。这就是说, shell 函数把执行操作系统命令后的输出作为函数返回 。于是,我们可以用操作系统命令以及字符串处理命令 awk,sed 等等命令来生成一个变量,如:
contents := $(shell cat foo)files := $(shell echo *.c)
注意,这个函数会新生成一个 Shell 程序来执行命令,所以你要注意其运行性能,如果你的Makefile 中有一些比较复杂的规则,并大量使用了这个函数,那么对于你的系统性能是有害的。特别是 Makefile 的隐晦的规则可能会让你的 shell 函数执行的次数比你想像的多得多。
make 提供了一些函数来控制 make 的运行。通常,你需要检测一些运行 Makefile 时的运行时信息,并且根据这些信息来决定,你是让 make 继续执行,还是停止。
$(error
产生一个致命的错误,
示例一:
ifdef ERROR_001$(error error is $(ERROR_001))endif
示例二:
ERR = $(error found an error!).PHONY: errerr: ; $(ERR)
示例一会在变量 ERROR001 定义了后执行时产生 error 调用,而示例二则在目标 err 被执行时才发生 error 调用。
$(warning
这个函数很像 error 函数,只是它并不会让 make 退出,只是输出一段警告信息,而 make 继续执行。
静态模式可以 更加容易地定义多目标的规则 ,可以让我们的规则变得更加的有弹性和灵活。我们还是先来看一下语法:
: : ....
这样描述这三个东西,可能还是没有说清楚,还是举个例子来说明一下吧。如果我们的
所以,我们的“目标模式”或是“依赖模式”中都应该有 % 这个字符,如果你的文件名中有 % 那么你可以使用反斜杠“\”进行转义,来标明真实的%字符。
看一个例子:
objects = foo.o bar.oCFLAGS = $(include_dirs) -Oinclude_dirs = -Ifoo -Ibarall: $(objects)$(objects): %.o: %.c$(CC) -c $(CFLAGS) $< -o $@
上面的例子中,指明了我们的目标从$object 中获取,“%.o”表明要所有以“.o”结尾的目标,也就是“foo.o bar.o”,也就是变量 $object 集合的模式,而依赖模式“%.c”则取模式“%.o”的 %,也就是“foo bar”,并为其加下“.c”的后缀,于是,我们的依赖目标就是“foo.c bar.c”。于是,上面的规则展开后等价于下面的规则:
foo.o : foo.c$(CC) -c $(CFLAGS) foo.c -o foo.obar.o : bar.c$(CC) -c $(CFLAGS) bar.c -o bar.o
试想,如果我们的“%.o”有几百个,那种我们只要用这种很简单的“静态模式规则”就可以写完一堆规则,实在是太有效率了。“静态模式规则”的用法很灵活,如果用得好,那会一个很强大的功能。
可以用gcc的-M选项自动生成目标文件和源文件的依赖关系。例如:
$ gcc -M main.cmain.o: main.c /usr/include/stdio.h /usr/include/features.h \/usr/include/sys/cdefs.h /usr/include/bits/wordsize.h \/usr/include/gnu/stubs.h /usr/include/gnu/stubs-32.h \/usr/lib/gcc/i486-linux-gnu/4.3.2/include/stddef.h \/usr/include/bits/types.h /usr/include/bits/typesizes.h \/usr/include/libio.h /usr/include/_G_config.h /usr/include/wchar.h \/usr/lib/gcc/i486-linux-gnu/4.3.2/include/stdarg.h \/usr/include/bits/stdio_lim.h /usr/include/bits/sys_errlist.h main.h \stack.h maze.h
-M选项把stdio.h以及它所包含的系统头文件也找出来了,如果我们不需要输出系统头文件的依赖关系,可以用-MM选项:
然后可以把这些规则包含到 Makefile 中,例如:$ gcc -MM *.cmain.o: main.c main.h stack.h maze.hmaze.o: maze.c maze.h main.hstack.o: stack.c stack.h main.h
all: main
main: main.o stack.o maze.o
gcc $^ -o $@
clean:
-rm main *.o
.PHONY: clean
sources = main.c stack.c maze.c
include $(sources:.c=.d)
%.d: %.cset -e; rm -f $@; \$(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \rm -f $@.$$$$
sources变量包含我们要编译的所有.c文件,$(sources:.c=.d)是一个变量替换语法,把sources变量中每一项的.c替换成.d,所以include这一句相当于:
include main.d stack.d maze.d
类似于C语言的#include指示,这里的include表示包含三个文件main.d、stack.d和maze.d,这三个文件也应该符合Makefile的语法。如果现在你的工作目录是干净的,只有.c文件、.h文件和Makefile,运行make的结果是:
$ makeMakefile:13: main.d: No such file or directoryMakefile:13: stack.d: No such file or directoryMakefile:13: maze.d: No such file or directoryset -e; rm -f maze.d; \cc -MM maze.c > maze.d.$$; \sed 's,\(maze\)\.o[ :]*,\1.o maze.d : ,g' < maze.d.$$ > maze.d; \rm -f maze.d.$$set -e; rm -f stack.d; \cc -MM stack.c > stack.d.$$; \sed 's,\(stack\)\.o[ :]*,\1.o stack.d : ,g' < stack.d.$$ > stack.d; \rm -f stack.d.$$set -e; rm -f main.d; \cc -MM main.c > main.d.$$; \sed 's,\(main\)\.o[ :]*,\1.o main.d : ,g' < main.d.$$ > main.d; \rm -f main.d.$$cc -c -o main.o main.ccc -c -o stack.o stack.ccc -c -o maze.o maze.cgcc main.o stack.o maze.o -o main
一开始找不到.d文件,所以make会报警告。但是make会把include的文件名也当作目标来尝试更新,而这些目标适用模式规则%.d: %c,所以执行它的命令列表。
-n选项只打印要执行的命令,而不会真的执行命令,这个选项有助于我们检查Makefile写得是否正确,由于Makefile不是顺序执行的,用这个选项可以先看看命令的执行顺序,确认无误了再真正执行命令。
-C选项可以切换到另一个目录执行那个目录下的Makefile,比如先退到上一级目录再执行我们的Makefile(假设我们的源代码都放在testmake目录下):
$ cd ..$ make -C testmakemake: Entering directory `/home/akaedu/testmake'cc -c -o main.o main.ccc -c -o stack.o stack.ccc -c -o maze.o maze.cgcc main.o stack.o maze.o -o mainmake: Leaving directory `/home/akaedu/testmake'
一些规模较大的项目会把不同的模块或子系统的源代码放在不同的子目录中,然后在每个子目录下都写一个该目录的Makefile,然后在一个总的Makefile中用make -C命令执行每个子目录下的Makefile。例如Linux内核源代码根目录下有Makefile,子目录fs、net等也有各自的Makefile,二级子目录fs/ramfs、net/ipv4等也有各自的Makefile。
在make命令行也可以用=或:=定义变量,如果这次编译我想加调试选项-g,但我不想每次编译都加-g选项,可以在命令行定义CFLAGS变量,而不必修改Makefile编译完了再改回来:
如果在Makefile中也定义了CFLAGS变量,则命令行的值覆盖Makefile中的值$ make CFLAGS=-gcc -g -c -o main.o main.ccc -g -c -o stack.o stack.ccc -g -c -o maze.o maze.cgcc main.o stack.o maze.o -o main
1:编译可执行程序。2:编译lib库 3:编译so库
本博针对上面三种目的各自写出了makefile模版,希望对大家有所帮助。
一.编译可执行程序
当前目录下制定文件编译成可执行文件(连接外部库的话只需要更改INC和LIB即可)
CXX = g++
TARGET = bitmaploctest
C_FLAGS += -g -Wall
LIB_FLAGS = -pthread
all: $(TARGET)
bitmaploctest: bitmaploctest.o bitmaploc.o file_lock.o
$(CXX) -o $@ $^ $(LIB_FLAGS) $(LIB) $(C_FLAGS)
.cpp.o:
$(CXX) -c -o $*.o $(INC) $(C_FLAGS) $*.cpp
.cc.o:
$(CXX) -c -o $*.o $(INC) $(C_FLAGS) $*.cc
clean:
-rm -f *.o $(TARGET)
二.编译成lib库
当前目录下指定文件编译成lib库(一般lib库在编译的时候不会将使用的外部库编译进来,而是等编译成可执行程序时或者.so时)
INC_DIR= ./
SRC_DIR= ./
OBJ_DIR= ./
LIB_DIR= ./
H_DIR= ./
OBJ_EXT= .o
CXXSRC_EXT= .cpp
CSRC_EXT= .c
LIB_EXT= .a
H_EXT= .h
OBJECTS = $(OBJ_DIR)bitmaploc$(OBJ_EXT) \
$(OBJ_DIR)file_lock$(OBJ_EXT)
LIB_TARGET = $(LIB_DIR)libbitmaploc$(LIB_EXT)
$(OBJ_DIR)%$(OBJ_EXT): $(SRC_DIR)%$(CXXSRC_EXT)
@echo
@echo “Compiling $< ==> $@…”
$(CXX) $(INC) $(C_FLAGS) -c $< -o $@
$(OBJ_DIR)%$(OBJ_EXT): $(SRC_DIR)%$(CSRC_EXT)
@echo
@echo “Compiling $< ==> $@…”
$(CC) -I./ $(INC) $(C_FLAGS) -c $< -o $@
all:$(LIB_TARGET)
$(LIB_TARGET): $(OBJECTS)
all: $(OBJECTS)
@echo
$(AR) rc $(LIB_TARGET) $(OBJECTS)
@echo “ok”
clean:
rm -f $(LIB_TARGET) $(OBJECTS)
三.编译成so库
当前目录下指定文件编译成so库(必须将所有引用的外部库都编译进来)
CC = gcc
CXX = g++
CFLAGS = -Wall -pipe -DDEBUG -D_NEW_LIC -g -D_GNU_SOURCE -shared -D_REENTRANT
LIB = -lconfig -ldl -lrt -L../../lib -lttc -g
INCLUDE = -I../spp_inc
OO = service.o tinystr.o tinyxml.o tinyxmlerror.o tinyxmlparser.o uin_conf.o stat.o
TARGETS = ../../lib/libRanch.so
all: $(TARGETS)
stat:tool_stat.cpp
$(CXX) $(INCLUDE) tool_stat.cpp -o tool_stat stat.o tinystr.o tinyxml.o tinyxmlerror.o tinyxmlparser.o -g
cp tool_stat ../../bin
$(TARGETS): $(OO)
$(CXX) $(CFLAGS) $(INCLUDE) $(OO) -o $@ $(LIBDIR) $(LIB)
.c.o:
$(CC) $(CFLAGS) -c $(INCLUDE) $<
echo $@
.cpp.o:
$(CXX) $(CFLAGS) -c $(INCLUDE) $<
echo $@
%:%.c
$(CC) $(CFLAGS) -o $@ $< $(OO) $(LDFLAGS)
echo $@
clean:
rm -f *.o
rm -f $(TARGETS)
rm -f tool_stat
OK,我常用的makefile也就这三种格式,希望对大家有用。