Skip to content

Commit 84c27d4

Browse files
committed
chore: harden release tooling
1 parent 1bdf7b8 commit 84c27d4

12 files changed

Lines changed: 629 additions & 97 deletions

docker/Dockerfile.dev

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,15 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
1313
libmbedtls-dev \
1414
ninja-build \
1515
python3 \
16+
python3-pip \
1617
upx-ucl \
1718
valgrind \
1819
wget \
1920
&& rm -rf /var/lib/apt/lists/*
2021

22+
COPY docker/requirements.txt /tmp/pikapython-requirements.txt
23+
24+
RUN pip3 install --no-cache-dir -r /tmp/pikapython-requirements.txt \
25+
&& rm /tmp/pikapython-requirements.txt
26+
2127
WORKDIR /workspace

docker/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
gitpython
22
toml
3+
tomlkit
34
sh
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# R1.1 发布基线调查报告
2+
3+
## 结论
4+
5+
- 正式版本建议为 `v1.14.0`,先固定 RC commit 再发布。`origin/master` 相对 `v1.13.4` 已有 89 个提交,包含新 `threading` 包、Python 3 语义修复、runtime/VM 优化和 fatal 错误可见性改进,不适合作为 `v1.13.5`
6+
- 现有发布由 `version_config.py``version.py`/`version.sh``push-core.sh``release_auto.py``packages.toml`、人工 tag 和人工 GitHub Release 串联;Actions 只覆盖 master push/PR,不响应 tag,也不创建 Release。
7+
- `v1.13.4` 历史发布分成版本锁定、包索引更新、正式 release 三个提交,tag 为 lightweight tag;GitHub Release 存在,只有人工维护的说明和源码资产。
8+
9+
## 元数据与工具风险
10+
11+
- `src/PikaVersion.h``port/linux/version_config.py` 仍为 `1.13.4`,运行时编辑时间已漂移到 2025-06-02;CMSIS Pack 仍为 `1.13.0`
12+
- `release_helper.py` 把 patch 大于 9 自动进位到 minor,不符合 SemVer;`toml.dump` 会重排全表。
13+
- `release_diff.py` 通过 checkout 历史提交和 master 生成差异,会扰动当前工作区。
14+
- 没有统一检查 tag、运行时版本、版本配置、CMSIS Pack、包版本和包 commit 一致性的入口。
15+
16+
## 包范围
17+
18+
- `pikascript-core v1.14.0``PikaStdLib v1.14.0`
19+
- 新增 `threading v0.1.0`,当前不在 `packages.toml`
20+
- `requests v1.0.4``mqtt v0.1.2``time v0.2.3`,其源码均晚于当前索引 commit。
21+
- `_thread`、STM32 等没有当前索引 commit 之后的有效源码变化,不机械升版。
22+
23+
## 发布阻断项
24+
25+
- `#372` optimize_speed runtime 缓存语义受分配布局影响,属于正式版 blocker。
26+
- `#369` 合法现有子集误拒绝需要在完整 default 构建复验并收口。
27+
- `#371` 当前默认 CI 排除了 REPL 不稳定测试,需修复测试合同并恢复覆盖。
28+
- `#367``#368` 的修复提交看起来已进入 master,需要复验并关闭,避免 issue 状态漂移。
29+
- `#373` 不阻断 runtime 正确性,但在解决前不能对单个性能提交宣称精确收益。
30+
31+
## CI 证据
32+
33+
- 调查时最新 `1bdf7b8dc` Action 尚在 benchmark;上一条 `644d482cd` 完成并成功。
34+
- Pages benchmark 可读取,但当前只发布 20 轮中按 Performance Points 选出的单份结果,缺少每项离散度统计。
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# R1.2 发布版本与包索引工具报告
2+
3+
## 实现
4+
5+
- 使用 `tomlkit` 读取和写回 `packages.toml`,保留未修改包和原文件格式,避免历史发布中出现的全表重排。
6+
- 修复 patch 大于 9 时错误进位:`v1.2.9` 的下一个 patch 现在是 `v1.2.10`,minor/major 只在明确选择对应版本类型时变化。
7+
- 严格按三段 SemVer 选择最新发布,保留但忽略历史 `lts2021``v1.3.5.1` 等非标准条目。
8+
- 修正新代码使用的 `VersionType` 拼写,同时保留旧 `VersoinType` 别名,避免既有本地脚本中断。
9+
- 修复 `pikadev` 未安装已声明 Python 发布依赖的问题,加入 `python3-pip` 并安装 `docker/requirements.txt`;仅影响开发镜像,不进入目标固件。
10+
11+
## 验证
12+
13+
- 重建 `pikadev` 成功,镜像内 GitPython、toml、tomlkit、sh 均可导入。
14+
- `python3 -m unittest -v test_release_helper.py`:3/3 通过。
15+
- `py_compile` 覆盖 release_helper、release_auto、release_diff 和新增测试,通过。
16+
- 对真实 `packages.toml` 做无修改 round-trip,`cmp` 完全一致。
17+
- 在真实索引副本中把 time 从 `v0.2.2` 升到 `v0.2.3`,diff 只新增目标 release 行和必要尾逗号,其他包无变化。
18+
- `git diff --check` 通过。
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# R1.3 非破坏发布差异与一致性检查报告
2+
3+
## 实现
4+
5+
- `release_diff.py` 不再 checkout 基线和 master,改为通过 `git show <baseline>:packages.toml` 读取历史索引,并通过 GitPython 读取提交范围。
6+
- 默认输出单一 JSON,包含 baseline/head、包 create/update/delete 和提交摘要;可显式写 Markdown 报告,失败也稳定输出 JSON 错误并返回非零。
7+
- 新增 `release_check.py`,统一检查 `version_config.py`、运行时头文件、CMSIS Pack、core/PikaStdLib 包版本、各包最新 commit 可解析性、可选 tag/HEAD 和工作区清洁状态。
8+
- 不要求 clean 时脏工作区作为 warning;正式发布使用 `--require-clean` 升级为错误。传入 tag 时可使用 `--require-tag-at-head` 检查不可移动发布点。
9+
- `PackageReleaseList.from_text()` 支持从 Git 对象内容解析索引,无需临时 checkout 或临时工作树。
10+
11+
## 验证
12+
13+
- 发布工具单元测试合计 6/6 通过,覆盖三类版本源解析、历史 TOML 文本解析、Markdown 渲染、SemVer patch 和格式保持。
14+
- 所有发布脚本及测试 `py_compile` 通过。
15+
- 在真实仓库运行 `release_diff.py v1.13.4`:返回 89 个提交、1 个索引版本变化;运行前后 `git status --porcelain` 完全一致。
16+
- 在当前元数据运行 `release_check.py --expected-version 1.13.4`:明确以非零退出,唯一版本错误为 CMSIS `1.13.0 != 1.13.4`,当前任务修改作为 dirty warning 可见。
17+
- 首次 Docker heredoc 集成探针因未传 `-i` 无输出,不计为验证;补 `docker run -i` 后获得上述有效 JSON 结果。
18+
- `git diff --check` 通过。
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# PikaPython 发布工程
2+
3+
- 发布版本遵循 SemVer,版本元数据、包索引、Git tag 和 GitHub Release 必须相互一致。
4+
- Linux 核心验证默认使用仓库 `pikadev` Docker 镜像并显式启用 `PIKA_CONFIG_ENABLE`
5+
- runtime/VM 正确性和性能优先于 parser 性能;发布候选不得扩大语法范围或改变大架构。
6+
- LVGL、网络及其他非核心依赖可从核心门禁隔离,但被发布包必须完成独立构建或 smoke。
7+
- 性能结论必须来自同环境重复样本;RAM/Flash 以 Release 或目标固件等价构建核算总体净变化。
8+
9+
## R1 [in_progress]
10+
11+
按已调查的发布链路准备并发布 PikaPython v1.14.0:先建立 RC,修复或裁决语法/runtime/REPL 发布阻断项,改进现有发布工具与版本一致性检查,发布 core、PikaStdLib、threading、requests、mqtt、time 对应版本;在不改变大架构、资源总体不膨胀的前提下完成 Docker 多配置、Valgrind、benchmark、包/BSP smoke,最终创建不可移动的 tag、GitHub Release 并做全新安装验证,完成任务后将详细报告写入[任务报告](./details/pikapython-release-engineering/R1_Task_Report.md)
12+
13+
### R1.1 [completed]
14+
15+
冻结 origin/master 发布基线,核对 v1.13.4 至候选提交的版本源、包变更、GitHub Release、Actions 和开放 issue,形成 v1.14.0 范围及 blocker 裁决,完成任务后将详细报告写入[任务报告](./details/pikapython-release-engineering/R1.1_Task_Report.md)
16+
17+
### R1.2 [completed]
18+
19+
修复 release_helper.py 的非 SemVer 进位和 packages.toml 全表格式抖动,为版本解析、patch 递增和定点包索引更新补自动测试;依赖 R1.1,完成任务后将详细报告写入[任务报告](./details/pikapython-release-engineering/R1.2_Task_Report.md)
20+
21+
### R1.3 [completed]
22+
23+
将 release_diff.py 改为不 checkout 工作区的只读比较,并增加 release-check 统一校验运行时版本、version_config、CMSIS Pack、包索引 commit 和 tag 一致性;依赖 R1.2,完成任务后将详细报告写入[任务报告](./details/pikapython-release-engineering/R1.3_Task_Report.md)
24+
25+
### R1.4
26+
27+
复验并收口 Issue #367#368#369:已修复项补证据后关闭,仍复现的现有语法子集误拒绝作为 RC blocker 最小修复并回归;依赖 R1.1,完成任务后将详细报告写入[任务报告](./details/pikapython-release-engineering/R1.4_Task_Report.md)
28+
29+
### R1.5
30+
31+
修复 Issue #372 的 optimize_speed runtime 缓存受 GTest 注册和分配布局影响问题,补不依赖偶然布局的回归并保持资源总体不膨胀;依赖 R1.1,完成任务后将详细报告写入[任务报告](./details/pikapython-release-engineering/R1.5_Task_Report.md)
32+
33+
### R1.6
34+
35+
修复 Issue #371 的 REPL 日志固定索引测试不稳定,恢复默认 TEST 与 VALGRIND 覆盖并验证多配置合同;依赖 R1.1,完成任务后将详细报告写入[任务报告](./details/pikapython-release-engineering/R1.6_Task_Report.md)
36+
37+
### R1.7
38+
39+
改进 Issue #373 的 benchmark 导出,保留重复样本聚合统计并支持可信的同环境版本 A/B 判定,不增加目标固件资源;依赖 R1.1,完成任务后将详细报告写入[任务报告](./details/pikapython-release-engineering/R1.7_Task_Report.md)
40+
41+
### R1.8
42+
43+
在 blocker 关闭后同步 v1.14.0 版本元数据和内容提交,定点更新 pikascript-core v1.14.0、PikaStdLib v1.14.0、threading v0.1.0、requests v1.0.4、mqtt v0.1.2、time v0.2.3 包索引;依赖 R1.3-R1.7,完成任务后将详细报告写入[任务报告](./details/pikapython-release-engineering/R1.8_Task_Report.md)
44+
45+
### R1.9
46+
47+
生成 RC 候选,在 pikadev Docker 中完成 default、minimal、optimize_speed、pool、float、定向与必要全量测试、Valgrind、fatal watchdog、Release 资源和重复 benchmark,并完成被发布包及至少一个 Cortex-M BSP/CMSIS smoke;依赖 R1.8,完成任务后将详细报告写入[任务报告](./details/pikapython-release-engineering/R1.9_Task_Report.md)
48+
49+
### R1.10
50+
51+
审核 RC 证据与 release-check 输出,创建 annotated v1.14.0 tag 和 GitHub Release,从全新 clone 及包管理器完成发布后验证;失败时不移动 tag,改走后续 patch;依赖 R1.9,完成任务后将详细报告写入[任务报告](./details/pikapython-release-engineering/R1.10_Task_Report.md)

port/linux/release_auto.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
if diff != '':
3232
newVersion = pkgReleases.versionRelease(
3333
folder,
34-
VersoinType.PATCH,
34+
VersionType.PATCH,
3535
commit_head
3636
)
3737
print(f"Changes detected: {folder} --> {newVersion}")

port/linux/release_check.py

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import argparse
2+
import json
3+
import re
4+
from pathlib import Path
5+
6+
import git
7+
8+
from release_helper import PackageReleaseList
9+
10+
11+
REPO_ROOT = Path(__file__).resolve().parents[2]
12+
CORE_PACKAGES = ("pikascript-core", "PikaStdLib")
13+
14+
15+
def parse_header_version(path: Path):
16+
text = path.read_text(encoding="utf-8")
17+
values = {}
18+
for field in ("MAJOR", "MINOR", "MICRO"):
19+
match = re.search(rf"^#define\s+PIKA_VERSION_{field}\s+(\d+)\s*$", text, re.M)
20+
if match is None:
21+
raise ValueError(f"PIKA_VERSION_{field} not found in {path}")
22+
values[field] = match.group(1)
23+
return f"{values['MAJOR']}.{values['MINOR']}.{values['MICRO']}"
24+
25+
26+
def parse_version_config(path: Path):
27+
text = path.read_text(encoding="utf-8")
28+
values = {}
29+
for field in ("MajorVersion", "MinorVersion", "MicroVersion"):
30+
match = re.search(rf'^{field}\s*=\s*["\'](\d+)["\']\s*$', text, re.M)
31+
if match is None:
32+
raise ValueError(f"{field} not found in {path}")
33+
values[field] = match.group(1)
34+
return (
35+
f"{values['MajorVersion']}."
36+
f"{values['MinorVersion']}."
37+
f"{values['MicroVersion']}"
38+
)
39+
40+
41+
def parse_cmsis_version(path: Path):
42+
text = path.read_text(encoding="utf-8")
43+
match = re.search(r'^version\s*=\s*["\'](\d+\.\d+\.\d+)["\']\s*$', text, re.M)
44+
if match is None:
45+
raise ValueError(f"version not found in {path}")
46+
return match.group(1)
47+
48+
49+
def collect_checks(
50+
repo: git.Repo,
51+
expected_version=None,
52+
tag=None,
53+
require_tag_at_head=False,
54+
require_clean=False,
55+
):
56+
checks = []
57+
errors = []
58+
warnings = []
59+
60+
def record(name, actual, expected):
61+
passed = actual == expected
62+
checks.append(
63+
{
64+
"name": name,
65+
"ok": passed,
66+
"actual": actual,
67+
"expected": expected,
68+
}
69+
)
70+
if not passed:
71+
errors.append(
72+
{
73+
"code": "version-mismatch",
74+
"check": name,
75+
"actual": actual,
76+
"expected": expected,
77+
}
78+
)
79+
80+
config_version = parse_version_config(
81+
REPO_ROOT / "port/linux/version_config.py"
82+
)
83+
expected = expected_version or config_version
84+
record("version_config", config_version, expected)
85+
record("runtime_header", parse_header_version(REPO_ROOT / "src/PikaVersion.h"), expected)
86+
record(
87+
"cmsis_pack",
88+
parse_cmsis_version(REPO_ROOT / "port/cmsis-pack/pikascript/makepdsc.py"),
89+
expected,
90+
)
91+
92+
releases = PackageReleaseList(REPO_ROOT / "packages.toml")
93+
for package_name in CORE_PACKAGES:
94+
package = releases.findPackage(package_name)
95+
actual = package.latestVersion().version if package is not None else None
96+
record(f"package:{package_name}", actual, f"v{expected}")
97+
98+
invalid_commits = []
99+
for package in releases.packages:
100+
try:
101+
commit = package.latestVersion().commit
102+
repo.commit(commit)
103+
except (ValueError, git.BadName):
104+
invalid_commits.append(package.name)
105+
checks.append(
106+
{
107+
"name": "package_commits",
108+
"ok": not invalid_commits,
109+
"invalidPackages": invalid_commits,
110+
}
111+
)
112+
if invalid_commits:
113+
errors.append(
114+
{
115+
"code": "invalid-package-commit",
116+
"packages": invalid_commits,
117+
}
118+
)
119+
120+
if tag is not None:
121+
expected_tag = f"v{expected}"
122+
record("tag_name", tag, expected_tag)
123+
try:
124+
tag_commit = repo.commit(tag)
125+
checks.append(
126+
{
127+
"name": "tag_exists",
128+
"ok": True,
129+
"commit": tag_commit.hexsha,
130+
}
131+
)
132+
if require_tag_at_head:
133+
record("tag_at_head", tag_commit.hexsha, repo.head.commit.hexsha)
134+
except (ValueError, git.BadName):
135+
checks.append({"name": "tag_exists", "ok": False, "tag": tag})
136+
errors.append({"code": "tag-not-found", "tag": tag})
137+
138+
dirty = repo.is_dirty(untracked_files=True)
139+
checks.append({"name": "worktree_clean", "ok": not dirty})
140+
if dirty:
141+
problem = {"code": "dirty-worktree"}
142+
if require_clean:
143+
errors.append(problem)
144+
else:
145+
warnings.append(problem)
146+
147+
return {
148+
"ok": not errors,
149+
"expectedVersion": expected,
150+
"head": repo.head.commit.hexsha,
151+
"checks": checks,
152+
"errors": errors,
153+
"warnings": warnings,
154+
}
155+
156+
157+
def main():
158+
parser = argparse.ArgumentParser(
159+
description="Validate PikaPython release metadata and Git state."
160+
)
161+
parser.add_argument("--expected-version")
162+
parser.add_argument("--tag")
163+
parser.add_argument("--require-tag-at-head", action="store_true")
164+
parser.add_argument("--require-clean", action="store_true")
165+
args = parser.parse_args()
166+
167+
try:
168+
result = collect_checks(
169+
git.Repo(REPO_ROOT),
170+
expected_version=args.expected_version,
171+
tag=args.tag,
172+
require_tag_at_head=args.require_tag_at_head,
173+
require_clean=args.require_clean,
174+
)
175+
except Exception as exc:
176+
result = {
177+
"ok": False,
178+
"errors": [{"code": "release-check-failed", "message": str(exc)}],
179+
}
180+
print(json.dumps(result, ensure_ascii=False))
181+
raise SystemExit(0 if result["ok"] else 1)
182+
183+
184+
if __name__ == "__main__":
185+
main()

0 commit comments

Comments
 (0)