Make和Makefile快速入门

2023-11-17

目的

我们可以在终端中通过命令来使用GCC编译代码生成可执行文件,对于实际的项目因为涉及的文件通常会比较多一些,情况也会复杂些。如果每次修改代码后都在终端中一行行输入命令来操作就比较麻烦了,这个时候可以把这些命名都写入一个shell脚本文件中,每次执行这个脚本就可以完成相应的工作。

当然实际操作中有比shell脚本更加合适运用到这个场景中的东西,那就是Make和Makefile。Make是一个程序,最常用的是 GNU Make ,Makefile是用Make来解析执行的脚本文件的默认文件名。这篇文章将对相关的内容做个说明。

官方页面:https://www.gnu.org/software/make/

GNU Make is a tool which controls the generation of executables and other non-source files of a program from the program’s source files.


Make gets its knowledge of how to build your program from a file called the makefile, which lists each of the non-source files and how to compute it from other files. When you write a program, you should write a makefile for it, so that it is possible to use Make to build and install the program.

基础入门

下面是一个Make和Makefile使用的基础演示:
在这里插入图片描述
Makefile文件内容是由一条一条的规则组成的,规则的语法如下:

target: dependencies ...
[tab]   commands

target为想要得到的目标;dependencies为要得到该目标需要的依赖,可以有零个或者多个;commands为为实现目标需要执行的命令,commands前 必须使用tab键 缩进。 target和dependencies都可以有多个,使用空格分隔。不加特殊修饰的情况下make工具会首先将target和dependencies字段当作文件名来看待。

上面演示中Makefile文件中有两条规则,当在终端中不带参数使用make命令时按照下面顺序进行了操作:

  • 寻找当前目录下找名字叫Makefile的脚本文件;
  • 执行Makefile文件中第一条规则,即目标为all的规则;
  • 目标all依赖于main.o,没有该文件时则会去寻找目标为main.o的规则去执行命令生成;
  • 当生成main.o之后有又返回去执行目标为all的规则中的命令;

make有一个重要的特性是在执行规则时会去比较target和dependencies的修改时间,只有在 target不存在 或是 dependencies比target新 的时候才会执行规则中的命令。 请看下面演示:
在这里插入图片描述

make也可以指定特定target的规则来执行,比如下面演示:
在这里插入图片描述
上面演示中有一个目标为clean的规则,该规则执行时用来删除一些文件。上面演示中出现了 .PHONY: clean 这一行语句, .PHONY 是一个内置目标,它的作用是将后面的依赖声明为伪目标,这样就算目录下有和伪目标同名的文件,伪目标的规则还是会执行。

到上面为止make和Makefile基本的一些功能与使用已经介绍的差不多了。接下来详细的内容就下面两块,对于make和Makefile的更多使用需要结合这些内容进行:

  • Makefile作为脚本来说有一些自己的语法规则,像是变量、函数等;
  • make命令的选项和使用;

Makefile语法

基础杂项

Makefile语法和Shell有部分相似的地方,也有差异的地方,这里对一些小的语法点进行说明:

  • 注释
    使用 # 开头行为注释;
  • 换行
    使用 \ 对长行进行换行;
  • 通配符
    在commands中可以 * 作为通配符来表示任意字符串,用它去匹配文件会得到单个文件名;在Makefile中还可以使用 % 来表示任意字符串,它用于匹配列表中符合条件文件;
  • 命令执行
    make在执行commands时,每一行都会在独立的子shell进程中执行,如果需要多个命令在同一进程中执行可以写在同一行使用 ; 分隔,太长的话可以使用换行符;
  • 隐藏命令
    默认情况下make在执行commands时会打印具体的命令,可以在命令前加 @ 来隐藏命令;
  • 忽略命令出错
    在make执行时如果某个规则中有命令出错了,那么这个规则就会终止执行,可以在命令前加上 - 来忽略命令出错;
  • 文件搜索路径
    在make执行时默认情况下都是在当前目录下搜索检查相关文件的,可以使用 VPATH 变量来增加文件搜索的路径,比如 VPATH := src:include 就是增加了src和include两个目录;

