Chinaunix首页 | 论坛 | 博客
  • 博客访问: 494178
  • 博文数量: 133
  • 博客积分: 1235
  • 博客等级: 少尉
  • 技术积分: 1201
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-08 19:59
文章分类

全部博文(133)

文章存档

2023年(12)

2022年(3)

2018年(2)

2017年(4)

2016年(4)

2015年(42)

2014年(1)

2013年(12)

2012年(16)

2011年(36)

2010年(1)

分类: 其他平台

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]),那么,其值为空。

$<: 依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。

其它举例:

%.so: %.c

$(CC) -fPIC -shared -o $@ $<

第一句的意思是所由的.so文件都倚赖相应的.c文件.
第二句里的$@和$<分别是什么意思呢?$@:目标文件



常用变量

变量名 说明
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。

函数

函数的调用语法

函数调用,很像变量的使用,也是以“$”来标识的,其语法如下:

$( )

或是
${ }


这里,就是函数名,make 支持的函数不多。是函数的参数,参数间以逗号“,”分隔,而函数名和参数之间以“空格”分隔。函数调用以 “$” 开头,以 圆括号或花括号 把函数名和参数括起。感觉很像一个变量,是不是?函数中的参数可以使用变量,为了风格的统一,函数和变量的括号最好一样,如使用 $(subst a,b,$(x)) 这样的形式,而不是 $(subst a,b,${x}) 的形式。因为统一会更清楚,也会减少一些不必要的麻烦。

还是来看一个示例:


comma:= , 
empty:=
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))


在这个示例中,$(comma)的值是一个逗号。$(space)使用了$(empty)定义了一个空格,$(foo)的值是“a b c”,$(bar)的定义用,调用了函数“subst”,这是一个替换函数,这个函数有三个参数,第一个参数是被替换字串,第二个参数是替换字串,第三个参数是替换操作作用的字串。这个函数也就是把$(foo)中的空格替换成逗号,所以$(bar)的值是“a,b,c”。

字符串处理函数

subst

$(subst ,,)

  • 名称:字符串替换函数——subst。
  • 功能:把字串中的字符串替换成
  • 返回:函数返回被替换过后的字符串。
  • 示例: $(subst ee,EE,feet on the street) 把“feet on the street”中的“ee”替换成“EE”,返回结果是“fEEt on the strEEt”。

patsubst

$(patsubst ,,)

  • 名称:模式字符串替换函数——patsubst。
  • 功能:查找中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式,如果匹配的话,则以替换。这里,可以包括通配符%,表示任意长度的字串。如果中也包含%,那么,中的这个%将是中的那个%所代表的字串。(可以用“\”来转义,以\%来表示真实含义的%字符)
  • 返回:函数返回被替换过后的字符串。
  • 示例: $(patsubst %.c,%.o,x.c.c bar.c) 把字串“x.c.c bar.c”符合模式[%.c]的单词替换成[%.o],返回结果是“x.c.o bar.o”
  • 备注: 这和我们前面“变量章节”说过的相关知识有点相似。

    如:

    $(var:=) 相当于 $(patsubst ,,$(var))

    而 $(var: =) 则相当于 $(patsubst %,%,$(var)) 。

    例如有: objects = foo.o bar.o baz.o

    那么, $(objects:.o.c)= 和 $(patsubst %.o,%.c,$(objects)) 是一样的。

strip

$(strip )

  • 名称:去空格函数——strip。
  • 功能:去掉字串中开头和结尾的空字符。
  • 返回:返回被去掉空格的字符串值。
  • 示例: $(strip a b c ) 把字串“a b c”去到开头和结尾的空格,结果是“a b c”。

findstring

$(findstring ,)

  • 名称:查找字符串函数——findstring。
  • 功能:在字串中查找字串。
  • 返回:如果找到,那么返回,否则返回空字符串。
  • 示例:

$(findstring a,a b c)
$(findstring a,b c)

第一个函数返回“a”字符串,第二个返回“”字符串(空字符串)

filter

$(filter ,)

  • 名称:过滤函数——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

$(filter-out ,)

  • 名称:反过滤函数——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

$(sort )

  • 名称:排序函数——sort。
  • 功能:给字符串中的单词排序(升序)。
  • 返回:返回排序后的字符串。
  • 示例: $(sort foo bar lose) 返回“bar foo lose”。
  • 备注:sort 函数会去掉中相同的单词。

word

$(word ,)

  • wordlist名称:取单词函数——word。
  • 功能:取字符串中第个单词。(从一开始)
  • 返回:返回字符串中第个单词。如果中的单词数要大,那么返回空字符串。
  • 示例: $(word 2, foo bar baz) 返回值是“bar”。

