首页 > 系统相关 >【Linux学习】(7)项目自动化构建工具make/Makefile

【Linux学习】(7)项目自动化构建工具make/Makefile

时间:2024-10-26 23:45:49浏览次数:10  
标签:文件 依赖 make Makefile 编译 Linux mycode

Linux项目自动化构建工具-make/Makefile

1. 背景介绍

  1. 会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力
  2. 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作
  3. makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率
  4. make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法
  5. make是一个命令,Makefile是一个当前目录下的一个文件,两个搭配使用,完成项目自动化构建

2. 先见一见Makefile是如何工作的?

  1. 在当前目录下,创建一个Makefile文件(Makefile的首字母大小写均可)在这里插入图片描述
  2. 编写Makefile文件,如下:
mycode:mycode.c # 依赖关系
	gcc mycode.c -o mycode # 依赖方法
clean: # 依赖关系
	rm -f mycode # 依赖方法
  1. 有了Makefile,编译项目我们不用再gcc命令了,可以直接make
  2. make命令会自动帮我们在当期目录下找Makefile,然后根据Makefile内部的依赖关系和依赖方法,执行对应的依赖方法,帮我们生成对应的可执行程序
  3. make之后我们再进行make它就不允许我们make了,这个时候我们需要rm清理可执行程序,但是rm风险太大,我们容易误删文件
  4. 所以我们的Makefile它不仅支持我们编译项目,也支持我们清理项目
  • 示例:在这里插入图片描述
  • 通过示例,我们现在只需要知道make是一个命令,Makefile是一个当前目录下的文件
  • 我们现在有如下三个问题,解决了这三个问题我们就可以大致了解make/Makefile了
  1. 什么是依赖关系、依赖方法?
  2. 为什么make之后我们就不能再make了?
  3. 为什么make直接就可以生成可执行程序,而清理需要make clean?

3. 依赖关系和依赖方法

  • 帮助理解:比如,生活中,到了月底我们需要问父母要生活费了,具体是怎么要的呢——①先要有正确依赖关系,你要生活费肯定是向自己父母,你向一个陌生人要他是不会给你的;②再有正确的依赖方法:有了依赖关系还不可以,还需要正确的方法,即你需要打电话给你父母,说到月底呢,需要1000的生活费,这样你父母才会打钱了。如果你说现在游戏出了一个新皮肤,需要1000,你的父母一般不会打钱给你的。
  • 依赖关系和依赖方法两者缺一不可
  • 在现实生活中我们做任何事情,一定是先有一个正确的关系,然后再有正确的方法才能完成
  • Makefile书写的一般格式:例如我们要把mycode.c编译形成一个可执行程序mycode
    • 先写正确的依赖关系:mycode,它依赖mycode.c——》mycode:mycode.c
    • 再写正确的依赖方法:【tab】gcc mycode.c -o mycode
    • 注意:依赖方法的书写格式一定要tab开头,不能是四个空格

4. make的工作原理

为了方便讲解make的工作原理,我们这里将Makefile写复杂一些:

mycode:mycode.o
	gcc mycode.o -o mycode
mycode.o:mycode.s
	gcc -c mycode.s -o mycode.o
mycode.s:mycode.i
	gcc -S mycode.i -o mycode.s
mycode.i:mycode.c
	gcc -E mycode.c -o mycode.i

运行实例:

在这里插入图片描述

  • make之后确实生成了mycode的可执行程序,也同时生成了mycode.i、mycode.s、mycode.o的临时文件这我们能理解,但是为什么make执行的过程和我们在Makefile中我们编写的顺序是相反的呢?这就需要我们理解make的原理了。
  • make是如何工作的,在默认的方式下,即我们只输入make命令。那么,
  1. make会在当前目录下找名字叫“Makefile”或“makefile”的文件
  2. 如果找到,它会找文件中的第一个目标文件,并把这个文件作为最终的目标文件。在上面的例子中,他会找mycode这个文件作为最终的目标文件
  3. 如果mycode文件不存在,那么,它就会根据它的依赖关系和依赖方法来生成mycode这个文件
  4. mycode依赖mycode.o,如果当前目录下mycode.o不存在,那么make会先去生成mycode.o,只有在当前目录形成了mycode.o,然后才能再回来,根据mycode.o形成mycode
  5. mycode.o有自己对应的依赖关系和依赖方法,可是mycode.o又依赖mycode.s,mycode.s在当前目录也不存在,所以make又需要在Makefile中扫描,形成对应的mycode.s
  6. mycode.s也有自己对应的依赖关系和依赖方法,形成mycode.s,mycode.s依赖于mycode.i,mycode.i在当前目录也没有,所以继续扫描,mycode.i依赖mycode.c,mycode.c在当前目录存在,他就可以根据mycode.c的关系形成mycode.i了,形成mycode.i之后,没有结束,我最终的目标是要形成mycode
  7. 要mycode就要有mycode.o,要mycode.o就要有mycode.s,要mycode.s就要有mycode.i,要mycode.i就要有mycode.c,有mycode.c还有依赖方法,所以先形成mycode.i,然后再逆向执行直到形成最终的目标文件mycode
  8. 在整个的执行过程中,make在扫描Makefile文件时,它会优先根据依赖关系,找依赖关系中他所依赖的的文件是否在当前目录存在,如果不存在,我们的make会类似于递归式的去进行先去形成你的依赖文件,回过头来再根据依赖文件形成目标文件,这个过程就是递归的过程,mycode.c就是递归的出口,而整个的结构,在保存这些依赖关系的时候就是栈的结构
  9. 这种基于递归,基于栈的这样结构的依赖关系推导,我们称为Makefile依赖关系的自动化推导(.i,.o,.s的编写顺序可以乱序,只要能找到就行,但是不建议这样做)
  10. 在我们make自动推导Makefile中依赖关系时,make会一层一层的去找文件的依赖关系,直到最终编译出第一个目标文件
  11. 注意:找找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或编译不成功,make根本不理——即make只管文件的依赖性
  12. 归纳:上面我们所讲的一切就是一句话make会自动推导Makefile中的依赖关系,推导过程是栈式结构

5. make的项目清理

  • 我们现在已经知道怎么通过Makefile形成可执行程序了,但还有一个问题怎么清理它和一些临时文件?
  • Makefile编写:
mycode:mycode.c
	gcc mycode.c -o mycode