在这里插入图片描述

变量

Makefile中变量的定义与赋值主要有四种方式:

  • VAR := value 将值赋给变量VAR;
  • VAR = value 在使用VAR时才将值赋给它;
  • VAR ?= value 如果VAR没有被定义过,则将值赋给VAR,否则忽略该句;
  • VAR += value VAR的值用空格隔开后追加新值;

Makefile中使用 $(VAR) 方式来使用变量。

在这里插入图片描述

Makefile中变量也可以通过使用make时进行赋值:
在这里插入图片描述

Makefile中还有一些自动化变量,常用的有下面这些:

自动化变量 说明
$@ 规则的目标
$< 规则的第一个依赖
$? 所有比目标新的依赖的列表
$^ 所有的依赖的列表,会自动去重

在这里插入图片描述

条件选择

Makefile中也有条件选择的语法。主要的可以分为两类,分别看下面演示:
在这里插入图片描述
上面是一类演示,用于比较两个参数。 ifeq 比较时参数相等为真 ,ifneq 比较时参数不相等为真。两者后面都可以使用 (arg1, arg2) 或是 "arg1" "arg2" 来表示要比较的参数。后续关键词中 else 是可选的, endif 是必选的。

在这里插入图片描述
上面是一类演示,用于判断是否有值。 ifdef 后参数存在或者有值时为真 ,ifndef 后参数不存在或者没有值时为真。后续关键词中 else 是可选的, endif 是必选的。这类方式使用时需要特别注意一种情况:
在这里插入图片描述

函数

Makefile中有很多内置的函数可以使用,使用方式如下:
$(<function> <arguments>)
其中 function 是函数名, arguments 是输入参数,有多的参数的话参数间用 , 分隔。

常用的一些函数如下:

  • $(wildcard <pattern>)
    列出当前目录下所有符合模式 pattern 的文件名;
    在这里插入图片描述
  • $(patsubst <pattern>,<replacement>,<text>)
    查找 text 中的单词是否符合模式 pattern,如果匹配的话,则用 replacement 替换;
    在这里插入图片描述
    这个函数这种替换的字段的功能经常还可以看到一种类似的方式 $(VAR:<pattern>=<replacement> )
    在这里插入图片描述
  • $(filter <pattern>,<text>)
    过滤出 text 中符合模式 pattern 的字符串;
    在这里插入图片描述
  • $(filter-out <pattern>,<text>)
    去除 text 中符合模式 pattern 的字符串;
  • $(foreach <var>,<list>,<text>)
    把 list 中的词条逐一取出,放到名为 var 的变量中,然后执行 text 表达式;
    在这里插入图片描述
  • $(shell command)
    调用shell执行命令;在这里插入图片描述

文件引用

Makefile中可以使用 include 关键词引入文件,被引入的文件中的内容将在引入的位置展开。下面是个简单的演示:
在这里插入图片描述

嵌套执行

很多大型的工程中通常包含很多模块,每个模块有各自都有各自的Makefile文件,这个时候经常也会涉及到嵌套执行,主要方式有两种,比如下面演示:
在这里插入图片描述
在嵌套执行时可以使用 export 来修饰变量,这样这些变量会传递到下级Makefile中;如果用 unexport 来修饰变量,可以让变量不传递到下级Makefile中。

make使用

对于make而言,除了前面的使用方式,更进一步的就是配合它的各个选项的使用,常用的一些选项如下:

  • -b-m 忽略和其它版本make的兼容性;
  • -B 强制重新编译;
  • -C 指定读取Makefile文件的目录;
  • -f 指定需要执行的脚本文件名称;
  • -i 执行时忽略所有错误;
  • -I 指定执行时搜索文件的路径;
  • -j 指定同时运行命令的个数,用来加快编译速度,比如常见的 -j4 -j8 这些,选择时主要视CPU线程数而定;
  • -n 打印输出过程但不真正执行编译操作,用于调试;

