【Linux系统】第六篇:Linux的自动化构建工具——make/makefile

时间:2023-02-12 10:54:35

一、make和makefile的区分

make是一个命令,makefile是一个文件

二、依赖关系和依赖方法

依赖关系: 依赖关系指明了两个文件之间的依赖性。例如test.o文件的生成要依赖test.s文件

依赖方法: 依赖方法就是我依赖这个关系需要做什么。例如test.o形成需要test.s进行汇编,即gcc  -c  test.s  -o  test.o

三、make/Makefile的使用

 #include <stdio.h>
 
 int main()
 {
	printf("hello Makefile\n");                                                                                                           
	return 0;
}

如上一串代码,如果我们需要在Linux中对其进行编写,生成可执行程序,以往我们需要敲一长串指令gcc test.c -o test,当我们学会makefile之后我们就不在需要敲这一长串指令了,只需要一个make指令即可。

1、makefile的编写

  1. 首先在源文件的目录下创建一个makefile或者Makefile文件(首字母可以大写,其他的不行)
    【Linux系统】第六篇:Linux的自动化构建工具——make/makefile

  2. 然后打开makefile文件,进行规则的编辑:
    【Linux系统】第六篇:Linux的自动化构建工具——make/makefile
    【Linux系统】第六篇:Linux的自动化构建工具——make/makefile
    第一行中冒号左边的mycode是目标文件,该目标文件依赖于冒号右边的mycode.c而产生,所以第一行写的是依赖关系,第二行必须使用一个tab键之后写上两个文件的依赖方法。

  3. 接着然后直接使用make命令即可。如下:
    【Linux系统】第六篇:Linux的自动化构建工具——make/makefile
    这样就形成了我们所需要的可执行程序文件。

2、临时文件的清理

完成上面的编写后我们就可以生成需要的可执行文件,但若我们需要清理生成的临时可执行程序文件,则可以进行如下编写:
【Linux系统】第六篇:Linux的自动化构建工具——make/makefile

.PHONY表示被该关键字修饰的对象是一个伪目标。(该伪目标总是可被执行)

使用: 如下图:
【Linux系统】第六篇:Linux的自动化构建工具——make/makefile

疑问: 为什么编译的时候直接使用make就可以了,而进行清理需要加上clean呢?

其实make后面也可以接mycod,只不过make默认对makefile文件中的第一个目标文件可以省略名称

3、makefile的工作原理

  1. make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
  2. 默认情况下Makefile的第一个目标为终极目标。
      即默认情况下,makefile只形成一个可执行目标文件,形成之后,后续的依赖关系和依赖方法不再执行(默认从上到下扫描总是执行一个,默认不指明情况下,只形成第一个)
  3. all:Makefile文件默认只生成第一个目标文件即完成编译,但是我们可以通过all 指定所需要生成的目标文件。
  4. 若需要完成指定的功能,只需在make命令后加上自己写入makefile中的命令

4、文件的三个时间

上方我们提到,.PHONY是修饰伪目标的,该伪目标总是可被执行。什么意思呢?如图:
【Linux系统】第六篇:Linux的自动化构建工具——make/makefile
进行第一次make的时候,我们可以进行编译生成可执行程序文件,但当我们进行第二次make的时候就不可以了而且提示我们说mycode是最新的,而我们由.PHONY修饰的clean就可以总是被执行的。


那为什么gcc编译知道mycode是最新的呢?接下来我们认识一下文件的三个时间:

[wyt@VM-20-4-centos lesson3]$ stat mycode.c
  File: ‘mycode.c’
  Size: 81        	Blocks: 8          IO Block: 4096   regular file
Device: fd01h/64769d	Inode: 794005      Links: 1
Access: (0664/-rw-rw-r--)  Uid: ( 1001/     wyt)   Gid: ( 1001/     wyt)
Access: 2023-02-11 17:08:52.893235379 +0800 //文件最近访问时间
Modify: 2023-02-04 13:21:39.841893206 +0800 //内容被修改时间
Change: 2023-02-04 13:21:39.841893206 +0800 //属性被修改时间
 Birth: -

4.1、Access:最近一次访问文件的时间

读取文件内容,修改文件内容,Access都会发生变化.

修改文件内容,Access的时间会立即更新;但是读文件操作,不会立即更新。
【Linux系统】第六篇:Linux的自动化构建工具——make/makefile
【Linux系统】第六篇:Linux的自动化构建工具——make/makefile

我们读取文件或者查看文件,如cat、find等操作,这些都属于高频操作,如果我们频繁使用这些命令,可能会频繁更新Access时间,导致Linux的处理速度下降

在较新的Linux内核中,这个问题被优化了,对文件进行读操作以后,Access时间不会立即更新,经过一定的时间间隔,OS才会自动进行更新时间

4.2、Modify:最近一次修改文件内容的时间

