基础知识:篇4-make工具与Makefile文件概念

说明
  本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。
  QQ 群 号:513683159 【相互学习】
内容来源
  C语言中文网-Makefile文件是什么?
  《GNU make中文手册 ver-3.8 》

上一篇:基础知识:篇3-静态库与动态库
下一篇:基础知识:篇5-Makefile示例

一、make与Makefile的相关描述:

1、前提知识:

  编译:源文件(.c)编译为目标文件(.o)的过程称为编译。
  链接:将大量的目标文件合成可执行文件的动作称为链接。
  库文件:将一些被许多模块反复使用的目标文件打包形成的文件。
    库文件分类:静态库文件(.a),动态库文件(.so)

2、Makefile文件是什么?及其优点?

  1️⃣ Makefile文件是什么?
  在Linux开发下,文件编译一般是通过指令在命令行实现,但若编写大工程还在命令行中使用指令执行就显得十分困难且繁琐,故使用文档描述编译链接的过程,而该文档便是Makefile文件
  即:描述整个工程文件的编译和链接等规则,使工程的编译自动化,且可进行更复杂的功能操作,甚至可像Shell脚本一样执行操作系统命令的一种描述文件
  2️⃣ 优点
    ①Makefile文件可实现多线程并发加快编译
    ②make命令只重新编译修改过的文件(耗时少)
      a.修改源文件则只编译对应源文件并链接目标程序。
      b.修改头文件则编译引用头文件的源文件并链接目标程序

3、make工具是什么?如何工作?

  1️⃣ make工具是什么?
  一个命令工具,解释Makefile文件中的指令(规则)。
  2️⃣ make工具如何工作?
  当在命令行中键入make指令后,背后会执行那些操作呢?
    ①在当前目录下找到“Makefile文件”,
    ②读入被 include 的其它“Makefile文件”。
    ③初始化文件中的变量。
    ④推导隐晦规则,并分析所有规则。
    ⑤为所有的目标文件创建依赖关系链。
    ⑥根据依赖关系,决定哪些目标要重新生成。
    ⑦执行生成命令。
  ①-⑤步为第一个阶段,⑥-⑦为第二个阶段。
    第一个阶段中,如果定义的变量被使用了,那么,make会把其展开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。
  3️⃣ 第二个阶段详细描述
    ①根据文件中的第一个目标(targets)作为想要生成的最终目标文件。
    ②若该目标文件不存在,或目标文件后的.o依赖文件(prerequisites)修改时间比目标文件新,则会执行后面所定义命令来生成目标文件,保持最新。
    ③若目标文件的.o依赖文件也不存在,则会在当前文件中找.o文件的依赖文件(.c文件和.h文件),若找到则会根据这些依赖性文件生成.o文件。
    ④生成.o文件后,会根据最新的.o文件,重新生成最新的目标文件。
    ⑤伪目标(.PHONY)不被目标文件依赖,故不会实现,除非make指令有特殊声明。
PS
  这样的过程其实蛮好理解的,就像我想出去野炊,野炊即为我的目标文件,而野炊需要装备1,装备2,装备3.装备1~3,则是我野炊所需的依赖.o文件,而装备我需要去商店购买装备的部件(.c文件和.h文件),有时需多个商店购买不同部件进行组装,若装备的某一部分部件(.c文件和.h文件)有最新版本的,则对于最喜最强装备的我,则要将其更新为最强的装备。
整个结构像一棵树,根结点即为最终要生成文件,不断的向下依赖,若子结点更新,父结点必然也要更新,最终影响到根结点
【工作原理:make 通过比较对应文件(规则的目标和依赖,)的最后修改时间,来决定哪些文件需要更新、哪些文件不需要更新。对需要更新的文件 make 就执行数据库中所记录的相应命令(在make 读取Makefile 以后会建立一个编译过程的描述数据库。此数据库中记录了所有各个文件之间的相互关系,以及它们的关系描述)来重建它,对于不需要重建的文件 make 什么也不做】

