Makefile详解&实战

2023-09-20 12:13:32

title: Makefile详解&实战
date: 2023-09-20 12:01:24
comments: true #是否可评论
toc: true #是否显示文章目录
categories: #分类
- CMake
tags: #标签
- CMake
- Makefile
summary: Makefile详解&实战


Makefile详解&实战

什么是Makefile

makefile是一种说明编译规则的文件,带来的好处就是自动化编译,极大提高开发的效率

关于编译和链接

无论是C耗时C++都需要把源文件编译成中间件,这个过程叫做编译

然后将这些大量的中间文件链接合成一个可执行文件,这个过程叫做链接

Makefile文件命名和规则

  1. 文件命名

文件命名必须是Makefile或是makefile只能是这两种

  1. Makefile规则: 文件中可以有一个或多个规则
目标 ... : 依赖 ...
	命令(shell命令)
	...
	...
	...
  • 目标: 最终要生成的文件(伪目标除外),可以使.o .i .s文件,也可以是可执行文件

  • 依赖: 生成目标锁依赖的文件或者目标

  • 命令: 通过执行命令对依赖进行操作生成目标

Makefile中的掐规则一般都是为第一条规则服务的

\是换行符方便阅读

基本原理

  1. 再命令执行前会检查依赖是否存在

    a. 如果存在,执行命令

    b. 如果不存在,向下检查其他规则,检查有没有一个规则用来生成这个依赖的,如果找到了,则执行该规则中的命令

  2. 检查更新

    a.如果依赖的时间比目标时间晚,需要重新生成目标

    b.如果依赖的时间比目标早,目标不需要进行更新,对应规则中的命令就不需要执行

变量、模式匹配、函数

可以参考这个链接

Makefile支持三个通配符==* ? ~==与shell是一样的

自动变量

链接

简单使用时目标和依赖文件都是一系例的文件,那么我们如何书写一个命令来完成从不同的依赖文件生成相应的目标?因为在每一次的对模式规则的解析时,都会是不同的目标和依赖文件。自动化变量就是完成这个功能的

  • $@:表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于目标中模式定义的集合。
  • $%:仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a(bar.o)“,那么,” %"就是"bar.o"," @“就是"foo.a”。如果目标不是函数库文件(Unix 下是[.a],Windows 下是[.lib]),那么,其值为空。
  • $<:依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%“)定义的,那么”$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
  • $?:所有比目标新的依赖目标的集合。以空格分隔。
  • $^:所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。
  • $+:这个变量很像"$^",也是所有依赖目标的集合。只是它不去除重复的依赖目标。
  • $*:这个变量表示目标模式中"%“及其之前的部分。如果目标是"dir/a.foo.b”,并且目标的模式是"a.%.b",那么,“ ∗ " 的值就是 " d i r / a . f o o " 。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么 " *"的值就是"dir/a.foo"。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么" "的值就是"dir/a.foo"。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么"“也就不能被推导出,但是,如果目标文件的后缀是make所识别的,那么” ∗ " 就是除了后缀的那一部分。例如:如果目标是 " f o o . c " ,因为 " . c " 是 m a k e 所能识别的后缀名,所以, " *"就是除了后缀的那一部分。例如:如果目标是"foo.c",因为".c"是make所能识别的后缀名,所以," "就是除了后缀的那一部分。例如:如果目标是"foo.c",因为".c"make所能识别的后缀名,所以,"“的值就是"foo”。这个特性是GNUmake的,很有可能不兼容于其它版本的 make,所以,你应该尽量避免使用” ∗ " ,除非是在隐含规则或是静态模式中。如果目标中的后缀是 m a k e 所不能识别的,那么 " *",除非是在隐含规则或是静态模式中。如果目标中的后缀是 make 所不能识别的,那么" ",除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么"*"就是空值。
  • $(@D):表示 “ @ " 的目录部分(不以斜杠作为结尾),如果 " @"的 目录 部 分( 不以斜杠作 为结尾 ), 如 果" @"的目录部分(不以斜杠作为结尾),如果"@“值是"dir/foo.o”,那么” ( @ D ) " 就是 " d i r " ,而如果 " (@D)"就是"dir",而如果" (@D)"就是"dir",而如果"@“中没有包含斜杠的话,其值就是”."(当前目录)。
  • $(@F):表示" @ " 的文件部分,如果 " @"的文件部分,如果" @"的文件部分,如果"@“值是"dir/foo.o”,那么" ( @ F ) " 就是 " f o o . o " , " (@F)"就是"foo.o"," (@F)"就是"foo.o""(@F)“相当于函数”$(notdir $@)"。
  • $(*D):和上面所述的同理,也是取文件的目录部分。对于上面的那个例子,"$(*D)“返回"dir”。
  • $(*F):和上面所述的同理,也是取文件的文件部分。对于上面的那个例子,"$(*F)“返回"foo”。
  • $(%D):表示了函数包文件成员的目录部分。这对于形同"archive(member)"形式的目标中的"member"中包含了不同的目录很有用。
  • $(%F):表示了函数包文件成员的文件部分。这对于形同"archive(member)"形式的目标中的"member"中包含了不同的目录很有用。
  • $(<D):表示依赖文件的目录部分。
  • $(<F):表示依赖文件的文件部分。
  • $(^D):表示所有依赖文件的目录部分。(无相同的)
  • $(^F):表示所有依赖文件的文件部分。(无相同的)
  • $(+D):表示所有依赖文件的目录部分。(可以有相同的)
  • $(+F):所有依赖文件的文件部分。(可以有相同的)
  • $(?D):表示被更新的依赖文件的目录部分。
  • $(?F):表示被更新的依赖文件的文件部分。
    最后,对于" < " ,为了避免产生不必要的麻烦,最好给 <",为了避免产生不必要的麻烦,最好给 <",为了避免产生不必要的麻烦,最好给后面的那个特定字符都加上圆括号,比如," ( < ) " 就要比 " (< )"就要比" (<)"就要比"<"要好一些。

