示意图

>
从代码完工到 apt install ./tux-im.deb:mypy strict、CI 演化、deb 打包。

本篇是 《TUX IM 开发日志:从零到 0.1》 系列第 8/8 篇。

第 8 篇 · 发布 0.1.0:mypy strict、CI、deb 打包

代码完工不等于能发布。从最后一行代码到 apt install ./tux-im_0.1.0-12_all.deb 之间,有 25 个 commit 在做"工程化"——lint、类型检查、CI、deb 打包、版本号管理。这篇讲这部分。

mypy strict:0 errors 是怎么逼出来的

commit 153587c mypy strict: 0 errors 是一个里程碑——整个项目 27 个 Python 文件,mypy --strict 通过。

mypy strict 包含什么:

  • 所有函数参数和返回值必须标类型。

  • 不能有 Any 类型逃逸。

  • 不能隐式 Optional

  • 不能 # type: ignore 滥用。

为什么值得做?

  • 重构时不慌——改一个函数签名,mypy 立刻把所有调用方列出来。

  • 新人 onboarding 快——IDE 自动补全准确,不用读整个文件猜类型。

  • 运行时类型 bug 几乎消失——AttributeError: 'NoneType' object has no attribute 'x' 这种经典 Python 错误在 strict 下根本写不出来。

>
几个有代表性的修复:

  • config.dict → config.dictionary——避免跟 Python 内置 dict 类型同名引起混淆。

  • shortcut handler 类型注解——之前都是 Callable 没有任何参数信息,mypy 看不到。

  • PEP 563 注释解析——from __future__ import annotations 之后 dataclass 字段的 list[int] 这种泛型在 Python 3.9 之前需要 typing.List[int]

  • ASR 模块整体加 **ignore_errors**——ctypes 的 cfunc 签名跟 mypy 不兼容,没法 strict。这是务实的妥协。

lint 债务是怎么还清的

项目历史上有过 53 个 ruff error。处理方式不是一次 commit 修完,而是三步走

  • 74ee0af style: manual lint fixes for the non-fixable 53 ruff errors——手修不能自动修的。

  • f36218d style: auto-fix ruff lint debt——ruff check --fix 自动修。

  • 2d5daf6 style: rename _MAX_PINYIN_LEN to _max_pinyin_len per ruff N806——按 N806 规则重命名常量。

之后 CI 强制 ruff check . 在 push 时跑,新债务不再积累

CI:GitHub Actions 的演化

d88808a ci: add GH Actions workflow 开始,CI 平台是一步步搭起来的。每一步都有坑:

坑 1:respx 是 PyPI 包不是 apt

示意图

第一次写 CI 时,我在 apt-get install 里加了 python3-respx——但 respx 是测试 HTTP mock 库,只在 PyPI 有,apt 仓库里没有。CI 跑 build 步骤时 pip 装不上 → 整个 workflow 失败。

修复 f942aff ci: respx is on PyPI, not apt; move to pip install:换成 pip install respx

坑 2:debian/control 缺依赖

CI 在 dpkg-buildpackage 时报 missing build dependencies。日志显示:

dpkg-checkbuilddeps: error: Unmet build dependencies:
build-essential python3-httpx python3-sounddevicedpkg-checkbuilddeps: error: Unmet build dependencies: build-essential python3-httpx python3-sounddevice

这些依赖在 debian/control 里漏了。修复 d82079d ci: add missing build deps

坑 3:sounddevice 在 Ubuntu noble 仓库里没有

更狠的来了:python3-sounddevice 在 Ubuntu 22.04 (jammy) 有,但 noble (24.04) 仓库里还没有。CI 跑在 noble runner 上,仍然失败。

修复 724c4a2 fix(control): drop python3-sounddevice from Build-Depends:从 build deps 里删掉,让它运行时再装(不强制打包时存在)。

坑 4:upload-artifact@v4 拒绝 .. 路径

CI 把 .deb 上传为 artifact 时报:

错误

Text
Error: Failed to CreateArtifact: path '../tux-im_*.deb' is outside of the workspace

原因:dpkg-buildpackage 默认把产物输出到上级目录(约定俗成),但 GitHub Actions v4 artifact 不允许 .. 相对路径。

修复 39ff09b ci: move built .deb into ./dist/:改 dpkg-buildpackage 输出到 ./dist/

CI 流水线最后的样子

从 6 个 fix commit 后,CI 包含 4 个 job:

  • lint——ruff check . + mypy --strict .

  • test——pytest tests/(当前 38 个测试)。

  • build-deb——dpkg-buildpackage -us -uc -b,上传 .deb 为 artifact。

  • release——只在 **v*** tag push 时触发,把 .deb 附到 GitHub Release。

关键设计:test + build-deb 在每次 push / PR 时跑(gate),release 只在 tag push 时跑。这样 "Releases" 页是干净的发布历史。

deb 打包:apt install ./tux-im.deb

用户拿到 .deb 后一条命令装好:

用户安装流程

Bash
sudo apt install ./tux-im_0.1.0-12_all.deb
ibus restart

装什么?

  • /usr/share/ibus/component/tux-im.xml——IBus 组件描述文件,让 ibus-daemon 认识 TUX IM。

  • /usr/lib/python3/dist-packages/tux_im/——Python 包。

  • /usr/bin/ibus-engine-tux-im——引擎启动脚本(symlink 到 python3 -m tux_im.main)。

  • /usr/bin/tux-im-setup——设置面板(tux_im.ui.settings:main)。

postinst 脚本自动跑 ibus restart

"清干净再提交"原则的代价

你可能在多个 commit message 里看到"清干净"这个词——这不是矫情,是原则:

>
原则:CI / lint / 类型检查错误必须全部修干净再合并。
不接受"先 shrink CI 跑通再说"或"先发布后修"的 deferred 方案。

代价:有些 commit 会花 1 小时才 push,只为了 53 个 lint error 全部清干净。但收益是:

  • 主线永远干净。

  • 每个 PR 只需要关心自己的 diff,不用同时修历史债务。

  • 新人 clone 仓库就能 pytest 全过。

v0.1.0-12:发布日期

截至最新,仓库标签:

git tag -l

Text
v0.1.0-10
v0.1.0-12

版本号约定:v0.1.0-N,N 是 deb revision。v0.1.0 是源码版本,-12 表示这是第 12 次 deb 重新构建(每次 Build-Depends 改了都要 rebuild)。

aa4b094v0.1.0-12

  • 46 个 commit

  • 约 4000 行 Python

  • 38 个测试

  • mypy strict 0 errors

  • ruff 0 errors

  • 3 种 input mode(pinyin / wubi / wbpy / latin / emoji / google_pinyin 共 6 种)

  • 1 个 ASR 子系统

  • 1 套 CI + 1 套 deb 打包

本篇小结(系列完结)

  • mypy strict 是值得的——重构不慌、IDE 补全准、运行时类型 bug 消失。

  • CI 演化是踩坑驱动的——4 个 fix commit 才稳定。

  • deb 打包 = 一次性学习 dpkg + debhelper,但有模板之后就是个配置文件。

  • "清干净再提交"是维护性的核心原则——主线永远干净,新人友好。

>
系列完结。从 aa4b094v0.1.0-12,46 个 commit 讲述了一个输入法引擎从空仓库到 deb 包的完整故事。

下一阶段:v0.2.0 路线图(feishu 实现 word-by-word 选择、整句输入完善、Linux Wayland 支持、更多 ASR provider)。

最后修改: 2026年7月1日

作者

评论

发表评论

您的邮箱地址不会被公开。