【Linux】项目自动化构建工具-make与Makefile的简单使用(模拟实现进度条)

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

目  录


前言

  • 会不会编写Makefile从侧面说明了一个人是否具备完成大型工程的能力。
  • 一个工程中的源文件不计其数按类型、功能、模块分别放在若干个目录中Makefile定义了一系列的规则来指定哪些文件需要先编译哪些文件需要后编译哪些文件需要重新编译甚至于进行更复杂的功能操作。
  • Makefile的好处在于自动化编译一旦编写好Makefile文件只需要一个make命令整个工程完全自动编译极大的提高了软件开发的效率。
  • make是一个用于解释Makefile中指令的命令工具通常大多数的IDE都有这个命令如Delphi的makeVisual C++的nmakeLinux下GUN的make。 可见Makefile成为了一种在工程方面的编译方法。
  • make是一条命令Makefile是一个文件二者搭配使用以完成项目自动化构建。

1 make与Makefile使用

首先以对 test.c 文件的编译为例来了解make与Makefile的基本使用

make与Makefile使用


Makefile的基本编写规则

  • Makefile文件中保存了编译器和连接器的参数选项并且描述了所有源文件之间的关系。make程序会读取Makefile文件中的数据然后根据文件中编写的相关指令调用编译器、汇编器、链接器以产生最后的输出。
  • Makefile文件中包括了 依赖关系依赖方法 。如上图所示我们以 目标文件:依赖文件 的格式来编写依赖关系表示要生成目标文件需要依赖有对应的依赖文件。在图例中即是test文件可执行文件的生成依赖于test.o文件二进制目标文件test.o文件的生成依赖于test.s文件汇编语言文件test.s文件的生成依赖于test.i文件经过预处理后的C原始程序test.i文件的生成依赖于test.c文件源文件。而单有依赖关系是无法通过依赖文件生成目标文件的因为依赖关系只告诉了目标文件从何而来并没有说如何根据依赖文件生成目标文件因此还需要依赖方法所谓依赖方法其实就是我们平常在命令行上输入的一条条指令如示例中我们通过命令 gcc test.o -o test 可以从test.o文件得到test文件这也就是依赖关系 test:test.o 对应的依赖方法以此类推gcc -c test.s -o test.o 是依赖关系 test.o:test.s 对应的依赖方法gcc -S test.i -o test.s 是依赖关系 test.s:test.i 对应的依赖方法gcc -E test.c -o test.i 是依赖关系 test.i:test.c 对应的依赖方法。有了依赖关系和依赖方法我们就可以生成对应的目标文件。 有两点值得注意的是每次编写依赖方法时应在依赖关系下另起一行并在开头空一个 Tab 键的位置这是固定的编写格式依赖文件列表可以为空当其为空时则表示不生成该目标文件只执行相应的命令。
  • Makefile中使用 .PHONY 来声明伪目标格式为.PHONY:伪目标 。Makefile中的伪目标表示目标名称并不代表真正的文件名与实际存在的同名文件没有相互关系因此伪目标不管同名文件是否存在都会执行对应的生成指令。伪目标的作用有两个使目标对象无论如何都要重新生成并不生成目标文件而是为了执行一些指令。
  • Makefile中可以在行首使用 # 以表示行注释。

make的工作规则

  • 在默认情况下make会在当前目录下按顺序寻找文件名为 GUNmakefilemakefileMakefile 的文件。因此如上图所示执行make命令前需要先在项目所在当前目录下创建相应的文件这里是Makefile。
  • make的执行规则是若make命令之后没有跟指定目标文件则默认只生成Makefile中所有目标文件中的第一个如图示例中的test文件即为第一个目标文件否则就只生成指定目标文件。 但如果该目标文件的依赖文件不存在make会根据语法规则递归生成第一个目标文件的所有依赖文件后再回头生成第一个目标文件如上图示例中一开始当前目录下只有源文件test.c根据依赖关系要生成第一个目标文件test就先要生成其依赖文件test.o以此类推就是要依次先生成文件test.i、test.s、test.o所以也可以看到在执行make命令时根据回显的命令执行过程是按照与Makefile文件中编写的依赖方法的倒序来执行命令的并且除第一个目标文件test外还生成了中间目标文件test.i、test.s、test.o。
  • 依赖方法目标后所跟的命令不总是被执行的如果目标文件不存在或是目标文件所依赖的文件的修改时间要比目标文件新才会执行后面相应的命令以形成目标文件。也就是说make会根据语法规则分析目标对象与依赖对象的时间信息来判断是否在上一次目标生成后源文件依赖文件发生了修改若发生了修改才需要重新生成。在最开始的图例中一开始当前目录下是不存在任何目标文件的所以执行make命令时根据Makefile文件中的依赖关系会正常生成所有的目标文件。而如果是目标文件已存在的情况如下图所示在当前目录下已存在目标文件且目标文件的最近修改时间要比源文件的最近修改时间新此时执行make命令表示当前目标已是最新不再执行命令。而当通过 touch 命令更新源文件最近修改时间后 说明 touch 命令在文件不存在时会创建文件而如果文件已经存在则将文件的修改时间更新至最新当然这里还可以通过重新编辑保存文件的方式来更新文件最近修改时间 使得源文件的最近修改时间新于当前目录下的目标文件再次执行make命令则显示成功并且当前目录下的目标文件的最近修改时间进行了更新又新于源文件了。