$(wordlist ,,)

  • words名称:取单词串函数——wordlist。
  • 功能:从字符串中取从开始到的单词串。和是一个数字。
  • 返回:返回字符串中从到的单词字串。如果比中的单词数要大,那么返回空字符串。如果大于的单词数,那么返回从开始,到结束的单词串。
  • 示例: $(wordlist 2, 3, foo bar baz) 返回值是“bar baz”。

$(words )

  • firstword名称:单词个数统计函数——words。
  • 功能:统计中字符串中的单词个数。
  • 返回:返回中的单词数。
  • 示例: $(words, foo bar baz) 返回值是“3”。
  • 备注:如果我们要取中最后的一个单词,我们可以这样: $(word $(words ),) 。

$(firstword )

  • 名称:首单词函数——firstword。
  • 功能:取字符串中的第一个单词。
  • 返回:返回字符串的第一个单词。
  • 示例:$(firstword foo bar)返回值是“foo”。
  • 备注:这个函数可以用 word 函数来实现:$(word 1,)。

字符串函数实例

以上,是所有的字符串操作函数,如果搭配混合使用,可以完成比较复杂的功能。

这里,举一个现实中应用的例子。我们知道,make 使用“VPATH”变量来指定“依赖文件”的搜索路径。于是,我们可以利用这个搜索路径来指定编译器对头文件的搜索路径参数 CFLAGS ,如:

override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH)))

如果我们的“$(VPATH)”值是“src:../headers”,那么 $(patsubst %,-I%,$(subst :, ,$(VPATH))) 将返回 -Isrc -I../headers ,这正是 cc 或 gcc 搜索头文件路径的参数。

文件名操作函数

下面我们要介绍的函数主要是处理文件名的。每个函数的参数字符串都会被当做一个或是一系列的文件名来对待。

dir

$(dir )

  • 名称:取目录函数——dir。
  • 功能:从文件名序列中取出目录部分。目录部分是指最后一个反斜杠(“/”)之前的部分。如果没有反斜杠,那么返回“./”。
  • 返回:返回文件名序列的目录部分。
  • 示例: $(dir src/foo.c hacks) 返回值是“src/ ./”。

notdir

$(notdir )

  • 名称:取文件名函数——notdir。
  • 功能:从文件名序列中取出非目录部分。非目录部分是指最后一个反斜杠(“ /”)之后的部分。
  • 返回:返回文件名序列的非目录部分。
  • 示例: $(notdir src/foo.c hacks) 返回值是“foo.c hacks”。

suffix

$(suffix )

  • 名称:取后缀函数——suffix。
  • 功能:从文件名序列中取出各个文件名的后缀。
  • 返回:返回文件名序列的后缀序列,如果文件没有后缀,则返回空字串。
  • 示例: $(suffix src/foo.c src-1.0/bar.c hacks) 返回值是“.c .c”。

basename

$(basename )

  • 名称:取前缀函数——basename。
  • 功能:从文件名序列中取出各个文件名的前缀部分。
  • 返回:返回文件名序列的前缀序列,如果文件没有前缀,则返回空字串。
  • 示例: $(basename src/foo.c src-1.0/bar.c hacks) 返回值是“src/foo src-1.0/bar hacks”。

addsuffix

$(addsuffix ,)

  • 名称:加后缀函数——addsuffix。
  • 功能:把后缀加到中的每个单词后面。
  • 返回:返回加过后缀的文件名序列。
  • 示例: $(addsuffix .c,foo bar) 返回值是“foo.c bar.c”。

addprefix

$(addprefix ,)

  • 名称:加前缀函数——addprefix。
  • 功能:把前缀加到中的每个单词后面。
  • 返回:返回加过前缀的文件名序列。
  • 示例: $(addprefix src/,foo bar) 返回值是“src/foo src/bar”。

join

$(join ,)

  • 名称:连接函数——join。
  • 功能:把中的单词对应地加到的单词后面。如果的单词个数要比的多,那么,中的多出来的单词将保持原样。如果的单词个数要比多,那么,多出来的单词将被复制到中。
  • 返回:返回连接过后的字符串。
  • 示例: $(join aaa bbb , 111 222 333) 返回值是“aaa111 bbb222 333”。

foreach函数

foreach 函数和别的函数非常的不一样。因为这个函数是用来做循环用的,Makefile中的 foreach 函数几乎是仿照于 Unix 标准 Shell(/bin/sh)中的 for 语句,或是 C-Shell (/bin/csh)中的 foreach 语句而构建的。它的语法是:

$(foreach ,,)

这个函数的意思是,把参数中的单词逐一取出放到参数所指定的变量中,然后再执行所包含的表达式。每一次会返回一个字符串,循环过程中,的所返回的每个字符串会以空格分隔,最后当整个循环结束时,所返回的每个字符串所组成的整个字符串(以空格分隔)将会是 foreach 函数的返回值。