使用进阶

有了前面的内容之后就可以对前面 基础入门 里的Makefile文件进行改造了,首先准备下面几个文件用于测试:
在这里插入图片描述
在这里插入图片描述
上面的Makefile中每个 .o 文件的生成都是手动编写命令处理的,如果文件很多的话这就会很麻烦,其实make很聪明的,如果要生成一个 xxx.o 的文件它会自己推导这个文件是由 xxx.c 等文件得到的(甚至你不用写 gcc -c xxx.c 这条命令也行,make也会自动推导,这点在后面会用到)。这个叫做 make的隐含规则(Implicit Rules) 。下面首先对这部分进行改造:
在这里插入图片描述
上面演示中第一条规则中直接写了依赖的文件,其实我们可以把它存储到一个变量中;另外现在默认生成的可执行文件名称不太好,我们也可以改一改:
在这里插入图片描述
上面变量赋值时一定要注意行末的空格,不然可能运行出错。经过上面的改造我们使用时只要改变前两行的变量值就可以调整需要编译的文件和编译生成的程序名称了。不过目前情况下还有个BUG,现在的Makefile的各个规则的依赖中并没有体现 .h 文件,而我们有些内容是定义在 .h 文件中的,如果修改这个文件那么再次执行make时依据make的规则其实是不会重新编译的,所以我们需要在依赖条件中加上 .h 文件:
在这里插入图片描述
通常上面这样在依赖中手动添加用到的 .h 文件其实是不现实的,因为每个 .c 文件中包含的 .h 文件其实是不确定的,有的可能还非常多,这个时候就要用到 gcc 的一些选项了 -M -MM ,这两个选项可以给出 .c 文件生成对应 .o 文件的依赖规则:
在这里插入图片描述
可以看到这些信息的格式就是Makefile规则的格式,比如 hello.o: hello.c hello.h ,只要把这个内容加入到Makefile文档中,再加上make隐含规则,它会自动推导生成该 .o 文件的命令。

这里我们先对Makefile进行些调整:
在这里插入图片描述
然后再加上生成依赖规则相关内容,把依赖规则保存到 .d 文件中,再使用 include 将文件内容引入到Makefile进行展开,这样就可以实现自动添加依赖规则的效果了:
在这里插入图片描述
在这里插入图片描述
到此为止实现了一个可以将当前目录下所有 .c 文件编译生成指定名称的应用程序的脚本,上面脚本中其实稍稍还有点小问题(当连续使用 make clean 时会重复生成 .d 文件再进行删除)。下面代码是修复并添加注释后的代码:

# 该脚本可以获取当前目录下所有的 .c 文件,然后编译生成名称由 TARGER 指定的程序文件

SRCS := $(wildcard *.c) # 使用 wildcard 函数获取所有 .c 文件
OBJS := $(patsubst %.c, %.o, $(SRCS)) # OBJS为生成 TARGER 的依赖
TARGER := sayhello # 这里是最终生成的程序文件名

# 生成最终程序的规则
$(TARGER): $(OBJS)
	gcc -o $@ $^

DEPS := $(patsubst %.c, %.d, $(SRCS)) # 每个 .c 文件对应一个 .d 的依赖规则文件

# MAKECMDGOALS 是 make 的一个环境变量,保存了当前操作的目录列表
# 只执行 make 时 MAKECMDGOALS 中没有 clean ,所以下面的 include 会被执行
# 执行 make clean 时,下面的 include 不会被执行
ifneq ($(MAKECMDGOALS), clean)
  -include $(DEPS) # 引用所有 .d 文件内容到这里展开
endif

# 获取依赖规则重定向保存到 .d 文件中
%.d: %.c
	gcc -MM $< > $@

# 生成 .o 文件的规则
%.o: %.c
	gcc -c $<

.PHONY: clean
# 清除编译生成的文件
clean: 
	rm -f $(shell find -name "*.o")
	rm -f $(shell find -name "*.d")
	rm -f $(shell find -name $(TARGER))

