| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- r"""版本记录生成/校验 (2026-09-17 用户令: 源代码开发维护与打包版本管理)。
- ## 为什么不是"再手写一个 CHANGELOG.md"
- 手写版本记录的老毛病是**和代码里的版本号对不上**:改了 `src/version.py` 忘了改 md,或者反过来
- (本仓 0.4.0 期间就出现过三处版本号各说各话)。这里把事实只放在 `src/version.py` 的 `HISTORY` 里,
- `docs/版本记录.md` 由本器**生成**:
- python scripts/version_log.py # 打印(人看)
- python scripts/version_log.py --write # 写进 docs/版本记录.md(自动块)
- python scripts/version_log.py --check # 校验 md 与 src/version.py 是否一致(rc=6 表示不一致)
- `guanlan.py check` 与打包器都会调用 `--check` 这一条:**版本记录与版本号必须一致**,
- 否则交付包里会出现"包说 2.5.0、记录表最后一行是 2.4.0"这种事。
- 退出码: 0 一致/已写 · 6 不一致(需要跑 --write) · 2 文件缺失
- """
- from __future__ import annotations
- try:
- from app_common.app_common_guanlan.api import install_root as _install_root
- except ImportError: # 理论不可达;包结构异常时回退到按位置上跳
- from pathlib import Path as _P
- def _install_root(_f): return _P(_f).resolve().parents[4]
- import argparse
- import pathlib
- import sys
- ROOT = _install_root(__file__)
- sys.path.insert(0, str(ROOT))
- from src import version as V # noqa: E402
- DOC = ROOT / 'docs' / '版本记录.md'
- BEGIN, END = '<!-- VERSION-LOG:BEGIN -->', '<!-- VERSION-LOG:END -->'
- LEVEL_CN = {'major': '大', 'minor': '中', 'patch': '小', 'legacy': '旧编号', 'same': '无', '?': '?'}
- def body() -> str:
- """生成自动块正文(规则 + 当前版本 + 版本表 + 怎么改)。"""
- L = ['## 版本号规则(用户令 2026-09-17)', '',
- f'编号 **`{V.RULE}`**,例如 `v{V.VERSION}`。', '',
- '| 级别 | 含义 |', '|---|---|']
- for k in ('major', 'minor', 'patch'):
- # LEVEL_MEANING 里是"大版本号 —— 系统解决方案…"整句(与用户令措辞一致),
- # 表头已经有"级别"一列,这里只取破折号后的定义,免得读成"大版本号 | 大版本号 —— …"。
- L.append(f'| {LEVEL_CN[k]}版本号 | {V.LEVEL_MEANING[k].split(" —— ", 1)[-1]} |')
- L += ['', f'**怎么改**:{V.BUMP_RULE}', '',
- f'**打包文件名** = `{V.package_name()}`(规则:`{V.PACKAGE_STEM}_v<版本号>.zip`)。', '',
- f'版本号只写在 `src/version.py` 一行(`VERSION = \'{V.VERSION}\'`);'
- '打包器、安装脚本、安装记录 `install-info.json`、自检都读它,不另设真源。', '',
- '| 版本 | 日期 | 级别 | 主要变更 | 说明 |', '|---|---|---|---|---|']
- for h in V.HISTORY:
- L.append(f"| `v{h.get('version', '?')}` | {h.get('date', '?')} | {LEVEL_CN.get(h.get('level', '?'), '?')} | "
- f"{h.get('title', '')} | {h.get('note', '')} |")
- L += ['', '## 本次交付', '',
- f'* 版本:`v{V.VERSION}`({V.NAME})',
- f'* 交付包:`{V.package_name()}`',
- f'* 服务名:`{V.SERVICE_NAME}`(Windows 服务 / systemd 单元)',
- f'* 安装 / 卸载:`install.bat`、`install.sh` / `uninstall.bat`、`uninstall.sh`', '']
- return '\n'.join(L)
- def render() -> str:
- return (f'# 版本记录({V.NAME})\n\n'
- '> 本文件由 `scripts/version_log.py` 生成,**勿手改**:事实(版本号、级别、变更)写在\n'
- '> `src/version.py` 的 `HISTORY` 里,改完跑 `python scripts/version_log.py --write`。\n'
- '> `guanlan.py check` 与打包器都会校验本文件与代码里的版本号是否一致。\n\n'
- f'{BEGIN}\n{body()}{END}\n')
- def block_of(text: str) -> str:
- i, j = text.find(BEGIN), text.find(END)
- return text[i + len(BEGIN):j].strip() if i >= 0 and j > i else ''
- def main() -> int:
- ap = argparse.ArgumentParser(description='版本记录生成/校验')
- ap.add_argument('--write', action='store_true', help='写进 docs/版本记录.md')
- ap.add_argument('--check', action='store_true', help='只校验是否一致 (rc=6 不一致)')
- a = ap.parse_args()
- want = body().strip()
- if a.write:
- DOC.parent.mkdir(parents=True, exist_ok=True)
- DOC.write_text(render(), encoding='utf-8', newline='\n')
- print(f'已写入 {DOC.relative_to(ROOT).as_posix()}(版本 v{V.VERSION},{len(V.HISTORY)} 条历史)')
- return 0
- if a.check:
- if not DOC.is_file():
- print(f'[X] 缺 {DOC.relative_to(ROOT).as_posix()} —— 跑 python scripts/version_log.py --write')
- return 2
- got = block_of(DOC.read_text(encoding='utf-8'))
- if got != want:
- print(f'[X] 版本记录与 src/version.py 不一致(版本 v{V.VERSION})'
- f' —— 跑 python scripts/version_log.py --write')
- return 6
- # ★2026-09-29 加:根 `VERSION` 文件曾被漏在 0.2.0(历史遗留),与真源脱节 →
- # 它是给外部工具看的**镜像标记**,必须与 version.py 同号(真源永远只有 version.py 一处)。
- vf = ROOT / 'VERSION'
- if vf.is_file():
- import re as _re
- m = _re.search(r'([0-9]+\.[0-9]+\.[0-9]+)', vf.read_text(encoding='utf-8'))
- if m and m.group(1) != V.VERSION:
- print(f'[X] 根 VERSION 文件是 {m.group(1)},与版本真源 {V.VERSION} 不一致'
- f' —— 真源只有 src/version.py 一处,VERSION 是它的镜像')
- return 6
- print(f'[OK] 版本记录一致(v{V.VERSION},{len(V.HISTORY)} 条)')
- return 0
- print(render())
- return 0
- if __name__ == '__main__':
- sys.exit(main())
|