Makefile基本语法入门(易于理解一文读懂)

Makefile:功能、语法与示例

前言


在软件开发中,自动化构建是一个至关重要的环节,它能够提高开发效率、减少人为错误并确保代码的质量。而 Makefile 作为一个强大的构建工具,在这一过程中扮演着重要的角色。

网上关于 Makefile 的教程大多数都是相当全面和详尽的,有时可能会涉及到一些高级的用法和复杂的示例。这些教程通常旨在帮助读者全面了解 Makefile 的功能和用法,从而能够应对各种复杂的项目需求。

然而,对于初学者来说,Makefile 的语法和功能可能会显得复杂和晦涩,有时甚至让人望而却步,因此,对于初学者来说,一个简单、易于理解的 Makefile 入门教程可能更为适合。所以我在自己学习Makefile之后 ,通过对知识点的总结记下了这篇笔记,希望同样可以作为Makefile 入门教程帮助初学者快速掌握 Makefile 的基本语法和常用功能,为日后深入学习和应用打下良好的基础。

介绍

Makefile 是软件开发中的重要工具,它可以自动化执行编译、链接和其他构建任务。本文将介绍 Makefile 的功能、基本语法以及一个简单的示例,帮助读者更好地理解和使用这一工具。

什么是 Makefile?

Makefile 是一个文本文件,其中包含了一系列规则和命令,用于告诉 make 工具如何编译和构建软件项目。它基于文件的依赖关系和修改时间戳,帮助系统自动化执行构建过程。

Makefile 的功能

  1. 定义规则和依赖关系:Makefile 中的规则定义了源文件、目标文件以及它们之间的依赖关系。通过规定文件之间的关系,Makefile 可以确保在需要时正确地重新生成文件。

  2. 自动化构建过程:Makefile 提供了一种自动化的方式来构建项目。开发者只需执行 make 命令,make 工具会根据 Makefile 中定义的规则和文件的状态来决定哪些文件需要重新生成。

  3. 增量编译:Makefile 可以识别文件的修改时间戳,仅重新编译发生变化的文件,而不是整个项目,从而提高了编译效率。

Makefile编译过程

时间戳不匹配
时间戳匹配
读取Makefile文件
分析依赖关系
比较时间戳
执行命令生成目标文件
更新目标文件
递归处理依赖关系
完成编译过程

Makefile 语法规则

一个基本的 Makefile 规则包含目标、依赖关系和命令。

target: dependencies
    commands

Makefile的核心规则,类似于一位厨神做菜,目标就是做好一道菜,那么所谓的依赖就是各种食材,各种厨具等等,然后需要厨师好的技术方法类似于命令,才能作出一道好菜。

目标(Target)与菜肴

在 Makefile 中,目标(Target)就像是要做的菜肴,它是整个规则的核心。每个目标都对应着一个生成的文件或者一个执行的动作。比如,一个程序的可执行文件、一个库文件,甚至是一个操作,如清理文件的操作。

依赖关系(Dependencies)与食材和厨具

依赖关系定义了目标生成所需要的原材料和工具,就像厨师需要各种食材和厨具来做菜一样。如果某个食材或厨具发生了变化,厨师需要重新考虑如何使用它们来制作菜肴。

在 Makefile 中,依赖关系通常是源文件、头文件、配置文件等。当依赖项发生变化时,Make 工具会根据依赖关系重新生成目标文件。

命令(Commands)与烹饪方法

命令是完成目标生成所需的具体步骤和操作,就像烹饪方法一样。厨师根据食谱和技能来执行一系列步骤,从而制作出美味的菜肴。

在 Makefile 中,命令是以 Tab 键开头的操作步骤。这些命令可以是编译源文件、链接目标文件、拷贝文件或者执行其他需要的操作,以完成目标的生成。

示例 Makefile

下面是一个简单的示例 Makefile,用于编译一个包含两个源文件的 C 语言程序。

# 定义目标为可执行文件
my_program: main.o utils.o
    gcc -o my_program main.o utils.o

