在软件开发中,自动化构建是一个至关重要的环节,它能够提高开发效率、减少人为错误并确保代码的质量。而 Makefile 作为一个强大的构建工具,在这一过程中扮演着重要的角色。
网上关于 Makefile 的教程大多数都是相当全面和详尽的,有时可能会涉及到一些高级的用法和复杂的示例。这些教程通常旨在帮助读者全面了解 Makefile 的功能和用法,从而能够应对各种复杂的项目需求。
然而,对于初学者来说,Makefile 的语法和功能可能会显得复杂和晦涩,有时甚至让人望而却步,因此,对于初学者来说,一个简单、易于理解的 Makefile 入门教程可能更为适合。所以我在自己学习Makefile之后 ,通过对知识点的总结记下了这篇笔记,希望同样可以作为Makefile 入门教程帮助初学者快速掌握 Makefile 的基本语法和常用功能,为日后深入学习和应用打下良好的基础。
Makefile 是软件开发中的重要工具,它可以自动化执行编译、链接和其他构建任务。本文将介绍 Makefile 的功能、基本语法以及一个简单的示例,帮助读者更好地理解和使用这一工具。
Makefile 是一个文本文件,其中包含了一系列规则和命令,用于告诉 make 工具如何编译和构建软件项目。它基于文件的依赖关系和修改时间戳,帮助系统自动化执行构建过程。
定义规则和依赖关系:Makefile 中的规则定义了源文件、目标文件以及它们之间的依赖关系。通过规定文件之间的关系,Makefile 可以确保在需要时正确地重新生成文件。
自动化构建过程:Makefile 提供了一种自动化的方式来构建项目。开发者只需执行 make
命令,make 工具会根据 Makefile 中定义的规则和文件的状态来决定哪些文件需要重新生成。
增量编译:Makefile 可以识别文件的修改时间戳,仅重新编译发生变化的文件,而不是整个项目,从而提高了编译效率。
一个基本的 Makefile 规则包含目标、依赖关系和命令。
target: dependencies
commands
Makefile的核心规则,类似于一位厨神做菜,目标就是做好一道菜,那么所谓的依赖就是各种食材,各种厨具等等,然后需要厨师好的技术方法类似于命令,才能作出一道好菜。
在 Makefile 中,目标(Target)就像是要做的菜肴,它是整个规则的核心。每个目标都对应着一个生成的文件或者一个执行的动作。比如,一个程序的可执行文件、一个库文件,甚至是一个操作,如清理文件的操作。
依赖关系定义了目标生成所需要的原材料和工具,就像厨师需要各种食材和厨具来做菜一样。如果某个食材或厨具发生了变化,厨师需要重新考虑如何使用它们来制作菜肴。
在 Makefile 中,依赖关系通常是源文件、头文件、配置文件等。当依赖项发生变化时,Make 工具会根据依赖关系重新生成目标文件。
命令是完成目标生成所需的具体步骤和操作,就像烹饪方法一样。厨师根据食谱和技能来执行一系列步骤,从而制作出美味的菜肴。
在 Makefile 中,命令是以 Tab 键开头的操作步骤。这些命令可以是编译源文件、链接目标文件、拷贝文件或者执行其他需要的操作,以完成目标的生成。
下面是一个简单的示例 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.o
和 utils.o
。main.o
和 utils.o
分别依赖于对应的 .c
文件和 utils.h
头文件。gcc -o my_program main.o utils.o
是生成 my_program
文件的命令。gcc -c main.c
和 gcc -c utils.c
是生成 .o
文件的命令。clean
是一个伪目标,用于清理生成的文件。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 更加灵活和易于维护。
=
进行赋值VIR_A = A
VIR_B = $(VIR_A) B
VIR_A = AA
这种方式是最普通的等号赋值方式。在 Makefile 中,如果使用 =
进行赋值,变量的值是整个 Makefile 中最后被指定的值。在上述示例中,VIR_B
的值会是 AA B
,因为 VIR_A
在最后被赋值为 AA
。
:=
进行赋值VIR_A := A
VIR_B := $(VIR_A) B
VIR_A := AA
:=
表示直接赋值,赋予当前位置的值。它是在定义时就展开变量的值,而不会受后续赋值的影响。在上述示例中,VIR_B
的值会是 A B
,因为 VIR_A
在使用 VIR_B
时的值是 A
。
?=
进行赋值VIR ?= new_value
?=
表示如果该变量没有被赋值,赋值为等号后面的值。如果变量已经被赋值,则保持原值不变。例如,如果 VIR
在之前没有被赋值,那么 VIR
的值就是 new_value
。
VIR := old_value
VIR ?= new_value
在这种情况下,VIR
的值就是 old_value
,因为 VIR
已经在之前被赋值。
+=
进行赋值VIR += additional_value
+=
表示将符号后面的值添加到前面的变量上。这种方式通常用于追加值到已有变量的末尾。
当使用Makefile时,wildcard
和 patsubst
函数是两个非常有用的函数,它们可以帮助您处理文件列表和文件名的模式匹配。
wildcard
函数用于展开通配符模式,返回匹配的文件列表。
SOURCES := $(wildcard src/*.c)
在上面的例子中,$(wildcard src/*.c)
将返回 src/
目录下所有以 .c
结尾的文件列表。
patsubst
函数用于查找字符串中的单词,将符合指定模式的单词替换为另一个模式。
OBJECTS := $(patsubst src/%.c,obj/%.o,$(SOURCES))
在上面的例子中,$(patsubst src/%.c,obj/%.o,$(SOURCES))
将把 $(SOURCES)
中所有以 src/
开头、以 .c
结尾的文件名替换为以 obj/
开头、以 .o
结尾的文件名。
在 Makefile 中,.PHONY
是一个特殊的伪目标(phony target),用于指定某些目标不是真正的文件名,而是作为一个命令的别名或者表示一个动作。这样做的好处是,即使存在同名的文件,也不会影响 Makefile 的执行。
通常情况下,.PHONY
用于声明一些常用的目标,例如 clean
、all
等,以避免与同名的文件产生冲突。
示例:
.PHONY: clean
clean:
rm -f *.o
在这个示例中,.PHONY: clean
声明了 clean
是一个伪目标。这意味着,无论是否存在名为 clean
的文件,执行 make clean
都会执行 clean
目标下的命令,而不是寻找同名的文件。这样可以确保执行 clean
时,总是会执行相应的清理操作,而不会因为同名文件的存在而导致问题。
Makefile 是一个强大的工具,可以帮助开发者管理和自动化编译项目中的源代码。通过定义好的规则,Makefile 可以根据文件之间的依赖关系自动执行编译过程,提高了开发效率。掌握 Makefile 的功能和基本语法对于进行软件开发和项目管理非常重要。