build: add compose shortcut targets and drop commit-msg lint hook

Generate <op>-<stack>[-<registry>] compose shortcuts and test/coverage
targets in the Makefile, remove the install-hooks target and the
.githooks commit-msg linter, and document the design.
This commit is contained in:
pnoker
2026-06-18 18:42:20 +08:00
parent a230ffd9fe
commit 35d985e2f3
4 changed files with 187 additions and 188 deletions
-21
View File
@@ -1,21 +0,0 @@
#!/bin/sh
#
# Copyright 2016-present the IoT DC3 original author or authors.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
dc3/bin/commit_msg_lint.py "$1"
+52 -12
View File
@@ -18,9 +18,9 @@
SHELL := /bin/bash
.DEFAULT_GOAL := help
.PHONY: help env init-env clean package \
.PHONY: help env init-env clean package test test-it test-e2e coverage deploy \
build up stop down ps logs config pull restart refresh reset \
dev-db dev-optional run changelog openapi install-hooks tag
run changelog openapi tag
ENV_FILE ?= $(firstword $(wildcard .env) .env.example)
RUNTIME_ENV_FILE ?= dc3/env/dev.env
@@ -63,7 +63,9 @@ RUN_MODULE := $(RUN_MODULE_$(RUN_SERVICE))
MVN_SETTINGS ?= .mvn/settings.xml
MVN_SETTINGS_ARG := $(if $(strip $(MVN_SETTINGS)),-s $(MVN_SETTINGS),)
MVN_SUB_SETTINGS_ARG := $(if $(strip $(MVN_SETTINGS)),-s ../$(MVN_SETTINGS),)
MVN := mvn $(MVN_SETTINGS_ARG)
MVN_SUB := mvn $(MVN_SUB_SETTINGS_ARG)
CHANGE_FILE ?= dc3/doc/CHANGE.md
FROM ?=
@@ -101,8 +103,11 @@ help:
@printf '%s\n' ''
@printf '%s\n' 'Common:'
@printf ' %-24s %s\n' 'make package' 'Build all Maven modules'
@printf ' %-24s %s\n' 'make test' 'Run unit tests'
@printf ' %-24s %s\n' 'make test-it' 'Run integration-test phase'
@printf ' %-24s %s\n' 'make test-e2e' 'Run E2E harness'
@printf ' %-24s %s\n' 'make coverage' 'Generate aggregated JaCoCo coverage'
@printf ' %-24s %s\n' 'make run SERVICE=auth' 'Run one Spring Boot service with env auto-loaded'
@printf ' %-24s %s\n' 'make install-hooks' 'Enable .githooks commit-msg lint'
@printf '%s\n' ''
@printf '%s\n' 'Compose:'
@printf ' %-24s %s\n' 'make build' 'Build STACK images, optional SERVICES="data gateway" or GROUP=core'
@@ -113,6 +118,12 @@ help:
@printf ' %-24s %s\n' 'make logs' 'Follow STACK logs'
@printf ' %-24s %s\n' 'make config' 'Render compose configuration'
@printf '%s\n' ''
@printf '%s\n' 'Shortcuts (auto-generated, no env vars needed):'
@printf ' %-32s %s\n' 'make <op>-<stack>[-<registry>]' 'e.g. make up-db-cn, make logs-dev, make down-app'
@printf ' %-32s %s\n' ' op' 'up down stop ps logs build pull restart refresh config reset'
@printf ' %-32s %s\n' ' stack' 'dev app db optional'
@printf ' %-32s %s\n' ' registry' 'cn global (only up/pull/build/refresh)'
@printf '%s\n' ''
@printf '%s\n' 'Variables:'
@printf ' %-24s %s\n' 'STACK=dev|app|db|optional' 'Compose stack selector'
@printf ' %-24s %s\n' 'REGISTRY=auto|global|cn' 'Image registry selector; auto uses .env/DC3_IMAGE_REGISTRY'
@@ -123,7 +134,8 @@ help:
@printf '%s\n' ''
@printf '%s\n' 'Examples:'
@printf ' %s\n' 'make init-env'
@printf ' %s\n' 'make dev-db'
@printf ' %s\n' 'make up-db'
@printf ' %s\n' 'make up-db-cn'
@printf ' %s\n' 'make up SERVICES="agentic"'
@printf ' %s\n' 'make up SERVICES="gateway agentic"'
@printf ' %s\n' 'make up GROUP=core'
@@ -152,6 +164,17 @@ clean:
package:
$(MVN) clean package
test:
$(MVN) -B -Dmaven.test.skip=false test
test-it:
$(MVN) -B -Dmaven.test.skip=false -Dskip.unit.tests=true verify
test-e2e:
DC3_E2E=true $(MVN) -B -Dmaven.test.skip=false -Dskip.unit.tests=true -pl dc3-e2e -am -Pe2e verify
coverage:
$(MVN) -B -Dmaven.test.skip=false -pl dc3-coverage -am verify
build:
$(call dc3_compose) build $(SELECTED_SERVICES)
@@ -192,11 +215,32 @@ reset:
run:
$(MVN) -pl "$(RUN_MODULE)" -am spring-boot:run
dev-db:
@$(MAKE) up STACK=db SERVICES= GROUP= REGISTRY=$(REGISTRY) COMPOSE='$(COMPOSE)' COMPOSE_DIR='$(COMPOSE_DIR)' $(MAKE_COMPOSE_OVERRIDE)
# Auto-generated compose shortcuts: <op>-<stack>[-<registry>]
# op : up down stop ps logs build pull restart refresh config reset
# stack : dev app db optional
# registry : cn global (only up/pull/build/refresh)
# Each shortcut recurses into the base op with STACK/REGISTRY set, so all
# existing logic (compose-file resolution, reset confirmation, SERVICES/GROUP
# filtering) is reused. Examples: make up-db-cn make logs-dev make down-app
COMPOSE_OPS := up down stop ps logs build pull restart refresh config reset
COMPOSE_REGISTRY_OPS := up pull build refresh
COMPOSE_STACKS := dev app db optional
COMPOSE_REGISTRIES := cn global
dev-optional:
@$(MAKE) up STACK=optional SERVICES= GROUP= REGISTRY=$(REGISTRY) COMPOSE='$(COMPOSE)' COMPOSE_DIR='$(COMPOSE_DIR)' $(MAKE_COMPOSE_OVERRIDE)
define dc3_stack_target
.PHONY: $(1)-$(2)
$(1)-$(2):
@$$(MAKE) $(1) STACK=$(2) SERVICES='$$(SERVICES)' GROUP='$$(GROUP)' REGISTRY='$$(REGISTRY)' COMPOSE='$$(COMPOSE)' COMPOSE_DIR='$$(COMPOSE_DIR)' $$(MAKE_COMPOSE_OVERRIDE)
endef
define dc3_stack_registry_target
.PHONY: $(1)-$(2)-$(3)
$(1)-$(2)-$(3):
@$$(MAKE) $(1) STACK=$(2) SERVICES='$$(SERVICES)' GROUP='$$(GROUP)' REGISTRY=$(3) COMPOSE='$$(COMPOSE)' COMPOSE_DIR='$$(COMPOSE_DIR)' $$(MAKE_COMPOSE_OVERRIDE)
endef
$(foreach op,$(COMPOSE_OPS),$(foreach st,$(COMPOSE_STACKS),$(eval $(call dc3_stack_target,$(op),$(st)))))
$(foreach op,$(COMPOSE_REGISTRY_OPS),$(foreach st,$(COMPOSE_STACKS),$(foreach rg,$(COMPOSE_REGISTRIES),$(eval $(call dc3_stack_registry_target,$(op),$(st),$(rg))))))
deploy: package
cd dc3-api \
@@ -204,10 +248,6 @@ deploy: package
&& cd ../dc3-common \
&& $(MVN_SUB) clean deploy -P deploy
install-hooks:
git config core.hooksPath .githooks
@printf '%s\n' 'Enabled .githooks (commit-msg lint active for git commit)'
tag:
dc3/bin/tag.sh
-155
View File
@@ -1,155 +0,0 @@
#!/usr/bin/env python3
#
# Copyright 2016-present the IoT DC3 original author or authors.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
from __future__ import annotations
import argparse
import re
import sys
from pathlib import Path
ALLOWED_TYPES = {
"feat",
"fix",
"perf",
"refactor",
"docs",
"build",
"ci",
"test",
"chore",
"style",
"security",
"revert",
}
WEAK_DESCRIPTIONS = {
".",
"add",
"add comment",
"change",
"change code",
"changes",
"fix",
"fix bug",
"misc",
"optimize",
"update",
"update code",
"wip",
}
SUBJECT_RE = re.compile(
r"^(?P<type>[a-z]+)"
r"(?:\((?P<scope>[a-z0-9][a-z0-9._/-]*(?:,[a-z0-9][a-z0-9._/-]*)*)\))?"
r"(?P<breaking>!)?: (?P<description>.+)$"
)
CJK_RE = re.compile(r"[\u4e00-\u9fff]")
FIXUP_RE = re.compile(r"^(fixup|squash)! +", re.IGNORECASE)
GENERATED_CHANGELOG_SUBJECT = "docs(release): update generated changelog"
MAX_SUBJECT_LENGTH = 100
MIN_DESCRIPTION_LENGTH = 10
def read_subject(message_file: Path) -> tuple[str, str]:
text = message_file.read_text(encoding="utf-8")
lines = [line.rstrip() for line in text.splitlines()]
content_lines = [line for line in lines if line.strip() and not line.lstrip().startswith("#")]
subject = content_lines[0] if content_lines else ""
body = "\n".join(content_lines[1:])
while FIXUP_RE.match(subject):
subject = FIXUP_RE.sub("", subject, count=1)
return subject, body
def is_git_generated_subject(subject: str) -> bool:
return subject.startswith("Merge ") or subject.startswith("Revert ")
def normalized_description(description: str) -> str:
return re.sub(r"[\s.。!?_-]+", " ", description.strip().lower()).strip()
def validate_subject(subject: str, body: str) -> list[str]:
errors: list[str] = []
if not subject:
return ["Commit subject is required."]
if is_git_generated_subject(subject):
return []
if subject == GENERATED_CHANGELOG_SUBJECT:
return []
if len(subject) > MAX_SUBJECT_LENGTH:
errors.append(f"Commit subject must be at most {MAX_SUBJECT_LENGTH} characters.")
if CJK_RE.search(subject):
errors.append("Commit subject must be written in English.")
match = SUBJECT_RE.match(subject)
if not match:
errors.append("Commit subject must match '<type>(optional-scope): <english summary>'.")
return errors
commit_type = match.group("type")
description = match.group("description").strip()
breaking = bool(match.group("breaking"))
if commit_type not in ALLOWED_TYPES:
allowed = ", ".join(sorted(ALLOWED_TYPES))
errors.append(f"Unsupported commit type '{commit_type}'. Allowed types: {allowed}.")
if len(description) < MIN_DESCRIPTION_LENGTH:
errors.append(f"Commit description must be at least {MIN_DESCRIPTION_LENGTH} characters.")
if normalized_description(description) in WEAK_DESCRIPTIONS:
errors.append("Commit description is too vague for generated release notes.")
if description[0].islower() is False and not description[0].isdigit():
errors.append("Commit description should start with lowercase imperative text.")
if breaking and "BREAKING CHANGE:" not in body:
errors.append("Breaking-change commits using '!' must include a 'BREAKING CHANGE:' body entry.")
return errors
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Validate IoT DC3 commit messages.")
parser.add_argument("message_file", type=Path)
return parser.parse_args()
def main() -> int:
args = parse_args()
subject, body = read_subject(args.message_file)
errors = validate_subject(subject, body)
if not errors:
return 0
print("Invalid commit message:", file=sys.stderr)
print(f" {subject or '<empty>'}", file=sys.stderr)
for error in errors:
print(f"- {error}", file=sys.stderr)
print("", file=sys.stderr)
print("Examples:", file=sys.stderr)
print(" feat(agentic): add session cleanup policy", file=sys.stderr)
print(" fix(manager): validate tenant scope for device queries", file=sys.stderr)
print(" docs(release): update generated changelog", file=sys.stderr)
return 1
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,135 @@
# Makefile compose 命令快捷体系
> 日期:2026-06-18 范围:`iot-dc3/Makefile` + 相关文档
## 背景与动机
`iot-dc3/Makefile` 的 compose 类操作受三个维度影响:
| 维度 | 取值 | 作用 |
|------|------|------|
| `STACK` | `dev` / `app` / `db` / `optional` | 选哪个 compose 文件 |
| `REGISTRY` | `auto` / `global` / `cn` | 镜像从哪个仓库拉(global=Docker Hubcn=阿里云) |
| `SERVICES` / `GROUP` | 服务过滤 | 操作哪些服务 |
但目前只有 `dev-db``dev-optional` 两个快捷入口,且都写死、不带 registry 选择。要切换 cn/global 只能 `make dev-db REGISTRY=cn` 手动传环境变量,体验差。
目标:为**所有** compose 操作建立统一的、命令名里就能表达 `stack`+`registry` 的快捷体系,无需手动传环境变量。
> 注:GNU make 的 target 名里冒号是语法保留字,`dev:db:cn` 无法作为 target,故采用连字符命名 `dev-db-cn` 形式。
## 命名约定
```
<op>-<stack>[-<registry>] 连字符分隔
op : up down stop ps logs build pull restart refresh config reset
stack : dev app db optional
registry : cn global ← 仅 up/pull/build/refresh(拉/建镜像的 op)可加
```
- 不带 registry 后缀 → 沿用现状 `REGISTRY=auto`(读 `.env`)。
- 生成数量:`11 op × 4 stack` = 44,加 `4 拉镜像 op × 4 stack × 2 registry` = 32**共 76 个**,全部用 `eval`/`foreach` 自动生成,零手写。
示例:
```bash
make up-db-cn # = up STACK=db REGISTRY=cn
make up-app-global # = up STACK=app REGISTRY=global
make logs-dev # = logs STACK=dev
make down-optional # = down STACK=optional
make pull-app-cn # = pull STACK=app REGISTRY=cn
make restart-db # = restart STACK=db
make config-db # = config STACK=db
make up-db-cn SERVICES="postgres" # 仍可叠加服务过滤
```
## 实现:递归复用,零逻辑重复
生成的 target 不重新实现逻辑,而是递归 `$(MAKE) <op> STACK=… REGISTRY=…` 调用已有基础规则(参数化现有 `dev-db` 的转发写法)。
```makefile
# <op>-<stack> → <op> STACK=<stack>REGISTRY 沿用当前值
define gen_stack_target
.PHONY: $(1)-$(2)
$(1)-$(2):
@$$(MAKE) $(1) STACK=$(2) SERVICES='$$(SERVICES)' GROUP='$$(GROUP)' REGISTRY='$$(REGISTRY)' COMPOSE='$$(COMPOSE)' COMPOSE_DIR='$$(COMPOSE_DIR)' $$(MAKE_COMPOSE_OVERRIDE)
endef
# <op>-<stack>-<registry> → <op> STACK=<stack> REGISTRY=<registry>
define gen_stack_registry_target
.PHONY: $(1)-$(2)-$(3)
$(1)-$(2)-$(3):
@$$(MAKE) $(1) STACK=$(2) SERVICES='$$(SERVICES)' GROUP='$$(GROUP)' REGISTRY=$(3) COMPOSE='$$(COMPOSE)' COMPOSE_DIR='$$(COMPOSE_DIR)' $$(MAKE_COMPOSE_OVERRIDE)
endef
OPS := up down stop ps logs build pull restart refresh config reset
REGISTRY_OPS := up pull build refresh
STACKS := dev app db optional
REGISTRIES := cn global
$(foreach op,$(OPS),$(foreach st,$(STACKS),$(eval $(call gen_stack_target,$(op),$(st)))))
$(foreach op,$(REGISTRY_OPS),$(foreach st,$(STACKS),$(foreach rg,$(REGISTRIES),$(eval $(call gen_stack_registry_target,$(op),$(st),$(rg))))))
```
直接收益:
- `COMPOSE_FILE` 解析、`SELECTED_SERVICES``dc3_compose` 全自动复用。
- `reset-db` 等**自动继承** `reset` 规则里的 `CONFIRM_RESET_VOLUMES` 保护。
- `SERVICES`/`GROUP` 仍可叠加,默认空=全部服务。
- 加新 stack / op 只需改一个列表变量。
## 删除旧名
- 移除 `dev-db` / `dev-optional` 两个 target 规则。
-`.PHONY` 行移除 `dev-db dev-optional`(生成的快捷名各自在模板内声明 `.PHONY`)。
## help 重写
新增"Compose shortcuts"一节,说明命名规则 + 取值表 + 例子;`Examples` 区把 `make dev-db` 改为 `make up-db` 等。形如:
```
Compose shortcuts (auto-generated):
make <op>-<stack>[-<registry>]
op : up down stop ps logs build pull restart refresh config reset
stack : dev app db optional
registry : cn global (only up/pull/build/refresh)
e.g. make up-db-cn make logs-dev make pull-app-global make down-optional
```
## 文档同步(13 文件)
把已删除的 `make dev-db` / `make dev-optional` 替换为 `make up-db` / `make up-optional`
- `github/AGENTS.md`
- `github/CLAUDE.md`
- `iot-dc3/.claude/skills/dc3-stack-test/SKILL.md`
- `iot-dc3/AGENTS.md`
- `iot-dc3/CONTRIBUTING.md`
- `iot-dc3/dc3/doc/USAGE.md`
- `iot-dc3/docs/guide/troubleshooting.md`
- `iot-dc3/docs/quickstart/environment.md`
- `iot-dc3/docs/quickstart/index.md`
- `iot-dc3/README.ja.md`
- `iot-dc3/README.md`
- `iot-dc3/README.vi.md`
- `iot-dc3/README.zh.md`
规则:
- **必须**替换被删的 `dev-db` / `dev-optional`(否则文档示例会报 "No rule to make target")。
- `make up STACK=db` / `REGISTRY=cn` 这类写法**仍有效**(基础 op 保留),不强制替换;仅在主 README / AGENTS 的"快速开始"处主推新快捷命令。
- `.env.example``REGISTRY=cn` 注释保留(机制不变)。
## 非目标(YAGNI
- 不做跨 stack 聚合命令(一条命令同时起 db+optional+dev)。
- 不引入交互式选择。
- `REGISTRY=auto``.env` 的现有机制不变。
## 验证
- `make -n up-db-cn` → 展开含 `STACK=db REGISTRY=cn`
- `make -n logs-dev` → 展开含 `STACK=dev`,且不含 `REGISTRY=cn/global`(无 registry 后缀)。
- `make config-db` 实跑能渲染 db compose 配置。
- `make reset-db`(无 `CONFIRM_RESET_VOLUMES=true`)被拒绝。
- `grep -rn "make dev-db\|make dev-optional" <13 文件>` 无残留。