Linux 自动化构建工具(make/Makefile)

绪论

        拼着一切代价,奔你的前程。——巴尔扎克. 本章继续学习Linux常用的工具,make是可以帮我们解决一些重复使用相同指令的冗杂的自动化构建工具。

话不多说安全带系好,发车啦(建议电脑观看)


附:红色,部分为重点部分;蓝颜色为需要记忆的部分(不是死记硬背哈,多敲);黑色加粗或者其余颜色为次重点;黑色为描述需要


思维导图:

要XMind思维导图的话可以私信哈


目录

1.make/Makefile

2.Linux小程序----进度条


1.make/Makefile

知识点:

make是一个指令而Makefile(makefile首字母大小写不管)是一个文件(这个文件内装着所要通过make快速执行的指令)

在Makefile内有着多个依赖关系和依赖方法,每个依赖关系都有其对应的依赖方法

使用方法:

用vim打开Makefile在文件中,写下依赖关系和依赖方法

  1. 依赖关系写在依赖方法的上边:
  2. 有依赖关系的文件用冒号(:)隔开,其中的目标文件是冒号左边的文件,而目标文件所依赖的文件放在右边,一个目标文件可以依赖于多个文件
  3. 在写依赖方法的之前必须用tab隔开一段距离后再写依赖方法
  4. 依赖方法就是指令(如gcc生成可执行程序的指令)

简单举例:

写完Makefile文件后,直接使用make指令就能使用第一个Makefile内部的依赖方法(自顶向下)

再在Makefile中写下一段依赖关系和依赖方法

此时要调用的话就需要指定依赖关系(其中目标文件clear不需要依赖任何文件所以就不用写):

指定使用的依赖关系:make clear指定到clear这个依赖关系上

细节:

依赖关系、依赖方法:

以例子来直接的表示如:mycode:mycode.c

其中,目标文件是mycode,而目标文件依赖于后面的mycode.c,

而既然有了依赖关系的话就需有他们之间的依赖方法gcc -o mycode mycode.c(这样才有意义),也就是根据依赖关系来执行依赖方法。

Makefile依赖关系的自动化推导

对于在Makefile中的依赖关系来说,假如其目标文件所依赖的文件不存在则会在Makefile中查找是否有生成该依赖文件的方法有则进行如递归式(栈式)的先形成依赖文件再返回到开始的依赖关系,反之没有形成依赖关系文件的方法则会报错。

附:

对于make来说当你进行了一次后,如果所指向的依赖关系中的文件没有被修改的话,一般来说就不会进行make指令(因为已近存在了所以编译器就选择不再执行,来提高编译效率)

那这种行为是如何实现的?:比较时间

首先了解:

在Linux中文件的时间(ACM):

Access(访问):打印、改变都算访问、增删查改(几乎任何操作)

Modify(修改文件内容)、change(改变文件属性)

其中当修改了文件内容的话文件属性也会更改(文件属性中包括文件的大小、最久的修改时间所以修改文件内容的话文件的属性也会跟着改变,同样对于Access来说也可能会更改)

查看文件的时间的指令:stat + 文件名

当我对文件进行修改过后其时间就会发生改变

当也可以发现Access并没有改变这是因为,Access因为太常用所以编译器进行了优化设置了他被使用一定次数后才会改变,当一个文件被调用很多次的时候能减少一定的损耗。

单独改变文件属性的例子:

手动更新时间指令 touch:

附加指令:

-a 

 -m 

总结:而对于make来说他是否执行依赖关系进行编译是通过判断比较的是源文件和可执行程序的modify内容修改时间的老旧()

比较源文件和可执行程序的修改时间:

  1. 源文件修改时间 老于 可执行程序的修改时间 则make不会再次执行
  2. 源文件文件修改时间 新于 可执行程序的修改时间 则make会再次执行
  3. 因为一般而言,源文件的修改时间是比可执行程序的修改早的(老的)所以此时就表示这源文件生成可执行程序后并没有被修改过,所以就不需要被再次编译。
  4. 若想不管这个时间的话可以用.PHONY来修饰(总是被执行),其中.PHONY修饰的文件被称为伪目标

特殊符号:

  1. $@:可以代替目标文件(冒号左边)、$^:可以代替目标文件所依赖的所有文件(冒号右边)所以就能改成来自动识别目标文件和所依赖的文件:
  2. 在依赖方法前面加上@:可以让依赖方法不显示

2.Linux小程序----进度条

知识点:

  1. 回车换行:
    1. 一般来说\n就直接代表着回车换行
    2. 回车、换行也能分开来看
      1. 回车:回到所在行的最开始(\r)
      2. 换行:换到下一行(就是\n)
    3. 缓冲区
      1. 对于\n来说可以用来刷新缓冲区,只有刷新缓冲区(刷新标准输出流stdout)才能拿出缓冲区的东西,如:用printf打印时会先把要打印的东西放进缓冲区内所以只有在后面加上\n才会在该语句结束时直接打印出所要打印的。反之若没有\n则会在程序结束后自动刷新缓冲区才会打印出来。
      2. 对此我们如果不想刷换行(\n)又想刷新缓冲区的话,那就可以用函数fflush(stdout)( int fflush ( FILE * stream ) 、stdout是标准输出流(一般编译器会在执行程序时会自动打开标准输入、输出、错误流))

