该系列文章总纲链接:专题分纲目录 Linux环境


本章节内容的思维导图整理如下:

Linux基础 Makefile_函数返回


1 makefile基础知识

makefile诞生的含义在于自动化编译,其执行是由所编译文件的依赖关系驱动的。它相当于一种脚本语言,在编写中可以使用变量、控制结构语句和函数等一般编程语言的特性。

1.1 makefile文件入门

1.1.1 makefile文件组成

makefile文件主要有以下5个部分组成:

显示规则:说明生成一个/多个目标文件的方法和步骤。

隐式规则:利用本身的自动推导功能。即很多地方可以略写。

变量定义:同脚本中的变量,都是字符串,执行时被展开到相应的位置上。

文件指示:类似于C语言中的宏。

          引用其他makefile

          定义一个多行的命令

          根据情况指定makefile的有效部分

注释     :与shell中注释昂是一致。用#表示。

1.1.2 makefile文件包含

一般情况下makefile在命名的时候用makefile或者Makefile,如果是其他的名字也可以,但是在make的时候要加参数-f,即

$make -f <name>

在makefile中使用include可以将其他的makefile包含进来。即条件语句

include <文件名>

在make执行文件时,用户设置了--include-dir参数,make就会在用户指定的目录下寻找需要包含的makefile文件。 即

$make --include-dir <path>

如果包含的文件没有找到,make会输出一条警告信息,反复读取文件失败时才会输出一条致命信息。用户可以忽略make工具产生的出错信息,而使makefile一直执行下去。方法如下:添加语句

-include <filename>

1.1.3 makefile退出码

makefile的退出码有3种情况:

  • 0:makefile文件执行成功。
  • 1:makefile执行过程中出现错误。
  • 2:若用户使用了make的-q选项,并且make使一些目标不需要更新。

1.2 makefile书写规则

1.2.1 使用基本规则

#基本依赖关系的模型:
     targets:prerequisites
          command
          /...
#或者
     targets:prerequisites;command
          /...

注意:

  • 在makefile中一行开始的空格必须用[tab]键。
  • make执行的是makefile中的第一条规则。    

1.2.2 隐式规则

隐式规则在makefile中很常见,就是利用本身的推导功能,比如.c文件到.o文件,可以不用说明。

1.2.3 使用伪目标

PHONY 目标并非实际的文件名:只是在显式请求时执行命令的名字。有两种理由需要使用PHONY 目标:避免和同名文件冲突,改善性能。如果编写一个规则,并不产生目标文件,则其命令在每次make 该目标时都执行。例如:

  clean:
  rm *.o temp

因为"rm"命令并不产生"clean"文件,则每次执行"make clean"的时候,该命令都会执行。如果目录中出现了"clean"文件,则规则失效了:没有依赖文件,文件"clean"始终是最新的,命令永远不会 执行;为避免这个问题,可使用".PHONY"指明该目标。如:

  .PHONY : clean

这样执行"make clean"会无视"clean"文件存在与否。已知phony目标并非是由其它文件生成的实际文件,make 会跳过隐含规则搜索。这就是声明phony 目标会改善性能的原因,即使你并不担心实际文件存在与否。例子如下:

     .PHONY : clean
     clean :
     rm *.o temp

1.2.4 通配符

makefile中可以使用通配符,make工具支持3种通配符*、?、[...]。

~表示用户的根目录,该目录通常在$HOME环境变量中保存。

1.2.5 搜索源文件

在使用make工具去推导依赖关系的时候,用户可以在文件前加上路径,但是最好将路径直接告诉make工具,让make自动寻找源文件并且推断其依赖关系。设置make工具搜索文件路径的方法有两种:

设置VPATH变量:

若未指定VPATH的值,则make只在当前目录下寻找依赖文件和目标文件。
若指定VPATH = path1:path2:...:pathn,则make工具顺序搜索当前目录、路径path1、路径path2、路径path3。注意:目录之间用:分隔。