所以,最好是一个变量名,可以是一个表达式,而中一般会使用这个参数来依次枚举中的单词。举个例子:


	





names := a b c d
files := $(foreach n,$(names),$(n).o)


上面的例子中,$(name)中的单词会被挨个取出,并存到变量“n”中,“$(n).o”每次根据“$(n)”计算出一个值,这些值以空格分隔,最后作为 foreach 函数的返回,所以,$(files)的值是“a.o b.o c.o d.o”。

注意,foreach 中的参数是一个临时的局部变量,foreach 函数执行完后,参数的变量将不在作用,其作用域只在 foreach 函数当中。

if函数

if 函数很像 GNU 的 make 所支持的条件语句——ifeq,if 函数的语法是:

$(if ,)

或是:

$(if ,,)

可见,if 函数可以包含“else”部分,或是不含。即 if 函数的参数可以是两个,也可以是三个。参数是 if 的表达式,如果其返回的为非空字符串,那么这个表达式就相当于返回真,于是,会被计算,否则会被计算。

而 if 函数的返回值是,如果为真(非空字符串),那个会是整个函数的返回值,如果为假(空字符串),那么会是整个函数的返回值,此时如果没有被定义,那么,整个函数返回空字串。

所以,只会有一个被计算。

call函数

call 函数是唯一一个可以用来 创建新的参数化的函数 。你可以写一个非常复杂的表达式,这个表达式中,你可以定义许多参数,然后你可以用 call 函数来向这个表达式传递参数。其语法是:

$(call ,,,...)

当 make 执行这个函数时,参数中的变量,如$(1),$(2),$(3)等,会被参数依次取代。而的返回值就是 call 函数的返回值。例如:


	





reverse = $(1) $(2)
foo = $(call reverse,a,b)


那么,foo 的值就是“a b”。当然,参数的次序是可以自定义的,不一定是顺序的,如:


	





reverse = $(2) $(1)
foo = $(call reverse,a,b)


此时的 foo 的值就是“b a”。

origin 函数

origin 函数不像其它的函数,他并不操作变量的值,他只是告诉你你的这个变量是哪里来的。

其语法是:

$(origin )

