词格原先的整句打分是一份没校准过的启发式 unigram,质量很差。这里内嵌 libime 在用的 kenlm 查询子集,改由它的三元模型 sc.lm 来打路径分。 - engine/ngram 只编 kenlm 的 query / trie-build 路径。不定义 WITH_THREADS, parallel_read.cc 因此退化成串行实现,整个子集零 Boost 依赖。 KENLM_MAX_ORDER=3 必须与建模阶数一致,改它等于读不懂已发布的模型文件。 - ngram::LanguageModel 是唯一 include kenlm 头文件的地方,对外只给出一个纯 std 的头,_HAS_AUTO_PTR_ETC 和 kenlm 的 include 目录都保持 PRIVATE。 - shared_language_model 按资源路径共享一份 mmap。kenlm 的查询是只读的且把 上下文放在调用方的 State 里,所以并发会话不需要额外加锁。 - 模型缺失时 valid() 为假,词格退回原来的启发式打分:整句候选只是变差,不会 停止工作。测试覆盖了缺模型降级与按路径共享。 - 出货的 ARPA 不带 <s>/</s>,所以解码从 null_state() 起算而非 begin_state()。 kenlm 是上游副本,不重排版、不进 clang-format,其 LGPL-2.1+ 与 BSD 例外清单 已补进 THIRD_PARTY_NOTICES.txt。googlepinyinime 那条 Fallback 整句路径不变。
6.4 KiB
参与 MSIME-Windows 开发
组织级的通用约定(行为准则、PR 要求、安全上报)见组织贡献指南。这份文档只讲本仓特有的东西:环境、怎么把它编出来、改哪一层、以及提交前要跑什么。
想找活干的话先看招募开源开发者和 good first issue。不写代码也有事情可做:词库条目、文档、图标资源、兼容性测试都在 no-code 标签下。
环境
- Windows 10 / 11
- Visual Studio 2026(需要 C++ 桌面开发工作负载)
- CMake 3.25+
- vcpkg,并把
VCPKG_ROOT设进环境变量 - Python 3.10+
- Inno Setup 6.6+(只有要打安装包时才需要)
Boost 按 -DBoost_ROOT=... → 环境变量 BOOST_ROOT → %USERPROFILE%\scoop\apps\boost\current 的顺序解析。preset 里的 vcpkg 路径取自 $env{VCPKG_ROOT},不需要改文件。
取代码
git clone --recursive https://github.com/metasequoiaime/MSIME-Windows.git
cd MSIME-Windows
忘了 --recursive 的话补一句 git submodule update --init --recursive。vendor/ 下是 opencc 和 cpp-pinyin,缺了它们 Server 编不过。输入引擎在 engine/,是本仓的目录而不是 submodule,普通 clone 就有。
词库不在版本库里,构建时从 dict-* release 按锁定的 tag 和摘要取用:
python scripts/product_lock.py fetch-dictionaries --staging-root .
构建
没有顶层聚合的 CMakeLists.txt,这是有意的:TSF DLL 用静态 CRT、Server 用动态 CRT,两者的 vcpkg manifest 与编译选项互不兼容,合成一个 build tree 会互相污染。每个组件是独立的 CMake 工程,从仓库根目录指定 -S:
cmake -S windows -B windows/build64 -A x64 # TSF 文本服务 DLL
cmake -S server -B server/build -A x64 # Server
cmake -S ui -B ui/build -A x64 # 自研 GUI 框架 msimeui
server/ 通过 add_subdirectory(../ui ...) 把 msimeui 拉进自己的构建,这是唯一的跨组件构建引用。
每个组件也各自带了本地脚本,比手敲 cmake 省事:
.\windows\scripts\prepare_env.py # 生成 .clangd 并把 preset 指向本机 vcpkg/Boost
.\windows\scripts\lcompile.ps1 64 # Debug;换 32 编 32 位
.\server\scripts\lcompile.ps1
各组件更细的说明在各自的 README 与 AGENTS.md 里,那两份文件写给人和 AI 都一样有效:
windows/AGENTS.md— TSF 层的构建、注册与手工回归清单(这份清单是本仓最难替代的知识,改 TSF 前先读)server/README.md— Server 的开发构建与本地词库准备ui/README.md— GUI 框架的构建与边界约束installer/README.md— 打包AGENTS.md— 跨组件契约:协议只有一份来源,改哪边要同时改哪边
打一个可安装的包
从仓库根开始,严格按顺序。任一步失败先修,不要拿旧产物继续打包:
Set-Location .\windows
.\scripts\lcompile-release-both.ps1
Set-Location ..\server
.\scripts\lcompile-release.ps1
Set-Location ..\installer
.\Prepare-PackageFiles.ps1 -RepoRoot .. -TsfDirectory windows -ServerDirectory server -UiHtmlDirectory ui-html -NoticesDirectory .
.\Sign-PackageBinaries-Local.ps1
.\Compile-Installer.ps1
Sign-PackageBinaries-Local.ps1 用的是本机测试证书,只用于内部安装验证;不要把本机证书指纹或私钥提交进仓库。
改动前要知道的几条边界
- 协议的唯一来源是
engine/contracts/。 IPC 线格式、opcode、语音分帧和 WebView 消息定义都在那里,两侧引用同一份头文件,不要在任何一侧另写一份。 engine/是本仓的一等代码。 要为 Windows 改引擎就直接改,和改server/一样评审和测试;引擎和调用它的代码可以、也应该在同一个 PR 里。例外是engine/下的googlepinyinime-rev/、ngram/kenlm/、utfcpp/、voice/third_party/,那是上游副本。见 engine/UPSTREAM.md。ui/不许反向依赖产品。 它不读 Server 配置、IPC、引擎、词库或全局输入状态。ui/scripts/check-boundary.py在 CI 里检查。- 窗口归
server/,页面归ui-html/。 改消息type、JSON 字段或页面导出的 JS 函数时两侧一起改。 ui-html/webview2/shared/是生成物,由ui-html/scripts/sync-contracts.py从engine/contracts/webview/同步,CI 用--check校验。要改契约就去改engine/contracts/,然后在同一个提交里重新生成。- 候选与输入状态的权威在 Server 和引擎,页面只负责展示。
提交前
ctest --test-dir server/build --output-on-failure # Server 单元测试(引擎随 Server 一起构建)
python -m unittest discover -s tests # 产品级组合验证
bash scripts/format.sh --check # 格式,覆盖 server/windows/ui/log/engine
bash scripts/ci/check-developer-paths.sh # 构建输入里不许出现个人目录路径
改了 TSF 层就照 windows/AGENTS.md 的手工回归清单跑一遍。输入法很难靠读代码判断对错,PR 里必须写清你怎么验证的:系统版本、宿主程序、操作步骤、观察到的结果、跑过的测试命令。
git status --short 确认只暂存本次任务涉及的路径,不要 git add -A。提交信息用 type(scope): 摘要,不要加 Co-Authored-By 或任何 AI 生成标记。
分支从 develop 切,PR 也合回 develop——它是本仓默认分支,开 PR 时不用改 base。main 是发布分支,只在发版时由维护者从 develop 合入;特性分支直接提到 main 会被 Branch guard 检查拦下,把 base 改回 develop 即可。发布链条见 docs/product-release.md。
不要在 PR、Issue、截图或日志里泄露 API Key。仓库开了 secret scanning 和 push protection,但那是最后一道防线不是第一道。