公司动态
在纯 Windows 环境下从源码编译 calibre:一次 AI 驱动的完整踩坑实录
本文记录了在 Windows 11 上从 calibre 源码v9.12.0编译生成 MSI 安装包的完整过程。没有 Linux、没有 Docker、没有虚拟机——只有一台真实的 Windows 机器、一个 AI 编程助手和一个晚上。为什么要在 Windows 上编译 calibrecalibre 是一款功能强大的开源电子书管理工具支持格式转换、元数据编辑、设备同步等。它的官方构建流程基于 Linux 虚拟机 bypy 编排系统Windows 构建被设计为在 Linux 主机上通过 SSH 远程触发 Windows VM 内的编译。这意味着官方从未提供过在纯 Windows 环境下直接构建的文档。真正的动机中文文件名之痛作为一个曾经重度 calibre 用户我有一个长期困扰calibre 保存到本地的电子书文件名全部是拼音。比如一本叫《三体》的书导出后文件名是san-ti.epub。对于中文用户来说这简直是灾难——你根本无法从文件名辨认出这是什么书。当然这个问题很多年前通过修改源码每次让calibre通过源码启动其实已经解决了保存为中文文件名的问题但是不完美。有人曾向 calibre 作者 Kovid Goyal 提交过修改建议希望支持保留中文文件名。但作者的回复很明确无意修改。calibre 的文件名策略使用作者名 书名的拼音/音译是有意为之的设计目的是避免跨平台文件系统兼容性问题。既然上游不会改那就自己改。而要改源码首先得能编译。这就是本文的起点。已经编译了一个解决保存为中文文件名的版本 详见我的github一个大胆的决定让 AI 来编译面对一个从未有人走通的构建路径我做了个大胆的决定让 AI 编程助手Qoder全程自动化完成编译构建。我的角色很简单提供环境和目标在 AI 遇到需要下载大文件时手动下载并告知文件位置然后……去睡觉结果第二天早上醒来C:\r\sw64\dist\calibre-64bit-9.12.0.msi195 MB已经安静地躺在那里了。整个过程AI 独立解决了十余个构建错误修改了 4 处 bypy 源码从官方 MSI 中提取了 50 个缺失的 DLL。我唯一的手动操作是下载了一个 274MB 的依赖包和一个 212MB 的官方 MSI——因为 AI 的网络下载遇到了问题。这篇文章就是那个晚上发生的一切的验尸报告。环境概览项目版本/规格操作系统Windows 11 21H2 (x64)编译器Visual Studio 2026 (MSVC 14.51)内嵌 Python3.14依赖包自带系统 Python3.13用于 bootstrapcalibre 版本9.12.0最终产物calibre-64bit-9.12.0.msi195 MB一、工具链准备calibre 的构建依赖链相当庞大。以下是我实际安装的全部工具1.1 Visual Studio 2026安装 Community 版本勾选MSVC v143 x64/x86 构建工具Windows 11 SDKC ATL、CMake 工具.NET SDKWiX 需要Git for Windows关键确保cl.exe和link.exe在 PATH 中可用。1.2 其他工具Python 3.13系统级用于 bootstrap Ruby 3.4 x64calibre 的模板引擎需要 Node.js LTSRapydScript 编译器 Strawberry Perl部分依赖的构建脚本 WiX Toolset v7生成 MSI Mesa OpenGLopengl32sw.dllQt 渲染需要1.3 WiX 的隐藏陷阱安装 WiX 后大多数人会直接验证版本号然后继续。别急。WiX v7 引入了一个开源维护费 EULA机制如果不提前接受构建到 MSI 打包阶段可能已经过了 30 分钟才会报错error WIX7015: You must accept the Open Source Maintenance Fee (OSMF) EULA to use WiX Toolset v7正确做法是安装后立即执行wix eula accept wix7 wix extension add -g WixToolset.Util.wixext wix extension add -g WixToolset.UI.wixext我在构建快完成时才发现这个问题白白浪费了一轮构建时间。1.4 RapydScript-NG一个容易忽视的依赖calibre 的 Web 组件阅读器、编辑器 UI使用 RapydScript 编写构建时需要编译为 JavaScript。calibre 内置了一个基于 Qt WebEngine 的编译器但在无头/远程桌面环境下它几乎必然超时TimeoutError: Creating RapydScript compiler took too long解决方案是安装外部编译器npm install -g rapydscript-ng安装后 calibre 的构建系统会自动检测并使用它完全绕过 WebEngine。二、源码与依赖2.1 目录结构我选择了简短的根路径C:\r避免路径过长问题C:\r\ ├── src\ ← calibre 源码 ├── bypy\ ← 构建编排工具 ├── sw64\sw\ ← 预编译依赖2.4GB └── run_build.ps1 ← 构建脚本2.2 克隆源码cd C:\r git clone https://github.com/kovidgoyal/calibre.git src git clone https://github.com/kovidgoyal/bypy.git2.3 预编译依赖包最大的坑calibre 官方 CI 提供了预编译的 Windows 依赖包包含 Qt 6、ICU、OpenSSL、Python、ffmpeg 等全部 C/C 依赖https://download.calibre-ebook.com/ci/calibre7/windows-64.tar.xz文件约 274MB压缩解压后 2.4GB。下载后解压到C:\r\sw64\sw。然而这个包是不完整的。当我满怀信心地启动构建编译 C 扩展一切顺利冻结 Python 字节码也顺利完成直到运行验证测试时ImportError: DLL load failed while importing icu: 找不到指定的模块。ICU——Unicode 国际化组件——calibre 最核心的依赖之一它的 DLL 不在依赖包里。经过逐一排查我发现缺失的文件远不止 ICU缺失文件用途icudt78.dll/icuin78.dll/icuuc78.dll/icuio78.dll/icutu78.dllUnicode 处理espeak-ng.dllTTS 语音合成freetype.dll字体渲染jpeg8.dllJPEG 处理lcms2-2.dll色彩管理brotli*.dll3个Brotli 压缩39 个 SQLite 扩展 DLLFTS 全文搜索等jpegtran-calibre.exe/cwebp-calibre.exe等图像优化工具2.4 解决方案从官方 MSI 中借DLL既然官方发布的安装包能正常运行那它里面一定有这些 DLL。思路很简单# 下载官方 MSI212MBInvoke-WebRequest-Urihttps://download.calibre-ebook.com/9.12.0/calibre-64bit-9.12.0.msi-OutFileC:\r\calibre-official.msi# 管理安装模式解压不需要真正安装Start-Processmsiexec-ArgumentList/a,C:\r\calibre-official.msi,/qn,TARGETDIRC:\r\calibre-extracted-Wait# 从解压目录复制缺失文件$srcC:\r\calibre-extracted\PFiles64\Calibre2\app\binCopy-Item$src\icu*.dllC:\r\sw64\sw\bin\-ForceCopy-Item$src\espeak-ng.dllC:\r\sw64\sw\bin\-ForceCopy-Item$src\freetype.dllC:\r\sw64\sw\bin\-Force# ... 其余文件同理踩坑提醒不要复制libffi-8.dll和sqlite3.dll。它们已存在于依赖包的 Python DLLs 目录中重复复制会导致后续构建时的文件锁定冲突。三、Bootstrap编译 C 扩展cd C:\r\src py.exe setup.py bootstrap --ephemeral这一步会编译约 40 个 C/C 扩展模块使用 MSVC生成 ISO 639/3166 语言代码数据编译翻译文件构建 GUI 资源含 RapydScript → JavaScript下载 CA 证书3.1 GBK 编码炸弹在中文 Windows 上第一个遇到的错误大概率是UnicodeEncodeError: gbk codec cant encode character \ufffd in position 42原因MSVC 编译器输出包含非 UTF-8 字节如路径中的特殊字符Python 将其解码为\ufffd替换字符然后尝试用 GBK 编码输出到控制台时失败。修复$env:PYTHONIOENCODING utf-8$env:PYTHONUTF8 1这两行必须放在所有 Python 调用之前。3.2 RapydScript 编译如果没装外部 rapydscript-ngsetup.py resources会尝试用内嵌的 WebEngine 编译器然后超时。安装外部编译器后问题解决。3.3 completed.json 缺失FileNotFoundError: [Errno 2] No such file or directory: manual/locale/completed.json创建一个空的 JSON 文件即可[System.IO.File]::WriteAllText(C:\r\src\manual\locale\completed.json,{})注意必须是无 BOM 的 UTF-8。PowerShell 的Set-Content -Encoding UTF8会加 BOM导致 JSON 解析失败。四、构建 MSI绕开 VM 管理层4.1setup.py win64为什么不能用按照 calibre 源码中的注释Windows 构建命令是py.exe setup.py win64 --dont-sign --dont-shutdown但在纯 Windows 环境下执行立即报错ValueError: Not a valid SSH URL: C:\r\src\bypy\b\windows\vm原因calibre 的构建架构是为 Linux 主机设计的。setup.py win64会调用 bypy 的 VM 管理层试图通过 SSH 连接到一台 Windows 虚拟机。在纯 Windows 环境下这个 SSH URL 解析直接失败。4.2 正确方式直接调用 bypy绕过 VM 管理层直接调用 bypy 的program子命令C:\r\sw64\sw\private\python\python.exeC:\r\bypyBYPY_ROOTC:\rBUILD_ARCH64BYPY_ARCHwindows-64PERLperlRUBYC:\Ruby34-x64\bin\ruby.exeMESAC:\mesaNODEJSnode program--skip-testsbypy 接受KEYVALUE格式的参数作为环境变量注入program是构建子命令执行完整的init_env → build_c_extensions → freeze → build_launchers → embed_manifests → copy_crt_and_d3d → create_installer → build_portable4.3 必须修改的 bypy 代码bypy 是为 Linux Cygwin 环境设计的在纯 Windows 下有 4 处必须修改修改 1跳过依赖重新安装bypy 的install_packages()会试图清除已解压的依赖目录并重新安装。但 DLL 文件可能被系统锁定导致PermissionError。# C:\r\bypy\bypy\deps.pydefinstall_packages(which_deps,dest_dirPREFIX):ifos.path.isdir(os.path.join(dest_dir,bin))andos.listdir(os.path.join(dest_dir,bin)):print(fDependencies already present in{dest_dir}, skipping install_packages)return# ... 原始代码 ...修改 2禁用 run_shell()构建失败时bypy 会尝试启动交互式 shell 供开发者调试。它硬编码了C:/cygwin64/bin/zsh——一个在纯 Windows 环境下不存在的路径。# C:\r\bypy\bypy\main.py 和 C:\r\src\bypy\init_env.py# 将所有 run_shell() 替换为 pass修改 3跳过缺失的可选工具freeze()函数会复制pdftohtml.exe、pdfinfo.exe等工具。如果依赖包中缺少这些文件原代码直接抛异常。改为存在才复制forxin(pdftohtml,pdfinfo,pdftoppm,pdftotext,...):exe_pathos.path.join(bindir,x.exe)ifos.path.exists(exe_path):copybin(exe_path)else:print(fWARNING: skipping missing optional binary:{x}.exe)修改 4避免 DLL 重复复制某些 DLL如libffi-8.dll同时存在于多个源目录中copybin()会尝试复制两次第二次因文件已存在且被占用而报权限错误defcopybin(x,destenv.dll_dir):dstos.path.join(dest,os.path.basename(x))ifos.path.isdir(dest)elsedestifos.path.exists(dst):return# 已复制跳过shutil.copy2(x,dest)五、构建过程中的其他惊喜5.1 Windows Defender 锁定 DLLbypy 使用C:\t\t作为临时构建目录硬编码在bypy/bypy/constants.py中。构建失败后重试时PermissionError: [WinError 5] 拒绝访问 C:\\t\\t\\build-xxx\\winfrozen\\app\\bin\\amatch.dllWindows Defender 实时保护扫描了构建产物中的 DLL导致文件被锁定无法删除。解决重命名旧目录而非删除创建新目录Rename-ItemC:\t\tC:\t\t_oldNew-Item-ItemType Directory-PathC:\t\t-Force长期方案将C:\t\t和C:\r加入 Windows Defender 排除列表。5.2 构建测试失败使用--skip-tests跳过的测试包括pyzstd模块缺失Zstandard 压缩非核心功能calibre_extensions.winsapi缺失Windows 特有 API 绑定7z API 变更导致的兼容性测试失败WEBP 图像透明度处理测试FTS 全文搜索测试这些都不影响 calibre 的核心功能书库管理、格式转换、阅读器。5.3 便携版签名MSI 生成成功后bypy 继续尝试构建便携版。便携版需要代码签名KeyError: SIGN_SERVER_PORT这需要一台运行签名服务的服务器。对于个人构建MSI 已经足够。便携版可以忽略。六、最终构建脚本经过反复调试最终稳定可用的构建脚本如下# C:\r\run_build.ps1# Calibre Windows 构建脚本# 编码设置中文 Windows 必须$env:PYTHONIOENCODING utf-8$env:PYTHONUTF8 1# 配置$ROOTC:\r$SRC$ROOT\src$SW$ROOT\sw64\sw$PYTHON$SW\private\python\python.exe# 环境变量$env:BUILD_ARCH 64$env:BYPY_ROOT $ROOT# PATH仅保留必要路径$env:PATH $SW\private\python;$SW\private\python\Lib\site-packages\pywin32_system32;$SW\bin;$SW\qt\bin;C:\Ruby34-x64\bin;C:\Program Files\nodejs;C:\Windows\System32;C:\Windows# 前置检查if(-not(Test-Path$SW\qt\bin\qmake.exe)){Write-Error依赖包未就绪请先解压 windows-64.tar.xzexit1}# 构建Set-Location$SRC$PYTHON$ROOT\bypyBYPY_ROOT$ROOTBUILD_ARCH64BYPY_ARCHwindows-64PERLperlRUBYC:\Ruby34-x64\bin\ruby.exeMESAC:\mesaNODEJSnodeprogram--skip-testsif($LASTEXITCODE-eq0){Write-Host构建成功-ForegroundColor GreenGet-ChildItem$ROOT\sw64\dist\*.msi}else{Write-Host构建失败 (exit code:$LASTEXITCODE)-ForegroundColor Red}执行powershell -ExecutionPolicy Bypass -File C:\r\run_build.ps1七、构建流程全景图┌─────────────────────────────────────────────────────────┐ │ 工具链安装 │ │ VS 2026 / Python / Ruby / Node / WiX / Mesa / RS-NG │ └────────────────────────┬────────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────────────┐ │ 源码准备 │ │ git clone calibre → C:\r\src │ │ git clone bypy → C:\r\bypy │ └────────────────────────┬────────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────────────┐ │ 依赖准备 │ │ 下载 windows-64.tar.xz → 解压到 C:\r\sw64\sw │ │ 从官方 MSI 提取缺失 DLL → 补充到 sw64\sw\bin │ └────────────────────────┬────────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────────────┐ │ Bootstrap │ │ py.exe setup.py bootstrap --ephemeral │ │ 编译 C 扩展 / 翻译 / GUI 资源 / RapydScript │ └────────────────────────┬────────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────────────┐ │ 修改 bypy │ │ 4 处代码修改见第四节 │ └────────────────────────┬────────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────────────┐ │ 执行构建 │ │ run_build.ps1 → bypy program --skip-tests │ │ init_env → freeze → launchers → WiX → MSI │ └────────────────────────┬────────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────────────┐ │ 产物 │ │ C:\r\sw64\dist\calibre-64bit-9.12.0.msi (195 MB) │ └─────────────────────────────────────────────────────────┘八、踩坑清单速查#错误信息原因解决1UnicodeEncodeError: gbk中文 Windows 控制台编码设置PYTHONIOENCODINGutf-82ImportError: DLL load failed (icu)依赖包缺少 ICU DLL从官方 MSI 提取3error WIX7015WiX v7 EULA 未接受wix eula accept wix74PermissionError: 拒绝访问Defender 锁定 DLL重命名C:\t\t加排除5ValueError: Not a valid SSH URLsetup.py win64需要 VM直接调用 bypyprogram6FileNotFoundError: zshbypy 尝试启动 Cygwin shell禁用run_shell()7TimeoutError: RapydScriptWebEngine 无头超时安装外部rapydscript-ng8KeyError: SIGN_SERVER_PORT便携版需要签名服务器忽略MSI 已生成9PermissionError: libffi-8.dllDLL 重复复制copybin()加存在检查10FileNotFoundError: completed.json翻译元数据缺失创建空{}文件九、经验与思考9.1 calibre 的构建架构calibre 的构建系统bypy是一个相当复杂的编排工具它的设计假设是Linux 主机作为控制中心Windows/macOS 虚拟机作为编译目标SSH作为通信通道Cygwin/zsh作为 Windows 上的 shell这种架构对官方 CI 很合适但对想在本地 Windows 上直接构建的开发者极不友好。bypy 中大量硬编码的路径C:/cygwin64/bin/zsh、C:\t\t和隐式假设依赖包完整、签名服务器可用都需要逐一绕过。9.2 依赖包的不完整性windows-64.tar.xz是 CI 流水线的中间产物它假设后续步骤会补充缺失文件。但对于脱离 CI 环境的独立构建者来说这个后续步骤并不存在。从官方 MSI 中提取 DLL 是一个务实的解决方案——毕竟官方产物就是最权威的正确文件集合。9.3 中文 Windows 的特殊性GBK 编码问题是中文 Windows 开发者的老朋友了。任何涉及子进程输出捕获的 Python 构建系统在中文 Windows 上都可能遇到这个问题。PYTHONUTF81是 Python 3.7 提供的核选项强制所有 I/O 使用 UTF-8一劳永逸。9.4 构建耗时参考阶段耗时8 核 CPU依赖包下载 解压15-30 分钟BootstrapC 扩展编译10-15 分钟Freeze 打包5-10 分钟WiX 生成 MSI3-5 分钟总计约 40-60 分钟十、写在最后编译成功了然后呢安装自己编译的 MSI打开 calibre——界面是英文的语言选择里也只有 English没有中文选项。排查后发现根因bootstrap 阶段翻译仓库kovidgoyal/calibre-translations克隆失败网络问题静默跳过导致locales.zip和stats.calibre_msgpack从未生成。calibre 通过stats.calibre_msgpack判断可用语言列表文件不存在就只显示英文。修复很简单cd C:\r\src git clone--depth1 https://github.com/kovidgoyal/calibre-translations.git translations py.exe setup.py translations编译完成后resources/localization/下出现 17MB 的locales.zip重新构建 MSI安装后中文语言选项正常出现。这个坑提醒我bootstrap 后一定要检查translations目录是否存在。下一步解决中文文件名编译跑通只是第一步。真正的目标是修改 calibre 的文件名生成逻辑让导出的电子书保留中文文件名。这涉及到calibre/utils/filenames.py中的ascii_filename()函数——它会将所有非 ASCII 字符转写为拼音。我的计划是增加一个选项允许用户选择保留原始 Unicode 文件名。现在编译环境已经就绪后续改一行代码、重新打包只需要再跑一次run_build.ps140 分钟后就能拿到新的安装包。这正是本地编译的价值。关于 AI 辅助构建这次经历让我对 AI 辅助开发有了新的认识。calibre 的构建系统复杂且文档稀缺错误信息往往指向不相关的方向。AI 的优势在于不会疲倦凌晨三点遇到PermissionError依然能冷静分析知识面广同时理解 MSVC、WiX、Python 打包、Qt 构建系统不怕试错一个方案不行立刻换下一个没有心理负担而人类的不可替代性在于知道为什么要做这件事以及在 AI 卡住时提供那一个关键的手动操作。致谢与期望在纯 Windows 环境下编译 calibre 并非不可能但确实需要绕过许多为 CI 环境设计的假设。整个过程最大的挑战不是技术难度而是信息的缺失——官方文档假设你使用 Linux 主机bypy 代码假设你有 Cygwin依赖包假设你在 CI 流水线中。希望这篇文章能为后来者节省一些时间。如果你也成功在 Windows 上编译了 calibre或者对中文文件名问题有想法欢迎交流。附录完整文件清单构建完成后的关键文件位置C:\r\ ├── src\ ← calibre 源码 ├── bypy\ ← 构建编排已修改 ├── sw64\ │ ├── sw\ ← 依赖库含补充的 DLL │ │ ├── bin\ ← 所有运行时 DLL 工具 │ │ ├── lib\ ← 链接库 │ │ ├── qt\ ← Qt 6 │ │ └── private\python\ ← 内嵌 Python 3.14 │ └── dist\ │ └── calibre-64bit-9.12.0.msi ← 最终产物 ├── run_build.ps1 ← 构建脚本 ├── calibre-official.msi ← 官方 MSI用于提取 DLL └── calibre-extracted\ ← 官方 MSI 解压目录本文基于 calibre 9.12.0 / bypy (2025-07) 版本撰写。后续版本可能有变化但核心流程和坑点大概率相同。