当前位置: 首页 > article >正文

makefile详解

1.背景介绍

linux中C语言可执行文件a.out的由来如下:

  • test.c经过预编译到test.i
  • test.i经过编译到test.s
  • test.s经过汇编到test.o
  • test.o经过链接到a.out

但是对于一个大型工程,每次生成新的可执行文件都要重新对每个文件(或者对修改了的文件)进行编译、链接等操作的工作量太大了。

因此提出就有了make/makefile,它是一个工程文件的编译链接规则,规则写好之后,只需要一个make就能生成可执行文件。

2.语法

2.1 语法格式

all: 目标文件1 目标文件2
目标文件1: 依赖文件1, 依赖文件2...
<tab>命令1
<tab>命令2...
目标文件2: 依赖文件1, 依赖文件2...
<tab>命令1
<tab>命令2...
  • 目标文件即要生成的文件,一半情况下makefile中的第一个目标文件为最终目标
  • 依赖文件:该文件由哪些文件生成
  • 命令:通过执行命令来由依赖文件生成目标文件。这里默认执行命令时会在终端输出命令的内容。如果在命令前加"@"符号,则终端隐藏命令,但是返回到标准输出的内容不会隐藏。
  • all:makefile文件默认只生成一个目标文件即完成编译。如果想要生成多个目标文件,则需要all并放在第一行列出所有的生成文件。

2.2 变量

2.2.1 自定义变量

变量定义:

  • 递归展开:VAR= var1 var2…多个值中间用空格隔开
  • 直接赋值:VAR:=var。

赋值操作:

  • “=”:使用"="进行赋值,变量的值是整个Makefile中最后被指定的值,例如:
VAR_A = A
VAR_B = $(VAR_A) B
VAR_A = AA

最后VIR_B的值为AA B而不是A B

  • “:=”:表示直接赋值,赋予当前位置的值
VAR_A := A
VAR_B := $(VAR_A) B
VAR_A := AA

最后VIR_B的值为A B

  • “?:”:表示如果该变量没有被赋值,则将后面的值赋给它
VAR ?= value

VAR的值为value

VAR := value1
VAR ?= value2

VAR的值为value1

  • “+=”:类似C语言,将右边的变量追加到左边的变量中。

“$“符号表示取变量的值,当变量名多于一个字符时,需要使用”()”

2.2.2 自动变量

  • “$^”: 表示所有的依赖文件,并以空格分开
  • “$@”: 表示生成的目标文件
  • “$<”: 代表第一个依赖文件
  • “$?”: 所有时间戳比目标文件晚的依赖文件,并以空格分开

2.2.3 预定义变量

  • CC:C编译器的名称,默认值为cc
  • RM:文件删除程序的名称,默认值为rm -f
  • CFLAGES:C编译器(gcc)的选项,默认无默认值,如-Wall、-g、-o
  • AR:库文件维护程序的名称,默认值为ar
  • CPP:C预编译器的名称,默认值为$(CC) -E
  • CPPFLAGS:C预编译器的选项,无默认值
  • CXXFLAGS:C++编译器(g++)的选项,无默认值

2.2.4 隐含变量

  • “%.o”: 任意的.o文件
  • “*.o”: 所有的.o文件

2.3 函数

2.3.1 名称处理函数

  1. wildcard
$(wildcard <pattern...>)

获取指定格式的文件列表
示例:

$(wildcard *.cpp test/*.cpp)

代表当前目录下所有的.cpp文件和test目录下所有的.cpp文件。

  1. dir
$(dir <names...>)

获取文件所在目录,本质上是获取最后一个"/“以前的内容,如果没有”/“,返回”./"
示例:

$(dir src/foo.c sum.txt)

返回"src/ ./"

  1. notdir
$(notdir <names...>)

获取一个文件路径的非目录不放呢,即获得文件名。本质上是获取最后一个反斜杠"/"之后的内容,如果没有反斜杠,则直接返回本身
示例:

$(notdir src/foo.c sum.txt)

返回"foo.c sum.txt"

  1. suffix
$(suffix <names...>)

获取文件的后缀,如果没有后缀,则返回空字符。本质是获取最后一个"."后面的内容
示例:

$(suffix src/foo.c test.c abc)

返回".c .c "

  1. basename
$(basename <names...>)

去除文件后缀
示例:

$(basename src/foo.c test.c abc)

返回"src/foo test abc"

2.3.2 字符串替换与分析函数

  1. subst
$(subst <src>,<dst>,<text>)

将<text>中的字符<src>替换为<dst>
示例:

$(subst aa,AA,aabbaa aAfd)

返回"AAbbAA aAfd"

  1. patsubst
$(patsubst <src_pattern>,<dst_pattern>,<text>)

使用目标字符格式替换源字符格式,常常搭配%使用,如果<src_pattern>和<dst_pattern>都包括%,那么此时%表示的字符内容是一样的。
示例:

$(patsubst %.cpp,%.o,a.cpp b.cpp)

返回"a.o b.o"

  1. $(C_SOURCES:.c=.o)
    将C_SOURCES变量中所有的.c替换成.o

  2. strip
    去掉字符串的开头和结尾的空格
    示例:

$(strip,  a.cpp b.cpp)
  1. findstring
    在某个字符串中查找指定字符串
    示例:
$(findstring a,a b c)
  1. filter
    保留指定格式的字符串
sources := a.c b.c c.c d.h
result := $(filter %.c %.s,$(sources))

返回"a.c b.c c.c"

  1. filter-out
    去除指定格式的字符串

  2. addprefix
    为字符串添加字符串

OBJ_FILES = main.o a.o b.o
PREFIXED_OBJ_FILES = $(addprefix obj/,$(OBJ_FILES))

返回"obj/main.o obj/a.o obj/b.o"

2.3.3 控制函数

  1. info
    向标准输出打印提示信息
$(info some debug info)

终端输出"some debug info"

  1. warning
    向标准输出打印,用于输出警告信息,make继续执行

  2. error
    向标准错误输出打印文本,make停止执行

2.3.4 其他函数

  1. foreach
$(foreach <var>,<list>,<text>)

将<list>中的参数逐一取出放到<var>中,然后执行<text>中的表达式

  • 循环执行中:每执行一次循环都会返回一个字符串,foreach循环会将返回的字符串汇总,不同字符串通过空格分隔
  • 循环执行结束:当整个循环结束的时候,返回汇总的字符串

示例:

name := a b c d
files := $(foreach n,$(name),$(n).o)

返回"a.o b.o c.o d.o"

  1. call
$(call <expression>,<parm1>,<parm2>,...)

调用自定义的函数或者表达式,也可以传参调用

  1. shell
    执行操作系统的shell命令,返回的是命令行命令执行的结果
    示例:
$(shell ps ajx | grep text)
  1. eval
$(eval <text>)

可以将<text>中的内容作为makefile的一部分,然后按照makefile的语法解析这些内容,无返回值

2.4 伪目标

伪目标的格式:

.PHONY:目标
目标:依赖
<tab>命令

伪目标与常规目标的区别(无.PHONY声明):

  • 常规目标会检查目标文件的修改时间与依赖文件的修改时间,如果目标文件的修改时间晚于依赖文件,则不用执行命令;反之再执行命令。
  • 伪目标无论何时都会执行命令

伪目标用来定义ALL的作用:

  • 避免与目标文件同名导致构建失败或者不小心touch了目标文件使得本应该重新编译链接的没有反应,提升鲁棒性
  • 大多数项目将ALL声明为伪目标,遵循社区约定

还用来定义无依赖对象的伪目标,例如:

.PHONY:clean
clean:
    rm *.o *.out

调用时要在命令行输入:“make clean”

3.编写方式

假定有如下a.c b.c c.c main.c 四个文件,将这四个文件编译链接形成一个目标文件output

3.1 Makefile+直接编译链接

output:a.c b.c c.c main.c
    gcc a.c b.c c.c main.c -o output

3.2 Makefile+编译+链接

output:a.o b.o c.o main.o
    gcc a.o b.o c.o main.o -o output

a.o:a.c
    gcc -c a.c a.o
b.o:b.c
    gcc -c b.c b.o
c.o:c.c
    gcc -c c.c c.o
main.o:main.c
    gcc -c main.c main.o

3.3 Makefile+变量

SRC = a.o b.o c.o main.o
TARGET = output
$(TARGET):$(SRC)
    $(CC) $^ -o $@

a.o:a.c
    $(CC) $^ -o $@
b.o:b.c
    $(CC) $^ -o $@
c.o:c.c
    $(CC) $^ -o $@
main.o:main.c
    $(CC) $^ -o $@

3.4 Makefile+模式匹配

SRC = a.o b.o c.o main.o
TARGET = output
$(TARGET):$(SRC)
    $(CC) $^ -o $@

%.o:%.c
    $(CC) $< -o $@

3.5 Makefile+函数

a.h和a.cpp和main.cpp文件生成一个项目的makefile文件如下:

CXX = g++
CXXFLAGS = -Wall -std=c++17


SRCS = a.cpp main.cpp
OBJS = $(SRCS:.cpp=.o)
TARGET = output


all = $(TARGET)

$(TARGET): $(OBJS)
    $(CXX) $(CXXFLAGS) -o $(TARGET) $(OBJS)

%.o:%.cpp
    $(CXX) $(CXXFLAGS) -c $< -o $@

clean:
    rm -f $(OBJS) $(TARGET)

.PHONY: all clean

问题:这里的.c文件的依赖文件.h为什么不写:
这里这么写是没有问题的,它会自动检测.c的.h依赖文件。但是会出现一个问题,如果是这里修改了.h文件,make是检测不出来依赖文件改变的,它只会看.c没变,然后就不会重新编译。因此每次修改之后都需要make clean && make重新生成。

所以可以改进一下,来可以显示生成依赖关系文件,然后-include到makefile文件中如下,这时make时候就会检测到.c对.h的依赖关系,会进行时间检测是否重新编译:

CXX = g++
CXXFLAGS = -Wall -std=c++17 -MMD -MP  # 启用依赖生成
SRCS = main.cpp utils.cpp
OBJS = $(SRCS:.cpp=.o)
DEPFILES = $(SRCS:.cpp=.d)
TARGET = app

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CXX) $(CXXFLAGS) -o $@ $(OBJS)

%.o: %.cpp
	$(CXX) $(CXXFLAGS) -c $< -o $@  #因为有MMD和MP参数所以会在执行这个的时候同时生成对应.d文件

-include $(DEPFILES)  # 包含依赖关系

clean:
	rm -f $(OBJS) $(TARGET) $(DEPFILES)

.PHONY: all clean

http://www.kler.cn/a/584552.html

相关文章:

  • Discuz建站教程之论坛头部logo跳转链接怎么修改?
  • HCIA复习实验拓扑详细版
  • mysql下载与安装、关系数据库和表的创建
  • DeepSeek-R1:开源大模型的技术革命与行业影响分析
  • Python第二十课:生成对抗网络 | AI创造力觉醒
  • 基于SpringBoot的“校园周边美食探索及分享平台”的设计与实现(源码+数据库+文档+PPT)
  • 【每日学点HarmonyOS Next知识】上下拉动作、图片预览、组件边距、this获取、svg旋转
  • 工业物联网平台系列技术介绍-分类、聚类、逻辑回归
  • 中服云官网改版上线,全力聚焦工业物联网平台IIOT领域
  • 【第五节】windows sdk编程:windows 控件基础
  • WPS的Excel文档如何利用VB脚本批量替换超链接的内容
  • Flutter:跑马灯公告栏
  • 如何使用logrotete定时切割mysql的慢日志
  • “量超融合”突破 澳Quantum Brilliance融资2000万美元探索量子与超算协同
  • 如何用AI轻松制作高效又专业的PPT演示文稿
  • 【WPF】在System.Drawing.Rectangle中限制鼠标保持在Rectangle中移动?
  • 电商网站价格监控:动态价格数据的实时抓取案例
  • XMI(XML Metadata Interchange)和XML之间的关系
  • 【工具】jdk与jmeter下载与安装
  • [ NodeJs ] worker pool