使用vpath关键字:与设置vpath变量类似,只是方式更加灵活。

    vpath <pattern> <dir1> <dir2> ...<dirn>:添加指定dir1-dirn目录下符合pattern模式的文件搜索目录。
    vpath <pattern>:清除符合模式pattern的文件的搜索目录。
    vpath:清除所有文件的搜索目录,即已经设置好了的文件搜索目录。

1.3 makefile使用命令

在makefile中可以执行shell中的命令,因此许多工作可以交由shell去完成,这样做简化了makefile的编写工作,也利于makefile的移植。

1.3.1 显示命令

当使用@在命令行前,这个命令将不会在将不会在make的时候显示出来;但是在make工具执行的时候加上参数-n或者是--just-print,则只是显示命令,但是不执行命令;

当make工具的参数是-s或者--slient,则不论命令前是否有@,都将禁止所有命令的显示。

1.3.2 执行命令

如果想让上一条命令的结果应用在下一条命令中时,应当在上一条命令结束时加上;来分隔这两条命令。

1.3.3 命令出错

当make工具执行规则的时候。某个命令出错了,make命令就会终止当前规则。由于makefile文件是基于文件依赖的,所以一个规则停止将有可能终止所有规则的执行。但很多时候make的一个规则出错是在正常的规则允许范围内,因此这时候要忽略命令的出错。make工具支持在规则的命令行前加上符号-,忽略该命令执行结果的判断,即

-command     //忽略对cammand返回值的判断

make的参数-i或者--ignore-errors可以使makefile文件中所有命令都忽略错误。如果一个规则是以.IGNORE声明作为目标,则规则中所有命令将会忽略错误。

1.4 makefile使用变量

makefile中使用变量是使结构变得更加紧凑。

1.4.1 使用普通变量

传统的makefile变量名是全大写的命名方式。对于一般的变量使用要加$,例如$(VARNAME)。要使用$符号的时候要用$$的形式,make会自动交给shell去处理,解释成$。

1.4.2 变量中的变量

使用“=”操作符       :右侧变量的值可以定义在文件中的任意一处,不一定是一个已经定义好的值。但是这个可以递归定义的方式并不实用。
使用“:=”操作符     :使用其定义变量时,只能使用前面定义的变量,后面定义的无效。
使用“?=”操作符     :如果变量之前没有被定义过,那么变量的值就被定义;如果前面定义过,则什么也不做。

1.4.3 追加变量的值

makefile允许给变量追加一个值,操作符为“+=”,实际上就是字符串的拼接。

1.4.4 自动化变量

所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。makefile本身定义好了一些列自动化变量,基础的自动化变量如下:

    $@:表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于目标中模式定义的集合。
    $%:仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a(bar.o)",那么,"$%"就 是"bar.o","$@"就是"foo.a"。如果目标不是函数库文件其值为空。
    $<:依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
    $?:所有比目标新的依赖目标的集合。以空格分隔。
    $^:所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。
    $+:这个变量很像"$^",也是所有依赖目标的集合。只是它不去除重复的依赖目标。
    $*:这个变量表示目标模式中“%”及其之前的部分。

上述自动化变量中,4个变量($@、 $<、 $%、 $*)在扩展时只会有一个文件,另外3个值是一个文件列表。7个自动化变量可以再搭配D(文件目录部分)或者F(文件名部分)字样,这两个字符的意义等价于dir和file,即dir/file的这种关系。


2 makefile的控制结构

2.1 makefile的条件表达式

常用的条件表达式共有4个:ifeq/ifneq/ifdef/ifndef,使用方式如下:

ifeq/ifneq表达式:
    第一种形式:
    ifeq/ifneq
         command
    endif
    第二种形式:
    ifeq/ifneq
         command1
    else
         command2
    endif

ifdef/ifndef表达式:
    第一种形式:
    ifdef/ifndef
         command
    endif
    第二种形式:
    ifdef/ifndef
         command1
    else
         command2
    endif

注意:只有在表达式的值为空的时候,表达式的值为假。    

2.2 makefile使用函数

2.2.1 函数调用的语法

makefile中函数模型如下:

$(<function> <arguments>)或者${<function> <arguments>}

其中function表示函数的功能,arguments表示函数的参数。例如:

$bar:=${subst $(A),$(B),$(C)}     #其中subst是函数名,A、B、C是参数。

后面介绍中<name...>表示参数组,可以是多个同类型的参数。

2.2.2 字符串处理函数

@1 字符串替换函数

表达式:$(subst,<from>,<to>,<text>)
函数功能:把字符串<text>中的<from>全部替换成<to>。
返回值:函数返回被替换后的字符串。

@2 模式字符替换函数

表达式:$(patsubst,<pattern>,<replacement>,<text>)
函数功能:把字符串<text>中本身的模式<pattern>替换成新的模式<replacement>。
返回值:函数返回被替换后的字符串。

@3 去空格函数

表达式:$(strip <string>)
函数功能:把字符串<string>中开头和结尾的空格全部去掉。
返回值:函数返回被去掉空格后的字符串。

@4 查找字符串函数

表达式:$(findstring <find>,<in>)
函数功能:在字符串<in>中查找<find>字符串。
返回值:函数如果找到指定字符串,则返回find,否则,返回空字符串。
注意:如果第一次已经找到指定字符串则第二次由于已经出现过一次而找不到了。

@5 过滤函数

表达式:$(filter,<pattern...>,<text>)
函数功能:以pattern模式过滤出<text>字符串中的单词,保留符合模式<pattern...>的单词。可以有多个模式。
返回值:返回符合模式<pattern>的字符串。

@6 反过滤函数

表达式:$(filter-out,<pattern...>,<text>)
函数功能:以pattern模式过滤出<text>字符串中的单词,去除符合模式<pattern...>的单词。可以有多个模式。
返回值:返回不符合模式<pattern>的字符串。

@7 排序函数

 表达式:$(sort <string>)
 函数功能:给字符串<string>中的单词按升序排列。
 返回值:函数返回排序好的字符串。
 注意:sort函数会去掉<list>中相同的单词。

@8 取单词函数

表达式:$(word <n>,<text>)
函数功能:取字符串<text>中的第n个单词。
返回值:函数返回字符串<text>中的第n个单词。
注意:
    单词不是字符,是以空格来分隔的。
    如果n超过text的单词的个数则为空。  

@9 取单词串函数

表达式:$(wordlist <s>,<e>,<text>)
函数功能:从字符串<text>中取从<s>到<e>的单词串。
返回值:函数返回从字符串<text>中取从<s>到<e>的单词串。
注意:<s>和<e>是数字,当s>e时,返回空字符串。

@a 单词个数统计函数

表达式:$(words <text>)
函数功能:统计<text>中字符串中的单词个数。
返回值:函数返回<text>中字符串中的单词个数。

@b 首单词函数

表达式:$(firstword <text>)
函数功能:取字符串<text>中的第1个单词。
返回值:函数返回字符串<text>中的第1个单词。

2.2.3 文件名操作函数
@1 取目录函数

表达式:$(dir <names...>)
函数功能:从文件名序列<names>中取出目录部分。
返回值:文件名序列<names>中目录部分。
注意:目录部分是指最后一个“/”之前的部分,如果没有“/”,则返回./。

@2 取文件函数

表达式:$(notdir <names...>)
函数功能:从文件名序列<names>中取出非目录部分。即最后一个“/”后面的部分。
返回值:文件名序列<names>中非目录部分。

@3 取后缀函数

表达式:$(suffix <names...>)
函数功能:从文件名序列<names>中取出各个文件名的后缀。
返回值:文件名序列<names>中各个文件名的后缀序列,若没有后缀则返回空字符串。
注意:所谓后缀,即文件的拓展名。

@4 取前缀函数

表达式:$(basename <names...>)
函数功能:从文件名序列<names>中取出各个文件的前缀部分。
返回值:文件名序列<names>中目录部分。
注意:所谓前缀,即除了文件拓展名的部分,也就是.之前的部分。

@5 加后缀函数

表达式:$(addsuffix <suffix>,<names...>)
函数功能:把后缀<suffix>加到<names>中的每个单词后面。
返回值:返回加过后缀的文件名序列。

@6 加前缀函数