# 定义 main.o 的依赖项为 main.c 和 utils.h
main.o: main.c utils.h
    gcc -c main.c

# 定义 utils.o 的依赖项为 utils.c 和 utils.h
utils.o: utils.c utils.h
    gcc -c utils.c

# 清理生成的文件
clean:
    rm -f my_program main.o utils.o

在这个示例中:

  • my_program 是目标文件,依赖于 main.outils.o
  • main.outils.o 分别依赖于对应的 .c 文件和 utils.h 头文件。
  • gcc -o my_program main.o utils.o 是生成 my_program 文件的命令。
  • gcc -c main.cgcc -c utils.c 是生成 .o 文件的命令。
  • clean 是一个伪目标,用于清理生成的文件。

Makefile变量

Makefile 中的变量是一种非常有用的功能,它允许您在 Makefile 中定义和引用各种值,从而使您的规则更加灵活和易于维护。变量可以包含文件名、编译器选项、目标名称等内容(相当于c语言里的宏),使得在多个地方使用相同值的情况下,可以通过更改变量的值来轻松地调整整个 Makefile 的行为。

以下是关于 Makefile 变量的一些重要概念和用法:

定义变量

在 Makefile 中,可以使用以下语法来定义变量:

VARIABLE_NAME = value

或者使用 := 符号来定义变量,这种方式可以立即展开变量值,而不是延迟到使用时才展开:

VARIABLE_NAME := value

引用变量

要在 Makefile 中引用变量,可以使用 $()${} 语法:

SOURCES = main.c utils.c
OBJECTS = $(SOURCES:.c=.o)

预定义变量

预定义变量是 Makefile 中的特殊变量,它们在 Makefile 中无需显式定义,而是由 Make 工具提供的一些默认值。这些预定义变量通常用于指定编译器、编译选项、目标文件名等常用的设置,以便于编写通用和可移植的 Makefile。

Makefile 中有一些预定义的特殊变量,例如:

  • CC:C 编译器的名称。
  • CFLAGS:C 编译器的选项。
  • LDLIBS:链接器的选项。
  • RM:用于删除文件的命令。

自定义变量

除了预定义的变量之外,您还可以定义自己的变量来存储项目特定的值,例如文件名、目录名称、编译器选项等。

# C 编译器
CC = gcc

# C 编译器选项
CFLAGS = -Wall -O2

# 目标文件列表
OBJS = $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(SRCS))

使用变量

变量可以在 Makefile 中的任何地方使用,包括目标、依赖项和命令:

my_program: $(OBJECTS)
    $(CC) $(CFLAGS) -o my_program $(OBJECTS)

自动化变量

$^$@$< 是 Makefile 中常用的自动化变量:

  • $^:表示所有的依赖文件,即目标所依赖的所有文件列表。
  • $@:表示生成的目标文件的名称。
  • $<:代表第一个依赖文件,即目标的第一个依赖项。

在 Makefile 中,这些自动化变量通常用于规则的命令部分,方便地引用相关的文件名和目标名称,使得 Makefile 更加简洁和易于维护。

下面是一个示例,演示了如何在 Makefile 中使用这些自动化变量:

# 定义目标为可执行文件
my_program: main.o utils.o
    gcc -o $@ $^

# 定义 main.o 的依赖项为 main.c 和 utils.h
main.o: main.c utils.h
    gcc -c $< -o $@

# 定义 utils.o 的依赖项为 utils.c 和 utils.h
utils.o: utils.c utils.h
    gcc -c $< -o $@

# 清理生成的文件
clean:
    rm -f my_program main.o utils.o

在这个示例中,$@ 用于表示生成的目标文件,$^ 用于表示所有的依赖文件,$< 用于表示第一个依赖文件。通过使用这些自动化变量,可以使 Makefile 更具可读性和可维护性,同时减少重复的代码量。

示例

以下是一个简单的示例,演示了如何在 Makefile 中使用变量:

# 定义编译器和选项
CC = gcc
CFLAGS = -Wall -O2