时间问题示例

那如果想让某目标下的命令不论时间如何每次都能执行呢如上Makefile编写规则中说到可以采用声明伪目标的方式使得对应命令总是被执行但此处并不建议将目标可执行文件声明为伪目标因为每次编译都需要一定的消耗如果当前目标已是最新没必要再执行命令生成一样的目标。

  • make在找寻依赖关系的过程中如果出现错误如最后被依赖的文件找不到那么make就会直接退出并报错而对于所对应的命令依赖方法的错误或是编译不成功make仍会执行正确的命令再退出。具体示例如下

错误示例

  • make只关注文件间的依赖关系如果找到了依赖关系但当前目录下却没有依赖关系中对应的依赖文件那make将停止工作如下图所示移除当前目录下的test.c文件最终依赖文件虽然Makefile中编写了依赖关系但由于找不到依赖文件make停止了工作。

当前目录缺失依赖文件


关于项目清理

工程是需要被清理的。如最开始图例中所示我们使用了 .PHONY 声明了伪目标 clean 顾名思义该目标的意义在于清理这个伪目标并没有所谓的依赖文件显然是起到上述中的第二个作用即不生成目标文件总是执行相应的指令。可以看到 clean 对应的指令为 rm -f test.i test.s test.o test 目的在于清除所有的目标文件以便重新编译也就是所谓的项目清理。由于伪目标 clean 没有被第一个文件直接或间接关联因此默认情况下执行 make 命令 clean 目标下的命令不会自动执行需要通过指定目标即以 make clean 的命令格式使得 clean 目标下的命令被执行。而基于伪目标的特性无论当前目录下是否有同名文件clean或是否有已经生成的目标文件只要执行 make clean 命令clean对应的指令都会被执行如下图所示。

项目清理


2 模拟实现进度条

在编写进度条程序之前得先谈谈两个概念\n\r

在我们编写C程序时常常会用到 \n 换行符来使内容另起一行输出那 \r 又表示什么呢其实在我们日常编辑中可以看到无论是输入还是输出文字显示总是跟着光标的位置光标在哪则在哪输入输出。同样的我们的程序在输出时也是跟随着光标的移动进行。\n 表示的是回车并换行即在输出完当前内容后将光标移动到下一行的行首再进行之后内容的输出当然光标的移动不是说突然就变到了那个位置而是根据上下左右的方向一步步到达指定位置的而光标移动到下一行行首的方式可以分为两种一是先向左移动到当前行的行首再向下移动到下一行二是先向下移动到下一行再向左移动到下一行行首。如图所示键盘上的回车键等同于 \n 其造型则表示出了光标的移动方式。而 \r 表示的是只回车不换行即输出完当前内容后将光标移动到当前的行的行首因此如果再碰到 \r 之后如果还有内容需要输出的话会重新从当前行行首开始输出覆盖上一次的输出内容。

回车键

\n 我们已经比较熟悉了那下面以一个例子来看看 \r 对输出的影响

  • 编写如下程序进行测试

回车符测试代码

  • 测试结果如图所示当执行编译后生成的可执行程序test时我们并没有看到相关内容的输出执行后就输出了命令提示行。前面说到 \r 会使光标回到当前行行首而后输出的内容会覆盖之前的内容那是不是因为命令提示行输出覆盖太快所以没能看到内容显示呢那我们试着在输出内容后进行延时。

回车符测试

  • 增加延时后测试结果可以看到增加延时后依旧没有显示相关内容只是在延时期间光标一直停在当前行首直到延时结束输出命令提示行。

延时测试


