Makefile学习笔记

来源:互联网 发布:手机专业录音软件 编辑:程序博客网 时间:2024/05/17 06:09

对一个嵌入式的开发者,对Makefile肯定都有一点了解,知道它的核心思想–比对时间戳然后去执行下面的动作,但要看懂工程里面的Makefile却感觉比较吃力。本文主要针对这个问题记录了一些学习笔记,把工程中经常用的一些点记录了下来,方便以后复习。

  • Makefile学习笔记
    • makefile语法
      • make的参数  
      • 变量
      • 命令的执行
      • 文件搜索
      • 条件判断
      • 内置函数
        • 字符串函数
        • 文件名操作函数
      • 自动化变量
      • 隐含规则
      • 一些注意点
    • makefile里的一些命令
      • gcc
      • ar
      • ld
      • strip
      • install
    • 讲解两个模块Makefile
      • Linux
      • Ecos

makefile语法

make的参数  

-f 指定文件-C 指定目录-I (--include-dir)include时的搜索路径-n (--just-print) 只显示命令,不执行命令-s (--silent或--quit) 禁止命令的显示-i (--ignore-errors) 忽略命令出错-w (--print-directory) 显示目录信息(make: Entering directory '/home/work/...'),当使用-C时, -w会参数会自动激活

变量

= 右侧变量的值可以定义在文件中任何一个地方,不一定要用已经定义好的值
:= 右侧的变量不能引用到后面定义的变量
?= 如果左侧的变量没有定义那么定义这个变量,如果定义了,则什么也不做
+= 如果定义了,则追加,没定义则定义只有当变量要被使用时,变量才会展开。

注意:注释符#的使用
例如语句dir := /foo/bar # comment,那么$(dir)/file的替换结果将是/foo/bar /file, 注意中间多了两个空格

命令的执行

一般来说,make会以UNIX的标准Shell,也就是/bin/sh来执行命令。

  • 时间戳对比测试
    从测试结果来看Make比对的是文件内容的修改时间,即对文件属性的修改(例如 chmod 777)不会造成重新编译的情况

  • 显示命令
    make在执行动作里的命令前会把命令打印出来,这是很有帮助的。但对于比如echo这种命令的执行就显得多余了,可以在命令前面加上@让它执行前不打印出来

  • 执行命令
    如果你的第二条命令在第一条命令基础上,那么你的第二条命令和第一条命令需要卸载同一行,并用&&连在一起。(例如 cd /work && pwd)

  • 命令出错
    命令执行出错(返回值不为0)都将终止makefile的继续执行。注意几条命令写一行时‘;’和’&&’的区别(‘;’前面的命令出错会继续执行’;’后面的内容)
    忽略命令出错:

    • 命令前面加’-’ (例如:-rm -rf *o)
    • make -i (–ignore-errors) 一般没人这么用
    • 声明目标是.IGNORE的,那么该目标下的所有命令出错都将被忽略

注意:命令是以tab键开头,如果你的编辑器设置了tab转空格,那么将会报错。你可能会比较苦恼不知道哪儿错了。

文件搜索

make会先在当前目录搜索,找不到则会到VPATH和vpath制定的目录搜索。

VPATH:是一个变量,在当前目录找不到时,就会去VPATH指定目录找,以’:’分割。查找顺序为当前目录,冒号分割从左到右查找
vpath:是一个关键字,它更灵活,可以指定不同的目录到不同的搜索路径
vpath <pattern> <directories> 为符合模式<pattern> 的文件指定搜索目录<directories>
vpath <pattern>清除符合模式<pattern> 的文件的搜索目录。
vpath 清除所有已被设置好了的文件搜索目录。
这里需要一个例子

条件判断

在makefile里面可以使用ifeq,ifneq,ifdef,ifndef来进行条件判断。
ifeq和ifneq是判断字符串是否相等

ifeq ($(CONFIG_USB), y)    somethingelse    somethingendif

ifdef和ifndef是判断变量是否有值

注意:条件判断不支持||, &&, !这些逻辑操作符。

内置函数

在工程makefile里面会使用很多函数,了解一些常用函数的作用对理解makefile也是大有益处。  

函数语法:$(<function> <arguments>),或则${<function> <arguments>}
<function>就是函数名,<arguments>为函数的参数,参数间以逗号’,’ 分隔,而函数名和参数之间以“空格”分隔
注意:
逗号间不要加多余的空格。例如@echo $(subst a, A, 123a123a)执行结果为123 A123 A

