0


【Linux必备工具】自动化构建工具makefile的使用详解

✨ ** 听风八百遍,才知是人间 ** 🌏

📃个人主页:island1314

🔥个人专栏:Linux—登神长阶

⛺️ 欢迎关注:👍点赞 👂🏽留言 😍收藏 💞 💞 💞


🚀前言

项目构建时遇到的各种挑战如文件编译顺序、库链接、依赖文件的管理等,在不同开发环境中会有不同的解决方案。

Visual Studio (VS) 环境中,这些问题往往被自动处理,运行直接 **Ctrl + F5 **就可以了,编译个项目非常轻轻松松。 但那是因为 VS 帮你自动维护了对应的项目结构!

那如果我们需要去手动实现呢:多文件 我们应该先编译哪一个程序?链接需要哪些库?整个项目结构,该如何维护......在 Linux 环境中,我们需要更手动、细致地管理这些方面。为了解决这个问题,Linux 提供了自动化构建工具 Makefile。

1. Make和Makefile的基本概念

🎈1.1 Make是什么?

  • make 是一个命令工具,是一个解释 makefile 中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi 的 make,Visual C++的nmake,Linux下GNU的make。可见,Makefile都成为了一种在工程方面的编译方法。

🎈1.2 Makefile是什么?

  • Makefile 是一个 文件。它是一个工程文件的编译规则,它记录了原始码如何编译的详细信息、描述了整个工程的编译链接等规则。
  • Makefile 是 Linux 下用于管理文件依赖和编译顺序的一个重要工具。它用于定义项目中的各个源文件如何编译链接,可以极大地提高开发效率。
  • Makefile 带来的好处就是——“自动化编译"。一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率🔥🔥

🎈1.3 Makefile的语法了解

target(目标文件):文件1 文件2(依赖文件列表)        //依赖关系
 
<Tab>gcc -o 欲建立的执行文件 目标文件1 目标文件2        ///依赖方法
     command
     ...
     ...

target就是我们想要建立的信息,一般称作 **

目标文件

。而后面的依赖文件列表就是具有相关性的 object files,也就是目标文件所依赖的文件**(可以是一个或多个,也可以没有)

简述一下其基本的语法规则:

  • 目标文件与依赖文件列表文件之间要使用 :(冒号) 来隔开目标文件:依赖文件列表
  • target可以是一个目标文件、执行文件,甚至可以是一个标签【后面会提到的伪目标】。
  • 依赖方法前面必须加** Tab空格键**。
  • 依赖方法以gcc为例,也可以是其他的shell指令【command】。

综上所述:

我们可以了解到** Makefile 文件中定义了一系列规则,指定文件编译顺序、文件依赖关系及各文件的编译方法。而 make 命令是一个解释 Makefile 文件的命令工具,可以完成项目的自动化构建**。

2. Make和Makefile的使用

2.1 基本使用

  • 创建Makefile

touch Makefile

  • ** 编写Makefile:**

vim Makefile

  • 编写要生成的可执行程序mybin和项目清理clean:

(注:clean 只是我们声明出来的名字,当然也可以声明为其他的)

注意:

  • 对于** test:test.c** ,冒号左侧是目标文件,右侧是它的依赖文件,所以就可以说它们之间存在一种 【依赖关系】,只有 test.c 存在才可以有 test。
  • 那要如何通过 test.c去生成 test 呢❓ 此时就需要使用到下面的这句 gcc指令** gcc -o test test.c** 👉它叫做 【依赖方法】
  • 使用make命令生成可执行程序

make

  • 使用make命令进行项目清理

make clean

2.2 基本语法规则

Makefile 中包括依赖关系(目标、**依赖)依赖方法(命令)**。
下面是 Makefile 中一些要素的基本语法规则:

🧩2.2.1 第一行不空行