伪目标

最早先的一个例子中,我们提到过一个“clean”的目标,这是一个“伪目标”,

clean:
    rm *.o temp

正像我们前面例子中的“clean”一样,既然我们生成了许多文件编译文件,我们也应该提供一个清除它们的“目标”以备完整地重编译而用。 (以“make clean”来使用该目标)

因为,我们并不生成“clean”这个文件。“伪目标”并不是一个文件,只是一个标签,由于“伪目标”不是文件,所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过显式地指明这个“目标”才能让其生效。当然,“伪目标”的取名不能和文件名重名,不然其就失去了“伪目标”的意义了。

当然,为了避免和文件重名的这种情况,我们可以使用一个特殊的标记“.PHONY”来显式地指明一个目标是“伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”。

.PHONY : clean

只要有这个声明,不管是否有“clean”文件,要运行“clean”这个目标,只有“make clean”这样。于是整个过程可以这样写:

.PHONY : clean
clean :
    rm *.o temp

伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为“默认目标”,只要将其放在第一个。一个示例就是,如果你的Makefile需要一口气生成若干个可执行文件,但你只想简单地敲一个make完事,并且,所有的目标文件都写在一个Makefile中,那么你可以使用“伪目标”这个特性:

all : prog1 prog2 prog3
.PHONY : all

prog1 : prog1.o utils.o
    cc -o prog1 prog1.o utils.o

prog2 : prog2.o
    cc -o prog2 prog2.o

prog3 : prog3.o sort.o utils.o
    cc -o prog3 prog3.o sort.o utils.o

我们知道,Makefile中的第一个目标会被作为其默认目标。我们声明了一个“all”的伪目标,其依赖于其它三个目标。由于默认目标的特性是,总是被执行的,但由于“all”又是一个伪目标,伪目标只是一个标签不会生成文件,所以不会有“all”文件产生。于是,其它三个目标的规则总是会被决议。也就达到了我们一口气生成多个目标的目的。 .PHONY : all 声明了“all”这个目标为“伪目标”。(注:这里的显式“.PHONY : all” 不写的话一般情况也可以正确的执行,这样make可通过隐式规则推导出, “all” 是一个伪目标,执行make不会生成“all”文件,而执行后面的多个目标。建议:显式写出是一个好习惯。)

随便提一句,从上面的例子我们可以看出,目标也可以成为依赖。所以,伪目标同样也可成为依赖。看下面的例子:

.PHONY : cleanall cleanobj cleandiff

cleanall : cleanobj cleandiff
    rm program

cleanobj :
    rm *.o

cleandiff :
    rm *.diff

“make cleanall”将清除所有要被清除的文件。“cleanobj”和“cleandiff”这两个伪目标有点像“子程序”的意思。我们可以输入“make cleanall”和“make cleanobj”和“make cleandiff”命令来达到清除不同种类文件的目的。

编译的过程

编译的过程分为

链接

Makefile示例

app:sub.c add.c mult.c div.c main.c
	gcc sub.c add.c mult.c div.c main.c -o app

这里生成app目标, 依赖的是.c文件 命令是gcc命令

