人们通常利用 make 工具来自动完成编译工作。这些工作包括:如果仅修改了某几个源文件,则只重新编译这几个源文件;如果某个头文件被修改了,则重新编译所有包含该头文件的源文件。利用这种自动编译可大大简化开发工作,避免不必要的重新编译。
make 工具通过一个称为 makefile 的文件来完成并自动维护编译工作。makefile 需要按照某种语法进行编写,其中说明了如何编译各个源文件并连接生成可执行文件,并定义了源文件之间的依赖关系。当修改了其中某个源文件时,如果其他源文件依赖于该文件,则也要重新编译所有依赖该文件的源文件。
1、make 会在当前目录下找名字叫"Makefile"或"makefile";
2、如果找到,它会找文件中的第一个目标文件(target),并把这个文件作为最终的目标文件;
3、如果 main 文件不存在或是 main 所依赖的后面的 .o 文件的文件修改时间要比 main 这个文件新,那么他就会执行后面所定义的命令来生成 main 这个文件;
4、如果 main 所依赖的 .o 文件也存在,那么 make 会在当前文件中找目标为 .o 文件的依赖性,如果找到则再根据那一个规则生成 .o 文件;
5、当然,你的 C 文件和 H 文件是存在的啦,于是 make 会生成 .o 文件,然后再用 .o 文件 make 的终极任务,也就是执行文件 main 了。
注:如果 DEPENDENCIES 中有一个或多个文件更新的话,COMMAND 就要执行,这就是Makefile 最核心的内容。
TARGET ... : DEPENDENCIES ...
COMMAND
...
如果 DEPENDENCIES 中有一个或多个文件更新的话,COMMAND 就要执行,这就是 Makefile 最核心的内容。
main:第一目标文件,且是可执行目标(即最终的目标文件)
main:main.o add.o subtract.o
gcc main.o add.o subtract.o -o main
main.o:main.c add.h subtract.h
gcc -c main.c -o main.o
add.o:add.c add.h
gcc -c add.c -o add.o
subtract.o:subtract.c subtract.h
gcc -c subtract.c -o subtract.o
.PHONY:clean
clean:
rm -f main.o add.o subtract.o main
在 Makefile 中,伪目标是一种特殊的目标,它们不代表真正的文件依赖关系,而是用于执行一系列命令或其他操作。
以下是一些常见的伪目标的示例:
1、clean:用于清理生成的文件或目录。通常用于删除编译生成的目标文件、可执行文件或其他临时文件。
示例:
.PHONY: clean //使用前进行伪目标声明,以确保它们不会与文件名冲突(标准用法)
clean:
rm -rf target_dir/*.o
rm -f executable
执行:
make clean
2、all:用于构建项目的所有目标或执行一系列操作。通常用于编译整个项目或执行一组任务。
示例:
all: target1 target2 target3
target1:
# 命令和规则用于构建 target1
target2:
# 命令和规则用于构建 target2
target3:
# 命令和规则用于构建 target3
执行:
make all
3、install:用于安装软件或将文件复制到指定位置。通常用于将生成的可执行文件、库文件或其他资源安装到系统目录或指定位置。
示例:
install: main
cp main /usr/local/bin/
其中 main 是可之心文件。
执行:
make install
4、test:用于运行测试套件或执行单元测试。通常用于自动化运行测试并生成测试报告。
示例:
test:
./run_tests.sh
执行:
make test
5、help:用于显示 Makefile 的帮助信息或目标列表。通常用于提供关于可用目标和其用途的简要说明。
示例:
help:
@echo "Available targets:"
@echo " target1 - Build target1"
@echo " target2 - Build target2"
@echo " clean - Clean up generated files"
执行:
make help
这些只是一些常见的伪目标示例,你可以根据项目的需求和特定的操作添加自定义的伪目标。在 Makefile 中,伪目标以 .PHONY 声明,以确保它们不会与文件名冲突。例如:
.PHONY: clean all install test help
通过使用伪目标,你可以更好地组织和管理 Makefile 中的任务和操作。
选项名 |
作用 |
$@ |
规则的目标文件名 |
$ |
规则的第一个依赖文件名 |
$^ |
规则的所有依赖文件列表 |
示例:
main:main.o add.o subtract.o
gcc $^ subtract.o -o $@
main.o:main.c add.h subtract.h
gcc -c $< -o $@
add.o:add.c add.h
gcc -c $< -o $@
subtract.o:subtract.c subtract.h
gcc -c $< -o $@
.PHONY:clean
clean:
rm -f main main.o add.o subtract.o
示例:
object=main.o add.o subtract.o 为自定义变量,变量在使用时需要 $()
object=main.o add.o subtract.o
main:$(object)
gcc $^ subtract.o -o $@
main.o:main.c add.h subtract.h
gcc -c $< -o $@
add.o:add.c add.h
gcc -c $< -o $@
subtract.o:subtract.c subtract.h
gcc -c $< -o $@
.PHONY:clean
clean:
rm -f $(object) main
GNU 的 make 很强大,它可以自动推导文件以及文件依赖关系后面的命令,于是我们就没必要去在每一个 [.o] 文件后都写上类似的命令,因为,我们的 make 会自动识别,并自己推导命令。
只要 make 看到一个 [.o] 文件,它就会自动的把 [.c] 文件加在依赖关系中,如果 make 找到一个 whatever.o,那么whatever.c,就会是 whatever.o 的依赖文件。并且 gcc -c whatever.c 也会被推导出来。
示例:
去掉 gcc 命令
object=main.o add.o subtract.o
main:$(object)
gcc $^ subtract.o -o $@
main.o:add.h subtract.h
add.o:add.h
subtract.o:subtract.h
.PHONY:clean
clean:
rm -f $(object) main
进一步简化:
去掉 .o 中间文件
object=main.o add.o subtract.o
main:$(object)
gcc $^ subtract.o -o $@
$(object):
.PHONY:clean
clean:
rm -f $(object) main
进一步简化:
ELF=main 为自定义变量,变量在使用时需要 $()
ELF=main
object=main.o add.o subtract.o
$(ELF):$(object)
gcc $^ subtract.o -o $@
$(object):
.PHONY:clean
clean:
rm -f $(object) $(ELF)
一旦新增加源文件就要修改 objects=main.o add.o subtract.o
问:有没一劳永逸的方法?不需要修改 makefile 就能适应呢?
答:借助 makefile 中的相关函数。
1、wildcard 函数:当前目录下匹配模式的文件
例如:src=$(wildcard *.c)
2、notdir 函数:去除路径
例如:$(notdir $src)
3、patsubst 函数:模式匹配替换
例如:$(patsubst%.c,%.o,$src)
等价于:$(src:.c=.o)
4、shell 函数:执行 shell 命令
例如:$(shell ls -d */)
ls -d */ 命令是获取当前目录下的文件夹,示例如下:
[root@vm10-0-0-236 make_test]# ls -d */
add/ subtract/
进一步简化:
使用 makefile 常见函数化简
ELF=main
src=$(wildcard *.c)
object=$(src:.c=.o)
$(ELF):$(object)
echo $(src) #打印变量结果
echo $(object) #打印变量结果
gcc $^ subtract.o -o $@
$(object):
.PHONY:clean
clean:
rm -f $(object) $(ELF)
使用 shell 命令
使用 shell 命令 find 寻找 .c 文件
ELF=main
src=$(shell find *.c)
object=$(src:.c=.o)
$(ELF):$(object)
gcc $^ subtract.o -o $@
$(object):
.PHONY:clean
clean:
rm -f $(object) $(ELF)
使用 shell 命令
使用 shell 命令 ls 寻找 .c 文件
ELF=main
src=$(shell ls *.c)
object=$(src:.c=.o)
$(ELF):$(object)
gcc $^ subtract.o -o $@
$(object):
.PHONY:clean
clean:
rm -f $(object) $(ELF)
如果源文件都在同一级目录下,使用上述 makefile 没有任何问题。
源文件在同一级目录下分布情况:
[root@vm10-0-0-236 make_test]# tree
.
├── add.c
├── add.h
├── main.c
├── makefile
├── subtract.c
└── subtract.h
但如果源文件分散在不同的目录下,上述的 makefile 便不再适用了。
源文件在不同目录下分布情况:
[root@vm10-0-0-236 make_test]# tree
.
├── add
│ ├── add.c
│ └── add.h
├── main.c
├── makefile
└── subtract
├── subtract.c
└── subtract.h
多级目录 makefile 示例:
ELF=main
src=$(shell find -name '*.c')
object=$(src:.c=.o)
$(ELF):$(object)
gcc $^ subtract.o -o $@
$(object):
.PHONY:clean
clean:
rm -f $(object) $(ELF)
如果有用到共享库,则可以在命令后面加 -llist
示例:
gcc $^ subtract.o -o $@ -llist
事实上 gcc $^ subtract.o -o $@ -llist 也可以省略,如下:
ELF=main
CC=gcc -g -llist
src=$(shell find -name '*.c')
object=$(src:.c=.o)
$(ELF):$(object)
$(object):
.PHONY:clean
clean:
rm -f $(object) $(ELF)
其中 CC=gcc -g -llist
在 Makefile 中,“:=” 和 “=” 是两种不同的赋值符号,它们在变量的展开和引用上有所不同。
(1)“:=” 赋值符号(简单赋值):
(2)“=” 赋值符号(递归赋值):
下面是一个示例,说明两者之间的区别:
# 使用 ":=" 进行赋值
VAR1 := $(shell echo "Hello, world!")
VAR2 := $(VAR1)
# 使用 "=" 进行赋值
VAR3 = $(shell echo "Hello, world!")
VAR4 = $(VAR3)
# 修改变量的值
VAR1 := $(shell echo "Goodbye!")
VAR3 = $(shell echo "Goodbye!")
# 输出变量的值
all:
@echo "VAR1: $(VAR1)" # 输出 "VAR1: Goodbye!"
@echo "VAR2: $(VAR2)" # 输出 "VAR2: Hello, world!"
@echo "VAR3: $(VAR3)" # 输出 "VAR3: Goodbye!"
@echo "VAR4: $(VAR4)" # 输出 "VAR4: Goodbye!"
在上述示例中,VAR1 和 VAR3 使用 “:=” 和 “=” 进行赋值,分别展开为 “Hello, world!” 和 “Goodbye!”。然后,VAR2 和 VAR4 分别引用 VAR1 和 VAR3 的值。
当修改 VAR1 和 VAR3 的值后,VAR1 的展开值由 “Hello, world!” 变为 “Goodbye!”,而 VAR3 的展开值仍然是 “Goodbye!”。由于 VAR2 使用的是 “:=” 赋值符号,它的展开值不会受到 VAR1 值的变化的影响,仍然是 “Hello, world!”。而 VAR4 使用的是 “=” 赋值符号,它的展开值会随着 VAR3 值的变化而更新为 “Goodbye!”。
如果需要本文 WORD、PDF 相关文档请在评论区留言!!!
如果需要本文 WORD、PDF 相关文档请在评论区留言!!!
如果需要本文 WORD、PDF 相关文档请在评论区留言!!!