注意,是变量的名字,不应该是引用。所以你最好不要在中使用“$”字符。Origin 函数会以其返回值来告诉你这个变量的“出生情况”,下面,是 origin函数的返回值:

  • “undefined”:如果从来没有定义过,origin 函数返回这个值“undefined”。
  • “default”: 如果是一个默认的定义,比如“CC”这个变量,这种变量我们将在后面讲述。
  • “environment” 如果是一个环境变量,并且当 Makefile 被执行时,“-e”参数没有被打开。
  • “file” 如果这个变量被定义在 Makefile 中。
  • “command line” 如果这个变量是被命令行定义的。
  • “override” 如果是被 override 指示符重新定义的。
  • “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 的命令。它和反引号 ` 是相同的功能。这就是说, shell 函数把执行操作系统命令后的输出作为函数返回 。于是,我们可以用操作系统命令以及字符串处理命令 awk,sed 等等命令来生成一个变量,如:


	





contents := $(shell cat foo)
files := $(shell echo *.c)


注意,这个函数会新生成一个 Shell 程序来执行命令,所以你要注意其运行性能,如果你的Makefile 中有一些比较复杂的规则,并大量使用了这个函数,那么对于你的系统性能是有害的。特别是 Makefile 的隐晦的规则可能会让你的 shell 函数执行的次数比你想像的多得多。

控制make的函数

make 提供了一些函数来控制 make 的运行。通常,你需要检测一些运行 Makefile 时的运行时信息,并且根据这些信息来决定,你是让 make 继续执行,还是停止。

error

$(error )

产生一个致命的错误,是错误信息。注意,error 函数不会在一被使用就会产生错误信息,所以如果你把其定义在某个变量中,并在后续的脚本中使用这个变量,那么也是可以的。例如:

示例一:


	





ifdef ERROR_001
$(error error is $(ERROR_001))
endif


示例二:


	





ERR = $(error found an error!)
.PHONY: err
err: ; $(ERR)


示例一会在变量 ERROR001 定义了后执行时产生 error 调用,而示例二则在目标 err 被执行时才发生 error 调用。

warning

$(warning )

这个函数很像 error 函数,只是它并不会让 make 退出,只是输出一段警告信息,而 make 继续执行。

技巧

静态模式

静态模式可以 更加容易地定义多目标的规则 ,可以让我们的规则变得更加的有弹性和灵活。我们还是先来看一下语法:


	





: :
....


  • targets 定义了一系列的 目标文件 ,可以有通配符。是目标的一个集合。
  • target-parrtern 指明了 targets 的模式,也就是的 目标集模式 。
  • prereq-parrterns 是 目标的依赖模式 ,它对 target-parrtern 形成的模式再进行一次依赖目标的定义。

这样描述这三个东西,可能还是没有说清楚,还是举个例子来说明一下吧。如果我们的定义成“%.o”,意思是我们的集合中都是以“.o”结尾的,而如果我们的定义成“%.c”,意思是对所形成的目标集进行二次定义,其计算方法是,取模式中的%(也就是去掉了“.o”这个结尾),并为其加上“.c”这个结尾,形成的新集合。

所以,我们的“目标模式”或是“依赖模式”中都应该有 % 这个字符,如果你的文件名中有 % 那么你可以使用反斜杠“\”进行转义,来标明真实的%字符。

看一个例子:


	





objects = foo.o bar.o
CFLAGS = $(include_dirs) -O
include_dirs = -Ifoo -Ibar
all: $(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.o
bar.o : bar.c
$(CC) -c $(CFLAGS) bar.c -o bar.o


试想,如果我们的“%.o”有几百个,那种我们只要用这种很简单的“静态模式规则”就可以写完一堆规则,实在是太有效率了。“静态模式规则”的用法很灵活,如果用得好,那会一个很强大的功能。

生成依赖关系

可以用gcc的-M选项自动生成目标文件和源文件的依赖关系。例如:


	





$ gcc -M main.c
main.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选项:


	





$ gcc -MM *.c
main.o: main.c main.h stack.h maze.h
maze.o: maze.c maze.h main.h
stack.o: stack.c stack.h main.h
然后可以把这些规则包含到 Makefile 中,例如:

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: %.c
set -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的结果是:


	




$ make
Makefile:13: main.d: No such file or directory
Makefile:13: stack.d: No such file or directory
Makefile:13: maze.d: No such file or directory
set -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.c
cc -c -o stack.o stack.c
cc -c -o maze.o maze.c
gcc main.o stack.o maze.o -o main


一开始找不到.d文件,所以make会报警告。但是make会把include的文件名也当作目标来尝试更新,而这些目标适用模式规则%.d: %c,所以执行它的命令列表。

常用命令行选项

-n选项

-n选项只打印要执行的命令,而不会真的执行命令,这个选项有助于我们检查Makefile写得是否正确,由于Makefile不是顺序执行的,用这个选项可以先看看命令的执行顺序,确认无误了再真正执行命令。

-C选项

-C选项可以切换到另一个目录执行那个目录下的Makefile,比如先退到上一级目录再执行我们的Makefile(假设我们的源代码都放在testmake目录下):


	




$ cd ..
$ make -C testmake
make: Entering directory `/home/akaedu/testmake'
cc -c -o main.o main.c
cc -c -o stack.o stack.c
cc -c -o maze.o maze.c
gcc main.o stack.o maze.o -o main
make: Leaving directory `/home/akaedu/testmake'


一些规模较大的项目会把不同的模块或子系统的源代码放在不同的子目录中,然后在每个子目录下都写一个该目录的Makefile,然后在一个总的Makefile中用make -C命令执行每个子目录下的Makefile。例如Linux内核源代码根目录下有Makefile,子目录fs、net等也有各自的Makefile,二级子目录fs/ramfs、net/ipv4等也有各自的Makefile。

直接在命令行定义Makefile变量

在make命令行也可以用=或:=定义变量,如果这次编译我想加调试选项-g,但我不想每次编译都加-g选项,可以在命令行定义CFLAGS变量,而不必修改Makefile编译完了再改回来:


	




$ make CFLAGS=-g
cc -g -c -o main.o main.c
cc -g -c -o stack.o stack.c
cc -g -c -o maze.o maze.c
gcc main.o stack.o maze.o -o main
如果在Makefile中也定义了CFLAGS变量,则命令行的值覆盖Makefile中的值



Makefile的几个模板:


1:编译可执行程序。2:编译lib库 3:编译so库
本博针对上面三种目的各自写出了makefile模版,希望对大家有所帮助。
一.编译可执行程序
当前目录下制定文件编译成可执行文件(连接外部库的话只需要更改INC和LIB即可)

CXX = g++
TARGET = bitmaploctest
C_FLAGS += --Wall
LIB_FLAGS = -pthread
all: $(TARGET)
bitmaploctest: bitmaploctest.o bitmaploc.o file_lock.o
    $(CXX) -o $@ $^ $(LIB_FLAGS) $(LIB) $(C_FLAGS)
.cpp.o:
    $(CXX) --o $*.o $(INC) $(C_FLAGS) $*.cpp
.cc.o:
    $(CXX) --o $*.o $(INC) $(C_FLAGS) $*.cc
clean:
    -rm -*.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 --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 -*.o
    rm -f $(TARGETS)
    rm -f tool_stat

OK,我常用的makefile也就这三种格式,希望对大家有用。



阅读(1174) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~