32-Makefile 工程管理 与 make
文章目录
一、工程管理器 make
make 是一个工程管理器,它根据 makefile 文件中的指令来编译和链接程序。makefile 文件定义了如何编译和链接程序,以及如何生成目标文件和可执行文件。
当我们需要编译一个比较大的项目时,编译命令会变得越来越复杂,需要编译的文件越来越多。其次就是项目中并不是每一次编译都需要把所有文件都重新编译,比如没有被修改过的文件则不需要重新编译。工程管理器就帮助我们来优化这两个问题。
MakeFile就类似于make工程管理的工作的脚本。用来告诉工程管理器如何正确的编译我们的程序。
二、Makefile 和 Make 的详细讲解
1.1 Make 和 Makefile 简介
make
是一个用于自动化构建工程的工具,可以根据 Makefile 中定义的规则自动完成编译、链接等操作。Makefile
则是 make
工具使用的脚本文件,定义了目标(target)、依赖(dependency)和命令(command)的关系。
1.2 Makefile 基本语法
目标: 依赖
[TAB]命令
- 目标:要生成的文件或执行的任务。
- 依赖:目标文件生成所需的文件或其他目标。
- 命令:生成目标的具体操作,必须以TAB开头。
==注意=:
目标必须存在,依赖可以没有,命令前面必须是一个制表符"TAB"
Makefile 文件的命名一般是 Makefile没有后缀也没有前缀
1.3 Makefile 示例
假设我们有一个简单的 C 项目,包含以下文件:
main.c
foo.c
foo.h
我们希望将这些源文件编译为可执行文件 main
。
1.4 简单的 Makefile 示例
# 定义目标和依赖
main: main.o foo.o
# 使用 gcc 编译
gcc -o main main.o foo.o
# 定义 main.o 的规则
main.o: main.c foo.h
gcc -c main.c
# 定义 foo.o 的规则
foo.o: foo.c foo.h
gcc -c foo.c
# 清理生成的文件
clean:
rm -f main main.o foo.o
1.5 Makefile 中的变量
Makefile 中的变量可以使脚本更易读、更易维护。
在Makefile 中变量属于弱类型,在Makefile中变量就是一个名字(像是C语言中的宏),代表一个文本字符串(变量的值),在Makefile的目标、依赖、命令中引用一个变量的地方。
在 Makefile 中,变量的特征和使用方式有以下几点:
-
变量和函数的展开:
- 变量和函数的展开(除规则的命令行以外)是在
make
读取 Makefile 文件时进行的。这里的变量包括了使用=
定义和使用指示符define
定义的变量。
- 变量和函数的展开(除规则的命令行以外)是在
-
变量的用途:
- 变量可以用来代表文件名列表、编译选项列表、程序运行的选项参数列表、搜索源文件的目录列表、编译输出的目录列表以及所有我们能够想到的事物。
-
变量名的限制:
- 变量名不能包括
:
、#
、=
、前置空白和尾空白的任何字符串。尽管在 GNU make 中没有对变量的命名有其它的限制,但定义包含除字母、数字和下划线以外字符的变量是不推荐的。这是因为这些字符可能在以后的 make 版本中被赋予特殊含义,并且这种命名的变量对于一些 Shell 来说不能作为环境变量使用。
- 变量名不能包括
-
大小写敏感:
- 变量名是大小写敏感的。变量
foo
、Foo
和FOO
是三个不同的变量。传统上,Makefile 中的变量名是全大写的。推荐的做法是对内部定义的一般变量(例如:目标文件列表objects
)使用小写方式,而对一些参数列表(例如:编译选项CFLAGS
)采用大写方式。对一个工程来说,Makefile 中的变量命名应保持一致的风格,以避免混乱。
- 变量名是大小写敏感的。变量
-
自动化变量:
- 有一些变量名只包含一个或几个特殊的字符,被称为自动化变量。这些变量的值会“自动地”发生变化。例如:
<
、@
、?
、#
、*
、@D
、%F
、^D
等。
- 有一些变量名只包含一个或几个特殊的字符,被称为自动化变量。这些变量的值会“自动地”发生变化。例如:
-
变量引用:
- 变量的引用与 Shell 脚本类似,使用美元符号和圆括号。例如,有个变量叫
A
,那么对它的引用是$(A)
;有个自动化变量@
,对它的引用是$(@)
;有个系统变量CC
,对其引用的格式是$(CC)
。对于单字符变量,引用时的括号可以省略,写成$A
和$@
。
- 变量的引用与 Shell 脚本类似,使用美元符号和圆括号。例如,有个变量叫
定义变量的方法如下:
CC = gcc
CFLAGS = -I.
main: main.o foo.o
$(CC) -o main main.o foo.o
main.o: main.c foo.h
$(CC) $(CFLAGS) -c main.c
foo.o: foo.c foo.h
$(CC) $(CFLAGS) -c foo.c
clean:
rm -f main main.o foo.o
系统预定义变量
1.6 自动化变量
自动化变量使得 Makefile 更加简洁、灵活。例如:
$@
表示目标文件$<
表示第一个依赖文件$^
表示所有依赖文件
使用自动化变量的示例:
CC = gcc
CFLAGS = -I.
main: main.o foo.o
$(CC) -o $@ $^
main.o: main.c foo.h
$(CC) $(CFLAGS) -c $<
foo.o: foo.c foo.h
$(CC) $(CFLAGS) -c $<
clean:
rm -f main main.o foo.o
1.7 通用规则
Makefile 支持通用规则,可以避免为每个文件单独定义规则。例如:
CC = gcc
CFLAGS = -I.
SRC = $(wildcard *.c)
OBJ = $(SRC:.c=.o)
main: $(OBJ)
$(CC) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c $<
clean:
rm -f main $(OBJ)
在这个例子中:
$(wildcard *.c)
用于获取当前目录下所有.c
文件。$(SRC:.c=.o)
用于将.c
文件扩展名替换为.o
。- 通用规则
%.o: %.c
用于定义如何从.c
文件生成.o
文件。
1.8 伪目标
伪目标(Phony Target)不会生成文件,只是执行一组命令。例如 clean
目标。使用 .PHONY
声明伪目标:
.PHONY: clean
clean:
rm -f main $(OBJ)
伪目标 clean
用于清理生成的文件,它不会生成实际文件,只是执行清理命令。
1.9 依赖管理
Make 会根据依赖关系和文件的==时间戳来判断是否需要重新编译目标。如果依赖文件比目标文件更新,Make 会重新执行规则。
1.10 递归定义和条件定义
Makefile 中支持递归定义和条件定义变量:
-
递归定义:变量可以引用尚未定义的其他变量。
A = apple B = $(A) tree
在这个例子中,
B
的值是apple tree
,即使A
是在B
之后定义的。 -
条件定义:仅当变量尚未定义时赋值。
A ?= I love China
只有当
A
未被定义时,它才会被赋值为I love China
。
1.11 函数
Makefile 支持使用函数,例如替换字符串和获取文件列表:
-
$(subst FROM,TO,TEXT)
:将字符串 TEXT 中的字符 FROM 替换为 TO。A = $(subst pp,PP,apple)
在这个例子中,
A
的值是aPPle
。 -
$(wildcard PATTERN)
:获取匹配模式为 PATTERN 的文件名。SRC = $(wildcard src/*.c)
在这个例子中,
SRC
包含src
目录下所有.c
文件的列表。
示例 Makefile
结合以上所有内容,下面是一个示例 Makefile:
CC = gcc
CFLAGS = -I./inc
SRC = $(wildcard src/*.c)
OBJ = $(SRC:.c=.o)
TARGET = bin/main
all: $(TARGET)
$(TARGET): $(OBJ)
$(CC) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
.PHONY: clean
clean:
rm -f $(TARGET) $(OBJ)
在这个例子中:
CC
和CFLAGS
定义了编译器及其选项。SRC
和OBJ
使用了函数来获取源文件和目标文件列表。all
是默认目标,依赖于$(TARGET)
。$(TARGET)
依赖于$(OBJ)
,并使用$(CC)
编译生成可执行文件。%.o: %.c
是通用规则,用于从.c
文件生成.o
文件。clean
是一个伪目标,用于清理生成的文件。
总结
Makefile 是管理大型项目编译过程的强大工具。通过定义目标、依赖和命令,以及使用变量、自动化变量、通用规则和伪目标,可以大大简化编译过程,提高工作效率。