4、总结:

  在Makefile 中“规则”就是描述在什么情况下、如何重建规则的目标文件,通常规则中包括了目标的依赖关系(目标的依赖文件)和重建目标的命令。make执行重建目标的命令,来创建或者重建规则的目标(此目标文件也可以是触发这个规则的上一个规则中的依赖文件)规则包含了文件之间的依赖关系和更新此规则目标所需要的命令。

二、make工具的可选参数:

详细请查看:参数和选项、特殊目标汇总参数和选项大汇总
  1️⃣ -C DIR,–directory=DIR :读取 Makefile 之前,进入到目录 DIR,然后执行 make。
  2️⃣ -f=FILE,–file=FILE,–makefile=FILE:指定文件 “FILE” 为 make 执行的 Makefile 文件
  3️⃣ -I DIR,–include-dir=DIR:指定包含 Makefile的搜索目录,在Makefile中出现 “include” 文件时,将在 “DIR” 目录下搜索。

三、Makefile文件有什么?

1、显式规则:

  直观上肉眼可见的描述:要生成的目标文件,文件依赖关系执行命令,其结构如下:

targets : prerequisites	...					
command							      			     
...									

(1)结构成员说明
  默认情况下, Makefile文件中的第一规则(依赖关系)的第一目标称之为“最终目标”。
  1️⃣target:规则的目标
    ①最后需要生成文件名(可执行文件)。
    ②为实现这个目的而必需的中间过程文件名(Object File)。
    ③ Label/伪目标,一般没有任何依赖也不会出现在其他规则的依赖列表中,只作为动作的标识,如:“clean”。
    注意
      a.避免伪目标与文件出现名字冲突。故最好写成:.PHONY:clean
      b.若伪目标为最终目标时可有依赖,实现多目标文件生成。
  2️⃣prerequisites:规则的依赖。(可没有[伪目标时],也可一个或多个文件
    生成规则目标所需的文件名列表。
  3️⃣command:规则命令行。(可以有多条命令,每一条命令占一行
    程序执行的动作(任意的shell命令或可在shell下执行的程序)。
    若命令行太长可使用\续行符将命令行分为两行。
(2)注意
    ① 目标和依赖文件之间要使用冒号分隔
    ② 每个命令行须以【Tab】字符开始,【Tab】字符告诉make此行是个命令行,但make程序会把出现在第一条规则之后的所有【Tab】字符开始的行都作为命令行来处理。
    ③Makefile是支持多目标文件的生成。
PS
  Makefile文件的大致框架就是以规则组成。
  make就是皇帝。target就是总帅(可执行文件),而target所需要的的prerequisites就是总帅所需的武将。
  而武将(中间文件)也是一个target,同样需要 prerequisites小兵来组成小队。
  军团里面除了总帅以外,还有皇帝直属的监军(伪目标)同样也是一个单独的target

2、隐晦规则:

  GNU的make工具很强大,可自动推导文件及其文件依赖关系后命令,如:
    1️⃣看到【.o文件】自动将【.c文件】加入依赖关系,如:add.o = add.o:add.c
    2️⃣并推导出对应的编译指令,如:$(CC) -c add.c
    3️⃣C/C++编译器都支持·-M选项= 自动寻源文件中包含的头文件,并生成依赖关系。
     (GNU的C/C++编译器最好使用-MM选项避免将标准库头文件包含进来)
     所有[.d]都依赖[.c]文件,自动生成依赖性。

3、变量:

  1️⃣作用:类似C语言的宏,可扩展到对应的引用位置。
  2️⃣格式变量名 = 值列表
    变量名:大小写字母、阿拉伯数字和下划线构成。
    值列表:既可零项,又可一项或多项
  3️⃣引用$(VALUE_LIST)${VALUE_LIST}
  4️⃣赋值方式

符号 作用
简单赋值 ( := ) 编程语言中常规理解的赋值方式,只对当前语句的变量有效
递归赋值( = ) 赋值语句可能影响多个变量,所有目标变量相关的其他变量都受影响。
条件赋值 ( ?= ) 若变量未定义,则使用符号中的值定义变量。若该变量已赋值,则该赋值语句无效。
追加赋值 (+= ) 原变量用空格隔开的方式追加一个新值

  5️⃣自动化变量

变量 说明
$@ 表示规则的目标文件名。如果目标是一个文档文件(Linux 中,一般成 .a 文件为文档文件,也成为静态的库文件), 那么它代表这个文档的文件名。在多目标模式规则中,它代表的是触发规则被执行的文件名。
$% 当目标文件是一个静态库文件时,代表静态库的一个成员名。
$< 规则的第一个依赖的文件名。如果是一个目标文件使用隐含的规则来重建,则它代表由隐含规则加入的第一个依赖文件。
$? 所有比目标文件更新的依赖文件列表,空格分隔。如果目标文件时静态库文件,代表的是库文件(.o 文件)。
$^ 代表的是所有依赖文件列表,使用空格分隔。如果目标是静态库文件,它所代表的只能是所有的库成员(.o 文件)名。 一个文件可重复的出现在目标的依赖中,变量“$^”只记录它的第一次引用的情况。就是说变量“$^”会去掉重复的依赖文件。
$+ 类似“$^”,但是它保留了依赖文件中重复出现的文件。主要用在程序链接时库的交叉引用场合。
$* 在模式规则和静态模式规则中,代表“茎”。“茎”是目标模式中“%”所代表的部分(当文件名中存在目录时, “茎”也包含目录部分)。

  ⑥环境变量
    1.Makefile中直接: export 变量
    2.目标命令中,在命令前:变量=值 command
    3.单独定义一个目标(不能有命令),目标1: export 变量=值

4、文件指示:

(1)include关键字:

  1️⃣作用 Makefile文件中引用另一个Makefile文件。(类似C语言的#include)
    当读取到include 时,会暂停读取当前的Makefile文件,去读include包含的文件,读取结束后再继读取当前的 Makefile文件

  2️⃣语法[-]include <filename>[]表示可省
     -:可忽略【文件不存在】或 【无法创建 】的错误。
    <filename>:可是当前操作系统shell的文件模式(可包含路径和通配符)
    PS:
      ①行首包含 【一/多 】个空格,读取时自动忽略。
      ②行首不可包含 [Tab],否则会当作命令处理。
      ③多个文件之间使用空格分开。
      ④若包含的Makefile文件存在 函数/变量 的引用,会将其展开
      ⑤include:文件列表中的【任意文件不存在】或【没有规则创建文件】时,make将会提示错误并保存退出。
       -include:当【任意文件不存在】或【没有规则创建文件】时,make 将会继续执行,只有真正由于不能完成终极目标重建时才会提示错误保存退出。
      ⑥若【当前目录】或【绝对路径】未找到文件时,还会去以下路径找文件:
        a. “-I” 或 “– –include-dir” 后面添加的指定路径
        b.“usr/gnu/include”、“usr/local/include” 和 “usr/include”。

  3️⃣适用场合
    一个工程文件中,每一个模块都有一个独立的 Makefile 来描述它的重建规则。它们需要定义一组通用的变量定义或者是模式规则。通用的做法是将这些共同使用的变量或者模式规则定义在一个文件中,需要的时候用 “include” 包含这个文件。
    当根据源文件自动产生依赖文件时,我们可以将自动产生的依赖关系保存在另一个文件中。然后在 Makefile 中包含这个文件。

(2)条件判断:

  1️⃣作用:根据情景指定Makefile文件中的有效部分。(类似C语言中的预编译条件#if)
    可根据变量的值来控制 make【执行或忽略】 Makefile 的特定部分,条件语句可是【两个不同的变量 或 常量和变量之间的比较】。
  2️⃣关键字

关键字 功能
ifeq 判断参数是否不相等,相等为 true,不相等为 false。
ifneq 判断参数是否不相等,不相等为 true,相等为 false。
ifdef 判断是否有值,有值为 true,没有值为 false。
ifndef 判断是否有值,没有值为 true,有值为 false。

  3️⃣语法

ifeq (ARG1, ARG2)/'ARG1' 'ARG2'/"ARG1" "ARG2" :
{...}
else
{...}
endif

  ifeq表示条件语句的开始,并指定比较条件。
(括号和关键字之间要使用空格分隔,两个参数之间要使用逗号分隔。参数中的变量引用在进行变量值比较的时候被展开).
  else表示当条件不满足的时候执行的部分,不是所有的条件语句都要执行此部分。
  endif是判断语句结束标志。

(3)定义一个多行命令

5、注释:

  与shell脚本相同,使用#字符标识注释行,若想使用#字符则使用\反斜杠进行转义。

四、通配符

通配符 使用说明
* 匹配0个或者是任意个字符
匹配任意一个字符
[] 我们可以指定匹配的字符放在 "[]"
% 匹配任意个字符,"%" 表示取出来文件的文件名(不包含后缀)

  既可使用在规则中亦可使用在命令中。
  不可直接通过引用变量使用,需使用函数 "wildcard"展开,即:$(wildcard *.c)
PS
  通配符其实就是程序员偷懒的结果,通过一些特定符号来替代一些符号,使程序更加简洁。

五、命令的编写

1、命令回显

  通常, make工具会将其执行的指令在命令执行前将指令本身内容输出到标准输出设备。
   一些手段可控制指令内容是否输出:
      ①在指令以“@”开头,则指令本身内容不会输出
      ②在make 时加参数 -n--just-print 则执行时只显示命令,但不会真正执行指令(“@”开始的指令也会显示,常用于调试)。
      ③在make 时加参数-s--slient则是禁止所有的执行命令的显示。就好像所有的命令行都使用“@”开始一样。

2、命令的执行

   书写在独立行的一条命令是一个独立的 shell 命令行
   书写在同一行中的多个命令属于一个完整的 shell 命令行。会将上一条命令的结果应用在下一条命令始,此时可使用分号“;”分隔这两条命令。若想将完整的shell命令行书写在多行上,需使用反斜杠 ()来对处于多行的命令进行连接,表示他们是一个完整的shell命令行。

3、并发执行命令

   GNU make 支持同时执行多条命令
   make 时加参数 “-j” 或 “--jobs” 后加整数n,来告诉 make工具 同一时刻可以允许n条命令同时执行,默认是1。
   并发执行的弊端:无法从凌乱的信息区分命令错误,可能导致make出错。

4、命令出错

  在命令指令中,每个指令运行后都会有个返回码,若返回码是非零(命令出错),则make会终止当前规则的执行,甚至可能终止所有规则的执行。
  而有时命令出错可能并不是致命的,故可选择忽略命令出错,
    单个指令忽略:在命令实现的参数前添加-表示不管出不出错都是成功的。
    全局方法忽略make 时加参数 “-i” 或 “--ignore-errors” ,则会Makefile文件中所有命令都会忽略错误。
     make 时加参数 “-k” 或 “--keep-going”,表示命令出错则终止该规则的执行,继续执行其他规则

5、嵌套执行make

  大工程中,会将不同模块的源文件放在不同目录中,故每个目录中都书写一个Makefile文件,有利于总控Makefile文件更简洁,故一般采用以下方法:
subdir为子目录,该目录有Makefile文件$(MAKE) 表示make + 参数):

subsystem:
cd subdir && $ (MAKE)
等价于
subsystem:
$(MAKE) -C subdir

  总控Makefile文件的变量可传递到下级Makefile文件,但不会覆盖下层定义的变量,除非指定-e参数。
    ①传递到下级Makefile文件:export <variable ...>
    ②不传递到下级Makefile文件:unexport <variable ...>
    ③传递所有变量:export
    ④特殊的变量:SHELL、MAKEFLAGS不论是否export总要传递到下层Makefile文件,
      MAKEFLAGS包含make后的参数。
      几个参数不下传:-C、-f、-h、-o、-W
      若就是不想传参数则可:$(MAKE) MAKEFLAGS=

六、目标文件搜索

(1)作用:
  在大工程中包含大量源文件,通常是将源文件分类放在不同目录中,故当make工具找寻文件依赖关系时,可文件前加路径,但最好便是将路径告诉make工具让其自动找,这就需要使用特殊变量VPATH
  添加后不仅会在当前目录下找寻依赖文件和目标文件,还会去VPATH指定的目录下寻找。
(2)分类:
  可分为:一般搜索VPATH选择搜索vpath
   1️⃣ 一般搜索:VPATH(搜索路径下所有的文件)
   ①描述VPATH特殊变量,更具体的说是环境变量,使用时需指定文件的路径;
     路径搜索:先搜索当前路径下的文件,若无才去VPATH的路径中去寻找。
   ②语法VPATH := 路径
      路径可为一个或多个,多个之间用空格或冒号分格。
   ③适用条件:【当前路径下的文件较少】或【搜索的文件不能使用通配符表示】
   2️⃣ 选择搜索vpath( 添加了限制条件,会过滤出一部分再去寻找)
   ①描述vpath关键字,按照模式搜索,也可以说成是选择搜索。
   搜索的时候不仅需要加上文件的路径,还需要加上相应限制的条件
   ②语法
      1) vpath <pattern> <directories>: 在 <directories>(可为多路径)查找符合模式<pattern>的文件
      2) vpath <pattern>:清除符合模式PATTERN文件的搜索目录。
      3) vpath:清除所有已被设置的文件搜索路径。
      <pattern>:寻找的条件,需%字符,表示匹配若干字符,如:%.h表示所有.h结尾的文件
      <directories>:寻找的路径
   ③适用条件:【某个路径的文件特别的多】或【可使用通配符表示】时。

