Files
msime-windows/docs/product-release.md
fanlusky bc37ac9a9e feat(installer): 安装时可自定义数据目录位置
词库、用户配置和皮肤此前固定在 %LOCALAPPDATA%\metasequoiaime,整包几百 MB
全压在 C 盘。安装向导新增一页,允许把这部分数据放到任意盘。

程序本体仍固定装在 Program Files:MetasequoiaImeServer.exe 带 uiAccess=true,
只有在受信任目录里这个标志才生效。

运行期的权威来源是 HKLM\Software\Metasequoia\MetasequoiaIME 的 DataDir。
Server、TSF DLL 和引擎三个组件各自按同一顺序解析:METASEQUOIA_IME_DATA_DIR
环境变量 -> 该注册表值 -> %LOCALAPPDATA%\metasequoiaime。三处各持一份实现,
因为组件边界规则禁止 windows/ 与 server/ 互相 include。32 位 TSF DLL 用
KEY_WOW64_64KEY 读,避开 WOW64 重定向。

安全相关的两个约束:

- 安装器在数据目录里写 .metasequoiaime-data 标记。覆盖安装的清理和卸载的整目录
  删除只在看到这个标记(或目录就是历史默认位置)时才执行——用户可能把数据目录
  指到一个本来就有自己文件的文件夹。
- 选择页与 /DATADIR= 走同一套校验:拒绝相对路径、盘符根目录、不存在的盘、程序
  目录内部,以及 Windows/Program Files/各 AppData/用户主目录本身。

升级时改了位置,PrepareToInstall 在停掉输入法进程后用 robocopy /MOVE 搬运用户
拥有的数据(msime_user.db 及其 WAL/SHM/journal、config.toml、config.base.toml、
skins\),词库和前端资源由本次安装重新写入。跨卷移动目录 MoveFile 会失败,所以用
robocopy。用户词库搬运失败会中止安装:否则新版本对着空的 msime_user.db 启动,
ssPostInstall 的回放只记一句 skipped,用户的自造词就静悄悄没了。

测试侧新增 METASEQUOIA_IME_CONFIG_DIR,只改 config.toml 的位置而不搬词库。原先
靠重定向 LOCALAPPDATA 做隔离的办法在装了正式版的机器上已不再成立——注册表值
的优先级高于它。
2026-09-17 22:49:19 +08:00