只要是修改文件内容,Modify的时间都会被更新,这个被视为低频操作,所以一般修改文件内容以后,会立即刷新。

但是修改文件内容,有可能会顺带着修改文件属性,比如新增内容会改变文件大小(文件大小属于文件属性)
【Linux系统】第六篇:Linux的自动化构建工具——make/makefile

4.3、Change:最近一次修改文件属性的时间

修改文件属性也被视为低频操作,修改文件属性会立即更新Change的时间

只是单纯的修改文件属性,不会影响到Modify和Access的时间

【Linux系统】第六篇:Linux的自动化构建工具——make/makefile

4.4、‘xxx’ is up to date问题

所以我们在上面遇到的make问题就很好解答了。
【Linux系统】第六篇:Linux的自动化构建工具——make/makefile
第一次make后,生成一个可执行目标文件,再之后make时,需要比较可执行目标文件mycode和源文件mycode.c的Modify的时间

【Linux系统】第六篇:Linux的自动化构建工具——make/makefile

可执行目标文件mycode的modify的时间比源文件mycode.c的modify时间新,所以不可以再进行编译了。

当我们的源文件时间进行修改后,我们又可以再次进行make编译了
【Linux系统】第六篇:Linux的自动化构建工具——make/makefile

总结:

当源文件的Modify时间比目标文件的Modify时间新时,才可以再次利用make进行编译。

四、makefile的推导规则

makefile文件在扫描时,是从上往下进行扫描的,比如下图:
【Linux系统】第六篇:Linux的自动化构建工具——make/makefile

make会一层一层地去找文件的依赖关系,直到最后编译出第一个目标文件。

拿上图举例:
【Linux系统】第六篇:Linux的自动化构建工具——make/makefile
如上,最后找到mycode.i目标文件后,就会在一层一层的往上,最后形成一个可执行的目标文件。

上述的推导过程就如同一个栈结构,先进后出。

五、进度条小程序

1、行缓冲区问题

先看下面两串代码执行的现象:
代码1:
【Linux系统】第六篇:Linux的自动化构建工具——make/makefile
现象:
【Linux系统】第六篇:Linux的自动化构建工具——make/makefile

代码2:
【Linux系统】第六篇:Linux的自动化构建工具——make/makefile

现象:

【Linux系统】第六篇:Linux的自动化构建工具——make/makefile

两串代码的区别仅仅就是代码1有\n,而代码2没有那为什么会造成不同的现象呢?

首先我们要明确一点就是,代码是顺序结构的,所以他是从上往下执行,所以先执行printf在执行sleep。至于为什么会有不同的现象,如下:

,这里就有一个行缓冲区的概念在这里。对于C语言级别的缓冲区而言,任何字符串都会先保存在这个缓冲区里面,等待刷新在显示屏上。但是显示器刷新是属于行刷新,就是遇到‘\n’就进行刷新。所以现在我们就理解了,代码2中没有’\n’,要打印的字符串一直保存在了C语言级别的缓冲区,只有程序运行快结束是才会把这些字符串刷新在屏幕上。

那我们该如何将代码2的printf打印立马显示到显示器上呢?

#include <stdio.h>    
#include <unistd.h>    
int main()    
{    
    printf("hello LInux");
    fflush(stdout);//fflush会立即刷新缓冲区
    sleep(2);    
    return 0;    
}   

2、回车换行(\r与\n)

理解:

  • \r:回车。回到当前行的最开始
  • \n:换行。换到下一行,但列不变

在语言层面:\n就是回车换行

3、倒计时功能

#include <stdio.h>    
#include <unistd.h>    
int main()    
{    
    int cnt=10;    
    while(cnt)    
    {    
        printf("%2d\r",cnt);//2d控制刷新两位,\r表示回车                                                                                   
        fflush(stdout);//手动刷新缓冲区   
        sleep(1);    
        --cnt;    
    }    
    return 0;    
}  

3、进度条的实现

多文件形式:
【Linux系统】第六篇:Linux的自动化构建工具——make/makefile

头文件process.h

#pragma once
 
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#define NUM 101
extern void ProncessOn(); //函数的声明 

源文件process.c

#include "process.h"

void ProncessOn() //函数的定义
{
    int cnt = 0;
    char bar[NUM];
    memset(bar, '\0', sizeof(bar));

    const char *lable = "|\\-/";
    while(cnt <= 100)
    {
        printf("[%-100s][%d%%][%c]\r", bar, cnt, lable[cnt%4]);
        fflush(stdout);//立即打印
        bar[cnt++] = '#';                                                                                                               
        //sleep(1);//单位是秒,太慢了
        usleep(50000); //微妙  5S/100 == 0.05S == 50000
    }
    printf("\n");
}

效果如下:

【Linux系统】第六篇:Linux的自动化构建工具——make/makefile