• 【Linux】自动化构建工具:make/Makefile


    在这里插入图片描述

    ​👻内容专栏: Linux操作系统基础
    🐨本文概括: 工具使用的背景、理解make/makefile工具、探索工作原理(文件修改时间的对比)、.PHONY伪目标、特性等。
    🐼本文作者: 阿四啊
    🐸发布时间:2023.9.14

    背景

    • make” 和 “Makefile” 是用于自动化构建和编译软件项目的工具和文件。它们通常用于编译源代码并生成可执行文件,库文件或其他项目构建目标。
    • 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。
    • makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
    • make是一条命令,makefile是一个文件,两个搭配使用,完成项目自动化构建。

    理解

    make

    📌"make" 是一个命令行,用于构建项目。它根据一个称为 “Makefile” 的文本文件中的规则来确定需要构建哪些文件以及如何构建它们。“make” 工具的基本语法如下:

    make [目标]
    
    • 1

    ⚠️注:其中 “目标” 是您要构建的项目中的一个特定目标,例如生成可执行文件或库。如果不提供目标,则 “make” 将默认构建 Makefile 中定义的第一个目标。

    Makefile

    📌Makefile是一个文本文件,文件名首字母M一般建议大写,也可以写成makefile,其中包含构建项目的依赖关系和依赖方法。每个依赖关系都描述了如何生成一个目标文件以及哪些文件或其他目标是其依赖方法。Makefile 使用如下:

    • 规则指定了依赖关系依赖方法
    1. 我们用C语言写一个简单的code.c文件:
      在这里插入图片描述

    2. 以下是一个简单的示例 Makefile,用于构建一个名为 “mybin” 的可执行文件:

      1 mybin: code.c #指定了依赖关系
      2   gcc code.c -o mybin #依赖方法,比如以制表符开头。
      3 clean:
      4 	rm -f mybin
      
      • 1
      • 2
      • 3
      • 4

      示例中,"mybin"是目标,依赖于code.c,与code.c构成依赖关系。 依赖方法是构建“mybin”的命令:gcc code.c -o mybin(用gcc命令编译code.c文件)。“clean”是另一个目标,用于删除生成的可执行文件mybin

    3. 运行“make”命令,指定要构建的目标。(注:若不指定目标,系统将自动扫描Makefile中第一个目标)

      [Asi@localhost ~]$ make
      gcc code.c -o mybin
      
      • 1
      • 2

      运行"make clean"命令。

      [Asi@localhost ~]$ make clean
      rm -f mybin
      
      • 1
      • 2

    工作原理

    原理

    • 如果我们再次执行几次make命令,发现编译不了了,会报出以下错误:

      [Asi@localhost ~]$ make
      make: `mybin' is up to date.
      [Asi@localhost ~]$ make
      make: `mybin' is up to date.
      [Asi@localhost ~]$ make
      make: `mybin' is up to date.
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      为什么会出现这样的问题呢?对于没有更改的code.c文件,系统会进行识别,不会再进行编译链接,到这可能有小伙伴们会问,这么一个小文件,对编译器来说不算什么吧?但是你们想想,若项目中有几百个上千个文件呢,程序翻译的过程的周期是非常长的,所以编译器会进行识别对于没更改过的源文件可以跳过,大大缩短编译的时间。所以make/Makefile可以提高编译效率!

    • 编译器是如何知道源文件是否被更改过呢?make是如何工作的?

      1. 源文件可执行程序时间的对比,就能体现源文件的新旧(重新编译会重新写入一个二进制可执行文件,其修改时间也会被修改)
      2. 第一次编译的时候,一定是源文件的修改时间 小于 可执行程序的修改时间,当我们对源文件做任何修改之后,源文件的修改时间就 大于 可执行程序的修改时间,然后进行程序编译之后,那么源文件的修改时间又 小于可执行程序的修改时间。

    文件的修改时间(ACM时间)

    下面,我们执行stat + [文件名]命令,可查看一个文件的修改时间
    在这里插入图片描述

    Access time(Atime):这是文件的最后一次访问时间。当文件被读取、浏览或访问时,Atime时间会被更新。
    Modify time(Mtime):这是文件的最后一次内容上修改时间。Mtime的更新,很可能会和Ctime或者Atime联动更改。
    Change time(Ctime):这是文件的最后一次属性上修改时间。Ctime的更新,其他时间基本不会更新。

    1. 我们尝试给源文件code.c修改他的任意属性,比如给所属组添加了执行权限,再次stat命令查看code.c的时间,发现与原来时间相比,修改文件权限,只改变了Ctime,其他时间则是保持不变。

      [Asi@localhost ~]$ ll
      total 36
      -rw-rw-r-- 1 Asi Asi  130 Sep 14 15:29 code.c
      [Asi@localhost ~]$ chmod g+x code.c
      
      • 1
      • 2
      • 3
      • 4

      在这里插入图片描述

    2. 下一步,我们试着修改一下code.c文件的内容,如下所示
      在这里插入图片描述
      我们可以发现,Mtime的更新,AtimeCtime发生了联动更改。

      在这里插入图片描述

    3. 然后,我们查看一下code.c文件,发现它的ACM时间都没有发生变化,再进行两次查看,时间也照样没有发生变化。

      [Asi@localhost ~]$ cat code.c
      #include 
      
      int main()
      {
          int i;
          for(i = 0; i <= 10; i++)
          {
            printf("num = %d\n",i);
          }
          printf("complete!\n");
        return 0;
      }
      [Asi@localhost ~]$ stat code.c
        File: ‘code.c’
        Size: 156       	Blocks: 8          IO Block: 4096   regular file
      Device: fd01h/64769d	Inode: 1063658     Links: 1
      Access: (0674/-rw-rwxr--)  Uid: ( 1003/     Asi)   Gid: ( 1003/     Asi)
      Access: 2023-09-14 15:39:11.012008120 +0800
      Modify: 2023-09-14 15:39:11.010008051 +0800
      Change: 2023-09-14 15:39:11.010008051 +0800
       Birth: -
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22

      🤔💡:那么为什么会这样子呢?
      PS:一般而言,一个文件被查看的频率是最高的。而文件是存放到磁盘中的,访问、更改文件本质其实就是访问磁盘,那么每次访问更改access time就是访问磁盘,这会让Linux系统充满大量的访问磁盘的IO操作,简介地减缓系统效率。所以,一些编译器一般会配置减少文件atime的更新频率,以提高性能。

    不修改内容,更新文件的修改时间

    我们再次回到前面一个问题,系统究竟是根据三个时间中的哪个时间来进行源文件与可执行程序的对比呢?当然最合理的是Modify time(Mtime)。
    所以我们这里再次去执行make命令,编译不了,其根本原因是因为code.c的Mtime早于mybin的Mtime!

    [Asi@localhost ~]$ make
    make: `mybin' is up to date.
    [Asi@localhost ~]$ stat code.c
      File: ‘code.c’
      Size: 156       	Blocks: 8          IO Block: 4096   regular file
    Device: fd01h/64769d	Inode: 1063658     Links: 1
    Access: (0674/-rw-rwxr--)  Uid: ( 1003/     Asi)   Gid: ( 1003/     Asi)
    Access: 2023-09-14 15:43:46.586536040 +0800
    Modify: 2023-09-14 15:43:46.584535971 +0800
    Change: 2023-09-14 15:43:46.584535971 +0800
     Birth: -
    [Asi@localhost ~]$ stat mybin
      File: ‘mybin’
      Size: 8496      	Blocks: 24         IO Block: 4096   regular file
    Device: fd01h/64769d	Inode: 1063645     Links: 1
    Access: (0775/-rwxrwxr-x)  Uid: ( 1003/     Asi)   Gid: ( 1003/     Asi)
    Access: 2023-09-14 16:09:31.152943398 +0800
    Modify: 2023-09-14 16:09:31.152943398 +0800
    Change: 2023-09-14 16:09:31.152943398 +0800
     Birth: -
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    所以我们可以通过对Mtime进行修改以更新文件的时间,那么除了修改源文件的内容,我们可以使用touch命令,对,你没看错,touch命令不仅仅可以创建一个新文件,它也可以去更新已存在文件的时间!

    touch [文件名]:所有时间都更新
    touch -a [文件名]:更新atime
    touch -m [文件名]:更新mtime

    [Asi@localhost ~]$ make
    make: `mybin' is up to date.
    [Asi@localhost ~]$ touch  -m code.c
    [Asi@localhost ~]$ make
    gcc code.c -o mybin
    [Asi@localhost ~]$ make
    make: `mybin' is up to date.
    [Asi@localhost ~]$ touch code.c
    [Asi@localhost ~]$ make
    gcc code.c -o mybin
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    .PHONY

    📌.PHONY 的主要作用是告诉 make 工具,不管是否存在对应的文件,总是执行这些目标中的命令,其依赖方法总是会被执行,不会被任何情况拦截。它用于指定一个伪目标(phony target)。

    .PHONY: target_name
    
    • 1
    • 样例测试:
      .PHONY指定了mybin为伪目标,所以我们执行make命令,无论是否存在可执行程序,始终会执行依赖方法相关联的命令。
      在这里插入图片描述
    • 结果:
      [Asi@localhost ~]$ make
      gcc code.c -o mybin
      [Asi@localhost ~]$ make
      gcc code.c -o mybin
      [Asi@localhost ~]$ make
      gcc code.c -o mybin
      [Asi@localhost ~]$ make
      gcc code.c -o mybin
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

    项目清理

    • 在开发中,我们的工程会根据情况来清理的,像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行。
    • 不过,我们可以显示用make执行。即命令“make clean”,我们在测试时,以此来清理所有的目标文件,以便重新编译。

    特性

    用 make/makefile 写出整个程序的翻译过程

    “make” 和 “Makefile” 具有依赖性的推导能力的。当运行make命令,make会自动检测依赖关系,mybin根据依赖关系去寻找code.o,如果code.o文件不存在,它不会执行依赖方法,回向下寻找code.o,如果向下没有找到code.o,编译器可能会报错,可能会隐式形成。那么code.o找到之后,它会根据依赖关系去寻找code.scode.s继续向下寻找……以此类推。直到找到code.c,执行依赖方法模块的命令之后形成code.i,然后在逆向回去扫描,执行其他尚未执行成功的依赖方法。此过程就如同入栈、出栈的过程,所以"make" 和 “Makefile” 就是根据这么一套逻辑执行完依赖关系和依赖方法。
    在这里插入图片描述

    执行结果

    [Asi@localhost ~]$ make
    gcc code.c -E -o code.i
    gcc code.i -S -o code.s
    gcc code.s -c -o code.o
    gcc code.o -o mybin
    
    • 1
    • 2
    • 3
    • 4
    • 5
  • 相关阅读:
    elasticsearch 中text与keyword的区别
    新闻列表页:通过WEUI框架实战我深入理解了块级作用域与点击事件
    【linux kernel】基于ARM64分析linux内核的链接脚本vmlinux.lds.S
    ROS2机器人-C++和Python怎么选
    Google Chrome 任意文件读取 (CVE-2023-4357)漏洞复现
    Java注解(3):一个真实的Elasticsearch案例
    制作macOS Ventura U盘启动盘教程
    命令行界面 (CLI) 应用程序
    vue仿企微文档给页面加水印(水印内容可自定义,超简单)
    C#上位机系列(4)—示波器一新窗口的建立
  • 原文地址:https://blog.csdn.net/qq_63320529/article/details/132869600