背景

最近在项目测试时,由于需要在不同场景下切换,导致有多个 docker-compose.yml 存在,而每次启动/停止都得用 -f 指定目标文件,这样操作太过繁琐,就想利用 Makefile 简化操作,实现 make aaa start,实现 aaa 的 启动,make bbb stop 实现 bbb 的停止这样的效果

实现

调研了下,可以通过 Makefile 中的模块处理实现,具体如下

1
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
28
29
30
31
32
33
34
35
36
37
# 定义一个变量来存储模块名称
MODULE := $(firstword $(MAKECMDGOALS))
# 动态创建目标
$(eval $(MODULE):;@:)

# 通用规则来处理模块和目标
define MODULE_RULE
$(1)-start:
@echo "Executing $(1) start"
ifeq ($(1),aaa)
docker compose -f docker-compose-aaa.yml up -d
else ifeq ($(1),bbb)
docker compose -f docker-compose-bbb.yml up -d
else ifeq ($(1),ccc)
docker compose -f docker-compose-ccc.yml up -d
endif

$(1)-stop:
@echo "Executing $(1) stop"
ifeq ($(1),aaa)
docker compose -f docker-compose-aaa.yml down
else ifeq ($(1),bbb)
docker compose -f docker-compose-bbb.yml down
else ifeq ($(1),ccc)
docker compose -f docker-compose-ccc.yml down
endif
endef

# 定义模块列表
MODULES := aaa bbb ccc

# 为每个模块生成规则
$(foreach module,$(MODULES),$(eval $(call MODULE_RULE,$(module))))

# 使用模式规则来捕获模块名称和目标
%: $(MODULE)-%
@$(MAKE) $@

在这个 Makefile 中:

  • MODULE 变量存储了模块名称
    • $(MAKECMDGOALS):这是一个内置变量,它包含了在命令行中传递给 make 的目标(targets)。例如,如果在命令行中执行 make clean all,那么 $(MAKECMDGOALS) 的值就是 clean all
    • $(firstword ...):这是一个函数,用于从给定的参数中提取第一个单词。它会返回传入参数中的第一个字符串
  • $(eval $(MODULE):;@:)
    • 使用 eval 动态创建一个名为 MODULE 值的目标(例如 aaa),并且该目标没有实际的命令。这样做是为了在 Makefile 中确保该目标存在,但不执行任何操作
    • ; 表示命令结束,但这里没有实际命令要执行,因此后面没有跟随任何有效命令
    • @ 表示在执行该命令时不打印命令本身
    • : 是一个伪命令,表示什么也不做
  • MODULE_RULE
    • 定义了一个通用规则模板,用于生成每个模块的目标
    • $(1) 是模板中的占位符,它会在后续调用中被具体的模块名替换
    • 使用 ifeq 条件语句来根据模块的不同,执行相应的 Docker Compose 命令
  • MODULES 列表包含所有模块名称
  • $(foreach module,$(MODULES),$(eval $(call MODULE_RULE,$(module))))
    • foreach 函数会遍历 MODULES 中的每个模块名,调用 MODULE_RULE 模板并生成相应的规则
    • 结果是为每个模块生成 aaa-startaaa-stopbbb-startbbb-stopccc-startccc-stop 等规则
  • %: $(MODULE)-%
    • 这是一个模式规则,表示任何目标(%)都会依赖于相应的 MODULE 名称和后缀(例如,aaa-startaaa-stop
    • 当调用 make aaa-start 时,Make 会寻找 aaa-start 目标,并执行它
  • @$(MAKE) $@
    • @:表示在执行该命令时不打印命令本身。这对于保持输出整洁很有用。
    • $(MAKE):这是一个特殊变量,表示当前的 Make 命令。使用 $(MAKE) 可以确保在调用 Make 时使用相同的参数和环境。
    • $@:这是一个自动变量,表示规则中的目标名。在这个上下文中,$@ 将被替换为当前目标的名称

这样,当你运行 make aaa start、make bbb start、make ccc start 等命令时,它们会执行相应的 Docker Compose 命令

注意:Makefile 中需要使用 tab,而非 空格 作为缩进,否则执行时,会报错类似 Makefile:28: *** missing separator. Stopvim 可以用 :set list 命令显示空格和制表符,制表符会显示为 ^I,如果不是制表符,可以使用 :%s/^\s\+/\t/g 进行替换