七、函数

  函数的调用
    格式$(<function> <arguments>) ${<function> <arguments>}
    function 是函数名,arguments 是函数的参数,参数之间要用逗号分隔开。

1、常用字符串处理函数

模式字符串替换函数:patsubst

  格式$(patsubst <pattern>,<replacement>,<text>)
  说明
    查找 text 中的单词是否符合模式 pattern,如果匹配的话,则用 replacement 替换。返回值为替换后的新字符串

字符串替换函数:subst

  格式$(subst <from>,<to>,<text>)
  说明
    把字符串中的 form 替换成 to,返回值为替换后的新字符串。

去空格函数:strip

  格式$(strip <string>)
  说明
    去掉字符串的开头和结尾的字符串,并且将其中的多个连续的空格合并成为一个空格。返回值为去掉空格后的字符串。

查找字符串函数:findstring

  格式$(findstring <find>,<in>)
  说明
    查找 in 中的 find ,如果我们查找的目标字符串存在。返回值为目标字符串,如果不存在就返回空。

过滤函数:filter

  格式$(filter <pattern>,<text>)
  说明
    过滤出 text 中符合模式 pattern 的字符串,可以有多个 pattern 。返回值为过滤后的字符串。

反过滤函数:filter-out

  格式$(filter-out <pattern>,<text>)
  说明
    去除符合模式 pattern 的字符串,保留符合的字符串。返回值是保留的字符串。