原因:第一行通常是一个目标,例如 all: 或者 clean:。在 Makefile 中,空行被视为分隔符,用于区分不同的规则或目标。
当 Make 工具解析 Makefile 文件时,它会忽略空行,并将第一行之后的非空行视为第一个规则或目标。如果第一行是一个空行,可能会导致 Make 工具不正确地解释 Makefile,从而产生意外的行为或错误。

🧩2.2.2 目标

目标:指定了要生成的文件或要执行的操作名。

例如:上面的test就是要生成的目标文件名。

🧩2.2.3 命令

命令(依赖方法):包含了生成目标所需的具体操作步骤,通常是一条或多条 Shell 命令。

第二行必须以Tab开头不能是空格(注意:按四下空格会报错,紧接着是生成目标文件的命令。

例如:上面的gcc test -o test.c

🧩2.2.4 伪目标

伪目标:伪目标是指在 Makefile 中**.PHONY定义的不对应实际文件的目标,通常用于执行一些特定的操作,比如清理临时文件**。
例如:上面的clean目标用于执行清理操作,删除test文件。
注:make默认执行的是第一行的命令,一般把清理工作放在最后面

🧩2.2.5 其他

注释:使用 # 符号来添加注释,注释从 # 开始一直到该行的末尾。

变量:可以使用变量来存储命令选项、编译器名称等信息,然后在规则中引用这些变量。
语法格式:VAR_NAME = value

取消回显:由于调用make命令,其会默认显示回显,因此一般通过使用 **@ **加在命令前面取消回显

条件判断:可以使用条件判断(ifeq、ifdef 等)来根据不同的条件执行不同的命令。

函数:Makefile 支持一些内置函数,可以用于字符串处理、文件查找等操作。

使用make和make clean,就可以方便地完成项目自动化构建和清理。

3. Makefile 工作原理

  1. **makefile 或者Makefile **文件会被make从上到下扫描,如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“test”这个文件,并把这个文件作为最终的目标文件
  2. 如果test文件不存在,或是test所依赖的后面的test.o文件的文件修改时间要比test这个文件新(可以用 touch 测试),那么,他就会执行后面所定义的命令来生成test这个文件。
  3. 如果test所依赖的test.o文件不存在,那么make会在当前文件中找目标为test.o文件的依赖性,如果找到则再根据那一个规则生成test.o文件。(这有点像一个堆栈的过程)
  4. 当然,你的C文件和H文件是存在的啦,于是make会生成 test.o 文件,然后再用 test.o 文件声明make的终极任务,也就是执行文件test了
  5. 这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。
  6. 在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。
  7. make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。

注意:make 默认只生成一个可执行程序

🌸3.1 make/Makefile识别文件新旧

make命令不是每次都会重新编译,只有更改过的文件才会重新编译。(提高编译效率)
若源代码没有更改也重新编译,那么每次预处理编译汇编链接的时间比较长,成本高

make/Makefile是如何知道文件更改过的?
答:通过源文件的修改时间和形成的可执行程序(也是文件)的修改时间做对比。

重新编译的本质:重新写入一个二进制的可执行文件(bin文件),文件的修改时间会跟着更改。

  1. 第一次的时候,一定是先有源文件,才有bin文件。源文件的修改时间 < bin文件的修改时间
  2. 第二 ~~ N 次的时候,我们对源文件做任何修改的时候,源文件的修改时间 > bin文件的修改时间​​​​​ 重新编译形成可执行

大部分情况下重新编译都没问题,问题的产生不仅仅是修改新文件就能解决的。有些历史问题需要重新清理项目才可以解决。

文件 = 内容 + 属性,所以文件的ACM时间肯定与内容或属性有关。

Access(最近访问时间):普通文本文件打开:cat、vim,或者对目录进入、ls显示等

Modify (对内容修改):当文件内容发生变化时,修改时间(mtime)会被更新。

Change(对属性修改):当文件的权限、所有者、链接数或文件名甚至文件大小发生变化时,更改时间(ctime)会被更新。

注:

  • 三种时间会出现联动,例如对内容修改,Access和Change时间也会更改。
  • Access时间不是每次访问时都更改,读取查看文件操作最频繁,如果每次都改的话,比较浪费时间,因为文件一般都在磁盘存放,更改时间的本质就是访问磁盘。但是访问磁盘的速度比较慢(相对cpu而言),读取查看文件操作又是很频繁,如果每次都更改Access time的话,系统效率就会降低很多,所以就会隔一段时间更改一次。 (具体间隔时间和是否间隔,由内核版本决定)
  • 使用touch命令可以修改ACM时间。-a 选项 修改Access时间,但同时也修改了change时间,因为access时间也是属性。**-m 选项 修改Modify时间**,但是Change时间也会跟着改。

综上,我们知道 make 是通过对比源文件和bin文件的Modify时间确定文件新旧的。

🌸3.2 .PHONY修饰的伪目标总是被执行

.PHONY配置项的目标clean并不是其他文件生成的实际文件,使make命令会自动绕过隐含规则搜索过程,也就是说执行命令make clean会自动忽略名为"clean"文件的存在,因此声明 **.PHONY **配置项会改善性能,并且不需要担心实际同名文件存在与否😮。
【通俗一点说】:.PHONY 修饰的目标clean并不是某个依赖项生成的实际文件,因此make命令不再去搜寻当前文件夹下是否有clean文件,这样少去做一些事,自然会改善性能,并且不用担心当前文件夹下是否有同名的文件。

通过时间对比,可以做到不让有些代码进行重新编译(不让某些操作进行)。

如上:右边的test被.PHONY修饰,则多次make时,都会执行gcc命令,把可执行程序重新形成。

🌸3.3 make/Makefile具有依赖性的推导能力

💢 越是接近目标文件的命令,就越是要写在前面。因为程序是按照递归的方式进行依赖文件查找的,看到第一行有一个没见过的依赖文件,就往下一行进行查找,以此类推。

对于gcc的编译,我们都知道要生成一个可执行程序需要经过预处理、编译、汇编和连接,中间会产生.i,.o,.s文件。但是在上面的操作中都没有生成中间文件
但是我们知道一件事:生成bin文件,就需要对应的.o文件。

以Makefile的推导过程如下:(类似一个栈结构)

test.o: test.s
  gcc -c test.s -o test.o
   
test.s: test.i
  gcc -S test.i -o test.s
   
test.i: test.c
  gcc -E test.c -o test.i
                                                                                                                                                                                                                 .PHONY:clean
clean:
  rm -f test test.o test.s test.i

但是一般都不会写成这么复杂,一般都是如下写法:

4. Makefile小知识

🍉4.1 替换

符号含义=替换%.o
任意的.o文件
%.c任意的.c文件

🥝 4.2 通配符

符号含义$^所有依赖文件$@所有目标文件$<所有依赖文件的第一个文件
此外:Makefile中可以编写变量,表达式之间不建议带空格
通过 $(变量名) 来引用变量的值。

$@ 和 $^,前者表示:左侧被编译的所有内容,即【目标文件】,后者表示:之后所有内容,即【依赖文件】。

此时,当我们再去make的时候,就可以发现这个特殊符号自动替换成了:两侧的【目标文件】和【依赖文件】。

🍍4.3 生成多个可执行程序

一般来说make 默认只生成一个可执行程序如下:

但是我们可以做出以下修改:


📖总结

以上就是Make 和 Makefile的全部内容啦,后面我们就要讲到进度条的相关内容,就会运用到这一节的知识,敬请期待咯!!!

💞 💞 💞那么本篇到此就结束,希望我的这篇博客可以给你提供有益的参考和启示,感谢大家支持!!!祝大家天天开心

标签: 自动化 运维 学习

本文转载自: https://blog.csdn.net/island1314/article/details/141086631
版权归原作者 island1314 所有, 如有侵权,请联系我们删除。

“【Linux必备工具】自动化构建工具makefile的使用详解”的评论:

还没有评论