Makefile文件的简单编写

5 篇文章 0 订阅
1 篇文章 0 订阅

参考:
MakeFile文件是什么——内容、工作原理、作用、使用
嵌入式操作系统linux篇(书)
Makefile伪目标
GNU make中文手册.pdf

  在嵌入式开发中,一个工程中的源文件是非常多的,如果一个个编译会很麻烦,Makefile的出现解决了这个麻烦事,只要我们把Makefile写好,只需要“make”一下,整个工程完全自动编译,极大的提高了软件开发的效率。
下图显示了makefile在工程中的位置,一个目录都有一个makefile文件。

在这里插入图片描述
  make是 一个命令解析工具,用来解释makefile中的规则,编译源代码,生成结果代码。一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。从事嵌入式系统开发需要掌握好makefile的编写。

一. Makefile 的规则:

  1. 一个规则是由目标条件(又称为先决条件)和命令所组成的。命令与条件之间的表达的就是依赖关系(因为这种关系,我们有时候会将条件与依赖不加于区分),这种依赖关系指明在构建目标之前,必须保证条件先满足(或已经构建好了条件)。
  2. 所谓目标就是我们想要生成的文件,然后要生成该文件所需的资源称为条件,将条件变成目标的“动作”称为命令
    在这里插入图片描述
  3. 当条件是目标时,必须先把目标构建出来。

在这里插入图片描述

  1. 一个makefile中是存在多个规则的,在编写规则时应先把各文件的依赖关系想清楚,需要表达什么样的依赖关系。拿下图举例,我们要生成最终的目标 需要的依赖是 目标1,而目标1还没有被构建出来,这时候会先去构建目标1,执行第二个规则,这时候目标2还没构建,又去执行第三个规则,这种依赖关系与函数的递归调用挺类似的。
    需要注意的是:如果第一个规则中的条件不存在,或者该条件不与其它规则有联系,那么规则中的第一个目标将被视为默认目标,其他目标就不会被执行。
    在这里插入图片描述

二.伪目标(又为假目标)

  1. 一般来说,make 的最终目标是 makefile 中的第一个目标,而其它目标一般是因为要生成这个最终目标而附带出来的。这是 make 的默认行为。当然,你的 makefile 是由许多个目标组成,你可以通过 make+目标,让其完成你所指定的目标。 make 可以指定所有 makefile 中的目标,那么也包括“伪目标”,于是我们可以根据这种性质来让我们的 makefile 根据指定的不同的目标来完成不同的事。
    所谓伪目标就是没有条件,只有目标和命令,然后再目标前面加了.PHONY关键字声明。它不代表一个真正的文件名,是一个“虚假”的目标。有时我们也可以将一个伪目标称为标签
    格式:
    在这里插入图片描述
  2. 伪目标的用途是什么,下面先举个例子:
    比如我们在当前目录下有个hello.c文件,在makefile编写一个规则编译这个.c文件成为可执行文件。
    在这里插入图片描述
    执行make命令,在当前目录找到makefile,然后执行makefile里面的命令。
    在这里插入图片描述
      通过makefile生成了一个可执行文件hello,如果我们这时候想把这个可执行文件删除,只需执行linux命令 rm hello删除该文件。这样是可行的,但当一个工程变大,生成的中间文件很多,还要一个个删除就很浪费时间了。这时候,就需要在加一个规则,在这个规则中添加删除命令。然后在命令行执行meke+标签命令,即可删除可执行文件hello,如
    在这里插入图片描述
      这里的clean是一个目标,在执行make时可以指定 clean这个目标来执行其所在规则定义的命令,也就是执行rm hello这个操作。也就是说,我们使用伪目标这种规则所定义的命令不是去创建目标文件,而是使用make指定具体的目标来执行一些特定的命令。 这里的clean还不是真正的“伪目标”,当在同一个目录下存在一个与该目标名一样的文件,这时候再去执行make clean 时,会提示该“clean”已经是最新了,如下图。
    在这里插入图片描述
      为什么会出现这种情况呢?其实,每次在执行make命令时,make都会去检查依赖关系中相关文件的时间戳,如果条件中的相关文件的时间戳大于目标的时间戳,也就是说依赖比目标新,则说明依赖文件有改变,那么需要运行规则当中的命令去重新构建目标。因为make工具将clean目标当成一个文件,且在当前目录下找到了这个文件,加上这个规则里没有依赖,所以,当要求make构建clean目标时,它就会认为clean是最新的了。
    在这里插入图片描述
  3. 在实际工作中会出现当前目录有的文件名会出现与目标名一样的情况,那么如何处理呢?
      只需要在目标前面加上.PHONU关键字,用来说明clean这个是一个伪目标,如果当前目录下有跟目标一样的文件,还可以去执行make clean命令了。对于伪目标,就是只将它作为一个“命令”使用,而不是一个“文件”,所以每一次make这个伪目标,其所在的规则中的命令都会被执行。如图:
    在这里插入图片描述
  4. GNU常用的伪目标名称
      all:这个伪目标是所有目标的目标,其功能一般是编译所有的目标。
      clean:这个伪目标功能是删除所有被 make 创建的文件。
      install :这个伪目标功能是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去。
      print:这个伪目标的功能是例出改变过的源文件。
      tar:这个伪目标功能是把源程序打包备份。也就是一个 tar 文件。
      dist:这个伪目标功能是创建一个压缩文件,一般是把 tar 文件压成 Z 文件。或是 gz 文件。
      TAGS:这个伪目标功能是更新所有的目标,以备完整地重编译使用。
      checktest:这两个伪目标一般用来测试 makefile 的流程。

