文章目录
- 自动化构建-make/Makefile
- 一、`make` 工具概述
- 二、`Makefile` 基本结构
- 三、 `Makefile`和`make`的基本使用
- 3.1最基本的gcc编译:
- 3.2make执行Makefile文件
- 3.3`.PHONY`伪目标
- 四、Makefile拓展
- 4.1直接根据文件名编写Makefile
- 4.2变量的使用
- 4.3Makefile的适度扩展语法(更通用使用)
自动化构建-make/Makefile
一、make
工具概述
make
是一个自动化构建工具,用于自动化编译和构建程序。它根据文件的修改时间和依赖关系,只重新编译那些需要更新的文件,从而节省编译时间,提高开发效率。make
通过读取Makefile
文件中的规则和指令来确定如何编译和构建程序。
二、Makefile
基本结构
Makefile
是一个文本文件,通常位于项目的根目录,包含了一系列的规则,其基本结构如下:
目标: 依赖命令(依赖方法)
- 目标(Target):是
make
要生成的文件或要执行的操作,例如可执行文件、目标文件或自定义的伪目标(如clean
)。 - 依赖(Dependencies):是生成目标所需的文件或其他目标,例如源文件、头文件或其他库文件。
- 依赖关系:由目标文件和依赖文件组成的依赖关系
- 命令(Commands):也是依赖方法,是
make
执行的操作,通常是编译命令或其他构建操作。注意,命令行必须以制表符(Tab)开头,而不能是空格。
三、 Makefile
和make
的基本使用
3.1最基本的gcc编译:
test:test.cgcc test.c -o test
假设当前目录下有一个test.c文件,使用make进行自动化构建
1.在当前目录下执行
vim Makefile
新建并编辑Makefile文件2.在Makefile文件中执行:
test:test.c
//此处格式为”编译出来的可执行程序名“ :”依赖文件“
gcc test.c -o test
//依赖方法,此处格式为Tab + gcc编译命令,必须以制表符(Tab)开头3.wq保存并退出回到刚才的目录
4.执行
make
命令构建出可执行文件test5.执行
./make
命令运行可执行文件
3.2make执行Makefile文件
test:test.cgcc test.c -o test.PHONY:clean
clean:rm -f mytest
- make会自顶向下扫描Makefile我呢见,默认形成第一个目标文件,如果想指定形成,就是用make + 指定的目标名称,例如下面将第一部分和第二部分颠倒过来:
.PHONY:clean
clean:rm -f mytesttest:test.cgcc test.c -o test
然后执行make,会发现执行的是rm -f test1;
这时候如果想要执行test1,就需要指定执行:
3.3.PHONY
伪目标
概念
-
伪目标是
Makefile
中不代表实际文件的目标,而是代表一个操作或一系列操作。 -
通常,
make
会根据文件的时间戳来判断是否需要重新构建目标。但对于伪目标,make
不会检查文件是否存在,而是直接执行相应的命令。
例如下面的Makefile:
test1:test1.cgcc test1.c -o test1.PHONY:clean
clean:rm -f test1;
当执行
make
命令的时候,只有第一次能够被执行,而make clean
可以一直执行,因为Makefile
中使用.PHONY
修饰了clean
,执行make clean
的时候不会判断是否需要重构目标,而是直接执行相应命令。而对于编译,由于编译文件可能会消耗很多的资源,所以一般不使用PHONY修饰,避免资源的浪费
使用PHONY的好处
- 避免命名冲突
- 假设你有一个名为
clean
的文件和一个clean
伪目标。如果不使用.PHONY
,当clean
文件存在时,make clean
会认为clean
已经是最新的,不会执行clean
目标的命令。使用.PHONY
可以避免这种情况。 - 例如:
- 假设你有一个名为
.PHONY: cleanclean:rm -f main.o main
这里,即使存在名为
clean
的文件,make clean
仍会执行rm -f main.o main
命令。
- 明确表示操作:
- 对于一些操作,如
all
、install
、test
等,它们不代表生成文件,而是执行一系列操作,使用.PHONY
可以明确表示它们是操作而不是文件生成目标。
- 对于一些操作,如
多个伪目标
四、Makefile拓展
4.1直接根据文件名编写Makefile
-
假设你有一个简单的 C 项目,包含以下文件:
-
main.c
-
func.c
-
func.h
-
一个简单的 Makefile
可以如下:
# 最终的可执行文件
all: main# 生成可执行文件 main
main: main.o func.ogcc main.o func.o -o main# 编译 main.c 生成 main.o
main.o: main.c func.hgcc -c main.c -o main.o# 编译 func.c 生成 func.o
func.o: func.c func.hgcc -c func.c -o func.o# 清理生成的文件
clean:rm -f main.o func.o main
- 解释:
all: main
:all
是一个伪目标,它依赖于main
,通常作为默认目标,当你只输入make
时会执行该目标。main: main.o func.o
:main
目标依赖于main.o
和func.o
,当main.o
或func.o
发生变化或不存在时,make
将执行gcc main.o func.o -o main
来生成main
可执行文件。main.o: main.c func.h
:main.o
目标依赖于main.c
和func.h
,当main.c
或func.h
发生变化或main.o
不存在时,将执行gcc -c main.c -o main.o
来编译main.c
生成main.o
目标文件。func.o: func.c func.h
:与main.o
的情况类似,编译func.c
生成func.o
。clean
:这是一个伪目标,用于清理生成的文件,当你输入make clean
时,将执行rm -f main.o func.o main
命令。
4.2变量的使用
- 可以在
Makefile
中使用变量来避免重复输入,提高可维护性。例如:
# 定义最终的可执行文件名
BIN=test1
# 定义源文件名
SRC=test1.c
# 定义目标文件名
OBJ=test1.o
# 定义编译器为 gcc
cc=gcc
# 定义删除命令为 rm -f
RM=rm -f# 定义构建最终可执行文件的规则,依赖于目标文件
$(BIN):$(OBJ)# 使用 gcc 编译器将目标文件链接为可执行文件$(cc) $(OBJ) -o $(BIN)# 定义生成目标文件的规则,依赖于源文件
$(OBJ):$(SRC)# 使用 gcc 编译器将源文件编译为目标文件,-c 选项表示只编译不链接$(cc) -c $(SRC) -o $(OBJ)# 声明 clean 为伪目标
.PHONY:clean
clean:# 清理生成的可执行文件和目标文件$(RM) $(BIN) $(OBJ)
解释说明:
- 这个
Makefile
主要用于自动化构建一个 C 语言程序。BIN=test1
:将最终要生成的可执行文件的名称存储在BIN
变量中,这里命名为test1
。SRC=test1.c
:存储源文件的名称,这里是test1.c
。OBJ=test1.o
:存储目标文件的名称,这里是test1.o
。cc=gcc
:指定使用gcc
编译器。RM=rm -f
:指定删除文件的命令,使用rm -f
可以强制删除文件而不提示。$(BIN):$(OBJ)
:定义test1
可执行文件的构建规则,它依赖于test1.o
目标文件。这意味着如果test1.o
不存在或者比test1
新,将执行下面的编译命令。$(cc) $(OBJ) -o $(BIN)
:使用gcc
编译器将test1.o
目标文件链接为test1
可执行文件。$(OBJ):$(SRC)
:定义test1.o
目标文件的构建规则,它依赖于test1.c
源文件。如果test1.c
不存在或者比test1.o
新,将执行下面的编译命令。$(cc) -c $(SRC) -o $(OBJ)
:使用gcc
编译器将test1.c
编译为test1.o
目标文件,-c
选项表示只进行编译而不进行链接操作。.PHONY:clean
:声明clean
为伪目标,即它不代表实际的文件,而是代表一个操作。clean:
:定义clean
目标,当执行make clean
时,将执行下面的命令。$(RM) $(BIN) $(OBJ)
:使用rm -f
命令删除test1
可执行文件和test1.o
目标文件。
使用此 Makefile
时,你可以:
- 输入
make
来编译并生成test1
可执行文件。 - 输入
make clean
来删除test1
可执行文件和test1.o
目标文件。
请注意,使用此 Makefile
时要确保命令行以制表符(Tab)开头,而不是空格,因为 make
要求命令行必须以制表符作为缩进。并且确保 test1.c
文件存在于当前目录中,否则可能会导致编译错误。
4.3Makefile的适度扩展语法(更通用使用)
当有多个文件需要编译的时候,一个个输入很复杂,可以有下面的做法:
- 创建多个.c文件用作示范,
touch test{2..10}.c
,创建test2.c test3.c … test10.c - 打开Makefile进行编写
BIN=test1
SRC=$(shell ls *.c)OBJ=test.o
cc=gcc
RM=rm -f.PHONY:test
test:echo $(BIN)echo $(SRC)
#使用echo进行测试,更直观一点
关于
SRC=$(shell ls *.c)
- 将当前目录下所有以.c结尾的文件的名称存储在变量SRC中
- shell是make的一个内置函数,允许在Makefile中调用系统的shell命令,语法为$(shell ),其中,是要执行的shell命令
- *.c是一个通配符表达式,用于匹配所有文件名以.c结尾的文件。所以ls *.c会调用系统的ls命令列出当前目录下的.c源文件。
- 当 make 执行到 SRC=$(shell ls *.c) 时,它会调用 shell 函数,进而调用 ls *.c 命令,将该命令的执行结果存储在 SRC 变量中。例如,如果当前目录下有 file1.c、file2.c 和 file3.c,那么 SRC 的值将是 file1.c file2.c file3.c(多个文件名之间以空格分隔)。
执行make后会发现结果如下,多打印了一遍,这是由于echo回显导致的
在
Makefile
的上下文中,回显(Echo)是指将命令行的内容输出到标准输出(通常是终端)的现象。当make
执行一个命令时,它会默认将该命令的文本输出显示出来,然后再显示该命令执行的结果。这是make
的默认行为,主要目的是为了让你看到正在执行的命令,以便你可以跟踪构建过程和进行调试。这里的
echo test1
和echo test10.c test1.c test2.c test3.c test4.c test5.c test6.c test7.c test8.c test9.c
就是回显的内容,它们是make
在执行echo
命令之前将该命令本身输出到终端的结果。在
Makefile
中,在命令前面添加@
符号可以抑制该命令的回显。例如,将echo $(BIN)
改为@echo $(BIN)
,make
就不会将@echo $(BIN)
这一命令本身输出到终端,而只输出该命令的执行结果。这样可以使输出更简洁,只关注命令的结果,而不是命令的执行过程。
可在echo前加上@符号抑制该命令的回显
较为完整的基本的Makefile文件如下( $< $@ 等内容看下面模式规则部分):
BIN=test1
#SRC=$(shell ls *.c) 采用shell命令行方式,获取当前所有.c文件名
SRC=$(wildcard *.c) #或者使用wildcard函数,获取当前多有.c文件
#OBJ=test.o
OBJ=$(SRC:.c=.o) #将SRC的所有同名.c替换成为.o形成目标文件
cc=gcc
RM=rm -f$(BIN):$(OBJ)#gcc $(OBJ) -o $(BIN)@$(cc) $^ -o $@ #$^表示规则中的所有依赖项链表#$@表示规则中的目标,执行make时会被替换为当前正在构建的目标名称%.o:%.c #% 是通配符,表示匹配内容,例如%.c就是匹配.c结尾的文件 @$(cc) -c $< #< 的意思是将上面展开的.c文件逐个交给对应的命令,在这里是将.c文件处理成.o文件
.PHONY:clean
clean:@$(RM) $(OBJ) $(BIN)
模式规则
- 对于多个相似的编译规则,可以使用模式规则来简化
Makefile
。例如:
# 定义变量
CC = gcc
CFLAGS = -c
OBJS = main.o func.o# 最终的可执行文件
all: main# 生成可执行文件 main
main: $(OBJS)$(CC) $(OBJS) -o main# 编译.c 文件生成.o 文件的模式规则
%.o: %.c %.h$(CC) $(CFLAGS) $< -o $@# 清理生成的文件
clean:rm -f $(OBJS) main
- 解释:
%.o: %.c %.h
:这是一个模式规则,%
是通配符,表示任何匹配的文件。当make
需要生成.o
文件时,会根据该规则查找相应的.c
和.h
文件,并使用$(CC) $(CFLAGS) $< -o $@
命令进行编译。$<
:表示第一个依赖文件,即.c
文件。$@
:表示目标文件,即.o
文件。