排序函数:sort

  格式$(sort <list>)
  说明
    将<list>中的单词排序(升序)。返回值为排列后的字符串.(:sort会去除重复的字符串。)

取单词函数:word

  格式$(word <n>,<text>)
  说明
    取出函数 <text> 中的第n个单词。返回值为我们取出的第 n 个单词。

统计单词数目函数:words

  格式$(words <text>)
  说明
    字算字串<text>中单词的数目,返回值:<text>字串中的单词数。

2、常用文件名操作函数

:下面的每个函数的参数字符串都会被当作或是一个系列的文件名来看待。

取目录函数:dir

  格式$(dir <names>)
  说明
    从文件名序列 names 中取出目录部分,如果没有 names 中没有 “/” ,取出的值为 “./” 。返回值为目录部分,指的是最后一个反斜杠之前的部分。如果没有反斜杠将返回“./”

取文件函数:notdir

  格式$(notdir <names>)
  说明
    从文件名序列 names 中取出非目录的部分。非目录的部分是最后一个反斜杠之后的部分。返回值为文件非目录的部分。

取后缀名函数:suffix

  格式$(suffix <names>)
  说明
    从文件名序列中 names 中取出各个文件的后缀名。返回值为文件名序列 names 中的后缀序列,如果文件没有后缀名,则返回空字符串。