三:扩展

   echo命令:该命令是Bash shell的一个命令,其功能用于打印字符串到终端上。在终端上输入echo “hello world”,它会输出原样输出hello world这个字符串。这个命令用于在编译或者调试过程中输出一些信息。
在这里插入图片描述
   在makefile中,先不加echo命令的情况,如下图,make 执行命令行并把命令行的内容进行输出。我们称之为“回显”,就好像我们输入命令执行一样。这样make后生成了一个可执行文件hello。需要注意的是,由于在makefille中“$”具有特殊含义,因此如果想采用echo输出“$”,则必须用两个连着的“$”。还有,如果想显示“$@”符号,需要在前面加一个脱字符“\"。在这里插入图片描述
   如果前面加一个“@”符号,这时候在执行make命令时,这些命令执行时不会在终端上显示,生成了一个可执行文件hello。如下图在这里插入图片描述
   如果只加了echo,然后在执行如下图,这时候会把makefile文件中命令显示出来:echo gcc hello.c -o hellogcc hello.c -o hello但不会去生成可执行文件hello。echo命令就只是用来显示后面的内容。(注意:echo前面必须只有Tab键的长度,且至少有一个Tab,不能用空格代替,否则编译错误为: *** missing separator. Stop)
在这里插入图片描述
  当通过上面的图片显示可以看到,显示了echo gcc hello.c -o hello的内容和gcc hello.c -o hello的内容,这样出现的内容就会很多余。这时候,在echo前面加个“@”符号,就不会显示echo行的内容,只显示echo后面的内容,而且如果echo后的内容是命令,make也不会去执行这个命令。如下图,这样我们可以使用@echo+要显示的内容,来显示一些调试信息。