# 清理项目
.PHONY:clean
clean:
	rm -rf mycode 
  • 示例:
    在这里插入图片描述
  1. 工程是需要被清理的
  2. 清理项目的依赖关系为空,不需要依赖其他任何文件,是独立存在的,所以我们直接clean:即可
  3. 为什么make直接就可以生成可执行程序,而清理需要make clean?

    因为make它是自顶向下去扫描Makefile,他把你所需要形成的第一个目标文件充当为make的默认动作,即谁是第一个目标文件,直接make就是谁
  4. 所以建议把你想要形成的可执行程序放到Makefile的最前面,把清理动作放到最后面
  5. 如果我们想要指定名称的执行某个依赖关系与它所匹配的依赖方法,make后直接跟目标文件即可
  6. 如我们上面的例子,clear没有被第一个目标文件直接或间接关联,那么他后面所定义的命令就不会自动执行,不过,我们可以显示要make执行。即命令——“make clean”,以此来清理项目,以便重新编译
  • 我们发现make第一次编译完之后,如果源代码没有修改,之后再make他就不允许再编译了。——因为源文件没有修改,再编译也是形成一样的可执行程序,所以为了提高编译效率,没有必要再编译
  • 那make是怎么做到这一点的?
  1. 一定是源文件形成可执行,先有源文件,才有可执行
  2. 一般而言,源文件的最近修改时间比可执行文件要老
  3. 所以如果我们更改了源文件,历史上有可执行,那么源文件的最近修改时间,一定比可执行程序要新
  4. make只需要比较,可执行程序的最近修改时间和源文件的最近修改时间
    • 可执行新于源文件,不需要重新编译
    • 可执行老于源文件,需要重新编译
  • 那make又是如何判断源文件和可执行文件的新旧?
  1. Linux中stat指令可以直接查看源文件和可执行文件对应的时间问题在这里插入图片描述
  2. 一般对一个文件来说有三种时间:Access、Modify、Change
    • Access:文件最近被访问的时间(原理上增删改查都算访问文件,所以几乎任何操作都会修改它,它更改的频率很高。但在实际中他更改的频率并没有我们想的这么高。)
    • 文件 =文件内容 + 文件属性,我们把对文件内容做修改的这种动作叫做Modify,把对文件属性做修改的这种动作叫做Change
    • Modify:文件内容最近被修改的时间
    • Change:文件属性最近被修改的时间
  3. 我们把这三个时间一秒记住,简称ACM时间
  4. 这三个时间不一定是割裂的,有时候我们多个时间可能会被同时修改
    • 一般我们文件内容改了,文件属性一般也会跟着改(Linux自动帮我们改)——因为文件内容改了,文件属性中的文件大小size自然也改。但是文件属性改了,文件内容不一定改
  5. 通过实例帮助我们理解这三个时间:
    • 文件属性改了,这三个时间中只有Change变了,Access和Modify没变在这里插入图片描述
    • 文件内容改了,这三个时间中一定Modify和Change都变了,但是Access可能不变
  6. 修改文件内容,打开文件访问了,为什么Access可能不变?

    Linux系统早期时确实只要访问文件Access就会变,但是后来Linux系统的设计者发现Access更改的频率太高了,而文件存在硬盘中,所以一旦更新Access就意味着要在硬盘中修改这个文件的属性,而硬盘是计算机中的一个外部设备(比较慢)。当我们整个系统被多个用户使用时,一定会有大量的文件的Access做更新,这些所有更新都要写到硬盘上,都要访问外设,最后就会变非常慢,所以它不利于Linux系统整机效率的提高,所以在现在比较新的Linux版本中,Access的更新策略被改了,不再是直接你访问就改,而是根据你Modify和Change的更新次数还有他最近文件被访问次数(计数器)是否满足要求才会改(比如,可能访问3,4次才会改一次Access),这样减少了Access改变的次数,从而提高整机效率!
  7. 如果我们不想这样,想让文件的更新时间变成最新的,该怎么做?——我们可以通过touch命令手动更新文件的最新时间
  8. touch后如果跟的是已经存在的文件,他会把文件的所有时间更新为最新
  • 了解了时间我们就可以验证make会根据源文件和目标文件的新旧(通过将Modify时间转换为时间戳(现行增长)作对比,看谁新谁旧),判断是否需要重新执行依赖关系进行编译!

验证:

  • 源文件比可执行旧,就不再编译了在这里插入图片描述
  • 源文件比可执行新,再次编译在这里插入图片描述
  • 从以上结论我们又可知,依赖关系不一定总是执行的! 他是有前提条件的。
  • 但是如果我们想让对应的依赖关系,总是被执行该怎么办?
  • 我们可以将目标文件设置为伪目标,我们把用.PHONY后面修饰的目标文件称为伪目标。
  • 伪目标的特性是,依赖关系和依赖方法总是被执行!
  • 不建议把形成可执行文件的目标文件设置为伪目标,用它修饰了确实每次编译都是最新的,但是有时候我就是想逼你去了清理项目,才能再编译形成可执行,因为在不清理旧的可执行文件在旧的可执行文件中新增,可能会导致老的问题依旧存在!
  • 但建议将清理动作设置为伪目标,因为清理的动作我们想让它总是被执行

6. Makefile的特殊符号

  • $@——代表依赖关系所对应的目标文件,即冒号左侧的所有内容
  • $^——代表依赖关系冒号右侧的所有内容
  • @——我们发现make命令的时候,他会把它所匹配的依赖方法回显出来,如果不想回显,可以在依赖方法前带@符号

标签:文件,依赖,make,Makefile,编译,Linux,mycode
From: https://blog.csdn.net/wangjiushun/article/details/141758390