app:sub.o add.o mult.o div.o main.o
	gcc sub.o add.o mult.o div.o main.o -o app

sub.o:sub.c
	gcc -c sub.c -o sub.o

add.o:add.c
	gcc -c add.c -o add.o

mult.o:mult.c
	gcc -c mult.c -o mult.o

div.o:div.c
	gcc -c div.c -o div.o

main.o:main.c
	gcc -c main.c -o main.o

# 依赖.o文件没有向下寻找生成.o文件的规则
#定义变量
src=sub.o add.o mult.o div.o main.o
target=app
$(target):$(src)
	$(CC) $(src) -o $(target)

sub.o:sub.c
	gcc -c sub.c -o sub.o

add.o:add.c
	gcc -c add.c -o add.o

mult.o:mult.c
	gcc -c mult.c -o mult.o

div.o:div.c
	gcc -c div.c -o div.o

main.o:main.c
	gcc -c main.c -o main.o

#定义变量
src=sub.o add.o mult.o div.o main.o
target=app
$(target):$(src)
	$(CC) $(src) -o $(target)

%.o:%.c
	$(CC) -c $< -o $@
# 使用变量和自动变量

MakeFile项目实战

主要展示makefile是如何一步步起作用的

  1. 执行cmake …
  2. 查看cmake
src/perf_control/perf_monitor_unit/cpu_perf_monitor_unit.cc.o:
	$(MAKE) $(MAKESILENT) -f CMakeFiles/perf_control_module.dir/build.make CMakeFiles/perf_control_module.dir/src/perf_control/perf_monitor_unit/cpu_perf_monitor_unit.cc.o
.PHONY : src/perf_control/perf_monitor_unit/cpu_perf_monitor_unit.cc.o

可知make -f的含义是Read File as a make file

image-20230920114923677

  1. 找到对应的makefile中的targey
CMakeFiles/perf_control_module.dir/src/perf_control/perf_monitor_unit/cpu_perf_monitor_unit.cc.o: CMakeFiles/perf_control_module.dir/flags.make
CMakeFiles/perf_control_module.dir/src/perf_control/perf_monitor_unit/cpu_perf_monitor_unit.cc.o: /Users/bytedance/Documents/Code/PerfControlModule/src/perf_control/perf_monitor_unit/cpu_perf_monitor_unit.cc
CMakeFiles/perf_control_module.dir/src/perf_control/perf_monitor_unit/cpu_perf_monitor_unit.cc.o: CMakeFiles/perf_control_module.dir/compiler_depend.ts
// 上面是依赖, 下面是命令
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green --progress-dir=/Users/bytedance/Documents/Code/PerfControlModule/build/CMakeFiles --progress-num=$(CMAKE_PROGRESS_1) "Building CXX object CMakeFiles/perf_control_module.dir/src/perf_control/perf_monitor_unit/cpu_perf_monitor_unit.cc.o"
	/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS) -MD -MT CMakeFiles/perf_control_module.dir/src/perf_control/perf_monitor_unit/cpu_perf_monitor_unit.cc.o -MF CMakeFiles/perf_control_module.dir/src/perf_control/perf_monitor_unit/cpu_perf_monitor_unit.cc.o.d -o CMakeFiles/perf_control_module.dir/src/perf_control/perf_monitor_unit/cpu_perf_monitor_unit.cc.o -c /Users/bytedance/Documents/Code/PerfControlModule/src/perf_control/perf_monitor_unit/cpu_perf_monitor_unit.cc

这里就可以找到最终执行的命令 cmake -E是为了覆盖环境变量,最后由c++命令进行编译构建。

像${CXX_INCLUDES}的位置在下面文件中

include CMakeFiles/perf_control_module.dir/flags.make

具体内容如下

# CMAKE generated file: DO NOT EDIT!
# Generated by "Unix Makefiles" Generator, CMake Version 3.25

# compile CXX with /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++
CXX_DEFINES = -Dperf_control_module_EXPORTS

CXX_INCLUDES = -I/Users/bytedance/Documents/Code/PerfControlModule/src -I/Users/bytedance/Documents/Code/PerfControlModule/src/third_party/spdlog/include

CXX_FLAGS =  -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.1.sdk -fPIC -std=gnu++11

更多推荐

redis设计规范

部分内容参考:阿里redis开发规范同时,结合shigen在实习中的实践经验总结。key的名称设计可读性和管理性业务名:表名:idpro:user:1001简洁性控制key的长度,可以用缩写transaction->tras拒绝bigkey防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、s

Redis 高性能设计之epoll和IO多路复用深度解析