58 lines
6.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Windows 产品版本组合
一个 Windows 版本的完整一方源码就是本仓的一个提交:TSF、Server、引擎、GUI 框架、页面和安装器都是目录,不需要再被清单钉住。
`product-lock.json` 只覆盖仍来自仓外的输入,现在只剩词库 Release 的 source commit 和每个产物的 SHA256。辅助码在 `engine/helpcode/`,引擎本身也已内嵌,两者都由这个提交钉住,不再单列。
引擎没有 `repositories` 条目是刻意的:它不再有 gitlink,清单里写一个上游 commit 就等于声称这棵树仍等于上游,而第一个 Windows 专门化的改动落地时这句话就假了,也没有任何 CI 能发现。来源改记在 [engine/UPSTREAM.md](../engine/UPSTREAM.md)。
发布任务不解析 `main` 或 `latest`。递归 submodule(opencc、cpp-pinyin)沿固定 gitlink 检出,不执行 `git submodule update --remote`。
## 更新依赖
显式刷新后评审并提交清单,CI 通过后才能发布:
```sh
python scripts/product_lock.py refresh --dictionary-tag dict-v1.0.0
python scripts/product_lock.py validate
```
`refresh` 不解析任何浮动源码引用,`--ref` 已无可覆盖的对象:唯一被锁的源是词库发布,按指定 tag 取。
词库 tag 本身可能被上游修改,因此构建验证的是本清单中已提交的摘要,连同模型授权文件和上游校验文件一起验证。只更新上游 SHA256SUMS 无法让被替换的数据通过构建。词库清单另外声明构建它的 commit 和当时工作树是否干净,两者都要与清单相符——摘要证明字节没被换过,证明不了这批数据出自一个能重建的源。
## 重建与追溯
自动路径构建的是 release-please 刚刚建出的那个 draft,`resolve-auto-release.sh` 要求它钉在完整 commit SHA 上;发布时自动路径把产物挂到同一提交的 `v<版本>-beta` Pre-release,稳定的 `v<版本>` draft 保留给人工挑选后发布。手动 Release 只接受已有稳定 draft tag,由 `validate-draft-release.sh` 做同样的校验。两条路径都只读该提交内的清单。变更词库需要新的源码提交和产品版本,不允许临时覆盖词库 tag。没有清单的历史版本不能用新流水线宣称可复现重建。
## 发布节奏
发版由把 `develop` 提升到 `main` 触发;提升之后是全自动的,`main` 上每一次带 `fix:` / `feat:` 的合并都会走完构建、签名、发布。
**签名额度不是约束。** 证书允许每月 3000 次签名,而这个项目一个月的发布次数远低于这个量级。此前把「省签名次数」当作攒版本理由的说法不成立,不要据此调整合并策略。
**真正花掉的是发布节奏本身。** 自动链条一旦起跑就没有人工落点,「把若干修复攒成一个版本」和「这次不值得发」这类判断必须发生在它起跑之前。`develop` 就是这个落点:日常修复合进 `develop` 不产生任何版本,攒够一批再一次性提升到 `main`,那批提交会落在同一次 push 上、合成同一个版本。要让某次提升完全不递增版本,把提交类型写成 `chore:` / `docs:` / `refactor:` / `test:` 即可。
这一段原本记录的是相反的处境:默认分支曾经就是 `main`,消耗版本号的决定权因此落在「有没有人合了一个 `fix:`」上,用户会看到一串只差一两个提交的版本,release notes 也跟着碎。`develop` 模型把那个决定权收了回来。
**换的是什么。** 从合并到安装包可下载之间不再有人工环节,也就不会出现改动躺在 draft 里没人发的情况。
**排队。** Release runs 按 `concurrency: release-${{ github.ref }}` 串行且不取消。一轮完整的自动发布 = 等 release PR 的 CI(十几分钟)+ 构建签名打包(二十分钟上下)。向 `main` 提升的频率高于这个节奏时队列只会越排越长,每个排队的 run 到头来都发一个版本。真出现这种情况,该调的是提升节奏或提交类型,不是把 `cancel-in-progress` 打开——那会在发布中途砍掉 run,留下挂着半个产物的 draft。
发布前 `product_lock.py verify-published` 要求清单里每个提交都能从各自仓库的默认分支到达,词库的 `source_commit` 一并检查——它是清单里仅剩的仓外输入,而摘要与词库清单只能证明这批数据出自锁文件写着的那个提交,证明不了那个提交有人合过。唯一的例外是已退休的词库仓库:它的历史 Release 是不可变的既有产物,仓库归档或删除后无从追溯默认分支,此时只记录不拦截。这一条只在发布路径上执行,不进 `ci.yml`:一个 PR 里出现尚未进默认分支的词库发布是正常的中间状态,在 PR CI 上强制会把它本该保护的那次落地直接锁死。到了发布这一刻判断相反——没进默认分支的输入就是没人合过的输入,锁文件为它背书等于宣称了一次并不存在的评审。各代码仓的默认分支现在是 `develop`,这条判据本身没有变:进了 `develop` 就是有人评审合过,发布提升与它无关。
安装包把 `product-manifest.json` 放进数据目录(默认 `%LOCALAPPDATA%/metasequoiaime/`,安装时用户可改到别的盘),Release 同时附加同名文件。其中包含本仓的发布提交、全部锁定输入和清单文件摘要。签名时间、工具链与 runner 更新仍可能改变二进制字节;此机制保证产品源码与数据组合确定,不承诺安装包逐字节一致。
本地检查:
```sh
python -m unittest discover -s tests -p 'test_product_lock.py'
python scripts/product_lock.py fetch-dictionaries --staging-root /tmp/msime-product-data
```
## 发布元数据令牌
版本 PR 的创建和更新使用 `RELEASE_PLEASE_TOKEN`,使正常 `pull_request` CI 自动运行并进入 PR 检查汇总。`land-release-pr.sh` 等待精确 head 的 PR CI 通过,再用 `GITHUB_TOKEN` 合并。维护 PR 的 release-please 调用设置 `skip-github-release: true`。它前后各有一次 `skip-github-pull-request: true` 的元数据调用,使用组织已有的 `RELEASE_PLEASE_TOKEN`:先补建已经手动合并的版本,再处理本次自动合并的版本,保持 release-please 原有的先发布、后计算下一版本的顺序。
后者需要仓库内容和 workflow 写权限(经典 PAT 的 `repo` + `workflow`,或等价的细粒度权限)。主分支先前包含 workflow 变更时,`GITHUB_TOKEN` 创建历史提交的 tag 可能被 Git refs API 拒绝。该令牌只更新版本分支,不用于合并;主分支仍由 `GITHUB_TOKEN` 写入,因此不会额外触发一次 Release。缺少令牌时提前报出配置错误。