Makefile概述
在Linux中,Makefile
是使用make
工具进行项目管理和自动化编译的核心。Makefile 是一个特殊格式的文件。Makefile 定义了一组规则和依赖关系,指导 make
如何编译和链接程序
目的:
Makefile解决的是编译的问题。编译有什么问题呢?
例如,你有3个.c文件,分别是linked.c,hash.c,main.c,你要编译成一个app.executable,你会怎么做呢?
根据Linux程序员的惯例,凡是要一次次重新执行的命令,都应该写成脚本,变成“一个动作”。所以,把上面这个命令序列写成一个build.sh,每次编译你只要执行这个脚本问题就解决了。但这个脚本有问题,假设我修改了linked.c,但我没有修改hash.c和main.c,那么执行这个脚本是很浪费的,因为它会无条件也重新编译hash.c和main.c。如果你面对一个问题,不要尝试重新去定义这个问题,而是看它和原来的问题相比,多出来的问题是什么,尝试解决那个多出来的问题就好了。多出来的问题就是文件修改时间比较,这个就是Makefile要解决的基本问题了。我们定义一种新的“脚本语言”(只是不用bash来解释,而是用make来解释)
app.executable : hash.o main.o linked.o
gcc *.o -o app.executable
hash.o : hash.c
gcc -c hash.c -o
hash.olinked.o : linked.c
gcc -c linked.c -o linked.o
main.o : main.c
gcc -c main.c -o main.o
clear :
rm -rf *.o
Makefile的本质是一个文件,需要配合make命令进行自动化编译
Makefile文件的命名:Makefile
make是一个命令工具,用来解释makefile文件中的代码,从而实现自动化编译。编译使用的编译器本质上还是gcc。
Makefile
定义了项目的构建规则和依赖关系,使得编译过程可以自动化,确保源代码及其依赖项在修改后能够正确地重新编译。
基本组件
-
目标(Targets):
- 目标是 Makefile 中的构建对象,可以是可执行文件、库文件或任何其他文件。每个目标可以有一组依赖项和一个或多个构建规则。
-
依赖项(Dependencies):
- 依赖项是构建目标所需的文件或目标列表。当依赖项发生变化时,
make
将根据规则重新构建目标 -
规则(Rules):
- 规则是一系列指令,定义了如何从依赖项生成目标。规则可以包括编译器调用、文件复制操作等
核心概念:
1.. 模式规则(Pattern Rules)
模式规则是一种通用的构建规则,可以根据文件扩展名或其他模式自动应用到多个目标。
2.. 伪目标(Phony Targets)
当makefile目录下有一个和目标相同的文件时,例如cleanall文件。我们在执行make命令的时候会出现错误。伪目标就是用于解决此种错误而产生。伪目标只是一个标签
伪目标不对应实际的文件,而是用于触发一系列操作,如 clean
用于清理构建生成的文件。
3.. 变量(Variables)
变量用于存储可重用的值,如编译器路径、编译选项、源文件列表等。
4.. 条件语句(Conditional Statements)
Makefile 支持简单的条件语句,如 ifeq
、ifneq
、ifdef
和 ifndef
,允许根据不同的条件包含不同的构建规则。
5.. 函数(Functions)
make
提供了一系列内置函数,用于执行字符串处理、文件名生成、查找文件等操作。
- 1.patsubst(pattern, replacement, text)
- 说明:
<pattern>
:要匹配的模式。<replacement>
:用于替换匹配到的模式的字符串。<text>
:要进行模式替换的原始文本。- 示例:
-
OBJ = $(patsubst %.c, %.o, $(txt)) #功能:这个函数有三个参数,意思是取出txt中的源文件列表,然后将.c 替换为.o 最后赋值给OBJ变量
2.wildcard(pattern...)
- 说明:
<pattern>...
:一个或多个匹配模式,通常使用通配符*
和?
。*
匹配任意数量的字符,?
匹配单个字符。- 示例:
-
SRC = $(wildcard ./*.c) #功能: 匹配目录下所有.c 文件,并将其赋值给SRC变量
6. 递归
构建(Recursive Builds)
Makefile 可以调用子目录中的 Makefile,实现项目的递归构建。
7. 包含(Include)
使用 include
指令可以将一个 Makefile 的内容包含到另一个 Makefile 中,实现规则和变量的共享。
8. 清理和维护(Cleaning and Maintaining)
Makefile 通常包含清理规则,用于删除所有构建生成的文件,帮助维护项目目录的整洁。
9. 可移植性(Portability)
Makefile 旨在跨平台工作,但某些特定于平台的规则或变量可能需要额外的处理。
示例代码:
# 定义编译器和链接器
CC=gcc
LD=ld
# 声明伪目标
.PHONY: all clean
# 定义编译选项
CFLAGS=-Wall -g
LDFLAGS=
# 定义源文件变量
SOURCES=main.c foo.c bar.c
# 通过源文件生成对象文件列表
OBJECTS=$(patsubst %.c,%.o,$(SOURCES))
# 默认目标(伪目标)
all: my_program
# 模式规则:从 .c 文件生成 .o 文件
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# 目标:可执行文件,依赖于所有的 .o 文件
my_program: $(OBJECTS)
$(CC) $(LDFLAGS) -o $@ $^
# 伪目标:清理构建生成的文件
clean:
rm -f my_program $(OBJECTS)
# 条件语句示例
ifeq ($(CC),gcc)
CFLAGS += -std=c99
endif
# 包含其他 Makefile
include debug.mk
# 使用内置函数
SOURCE_NAMES=$(notdir $(SOURCES))
CFLAGS 变量
CFLAGS
用于存储编译器的选项,这些选项通常用于控制编译器的行为和生成代码的方式。- 在代码中,
CFLAGS
被设置为-Wall -g
,其中:-Wall
是一个编译器选项,用于打开几乎所有的警告信息,帮助开发者发现潜在的问题。-g
是一个编译器选项,用于生成调试信息。这使得在程序运行时可以使用调试器(如 gdb)进行调试。
LDFLAGS 变量
LDFLAGS
用于存储链接器的选项,这些选项用于控制链接器的行为和生成的可执行文件的属性。- 在代码中,
LDFLAGS
没有被设置任何值(为空字符串),这意味着链接器将使用默认的行为。如果需要,可以添加链接器选项,如指定库的搜索路径或设置生成的可执行文件的属性。
在 Makefile中处理库
在Makefile中处理库通常涉及几个关键步骤:编译源文件为库文件、指定库文件的路径以及链接时包含库
1. 编译源文件为库文件
库文件可以是静态库(.a)或动态库(.so)
2. 指定库文件的路径
在链接阶段,你需要告诉链接器库文件的位置。可以使用 -L
选项指定库的路径,并使用 -l
选项链接库
# 定义库的路径
LIB_PATH=-L./
# 定义要链接的库
LIBS=-lmylib
3. 创建库
a.静态库
# 定义编译器
CC=gcc
# 定义编译选项
CFLAGS=-Wall -g
# 定义源文件
SOURCES=main.c foo.c bar.c
# 定义库文件
LIB_NAME=mylib
STATIC_LIB=$(LIB_NAME).a
# 规则:创建静态库
$(STATIC_LIB): $(SOURCES)
$(AR) rcs $@ $^
# 伪目标:清理库文件和对象文件
clean:
rm -f $(STATIC_LIB) *.o
b.动态库
# 定义动态库文件
DYNAMIC_LIB=$(LIB_NAME).so
# 规则:创建动态库
$(DYNAMIC_LIB): $(SOURCES)
$(CC) $(CFLAGS) -shared -fPIC -o $@ $^
# 更新伪目标 clean
clean:
rm -f $(STATIC_LIB) $(DYNAMIC_LIB) *.o
-shared
和 -fPIC
(Position Independent Code)选项用于生成动态库
4. 链接动态库
链接动态库时,需要确保链接器能够找到库文件,并链接所需的符号。这通常通过设置 LD_LIBRARY_PATH
环境变量或使用 ldconfig
实现。
Makefile执行的主要原理和步骤
Makefile 的执行原理基于一组规则和依赖关系,这些规则和依赖关系定义了项目的构建过程
1. 解析 Makefile:
当运行make命令时,make工具首先解析当前目录下的 Makefile(或指定的 Makefile 文件)。
2. 确定默认目标:
如果没有指定目标,make 将使用 Makefile 中的第一个目标作为默认目标。
3. 检查依赖关系:
对于每个目标,make 检查其依赖文件的最后修改时间。如果依赖文件比目标文件新,或者目标文件不存在,则需要重新构建目标。
4. 读取规则:
make 查找与目标相关联的规则,这些规则定义了如何构建目标。
5. 执行命令:
根据规则中的命令,make逐行执行命令来构建目标。这些命令通常包括调用编译器、链接器或复制文件等。
6. 使用变量:
在执行过程中,make 替换 Makefile 中的变量为它们的值。
7. 处理函数和模式:
make处理 Makefile 中的函数调用和模式规则,以生成所需的文件名和路径。
8. 递归构建:
如果 Makefile 调用子目录中的 Makefile,make 将递归地进入子目录并执行相应的构建规则。
9. 处理伪目标:
对于伪目标(如 clean),make 执行与之关联的命令,但不会检查文件的依赖关系。
10. 使用内置规则:
如果没有为目标提供规则,make将尝试使用内置规则来构建目标,例如根据文件扩展名推断如何构建。
11. 处理条件语句:
make根据条件语句(如 ifeq、ifneq、ifdef、ifndef)包含或排除特定的构建规则
12.处理包含文件:
当遇到 include
指令时,make
将读取并解析指定的文件,将它们的内容作为 Makefile 的一部分。
13. 生成隐式规则:
make 可以生成隐式规则来处理常见的编译任务,如从 .c文件编译成 .o文件。
14. 处理文件的最后修改时间:
构建完成后,make更新目标文件的最后修改时间,以便在下次构建时正确地检查依赖关系。
15. 错误处理:
如果在执行命令时遇到错误,make将停止构建过程,并报告错误信息。
16. 返回状态码:
make 返回状态码以指示构建成功(0)或失败(非0)。
Makefile多文件管理
Makefile的多文件管理是指在构建系统中有效组织和编译多个源文件、头文件、资源文件等,以提高构建效率和可维护性
实际开发中我们不可能只有几个文件, 类似于上图多文件,面对多文件处理,我们会把文件分为各种各样的模块来进行管理,以便于我们处理文件,在工作中提高效率
Makefile在管理多文件项目时提供了多种功能和技巧,可以帮助我们更有效地组织和构建项目。
在主目录下建立多个子目录和主Makefile文件,子目录下存放相关文件和子Makefile文件
如上图:
主Makefile文件(主Makefile调用子Makefile)
通过Makefile实现对多文件管理
PHONY : cleanall
GCC = gcc
RMRF = rm -rf
SRC = ${shell pwd}
INCLUDE_DIR = -I $(SRC)/include/
MAIN_DIR = $(SRC)/main
FUNS_DIR = $(SRC)/funs
OBJ_DIR = $(SRC)/obj
BIN_DIR = $(SRC)/bin
SUB_DIR := funs main obj
APPEXE := app.exe
export GCC INCLUDE_DIR MAIN_DIR FUNS_DIR OBJ_DIR BIN_DIR SUB_DIR APPEXE
all : $(SUB_DIR)
@echo "success project"
$(SUB_DIR) : MK_BIN
make -C $@
MK_BIN :
mkdir $(BIN_DIR)
cleanall :
$(RMRF) $(OBJ_DIR)*.O $(BIN_DIR)
bin目录中存放最终生成的可执行文件
funs目录中存放相关.c文件(main.c除外)
funs目录下的子Makefile文件
SCR = $(wildcard ./*.c)
OBJ = $(patsubst %.c,%.o,$(SCR))
all : $(OBJ)
@echo "success funs"
$(OBJ):%.o:%.c
$(GCC) -c $(INCLUDE_DIR) $< -o $(OBJ_DIR)/$@
main目录中存放main.c文件
main目录下的子Makefile文件
SCR = $(wildcard ./*.c)
OBJ = $(patsubst %.c,%.o,$(SCR))
all : $(OBJ)
@echo "success funs"
$(OBJ):%.o:%.c
$(GCC) -c $(INCLUDE_DIR) $< -o $(OBJ_DIR)/$@
include目录中存放相关.h文件(无子Makefile文件)
obj目录中存放相关临时生成文件( .o文件等)
obj目录下的子Makefile文件
$(BIN_DIR)/$(APPEXE) : *.o
$(GCC) $(INCLUDE_DIR) $^ -o $@