表达式:$(addprefix <prefix>,<names...>)
函数功能:把后缀<prefix>加到<names>中的每个单词前面。
返回值:返回加过前缀的文件名序列。

@7 连接函数

表达式:$(join <list1>,<list2>)
函数功能:把<list2>中的单词对应地加到<list1>的单词后面。
    如果<list1>的单词个数要比<list2>的多,那么,<list1>中的多出来的单词将保持原样。
    如果<list2>的单词个数要比<list1>多,那么,<list2>多出来的单词将被复制到<list2>后。
返回值:返回连接过后的字符串。

2.2.4 foreach函数

foreach函数是用来控制循环的,类似于UNIX下的shell中的for语句,语法如下:

表达式:$(froeach <var>,<list>,<text>)
函数功能:把参数<list>中的单词逐一取出放到参数<var>所指定的变量中,然后再执行<text>所包含的表达式。每一次<text>会返回一个字符串,循环过程中,<text>的所返回的每个字符串会以空格分隔, 所以,<var>最好是一个变量名,<list>可以是一个表达式,而<text>中一般会使用<var>这个参数来依次枚举<list>中的单词。
返回值:最后当整个循环结束时,<text>所返回的每个字符串所组成的整个字符串(以空格分隔)将会是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中的<var>参数是一个临时的局部变量,foreach函数执行完后,参数<var>的变量将不在作用,其作用域只在 foreach 函数当中。

2.2.5 if函数

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

表达式:$(if <condition>,<then-part> )或者$(if <condition>,<then-part>,<else-part> )
函数功能:<condition>参数是if的表达式,如果其返回的为非空字符串,那么这个表达式就相当于返回真,于是,<then-part>会被计算,否则<else-part>会被计算。
返回值:
     如果<condition>为真(非空字符串),那个<then-part>会是整个函数的返回值,
     如果<condition>为假(空字符串),那么<else-part>会是整个函数的返回值,此时如果<else-part>没有被定义,那么,整个函数返回空字串。

2.2.6 call函数

call函数为用户创建一个自己定义的函数。用户可以写一个非常复杂的表达式,每次用到这个表达式的时候,使用call函数跳转到该表达式处执行即可。call的语法规则如下:

表达式:$(call <expression>,<parm1>,<parm2>,<parm3>...)
函数功能:当make执行这个函数时,<expression>实际上就是函数名,它的参数中的变量,如$(1),$(2),$(3)等,会被参数<parm1>,<parm2>,<parm3>依次取代。
返回值:call函数的返回值。

2.2.7 origin函数

origin 函数可以将变量的定义信息返回给用户,其语法格式如下:

表达式:$(origin <variable> )
函数功能:将变量的定义信息返回给用户
返回值:
     “undefined”:如果<variable>从来没有定义过,origin函数返回这个值“undefined”。
     “default”:如果<variable>是一个默认的定义,比如“CC”这个变量,则origin函数返回default。
     “environment”:如果<variable>是一个环境变量,并且当Makefile被执行时,“-e”参数没有被打开,origin函数返回environment。
     “file”:如果<variable>这个变量被定义在Makefile中。
     “command line”:如果<variable>这个变量是被命令行定义的。
     “override”:如果<variable>是被override指示符重新定义的。
     “automatic”:如果<variable>是一个命令运行中的自动化变量。origin函数返回override。
注意:<variable>是变量的名字,不应该是引用。所以你最好不要在<variable>中使用“$”字符。

2.2.8 shell函数

shell函数用来执行操作系统的命令,该函数把操作系统执行命令后的输出作为函数返回。函数的语法规则如下:

表达式:${shell <command>,<parm1>,<parm2>,<parm3>...}
函数功能:执行系统的shell命令,<parm1>,<parm2>,<parm3>...是shell的参数。
返回值:执行操作系统命令后的输出。
注意:这个函数会新生成一个Shell程序来执行命令,所以要注意其运行性能,如果Makefile中有一些比较复杂的规则,并大量使用了这个函数,那么对于你的系统性能是有害的。特别是Makefile的隐晦的规则可能会让你的shell函数执行的次数比你
想像的多得多。