相关文章

  • Linux:基础IO
    一、文件fd1.1共识原理 1、文件=内容+属性 2、文件分为打开的文件和没打开的文件(如c中的fopen和fclose)      可以用以下的例子去理解:快递(文件) 有被人(进程)取走的快递(打开的文件)和没被取走的快递(没打开的文件),被人取走的快递研究的是人和快递的关系(进程和文......
  • 【Linux探索学习】第八弹——Linux工具篇(三):Linux 中的编译器 GCC 的编译原理和使用详
    #1024程序员节|征文#Linux下的vim编辑器:【Linux探索学习】第七弹——Linux的工具(二):Linux下vim编辑器的使用详解-CSDN博客前言:在上一篇我们学习了如何在Linux环境下直接用vim编辑器来进行编辑代码,今天我们来学习如何运行我们所编辑的代码,运行代码就需要编译器,也就是我们下......
  • linux之系统调用与文件IO编程
    linux之系统调用与文件IO编程系统调用主要包含以下内容:文件操作:打开、读取、写入、关闭文件。目录和文件系统:创建目录、遍历目录、文件属性管理。进程管理:进程创建、进程终止、信号处理、进程间通信(IPC)。内存管理:内存映射、共享内存、动态内存分配。时间管理:时间获取、时......
  • linux学习day1
    1.常见命令介绍(1)ctrlc:取消命令,并且换行(2)ctrlu:清空本行命令(3)tab键:可以补全命令和文件名,如果补全不了快速按两下tab键,可以显示备选选项(4)ls:列出当前目录下所有文件,蓝色的是文件夹,白色的是普通文件,绿色的是可执行文件(5)pwd:显示当前路径(6)cdXXX:进入......
  • 0-petalinux2018.3 摸索记录 - 快速亮机
    一、环境搭建1、环境要求①需要注意petalinux、vivado、vitis、linux之间的版本对应关系,在ug1144上可以找到②需要注意linux的硬件要求,运存8G以上不然会报错等等2、环境依赖配置2018.3_PetaLinux_Package_List.xlsx①安装包sudoapt-getinstalltofrodos......
  • Linux基础命令指南
    Linux是一个强大的操作系统,广泛用于服务器、桌面、移动设备和嵌入式系统。掌握一些基础的Linux命令对于任何想要高效使用Linux的用户来说都是至关重要的。以下是一些最常用的Linux命令及其简要说明。1.文件和目录操作ls列出目录内容。ls-l #以长格式列出文件和目录ls......
  • 【Linux】线程池详解及其基本架构与单例模式实现
    目录1.关于线程池的基本理论     1.1.线程池是什么?1.2.线程池的应用场景:2.线程池的基本架构2.1.线程容器2.2.任务队列2.3.线程函数(HandlerTask)2.4.线程唤醒机制3.添加单例模式3.1.单例模式是什么?3.2.饿汉实现方式和懒汉实现方式饿汉式单例模式:懒汉式单例......
  • Linux 操作系统下 dstat 命令介绍和使用案例
    Linux操作系统下dstat命令介绍和使用案例dstat命令介绍dstat是一个功能强大的Linux系统监控工具,旨在替代多个传统命令,如vmstat、iostat、netstat、nfsstat和ifstat。它能够实时收集和显示系统性能数据,包括CPU、内存、磁盘I/O和网络流量等信息,帮助用户快速识别和......
  • Linux 操作系统下 dris 命令介绍和使用案例
    Linux操作系统下dris命令介绍和使用案例dris命令在Linux中用于显示和清空目录堆栈中的内容dris命令简介功能:dris命令用于显示当前的目录堆栈,并可以清空堆栈中的内容。语法:bashdris[选项]常用选项+n:显示从左边算起第n个目录。-n:显示从右边算起第n个目录。......
  • 修改Linux服务的文件打开句柄数
    在bash中,有个ulimit命令,提供了对shell及该shell启动的进程的可用资源控制。主要包括打开文件描述符数量、用户的最大进程数量、coredump文件的大小等。在centos5/6等版本中,资源限制的配置可以在/etc/security/limits.conf设置,针对root/user等各个用户或者*代表所有用户来设......