Makefile入门
# 一、Makefile简介
官网:https://www.gnu.org/software/make/manual
# 1、Makefile是什么
Makefile是一种用于自动化构建程序的工具,它提供了一系列规则来指定源代码文件之间的依赖关系,以及如何生成目标文件。通过使用Makefile,程序员可以有效地管理和组织软件项目的编译过程,从而提高开发效率。
Makefile文件是一个文本文件,其中包含一系列规则和指令,用于编译源代码并生成可执行文件或库。每个规则由一个目标文件、一个或多个依赖文件和一组命令组成,这些命令描述了如何从依赖文件生成目标文件。
Makefile的主要作用是简化或组织编译代码的过程,它可以帮助程序员自动化编译、链接和打包程序。通过将整个项目分解为多个模块,并定义每个模块之间的依赖关系,当某个模块发生变化时,只需要重新编译该模块及其依赖的其他模块即可。这有助于减少手动操作和错误,并提高代码质量。
此外,Makefile还支持变量定义和隐晦规则等特性,这些特性可以帮助程序员更加灵活地编写Makefile,并使构建过程更加易于维护和扩展。
# 2、make 和 Makefile的关系
Make是一个命令工具,用于解释和执行Makefile中的指令,完成项目的自动化构建。Makefile是一个文件,其中定义了一系列的规则来指定哪些文件需要先编译、哪些文件需要后编译、哪些文件需要重新编译等。
当我们在命令行中输入make命令时,Make会查找当前目录下是否存在名为Makefile或makefile的文件。如果找到,Make会按照Makefile文件中的规则和指令,自动执行相应的命令来编译和链接源代码文件,生成可执行文件或库。
# 二、Makefile 三要素
Makefile的三个要素是目标、依赖和命令。它们在Makefile中的格式如下:
目标(Target):目标是指需要生成的文件或目标体,可以是Object File(一般称为中间文件)、可执行文件或标签。目标定义了生成的目标体,并指明生成目标体需要哪些依赖文件。
依赖(Dependency):依赖是指生成目标体所需的文件或另一个目标。它可以是一个或多个文件,也可以没有。依赖项描述了目标文件与源文件之间的依赖关系,告诉Make如何从源文件生成目标文件。
命令(Command):命令是Make需要执行的命令行指令,可以是任意的shell命令。这些命令描述了如何从依赖文件生成目标文件。在Makefile中,命令部分需要有一定的缩进,可以是一行或多行,它们会依次执行。
以下是Makefile三要素的格式示例:
目标: 依赖
命令
2
其中,目标和依赖之间用冒号(:)分隔,命令部分需要缩进。
示例:
# vim Makefile
targeta:targetb targetc
echo "targeta"
targetb:
echo "targetb"
targetc:
echo "targetc"
2
3
4
5
6
7
8
9
10
执行make命令
# make
echo "targetb"
targetb
echo "targetc"
targetc
echo "targeta"
targeta
2
3
4
5
6
7
8
指定执行命令
# make targetb
echo "targetb"
targetb
# touch targetb
# make targetb # 当前目录下targetb是最新文件不需要改变
make: `targetb' is up to date.
2
3
4
5
6
7
8
修改Makefile文件
# vim Makefile
.PHONY:targetb
targeta:targetb targetc
echo "targeta"
targetb:
echo "targetb"
targetc:
echo "targetc"
# make targetb
echo "targetb"
targetb
2
3
4
5
6
7
8
9
10
11
12
13
14
.PHONY:targetb的意思是声明``targetb是一个伪目标,即使存在名为targetb的文件。这意味着,每次当你运行make targetb时,make不会尝试查找一个叫做targetb的文件并尝试运行它的命令,而是会执行与targetb` 关联的命令。
在这个例子中,如果你运行 make targetb,它会输出 "targetb"。如果你运行 make targeta,它会首先运行 targetb 和 targetc 的命令,然后执行与 targeta 关联的命令,输出 "targeta"。
# 三、引入Makefile管理项目
生成两个文件
# vim mp3.c
#include<stdio.h>
void play()
{
printf("paly music!\r\n");
}
void stop()
{
printf("stop music!\r\n");
}
# vim main.c
#include<stdio.h>
int main()
{
play();
stop();
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
编写Makefile
# vim Makefile
mq3:main.c mp3.c
gcc main.c mp3.c -o mp3
.PHONE:clean
clean:
rm mp3
# make
# ls
main.c Makefile mp3 mp3.c
# ./mp3
paly music!
stop music!
2
3
4
5
6
7
8
9
10
11
12
13
14
15
改造Makefile,目的是将mp3.c和main.c解耦,当修改mp3.c或者main.c时,不需要重新编译另一个文件
# vim Makefile
mq3:main.o mp3.o
gcc main.o mp3.o -o mp3
main.o:
gcc -c main.c -o main.o
mp3.o:
gcc -c mp3.c -o mp3.o
.PHONE:clean
clean:
rm mp3
# make clean
rm mp3
# make
# ./mp3
paly music!
stop music!
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 四、Makefile的变量和模式匹配
# 1、系统变量
# vim Makefile
.PHONY:all
all:
echo "${CC}"
echo "${AS}"
echo "${MAKE}"
echo "${PATH}"
# make
echo "cc"
cc
echo "as"
as
echo "make"
make
echo "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 2、自定义变量
1、延迟赋值
在Makefile中,变量的默认赋值方式是“延迟”的。这意味着变量只有在被引用时才会被计算,并且只计算一次。如果一个变量在多个地方被引用,那么只有第一次引用时会被计算,后续的引用会使用第一次计算的结果。
# vim Makefile
A=123
B=$(A)
A=456
.PHONY:all
all:
echo "${B}"
# make
echo "456"
456
2
3
4
5
6
7
8
9
10
11
12
13
2、立即赋值
为了改变默认的延迟赋值行为,我们可以使用:=操作符进行“立即”赋值。这意味着变量会在声明时立即被计算,并且在之后的所有引用中都会使用这个计算结果。即使变量的值在后续发生了变化,之前的引用也不会受到影响。
# vim Makefile
A=123
B:=$(A)
A=456
.PHONY:all
all:
echo "${B}"
# make
echo "123"
123
2
3
4
5
6
7
8
9
10
11
12
13
3、空赋值
使用=操作符可以将变量设置为空字符串。这在需要临时清除变量的内容时很有用。
# vim Makefile
A?=123
#B:=$(A)
A?=456
.PHONY:all
all:
echo "${A}"
# make
echo "123"
123
2
3
4
5
6
7
8
9
10
11
12
13
4、追加赋值
如果想要将一个值追加到变量的末尾,可以使用+=操作符。这不会覆盖变量的现有内容,而是将新值添加到变量的末尾。
# vim Makefile
A?=123
#B:=$(A)
A+=456
.PHONY:all
all:
echo "${A}"
# make
echo "123 456"
123 456
2
3
4
5
6
7
8
9
10
11
12
13
# 3、自动化变量
1、$<
代表第一个依赖项。在规则的命令部分,$<将被替换为第一个依赖项的文件名。
# vim Makefile
all:targeta targetb
echo "$<"
targeta:
targetb:
# make
echo "targeta"
targeta
2
3
4
5
6
7
8
9
10
11
2、$^
代表所有的依赖项。在规则的命令部分,$^将被替换为一个空格分隔的列表,包含了所有依赖项的文件名
# vim Makefile
all:targeta targetb
echo "$^"
targeta:
targetb:
# make
echo "targeta targetb"
targeta targetb
2
3
4
5
6
7
8
9
10
11
3、$@
代表目标文件。在规则的命令部分,$@将被替换为目标文件的名字。
# vim Makefile
all:targeta targetb
echo "$@"
targeta:
targetb:
# make
echo "all"
all
2
3
4
5
6
7
8
9
10
11
# 4、变量使用
# vim Makefile
CC=gcc
TARGET=mp3
OBJS=main.o mp3.o
$(TARGET):$(OBJS)
$(CC) $^ -o $@
main.o:main.c
$(CC) -c main.c -o main.o
mp3.o:mp3.c
$(CC) -c mp3.c -o mp3.o
.PHONY:clean
clean:
rm mp3
# make
gcc main.o mp3.o -o mp3
# ./mp3
paly music!
stop music!
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 5、模式匹配
%:匹配任意多个非空字符
# vim Makefile
%:
echo "$@"
# make test # make后跟任意字符
echo "test"
test
2
3
4
5
6
7
优化Makefile
# vim Makefile
CC=gcc
TARGET=mp3
OBJS=main.o mp3.o
$(TARGET):$(OBJS)
$(CC) $^ -o $@
# 因为.o文件默认使用.c文件来进行编译,所以可以注释以下两行
#%.o:%.c
# ${CC} -c $< -o $@
.PHONY:clean
clean:
rm mp3
# make
gcc main.o mp3.o -o mp3
# ./mp3
paly music!
stop music!
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 五、Makefile的条件分支
# vim Makefile
ARCH ?= x86
ifeq ($(ARCH),x86)
CC=gcc
else
CC=arm-linux-gnueabihf-gcc
endif
TARGET=mp3
OBJS=main.o mp3.o
$(TARGET):$(OBJS)
$(CC) $^ -o $@
.PHONY:clean
clean:
rm mp3 *.o
# make
gcc -c -o mp3.o mp3.c
gcc main.o mp3.o -o mp3
# make ARCH=arm
arm-linux-gnueabihf-gcc -c -o mp3.o mp3.c
arm-linux-gnueabihf-gcc main.o mp3.o -o mp3
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 六、Makefile管理Docker
# 1、创建一个Dockerfile
FROM nginx
RUN echo '<h1>Hello World!</h1>' > /usr/share/nginx/html/index.html
2
3
# 2、创建Makefile文件
# 定义 Docker 镜像名称和标签
REGISTRY := your-private-registry
USERNAME := your-username
PASSWORD := your-password
TAG := latest
IMAGE := my-web
# 这是一个条件赋值。如果变量 CONTAINER_NAME 还没有被赋值,那么它会被赋值为 my-container
CONTAINER_NAME ?= my-container
# 构建镜像
build: docker-build
docker build -t $(IMAGE):$(TAG) .
# 上传镜像到私有仓库
upload: docker-push
docker login -u $(USERNAME) -p $(PASSWORD) $(REGISTRY)
docker push $(IMAGE):$(TAG)
# 运行镜像
run: docker-run
docker run -p 80:80 $(CONTAINER_NAME) $(IMAGE):$(TAG)
# 停止并删除容器
clean: docker-stop docker-rm
docker stop $(CONTAINER_NAME)
docker rm $(CONTAINER_NAME)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 3、执行命令
# make build
# make upload
# make run
# make clean
2
3
4
5