回车符延时测试

  • 为什么使用了回车符 \r 后我们没能看到相关内容输出呢这里不得不提到一个概念缓冲区 。我们所编写的内容并不是直接输出到屏幕上标准输出流的而是先输出到缓冲区中在由缓冲区输出到屏幕。这里没能看到相关内容的输出是因为其仍保留在缓冲区中没有输出只有当缓冲区刷新时其中的保留的内容才会输出而对于没有添加换行符和回车符或者添加的是换行符 \n 的情况通常会自动刷新缓冲区因此没有主动刷新也可以正常输出内容。那了解的原因所在后我们在程序中主动刷新缓冲区再看看输出结果可以看到增加刷新缓冲区后相关内容正常显示了也符合输出后光标回到当前行首由命令提示行覆盖输出内容。

缓冲区刷新测试


缓冲区刷新测试


我们不是要编写进度条程序吗这与 \r 有什么关系呢想来进度条大家都不陌生就是以一行上显示的移动进程来表示某项工作的进度。也就是说从0-100的进度需要再同一行上进行变化而 \r 具有输出内容后将光标移动到当前行首的作用这就契合了进度条的变化过程基于此以下模拟实现进度条

  • 我们将进度条程序的主要实现封装为一个函数编写在 proc.c 文件中将对应的函数声明编写在头文件 proc.h 中再创建测试文件 procTest.c 在其中编写主函数并调用进度条函数。这里实现了两种不同形式的进度条符号移动版和色块移动版。

    proc.h
    proc.h
    proc.c后附源代码有需要者可再自行演示
    proc.c
    procTest.c
    procTest.c


    进度条函数源代码
#include "proc.h"

#define STYLE '=' //进度条移动符号
    
//符号形式移动版    
void process(){    
  char bar[101];//进度条字符串预留一个'\0'的位置    
  char status[4] = {'|', '/', '-', '\\'}; //表示运行状态循环数组中的字符    
  memset(bar, '\0', sizeof(bar));//初始将进度条字符串中的内容全部置为'\0'    
  int i = 0;//考虑执行标准问题这里在外初始化    
  for(; i <= 100; i++){    
    //控制格式循环输出进度条字符串通过字符串的变化来表示进度条的变化    
    //\033[选项;选项;选项m表示对其后输出内容的颜色控制0m默认无颜色    
    printf("[\033[0;36m%-100s\033[0m][%d%%][%c]\r", bar, i, status[i%4]);    
    fflush(stdout);//刷新缓冲区确保字符串内容正常输出    
    bar[i] = STYLE;//每次输出后修改一个字符为对应的进度条移动符号    
    if(i < 99)    
    bar[i+1] = '>';//增加箭头显示当进度达到100%去掉箭头    
    usleep(100000);//延时0.1s显示    
  }    
  printf("\n");//结束后换行输出命令提示行    
}    
    
//色块形式移动版                                                                                                                                           
void process_color(){    
  char bar[102];//进度条字符串预留两个'\0'的位置    
  char status[4] = {'|', '/', '-', '\\'};     
  memset(bar, ' ', sizeof(bar));//初始将字符串中内容全部置' '(空字符)    
  bar[101] = '\0';//保持最后一个字符总是为'\0'    
  int i = 0;                                                                                                                                               
  //控制中间的'\0'字符将整个字符数组分为两个字符串输出确保两个字符串的长度加起来总是为100
  //循环输出两个字符串控制前一个字符串总是有颜色后一个字符串总是无颜色
  for(; i <= 100; i++){
    bar[i] = '\0';//控制分隔字符串的'\0'移动
    //输出带背景色的空字符串与不带背景色的空字符串
    printf("[\033[0;30;46m%s\033[0m%s][%d%%][%c]\r", bar, bar+i+1, i, status[i%4]);
    fflush(stdout);//刷新缓冲区
    bar[i] = ' ';//将前一个字符串中的内容均置为空字符' '
    usleep(100000);//延时0.1s显示
  }
  printf("\n");//结束后换行输出命令提示行
}

  • 前面我们讲解了make与Makefile的基本使用接下来我们就可以在进度条程序中使用起来了如下先在当前目录中创建Makefile文件文件编写如下
    Makefile
    接着执行make命令生成进度条可执行程序如下
    生成进度条可执行程序

  • 结果演示网络原因可能稍有卡顿
    符号移动版
    符号版进度条
    色块移动版
    色块版进度条

以上是我对make与Makefile工具使用的一些学习记录总结如有错误希望大家帮忙指正也欢迎大家给予建议和讨论谢谢

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: linux

“【Linux】项目自动化构建工具-make与Makefile的简单使用(模拟实现进度条)” 的相关文章