字符串函数

$(subst <from>,<to>,<text>)• 名称:字符串替换函数• 功能:把字串 <text> 中的 <from> 字符串替换成 <to> 。• 返回:函数返回被替换过后的字符串。$(patsubst <pattern>,<replacement>,<text>)• 名称:模式字符串替换函数。• 功能:查找 <text> 中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式<pattern> ,如果匹配的话,则以 <replacement> 替换。这里,<pattern> 可以包括通配符 % ,表示任意长度的字串。如果 <replacement> 中也包含 % ,那么, <replacement> 中的这个 % 将是 <pattern> 中的那个 % 所代表的字串。(可以用 \ 来转义,以 \% 来表示真实含义的 % 字符)• 返回:函数返回被替换过后的字符串。$(strip <string>)• 名称:去空格函数。• 功能:去掉 <string> 字串中开头和结尾的空字符。• 返回:返回被去掉空格的字符串值。$(findstring <find>,<in>)• 名称:查找字符串函数• 功能:在字串 <in> 中查找 <find> 字串。• 返回:如果找到,那么返回 <find> ,否则返回空字符串。$(filter <pattern...>,<text>)• 名称:过滤函数• 功能:以 <pattern> 模式过滤 <text> 字符串中的单词,保留符合模式 <pattern> 的单词。可以有多个模式。• 返回:返回符合模式 <pattern> 的字串。$(filter-out <pattern...>,<text>)• 名称:反过滤函数• 功能:以 <pattern> 模式过滤 <text> 字符串中的单词,去除符合模式 <pattern> 的单词。可以有多个模式。• 返回:返回不符合模式 <pattern> 的字串。$(sort <list>)• 名称:排序函数• 功能:给字符串 <list> 中的单词排序(升序)。• 返回:返回排序后的字符串。• 备注:sort 函数会去掉 <list> 中相同的单词。$(word <n>,<text>)• 名称:取单词函数• 功能:取字符串 <text> 中第 <n> 个单词。(从一开始)• 返回:返回字符串 <text> 中第 <n> 个单词。如果 <n> 比 <text> 中的单词数要大,那么返回空字符串。$(wordlist <ss>,<e>,<text>)• 名称:取单词串函数• 功能:从字符串 <text> 中取从 <ss> 开始到 <e> 的单词串。<ss> 和 <e> 是一个数字。• 返回:返回字符串 <text> 中从 <ss> 到 <e> 的单词字串。如果 <ss> 比 <text> 中的单词数要大,那么返回空字符串。如果 <e> 大于 <text> 的单词数,那么返回从 <ss> 开始,到 <text> 结束的单词串。$(words <text>)• 名称:单词个数统计函数• 功能:统计 <text> 中字符串中的单词个数。• 返回:返回 <text> 中的单词数。

综合例子:
CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH)))含义?

文件名操作函数