取前缀函数:basename

  格式$(basename <names>)
  说明
    从文件名序列 names 中取出各个文件名的前缀部分。返回值为被取出来的文件的前缀名,如果文件没有前缀名则返回空的字符串。(获取的是文件的前缀名,包含文件路径的部分)

添加后缀名函数:addsuffix

  格式$(addsuffix <suffix>,<names>)
  说明
    把后缀 suffix 加到 names 中的每个单词后面。返回值为添加上后缀的文件名序列。

添加前缀名函数:addperfix

  格式$(addperfix <prefix>,<names>)
  说明
    把前缀 prefix 加到 names 中的每个单词的前面。返回值为添加上前缀的文件名序列。

链接函数:join

  格式$(join <list1>,<list2>)
  说明
    把 list2 中的单词对应的拼接到 list1 的后面。如果 list1 的单词要比 list2的多,那么,list1 中多出来的单词将保持原样,如果 list1 中的单词要比 list2 中的单词少,那么 list2 中多出来的单词将保持原样。返回值为拼接好的字符串。

获取匹配模式文件名函数:wildcard

  格式$(wildcard PATTERN)
  说明
    列出当前目录下所有符合模式的 PATTERN 格式的文件名。返回值为空格分隔并且存在当前目录下的所有符合模式 PATTERN 的文件名