# 定义源文件和目标文件
SOURCES = main.c utils.c
OBJECTS = $(SOURCES:.c=.o)

# 默认目标
all: my_program

# 生成可执行文件
my_program: $(OBJECTS)
    $(CC) $(CFLAGS) -o my_program $(OBJECTS)

# 生成目标文件
%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@

# 清理生成的文件
clean:
    rm -f my_program $(OBJECTS)

在这个示例中,我们定义了编译器名称为 gcc,编译器选项为 -Wall -O2。然后定义了源文件列表和对应的目标文件列表。最后,通过变量来引用这些值,使得 Makefile 更加灵活和易于维护。

Makefile变量赋值

1. 使用 = 进行赋值

VIR_A = A
VIR_B = $(VIR_A) B
VIR_A = AA

这种方式是最普通的等号赋值方式。在 Makefile 中,如果使用 = 进行赋值,变量的值是整个 Makefile 中最后被指定的值。在上述示例中,VIR_B 的值会是 AA B,因为 VIR_A 在最后被赋值为 AA

2. 使用 := 进行赋值

VIR_A := A
VIR_B := $(VIR_A) B
VIR_A := AA

:= 表示直接赋值,赋予当前位置的值。它是在定义时就展开变量的值,而不会受后续赋值的影响。在上述示例中,VIR_B 的值会是 A B,因为 VIR_A 在使用 VIR_B 时的值是 A

3. 使用 ?= 进行赋值

VIR ?= new_value

?= 表示如果该变量没有被赋值,赋值为等号后面的值。如果变量已经被赋值,则保持原值不变。例如,如果 VIR 在之前没有被赋值,那么 VIR 的值就是 new_value

VIR := old_value
VIR ?= new_value

在这种情况下,VIR 的值就是 old_value,因为 VIR 已经在之前被赋值。

4. 使用 += 进行赋值

VIR += additional_value

+= 表示将符号后面的值添加到前面的变量上。这种方式通常用于追加值到已有变量的末尾。

Makefile函数

当使用Makefile时,wildcardpatsubst 函数是两个非常有用的函数,它们可以帮助您处理文件列表和文件名的模式匹配。

1. wildcard 函数

wildcard 函数用于展开通配符模式,返回匹配的文件列表。

SOURCES := $(wildcard src/*.c)

在上面的例子中,$(wildcard src/*.c) 将返回 src/ 目录下所有以 .c 结尾的文件列表。

2. patsubst 函数

patsubst 函数用于查找字符串中的单词,将符合指定模式的单词替换为另一个模式。

OBJECTS := $(patsubst src/%.c,obj/%.o,$(SOURCES))

在上面的例子中,$(patsubst src/%.c,obj/%.o,$(SOURCES)) 将把 $(SOURCES) 中所有以 src/ 开头、以 .c 结尾的文件名替换为以 obj/ 开头、以 .o 结尾的文件名。

Makefile伪目标 .PHONY

在 Makefile 中,.PHONY 是一个特殊的伪目标(phony target),用于指定某些目标不是真正的文件名,而是作为一个命令的别名或者表示一个动作。这样做的好处是,即使存在同名的文件,也不会影响 Makefile 的执行。

通常情况下,.PHONY 用于声明一些常用的目标,例如 cleanall 等,以避免与同名的文件产生冲突。

示例:

.PHONY: clean

clean:
    rm -f *.o

在这个示例中,.PHONY: clean 声明了 clean 是一个伪目标。这意味着,无论是否存在名为 clean 的文件,执行 make clean 都会执行 clean 目标下的命令,而不是寻找同名的文件。这样可以确保执行 clean 时,总是会执行相应的清理操作,而不会因为同名文件的存在而导致问题。

结语

Makefile 是一个强大的工具,可以帮助开发者管理和自动化编译项目中的源代码。通过定义好的规则,Makefile 可以根据文件之间的依赖关系自动执行编译过程,提高了开发效率。掌握 Makefile 的功能和基本语法对于进行软件开发和项目管理非常重要。

你可能感兴趣的:(Ubuntu,服务器,linux,ubuntu)