$(dir <names...>)• 名称:取目录函数——dir。• 功能:从文件名序列 <names> 中取出目录部分。目录部分是指最后一个反斜杠(/ )之前的部分。如果没有反斜杠,那么返回 ./ 。• 返回:返回文件名序列 <names> 的目录部分。• 示例:$(dir src/foo.c hacks) 返回值是 src/ ./ 。$(notdir <names...>)• 名称:取文件函数——notdir。• 功能:从文件名序列 <names> 中取出非目录部分。非目录部分是指最後一个反斜杠(/ )之后的 部分。• 返回:返回文件名序列 <names> 的非目录部分。• 示例: $(notdir src/foo.c hacks) 返回值是 foo.c hacks 。$(suffix <names...>)• 名称:取後缀函数——suffix。• 功能:从文件名序列 <names> 中取出各个文件名的后缀。• 返回:返回文件名序列 <names> 的后缀序列,如果文件没有后缀,则返回空字串。• 示例:$(suffix src/foo.c src-1.0/bar.c hacks) 返回值是 .c .c。$(basename <names...>)• 名称:取前缀函数——basename。• 功能:从文件名序列 <names> 中取出各个文件名的前缀部分。• 返回:返回文件名序列 <names> 的前缀序列,如果文件没有前缀,则返回空字串。• 示例:$(basename src/foo.c src-1.0/bar.c hacks) 返回值是 src/foo src-1.0/bar hacks 。$(addsuffix <suffix>,<names...>)• 名称:加后缀函数——addsuffix。• 功能:把后缀 <suffix> 加到 <names> 中的每个单词后面。• 返回:返回加过后缀的文件名序列。• 示例:$(addsuffix .c,foo bar) 返回值是 foo.c bar.c 。$(addprefix <prefix>,<names...>)• 名称:加前缀函数——addprefix。• 功能:把前缀 <prefix> 加到 <names> 中的每个单词后面。• 返回:返回加过前缀的文件名序列。• 示例:$(addprefix src/,foo bar) 返回值是 src/foo src/bar 。$(join <list1>,<list2>)• 名称:连接函数——join。• 功能:把 <list2> 中的单词对应地加到 <list1> 的单词后面。如果 <list1> 的单词个数要比 <list2> 的多,那么,<list1> 中的多出来的单词将保持原样。如果 <list2> 的单词个数要比 <list1> 多,那么,<list2> 多出来的单词将被复制到 <list1> 中。• 返回:返回连接过后的字符串。• 示例:$(join aaa bbb ,111 222 333) 返回值是 aaa111 bbb222 333 。$(foreach <var>,<list>,<text>)没感觉出大用if函数call函数origin函数 -- 可以判断变量是哪儿定义的shell函数$(error <text ...>)打印text信息,停止make的运行$(warning <text ...>)打印提示信息

实战例子:

如下两句执行后的结果是什么?
OBJ_DIRS += cbb prod net_drive
TRX_OBJS = $(join $(OBJ_DIRS), $(patsubst %, /obj-%.o, $(OBJ_DIRS)))

cbb/obj-cbb.o prod/obj-prod.o net_drive/obj-net_drive.o

自动化变量

自动化变量有很多,下面只写3个经常使用的。

  • $@ :表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,$@ 就是匹配于目标中模式定义的集合。
  • $^ : 所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。
  • $< : 依赖目标中的第一个目标名字。如果依赖目标是以模式(即 % )定义的,那么 $< 将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
  • $* : 这个变量表示目标模式中 % 及其之前的部分。如果目标是 dir/a.foo.b ,并且目标的模式是 a.%.b ,那么,$* 的值就是 dir/a.foo 。(make %-only就是用这个实现)

隐含规则

初学makefile一定会有这样的疑问,一些依赖关系并没有写,它怎么就编出来了呢?就是隐含规则在作祟。下面只列出我们常使用的隐含规则:    

  1. 编译 C 程序的隐含规则。
    %.o 的目标的依赖目标会自动推导为%.c ,并且其生成命令是 $(CC) –c $(CPPFLAGS) $(CFLAGS)

  2. 编译 C++ 程序的隐含规则。
    %.o 的目标的依赖目标会自动推导为 %.cc 或是 %.C ,并且其生成命令是 $(CXX) –c$(CPPFLAGS) $(CFLAGS) 。(建议使用 .cc 作为 C++ 源文件的后缀,而不是 .C )

命令的变量

  • CC : C 语言编译程序。默认命令是 cc
  • CXX : C++ 语言编译程序。默认命令是 g++

参数的变量,都默认为空

  • CFLAGS : C 语言编译器参数。
  • CXXFLAGS : C++ 语言编译器参数。
  • CPPFLAGS : C 预处理器参数。(C 和 Fortran 编译器也会用到)。
  • LDFLAGS : 链接器参数。(如:ld )

隐含规则链
有些时候,一个目标可能被一系列的隐含规则所作用。例如,一个 .o 的文件生成,可能会是先被Yacc 的 [.y] 文件先成 .c ,然后再被 C 的编译器生成。我们把这一系列的隐含规则叫做“隐含规则链”。

模式规则
模式规则中,至少在规则的目标定义中要包含 % ,否则,就是一般的规则。目标中的 % 定义表示对文件名的匹配,% 表示长度任意的非空字符串。

重载内建隐含规则
可以利用上面的模式规则改写隐含规则,如下

%.o : %.c    $(CC) -c $(CPPFLAGS) $(CFLAGS) -D$(date)

你也可以取消内建的隐含规则,只要不在后面写命令就行。如:
%.o : %.s

一些注意点

  • 如果第一条规则中的目标有很多个,那么,第一个目标会成为最终的目标。make 所完成的也就是这个目标。   