总结

make和Makefile基础的一些内容主要就是上面这些,更多内容可以参考可以参考下面链接:
https://seisman.github.io/how-to-write-makefile/index.html
http://c.biancheng.net/makefile/

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Make和Makefile快速入门 的相关文章

  • Linux 上的 Python 3.6 tkinter 窗口图标错误

    我正在从 Python GUI 编程手册 学习 Python GUI 某项任务要求我通过将以下代码添加到我的配方中来更改窗口图标 Change the main windows icon win iconbitmap r C Python3
  • 如何从 Linux 命令行获取视频文件的分辨率(宽度和高度)?

    我一直在挖掘 mplayer mencoder 和 ffmpeg 文档 但我似乎无法想出anything 我对输出格式不是特别挑剔 因为我可以使用正则表达式将其拉出来 我只是似乎无法首先获取数据 Use ffprobe https ffmp
  • Scrapy FakeUserAgentError:获取浏览器时发生错误

    我使用 Scrapy FakeUserAgent 并在我的 Linux 服务器上不断收到此错误 Traceback most recent call last File usr local lib64 python2 7 site pack
  • 在 Mac OS X 上的 Makefile 中设置 PATH(但它适用于 Linux)

    我可以在 Linux 上的 Makefile 中设置 PATH 但不能在 Mac OS X 上设置 在 OS X 中 可以设置 PATH 但不会使用 这是一个演示 在带有 bash 4 1 2 1 release 和 GNU Make 3
  • 如何使用AWK脚本检查表的所有列数据类型? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 在这里 我正在检查表中第一列的数据类型 但我想知道AWK中表的所有列数据类型 我尝试过 但只能获得一列数据类型 例如 Column 1
  • InstaPy:“错误,无法确定 64 位 Linux 的正确文件名”

    有人知道如何解决或解决这个问题吗 来自控制台的堆栈跟踪 执行后报告错误 InstaPy Version 0 6 9 Workspace in use home zanettra InstaPy Error unable to determi
  • 如何让 clangd 转向 c++20

    当没有其他信息时 如何让 clangd 回退到 c 20 例如 在第一次构建之前 cmake 可以生成一个 这是在带有最新 LLVM 的 Arch Linux 上 这是通过 Emacs LSP 运行的 但这应该没有什么区别 你可以加 Com
  • 无法安装 WWW::Curl::Easy: SZBALINT/WWW-Curl-4.17.tar.gz : make NO

    我正在尝试在我的 Fedora 26 机器上安装 WWW Curl Easy gcc c I usr include D REENTRANT D GNU SOURCE O2 g pipe Wall Werror format securit
  • Linux 中热插拔设备时检测设备是否存在

    我正在运行 SPIcode http lxr free electrons com source drivers spi spi omap2 mcspi c在熊猫板上 我想知道其中的哪个功能code http lxr free electr
  • 什么是“制定目标”?

    为什么我需要制作一个make target在能够构建我的源代码之前 更具体地说 什么是制定目标 http publib boulder ibm com infocenter rsdvhelp v6r0m1 index jsp topic o
  • 为 Qt 应用程序创建 Linux 安装

    我刚刚用 Qt Creator 制作了一个很棒的程序 我对自己很满意 如何将其从台式机移至笔记本电脑 那么 最好的方法是安装程序 对吗 对于 Ubuntu 这是一个 Debian 软件包 对吗 我怎么做 有人这样做过吗 他们可以分享 QT
  • 在 Ubuntu 16.04 上找不到 printf.c

    我最近切换到Ubuntu 16 04 我在用vscode作为 Ubuntu 上的 IDE 我配置了其他语言 但我无法做到这一点C C 我创建c cpp properties json launch json tasks json 当我开始编
  • sleep 0 有特殊含义吗?

    我看到很多用法sleep 0在我的一个客户项目中 代码看起来像这样 while true sleep 0 end 阅读一些像这样的答案this https stackoverflow com questions 3727420 signif
  • 从多线程程序中调用 system()

    我们正在开发一个用 C 编写的多线程内存消耗应用程序 我们必须执行大量的 shellscript linux 命令 并获取返回码 读完之后article http www linuxprogrammingblog com threads a
  • 无需 cron 在后台发送邮件

    我想知道是否有一种方法可以运行 PHP 循环 以便在后台向订阅者发送几百封电子邮件 我的目标是格式化新闻通讯 单击发送 然后关闭浏览器或更改页面 当然 发送电子邮件的实际过程将在后台运行 不会因浏览器关闭而中断 我知道这可以通过 cron
  • 如何查找连接到 AF_INET 套接字的客户端的 UID?

    有什么方法或类似的东西ucred for AF UNIX如果是AF INET插座 TCP在我的例子中 找出连接到我的套接字的客户端的UID 还有 proc net tcp但它显示了UID of the creator插座的而不是连接的cli
  • 如何使用 sed 仅删除双空行?

    我找到了这个问题和答案 https stackoverflow com questions 4651591 howto use sed to remove only triple empty lines关于如何删除三重空行 但是 我只需要对
  • 信号处理程序有单独的堆栈吗?

    信号处理程序是否有单独的堆栈 就像每个线程都有单独的堆栈一样 这是在 Linux C 环境中 来自 Linux 手册页signal 7 http kernel org doc man pages online pages man7 sign
  • 在 Linux 上的 Python 中使用受密码保护的 Excel 工作表

    问题很简单 我每周都会收到一堆受密码保护的 Excel 文件 我必须解析它们并使用 Python 将某些部分写入新文件 我得到了文件的密码 当在 Windows 上完成此操作时 处理起来很简单 我只需导入 win32com 并使用 clie
  • 嵌入式Linux poll()不断返回

    我有一个特别的问题 当我知道没有什么可读时 民意调查不断返回 因此设置如下 我有 2 个文件描述符 它们构成fd设置民意调查监视 一种用于引脚从高到低的变化 GPIO 另一个用于代理输入 代理输入出现问题 处理的顺序是 启动main函数 然