3、其它常用函数

循环函数:foreach

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

条件函数:if

  格式$(if <condition>,<then-part>)或(if<condition>,<then-part>,<else-part>)
  说明
    if 函数else部分可有可无,即if函数的参数可以是两个,也可以是三个。
    condition参数是 if 表达式,如果其返回的是非空的字符串,那么这个表达式就相当于返回真,于是,then-part就会被计算,否则else-part会被计算。
    而if函数的返回值是:如果condition为真(非空字符串),那么then-part会是整个函数的返回值。如果condition为假(空字符串),那么else-part将会是这个函数的返回值。此时如果else-part没有被定义,那么整个函数返回空字串符。所以,then-partelse-part只会有一个被计算.

创建新的参数化的函数:call

  格式$(call <expression>,<parm1>,<parm2>,<parm3>,...)
  说明
    可用来写一个非常复杂的表达式,这个表达式中,我们可以定义很多的参数,然后你可以用 call 函数来向这个表达式传递参数。

找变量来源函数:origin

  格式$(origin <variable>)
  说明
    不操作变量的值,只告诉你这个变量是哪里来的。
    注意: variable 是变量的名字,不应该是引用,所以最好不要在 variable 中使用“$”字符。origin 函数会员其返回值来告诉你这个变量的“出生情况”。
  origin函数返回值
    “undefined”:如果从来没有定义过,函数将返回这个值。
    “default”:如果是一个默认的定义,比如说“CC”这个变量。
    “environment”:如果是一个环境变量并且当Makefile被执行的时候,“-e”参数没有被打开。
    “file”:如果这个变量被定义在Makefile中,将会返回这个值。
    “command line”:如果这个变量是被命令执行的,将会被返回。
    “override”:如果是被override指示符重新定义的。
    “automatic”:如果是一个命令运行中的自动化变量。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值