makefile里的一些命令

光是知道了语法去看工程makefile可能还是有点捉急。还需要了解一下常用命令才行。

gcc

编译一个文件的例子,删减省略了很多

/projects/hnd/tools/linux/hndtools-arm-linux-2.6.36-uclibc-4.5.3/bin/arm-brcm-linux-uclibcgnueabi-gcc  -I/home/work_sdc1/tenda3/AC6_ITB01/AC_PRODUCT_SVN5449/cbb/cbb_tpi/include -fPIC -I/home/work_sdc1/tenda3/AC6_ITB01/AC_PRODUCT_SVN5449/cbb/public/include/ DMAX_DHCPS_STATIC_IP_NUM=32 -DCONFIG_NET_MULTI_WAN=1 -DCONFIG_NET_DHCP=1 -DCONFIG_NET_PORT_CFG_MAC_CLONE=1 -DCONFIG_COMPATIBILITY=1 -g -I ./ -I/home/work_sdc1/tenda3/AC6_ITB01/AC_PRODUCT_SVN5449/prod/nkgw_bak/include -I../../netctrl  -I./cgi    -c -o main.o cgi/main.c/projects/hnd/tools/linux/hndtools-arm-linux-2.6.36-uclibc-4.5.3/bin/arm-brcm-linux-uclibcgnueabi-gcc   -o dhttpd action.o alloc.o auth.o cgi.o crypt.o file.o fs.o http.o js.o jst.o options.o osdep.o rom-documents.o route.o runtime.o socket.o goahead.o cJSON.o wan_err_info.o Form_define.o main.o  -L/home/work_sdc1/tenda3/AC6_ITB01/AC_PRODUCT_SVN5449/vendor/bcm47189_wl_9.10.178.7/cbb_tpi/bcm47189_4x32_p10/lib -L/home/work_sdc1/tenda3/AC6_ITB01/AC_PRODUCT_SVN5449/vendor/bcm47189_wl_9.10.178.7/cbb_tpi/bcm47189_4x32_p10/usr/lib -lCfm -lcommon -lChipApi -lvos_util -lz -lpthread -lnvram -lshared -l m -l tpi

一些参数:

  • -c 只编译但不链接
  • -o 指定输出文件
  • -E 只进行预处理
  • -Wall 打开所有警告
  • -Werror 把所有警告当做错误处理
  • -g 加入debug信息
  • -I 头文件搜索路径
  • -D 传递宏
  • -L 链接库时库的搜索路径
  • -l 要链接的库
  • -fPIC 生成地址无关代码
  • -MMD-MD 生成.o的依赖关系对保存在对应的.d文件中
    区别:-MMD 不会包含尖括号的头文件,如#include <aaa.h>,而-MD则会包含所有头文件,这里面当然会有很多c库头文件。

ar

创建静态库的时候会使用ar命令,例如写成这样:
ar rcs libxxx.a xx1.o xx2.o

一些参数:
ar的参数不带 ‘-’ 注意下

  • c 创建一个库
  • r 在库中插入模块,如果有同名模块则替换
  • s 创建目标索引,在创建较大的库时能够加快速度

ld

链接器,把多个目标文件链接起来重定位他们的符号。

  • -r 产生可重定位的输出, 比如,产生一个输出文件它可再次作为’ld’的输入.这经常被叫做”部分连接”. (只支持ELF平台)
  • -e 自定义入口函数(缺省为main)
  • -T 使用自定义的链接脚本
  • --wrap SYMBOL 对SYMBOL符号使用包装函数
    例如为了调查内存泄露,我们对malloc进行如下封装,链接时使用--wrap malloc参数,那么程序里面在调用malloc的时候就会调用__wrap_malloc,而__real_malloc则会调用C库真正的malloc
void *__wrap_malloc (int size) {      /* 记录一些信息 */      return __real_malloc(size); }

strip

剥掉可执行程序的符号信息和调试信息,为我们的目标程序瘦身。

install

一般而言你把install理解为cp就好了,它只是比cp更加灵活,还可已创建目录,指定属性。如:

install -D -m 0755 httpd /usr/mybin# 相当于mkdir -p /usr/mybincp httpd /usr/mybinchmod 0755 /usr/mybin
  • -D 创建目的地的目录
  • -m 设置权限

0 0