练习(倒计时小程序):

sleep(second)函数的用处是休眠second秒、头文件:#include <unistd.h>

usleep(usec) 函数用处是休眠usec微秒(1s = 1000000um ) 、头文件...一样

#include<stdio.h>
#include<unistd.h>
int main()
{
     int count = 10; 
 
     while(count>=0)
      {
         printf("%-2d\r",count--);//%-2d 对打印的整形进行右对齐并且每次打印两个字符
         fflush(stdout);//因为没有\n所以需要自行刷新缓冲区
         sleep(1);//休眠1s
     }
     return 0;
}                                                                                                                                       

附:此程序是在Linux,gcc编译器下,

若在vs下的话需要把sleep换成Sleep(ms) 内部为毫秒 并且加上头文件#include<windows.h>

下面开始正式的来实现一个进度条:

  1. 分源管理创建文件: processBar.c、processBar.h、main.c 
  2. 创建Makefile
  3. 根据进度条的特性模仿一个进度条
    1. 方法一:单纯的实现进度条
      1. processBar.c

        #include "processBar.h"
        
        const char *lable = "|/-\\";// \ 反斜杠是特殊的字符所以需要用\\ 来表示成 \
        
        
        void processBar(int speed)
        {
            int cnt = 0;
            char bar[102];
            memset(bar,'\0',sizeof(bar));
            int len =  strlen(lable);
        
            while(cnt <= 100)
            {
                printf("[%-100s][%d%%][%c]\r",bar,cnt,lable[cnt%len]);//%-100d 先预先开辟100个字符位并且向左对齐、对于最后的旋转可以用不断遍历数组显示、%%表示%因为%是特殊字符 所以若要表示出来单独的%则需要用%% 或者 \% 
                fflush(stdout);//没有\n来刷新,所以需要自己刷新缓冲区
                bar[cnt++] = BODY;//逐渐增加的进度条
                if(cnt < 100) bar[cnt] = RIGHT;// > 在最后一个位置就不需要在出现了
                usleep(speed);//休眠多少微秒
            }
            printf("\n");
        
        }
        
      2. processBar.h

        #include<stdio.h>
        #include<unistd.h>
        #include<string.h>
        
        #define BODY '='
        
        #define RIGHT '>'
        
        
        void processBar(int speed);
        
        //extern void processBar(); extern 可写可不写因为对于函数来说这样写就表示这是一个声明
        
        //但注意的是假如是一个变量的话就需要加上extern了因为分不清到底是不是编译器声明
        
      3. main.c

        #include<stdio.h>
        #include"processBar.h"
        #include<unistd.h>
        
        int main()
        {
            processBar(50000);
        
            return 0;
        }

    2. 方法二:更好的适应正常情况下载东西时的表现
      1. processBar.c
        #include "processBar.h"
        
        const char *lable = "|/-\\";// \ 反斜杠是特殊的字符所以需要用\\ 来表示成\ 
        
        char bar[102] = {0};
        //函数processBar来展示下载的过程
        //原理和之前一样,只不过此时把逐渐完成的过程放到了外部
        void initbar()//清理数组中的内容
        {
            memset(bar,'\0',sizeof(bar));//将内容全部置为\0
        }
        
        void processBar(int rata)
        {
            if(rata < 0 || rata > 100) return ;
            int len =  strlen(lable);
            
            printf("[%-100s][%d%%][%c]\r",bar,rata,lable[rata%len]); 
            fflush(stdout);
            bar[rata++] = BODY;
            if(rata < 100) bar[rata] = RIGHT;
                
        }
        
      2. processBar.h

        #include<stdio.h>
        #include<unistd.h>
        #include<string.h>
        
        #define BODY '='
        
        #define RIGHT '>'
        
        typedef void (*processbar)(int);//将函数指针类型重命名为processbar 然后利用函数指针来实现一个回调函数的过程
        
        void processBar(int rata);
        
        //extern void processBar(); extern 可写可不写因为对于函数来说这样写就表示这是一个声明
        
        //但注意的是假如是一个变量的话就需要加上extern了因为分不清到底是不是编译器声明
      3. main.c

        #include<stdio.h>
        #include"processBar.h"
        #include<unistd.h>
        // 模拟 下载 的情况
        void DownLoad(processbar i)//利用函数指针
        {
            int total = 1000;//假如有1000MB
            int cur = 0;//一开始从0开始
            while(cur <= total)
            {
                int rata  = cur*100 / total;//计算完成的百分比
                i(rata);//把rata传给i 也就是 processBar
                cur += 10;//模拟下载逐渐完成
                usleep(50000);//模拟下载的所需的时间
            }
            printf("\n");
        }
        
        int main()
        {
            DownLoad(processBar);
            initbar();//清理一下数组内容,为下面处理时腾出空的空间
            return 0;
        }
        

本章完。预知后事如何,暂听下回分解。

如果有任何问题欢迎讨论哈!

如果觉得这篇文章对你有所帮助的话点点赞吧!

持续更新大量Linux细致内容,早关注不迷路。

猜你喜欢

转载自blog.csdn.net/ZYK069/article/details/131714340