I/O多路复用模型是什么I/O:网络I/O多路:多个客户端连接(连接就是套接字描述符,即socket或者channel),指的是多条TCP连接复用:用一个进程来处理多条的连接,使用单进程就能的够实现同时处理多个客户端的连接一句话:实现了用一个进程来处理大量的用户连接,IO多路复用类似一个规范和接口落地实现:可以分sel

深度对话|Sui在商业技术堆栈中的地位

近日,我们采访了MystenLabs的商业产品总监LolaOyelayo-Pearson,共同探讨了区块链技术如何为企业提供商业服务,以及为什么Sui特别适合这些用例。1.请您简要介绍一下自己、您的角色以及您是如何开始涉足Web3领域的?目前,我领导MystenLabs的商业产品团队。通常来说,商业涵盖了一切,它可能是

山石网科国产化入侵防御系统,打造全生命周期的安全防护

随着互联网的普及和网络安全的威胁日益增加,botnet感染成为了企业面临的重要问题之一。botnet是一种由分散的客户端(或肉鸡)组成的网络,这些客户端被植入了bot程序,受控于攻击者。攻击者通过这些客户端的bot程序,利用C&C服务器对这些客户端进行管理和控制,以达到非法牟利的目的。被感染攻击的企业不仅会面临公司和个

GET和POST的区别,java模拟postman发post请求

目录一、先说一下get和post1、看一下人畜无害的w3schools怎么说:2、问一下文心你言哥,轻轻松松给你一个标准答案:3、卧槽,懂了,好像又没懂二、让我们扒下GET和POST的外衣,坦诚相见吧!三、我们的大BOSS还等着出场呢四、java模拟post请求1、弯了?那就给它掰回来。2、HttpURLConnect

GraphQL基础知识与Spring for GraphQL使用教程

文章目录1、数据类型1.1、标量类型1.2.高级数据类型基本操作2、SpringforGraphQL实例2.1、项目目录2.2、数据库表2.3、GraphQL的schema.graphql2.4、Java代码3、运行效果3.1、添加用户3.2、添加日志3.3、查询所有日志3.4、查询指定用户日志3.5、数据订阅4、总结

从李佳琦到背后的商业逻辑再到游戏行业

引言前阵子,李佳琦在直播间带货某牌子的眉笔时,被网友质疑越来越贵,对此李佳琦回应表示,79的眉笔不贵,国货品牌很难的,买不起的话,要找找自己的原因并反思这么多年有没有涨工资,有没有认真工作。他飘了吗?从他的语气和神情来看,的确是有点上头。像极了考上了985、211之后嫌弃父母不够体面孩子。小伙伴都知道,我们人一直以来都

2024考研王道计算机408数据结构+操作系统+计算机组成原理+计算机网络

2024考研王道计算机408数据结构+操作系统+计算机组成原理+计算机网络链-接:https://pan.baidu.com/s/152XLyH64TlcLXwmU-zlAsQ?pwd=r7zf提取码:r7zf信道利用率在408中经常考察到这里,我给大家总结一下这一类题目的做题方法以及技巧。首先,我们假定发射窗口大小是

在 Linux 文件系统中使用 attr 添加扩展属性

我使用开源的XFS文件系统是为了其扩展属性带来的小小便利。扩展属性是一种为我的数据添加上下文的独特方式。“文件系统”是一个描述你的计算机怎样跟踪你创建的所有文件的完美词语。你的计算机存储有大量的数据,无论是文档、配置文件还是数以千计的照片。这需要一种对人和机器都友好的方式。诸如Ext4、XFS、JFS、BtrFS的文件

的修大数据管理平台有哪些功能模块?它可以为企业带来什么好处?

的修大数据管理平台的功能比较强大,它提供了报修、维修、巡检、能耗、智识库、管线智慧云等应用场景服务,同时还可以为企业提供维保进度追踪、员工考核、服务流程管控、设备资产管理等一站式解决方案。平台通过多渠道报修、“一站式”投诉建议服务、企业云课堂、智能巡检、配件管理、多维度数据分析等功能,打造了移动、便捷、高效、安全、智能

国外发达国家码农是真混得好么?

来看看花旗工作十多年的码农怎么说吧!美国最大的论坛Reddit,之前有一个热帖:一个程序员说自己喝醉了,软件工程师已经当了10年,心里有好多话想说,“我可能会后悔今天说了这些话。”他洋洋洒洒写了一大堆,获得9700多个赞。内容很有意思,和题主“国外发达国家码农真的混的好么”这个问题很贴切,而且是10年老程的员的肺腑之言

热文推荐