随机推荐

  • 运放增加输出电流

    功率输出模块 采用三极管9012 9013来进行双向扩流以提高其带载能力 可以完全满足发挥部分所要求的稳幅输出能力 当负载变化时 其输出电压幅度变化小于3 如图所示 集成运放的扩流和扩压 一 集成运放的扩流 在集成运放的输出端再加一级互补对
  • k8s-client(java)从6.0.1升级到11.0.0出现patch问题may not be specified for non-apply patch/cannot unmarshal...

    背景 kubernetes client java升级 复杂的patch出现各种问题 并且没有找到解决方案 经过研究 测试 找到了解决方案 希望能帮助到使用kubernetes client java客户端的同学 patch方法调用出现异常
  • IEEE 1471(ISO/IEC/IEEE 42010)架构描述方法

    ISO IEC IEEE 42010 架构描述方法 关于 背景 架构描述 利益相关者和关注点 架构视图和架构观点 架构模型 架构关系 架构原理 关于 本文对软件体系架构的描述方法的研究基于 ISO IEC IEEE 42010 ISO IE
  • unity ethan_响应式网页设计的挑战,Ethan Marcotte

    unity ethan In this episode of the Versioning Show David and Tim are joined by Ethan Marcotte a well known designer who
  • c语言设计(TVI)地铁自动售票机---@颜麓

    设计TVI 地铁自动售票机 机软件 输入站数 计算费用 计费规则 6站2元 7 10站3元 11站以上为4元 输入钱数 计算找零 找零时优先找回面额大的钞票 找零方式为各种面额张数 可识别面额 100 50 20 10 5 1 includ
  • Sentinel整合Ribbon/OpenFeign,Sentinel自定义限流熔断

    Sentinel服务熔断环境搭建 服务熔断 应对微服务雪崩效应的一种链路保护机制 类似保险丝 需要完成Sentinel整合Ribbon openFeign 所以我们先要搭建环境 那么先从整合Ribbon开始 环境搭建 为了演示操作 所以在这
  • DBeaver改成英语

    DBeaver改成英语 安装目录中有个dbeaver ini文件 追加 Duser language en可以改回英文 有些数据库术语翻译后反而不太容易理解
  • java获取唯一时间戳Id.多线程保证唯一性

    工程里有获取唯一时间戳作为id的需求 想了想用乐观锁cas实现 自旋 cas原子性操作获得了绝对唯一的时间戳 系统时间 纳秒版本 单机有效 不能分布式调用 public class AtomicTimeStamp private Atomi
  • Anaconda的安装与环境配置

    这里简单的记录一下Anaconda的安装过程 没有干货 只是记录 但也看了就会 下载安装包 这里有两个下载地址 分别是官网和清华镜像源 推荐清华镜像 下的快 官网下半天还可能失败 无论是哪个下载地址 找到对应你系统的安装程序下载即可 官网
  • Ubuntu下安装JDK图文教程详解

    操作系统 Ubuntu 11 10 我们选择的是jdk1 6 0 30版本 安装文件名为jdk 6u30 linux i586 bin 1 复制jdk到安装目录 1 假设jdk安装文件在桌面 我们指定的安装目录是 usr local jav
  • 可以编辑的table antd

    版本一 效果图 有保存按钮 antd3 代码 import React Component from react import Form Input Button Table Divider Popconfirm Tooltip from
  • 19_复制目录下所有文件夹和目录CopyFile()

    复制目录下所有文件夹和目录CopyFile 想一下 若要复制文件夹 可以在目录位置新建一个文件夹 然后将源文件夹中里面的文件进行遍历 一个一个的复制到目标文件夹中即可 void EnumCopyFile 输入参数 TCHAR szSrcRo
  • STL——vector以及emplace_back分析

    1 这里需要注意凡是连续空间的容器都提供operator 是为了数组操作 2 back 应该是 end 1 3 vector的大小为12 vector的迭代器为指针 1 emplace back 1 相比push back 如果传入临时对象
  • matlab中的锐度测量,锐度(解析度)MTF定义及测试

    Image sharpness 图像锐度 解析度 锐度无疑是最重要的摄影图像质量的评价因素 它是关系到图片中有多少细节可以被辨认的最密切因素 但它不是唯一的重要因素 其他重要因素包括色差 与锐度密切相关 噪音 动态范围 与噪声密切相关 和色
  • mysql常用命令

    mysql命令 查询 限制返回个数及偏移量 m 偏移量 n 个数 只限制个数可省略m 如limit 2 select from
  • IntelliJ IDEA破解

    今天写个IDEA破解 废话不多说 直接给网址 官网 https www jetbrains com idea 破解注册码 http idea lanyus com 里面有永久破解 还有注册码 可你根据你的喜爱 来选择 今天写一下它的破解补丁
  • HashMap之扩容原理

    一 什么是HashMap HashMap 数据结构为 数组 链表 JDk1 7 JDK1 8中增加了红黑树 其中 链表的节点存储的是一个 Entry 对象 每个Entry 对象存储四个属性 hash key value next 二 为什么
  • explicit关键字解析

    C 提供了关键字explicit 可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生 声明为explicit的构造函数不能在隐式转换中使用 C 中 一个参数的构造函数 或者除了第一个参数外其余参数都有默认值的多参构造函数 承担了两个角
  • Vue3.0-计算属性computed

    Vue3 0中的computed的功能与2 0中的计算函数功能一样 引入方法 import computed from vue 使用 在引入之后 设计箭头函数 var name computed gt 函数体 retrun 返回值 也就是计
  • Make和Makefile快速入门

    文章目录 目的 基础入门 Makefile语法 基础杂项 变量 条件选择 函数 文件引用 嵌套执行 make使用 使用进阶 总结 目的 我们可以在终端中通过命令来使用GCC编译代码生成可执行文件 对于实际的项目因为涉及的文件通常会比较多一些