在这里插入图片描述
  通过一个例子来说明之前说写的内容:在一个目录里写了三个c文件,main.c1.c2.c,然后使用makefile文件去构建可执行文件main。(#号表示注释,类似c语言的//
在这里插入图片描述
  执行make命令,从下图可以看到,写的编译信息通过终端显示了出来。通过编译信息显示的顺序也可以知道,在make执行第一条规则时,由于条件main.o1.o2.o三个先决条件没有被构建,所以按从左到右的顺序去构建这三个条件,最后构建这三个条件后,执行第一个规则,将这三个条件去链接成可执行文件main
在这里插入图片描述
  使用ls -a命令可以看到,make结束后产生了一些中间文件:main.o、1.c、2.c。通过执行伪目标make clean去清除这些中间文件。

四:Makefile变量

   当工程文件变多,makefie文件会写的很庞大,这时就需要使用变量来使makefile内容变得简洁、具有可维护性。比如可以将上个图中的makefile改成下图:
在这里插入图片描述
makefile中的变量只代表文本数据(字符串)
makefile中的变量名规则:
  ----变量名可以包含字符,数字,下划线
  ----不能包含“:”、“#”,“=”或“ ”
  ----变量名大小写敏感
对变量的引用采用$(变量名)或者${变量名}这两种方式。

变量可分为三种:1.用户自定义变量、2.预定义变量、3.自动变量

  1. 用户自定义变量:上面的OBJ、OBJS就是用户自定义的变量
  2. 预定义变量:上面的CC、RM就是预定义变量,该变量的值可重新被改变,如果不改变的话,保持默认值。上图又可改成:
    在这里插入图片描述
    make输出,从下图可以看到,$(CC)替换成cc,$(RM)替换成rm -f在这里插入图片描述
    makefile常见的预定义变量:
    在这里插入图片描述在这里插入图片描述
      ar:建立、 修改、 提取归档文件。 归档文件是包含多个文件内容的一个大文件, 其结构保证了可以恢复原始文件内容
    GCC的执行过程
      1.调用cpp进行预处理, 对源代码文件中的文件包含(include)、 预编译语句(如宏定义define等)进行分析;
      2.调用ccl进行编译, 生成.s为后缀的目标文件;
      3.调用as进行汇编, 汇编语言文件经过预编译和汇编之后都生成以.o为后缀的目标文件;
      4.调用ld进行链接, 所有的目标文件被安排在可执行程序中的恰当的位置。 同时, 该程序所调用到的库函数也从各自所在的档案库中链接到合适的地方。
  3. 自动变量:所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直到所有的符合模式的文件都取完了。这种自动化变量直营出现在规则的命令中。上图又可改成:
    在这里插入图片描述

常用的自动变量:

  1. $@:代表规则中的目标文件名。如果目标是一个文档( Linux中,一般称.a文件为文档),那么它代表这个文档的文件名。在多目标的模式规则中,它代表的是哪个触发规则被执行的目标文件名(当目标有多个时)。如下图,第一个规则中$@代表libtest.a文件,第二个规则中$@代表1.o、2.o文件.。在这里插入图片描述

  2. $%:规则的目标文件是一个静态库文件时,代表静态库的一个成员名。例如,规则的目标是“foo.a(bar.o)”,那么,“$%”的值就为“bar.o”,“$@”的值为“foo.a”。如果目标不是函数库文件,其值为空。如下图。在这里插入图片描述

  3. $<:规则的第一个依赖文件名。如果是隐含规则,则它代表通过目标指定的第一个依赖文件。

  4. $?:所有比目标文件更 新的依赖文件列表,空格分割。如果目标是静态库文件名,代表的是库成员( .o文件)的更新情况。

  5. $^:规则的所有依赖文件列表,使用空格分隔(当依赖有多个时)。如果目标是静态库文件名,它所代表的只能是所有库成员( .o文件)名。一个文件可重复的出现在目标的依赖中,变量“$^只记录它的一次引用情况。就是说变量“$^”会去掉重复的依赖文件。如下图,在规则1中目标是静态库,所以$^代表该静态库的成员1.o、2.o
    在这里插入图片描述

  6. $+:类似“$^”,但是它保留了依赖文件中重复出现的文件。主要用在程序链接时,库的交叉引用场合。

四种变量定义(赋值)

GNU make 中,一个变量的定义有四种方式(或者称为风格)。我们把这四种方式定义的变量可以看作变量的四种不同风格。变量的这四种不同的风格的区别在于: 1. 定义方式; 2. 展开时机。

  1. 递归展开式变量(递归赋值)–影响的变量可能会是多个
    这一类型的变量的定义是通过“=”或者使用指示符“define”定义的变量。
    比如下图,整个变量的替换过程时这样的:首先“$(name1)”被替换为
    $(name2)”,接下来“$(name2)”被替换为“$(name3)”,最后“$(name3)”被替换为“小明”。整个替换的过程是在执行“echo $(name1)”命令行时进行的。
    在这里插入图片描述优点是:这种类型变量的定义时,可以引用其它的之前没有定义的变量(可能在后续部分定义,或者是通过 make 的命令行选项传递的变量)。
    但这种递归展开式又有缺点,如下图:使用此风格的变量定义,可能会由于出现变量的递归定义而导致 make 陷入到无限的变量展开过程中,最终使 make 执行失败。 它将会导致 make 进入对变量name1、name2、name3的无限展开过程中去(这种定义就是变量的递归定义)。展开过程将是套嵌的、不能终止的(在发生这种情况时, make 会提示错误信息并结束)。一般在书写 Makefile 时,使用这种追加变量值的方法也很少使用(也不是我们推荐的方式)。
    在这里插入图片描述
  2. 直接展开式变量(简单赋值):对“递归展开式”的优化
    这一类型的变量的定义是通过“:=”定义的变量。
    如下图,变量值中对另外变量的引用或者函数的引用在定义时被展开(对变量进行替换)。所以在变量被定义以后就是一个实际所需要定义的文本串,其中不再包含任何对其它变量的引用。这种定义变量的方式更适合在大的编程项目中使用,
    因为它更像我们一般的编程语言。需要注意的是:此风格变量在定义时就完成了对所引用的变量的展开,因此它不能实现对其后定义变量的引用。
    在这里插入图片描述
    如果在定义name2时前面没有name1的定义,那么name2的值是空的,如下图,由于在变量“name1”的定义出现在“name2”定义之后。因此在“name2”的定义中,“name2”的值为空。这一点也是直接展开式与递归展开式的不同点。在这里插入图片描述
    如果将name2的定义改成递归展开式,那么name2的值是不为空的,如下图:在这里插入图片描述
  3. 条件赋值(?=):意思就是如果变量在前面没有定义,使用赋值符号中的值定义该变量。如果变量在前面已经定义,对该变量的赋值无效。比如下图,由于name1之前已经定义了,所以第二次定义name1无效。
    在这里插入图片描述
    如果前面name1未定义,这将name1赋值为"小明",如下图在这里插入图片描述
  4. 追加赋值(+=):意思就是在原来已经被赋值的变量上在附加内容。如下图:
    在这里插入图片描述

隐式规则

  所谓隐式,就是有些中间的规则不用写出来,make 会在自己的“隐含规则”库中寻找可以用的规则,如果找到,那么就会使用。如果找不到,那么就会报错。在之前所写的规则都是显式的。在下面例子中,make 调用的隐含规则是,把[.o]的目标的依赖文件置成[.c],并使用 默认规则“$(CC) $(CFLAGS) $(CPPFLAGS) -c -o”来生成[.o]的目标,这样就不用再写将.c文件编译成.o的规则了。三个.o文件构建后,因为该规则没有命令,然后就不会去生成可执行文件a.out。
在这里插入图片描述
  这里需要注意的是,使用隐式规则是会使用到一些变量(比如预定义变量),这里的CFLAGS、CPPFALGS是无默认值的,如果不给这些变量赋值,这些值是”空“的。如果给这些赋予一些参数,如下图,这样在执行默认规则时就会显示出来了。
在这里插入图片描述
在规则中添加生成可执行文件a.out的命令,如下图
在这里插入图片描述

模式规则

  模式规则中,至少在规则的目标定义中要包含"%",否则,就是一般的规则。目标中的"%“定义表示对文件名的匹配,”%“表示长度任意的非空字符串。例如:”%.c“表示以”.c“结尾的文件名 , 而"s%.c"则表示以"s"开头、”.c"结尾的文件名 。从下图可以看到,main.o : main.c 、1.o : 1.c、2.o : 2.c 三个规则都相同,可以使用模式规则把这三个规则写成%.o : %.c。
在这里插入图片描述
  这里需要注意的是,前面a.out : $(OBJS)原来是隐式规则,然后这里使用了模式规则,从.c文件生成.o文件的规则不在按照隐式规则的规定去生成,而是按照模式规则的规定。如下图,执行make后按照模式规则里面的命令去执行。在这里插入图片描述

make函数(以下只列举两个函数,更多信息请参考GNU make手册)

  1. 文件名处理函数:
    在这里插入图片描述
    测试:在这里插入图片描述
  2. 文本处理函数:
    在这里插入图片描述
    测试:
    在这里插入图片描述
    两个函数结合:在这里插入图片描述
  3. shell函数:
    在这里插入图片描述
    测试: 使用shell命令打印当前目录的文件属性在这里插入图片描述
    在这里插入图片描述
  • 17
    点赞
  • 76
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值