拆开 LibreOffice 的 151 个模块,40 年的文件格式都还活着
- LibreOffice 代码可追溯至 1985 年 CP/M 上的 StarWriter,四十年连续维护、从未推倒重写
- sal 层只导出 C ABI、VCL 预留多平台渲染插槽,新平台以新后端形式接入而不触发内核重写
- UNO 组件模型支撑跨语言跨进程调用,靠 24 套手写 ABI 桥与 binaryurp 协议实现,维护成本高昂
- 自研 gbuild 构建系统钉死 144 个外部依赖版本,162 个 clang 编译器插件将代码纪律编进构建流程
- 海量老旧文件格式兼容构成护城河,但文档内核尚无原生协同编辑能力,构建成本与三重授权也构成门槛
北京时间今天下午两点十分,GitHub 上那个叫 core 的仓库又同步进来一波提交。这是 LibreOffice 在 GitHub 上的家,4331 个 star,935 个 fork,主语言 C++。数字放在 GitHub 上不算起眼,可这个仓库的 git 包体积接近 7 GB,顶层目录 151 个,README 自己说模块有两百来个。9 月头三天,从 Gerrit 同步进来的提交就超过了一百个。
说真的,star 数在这里是最没信息量的指标。
我把提交历史翻到最底下,最早的一条停在 2010 年 9 月 29 日,信息写着 make build.pls message,改的是构建脚本里的一句提示文案。同一天另一条提交,Ported fontconfig-cache-pre-substitution.diff from ooo-build,把 go-oo 分支攒的补丁搬了过来。那是 fork 之夜,OpenOffice.org 社区刚宣布离家出走,Document Foundation 成立。git 历史从这一夜开始,但代码的岁数比这大得多。它一路穿回 2000 年开源的 OpenOffice.org,穿回 1999 年被 Sun 收购的 StarDivision,按官方年表,终点是 1985 年跑在 CP/M 上的 StarWriter。
从 CP/M 到浏览器里的 WASM,一份代码连续维护了四十年。今天拆它,我只想搞清楚一个问题,它凭什么没散架。
移植性是刻在骨子里的,先看三明治的底层
StarDivision 当年是商业软件公司,一份代码要同时卖到 DOS、Windows、Mac、OS/2、Solaris,移植性不是架构师的情怀,是生死线。这个基因在仓库里留下了三层结构,最底下是 sal,System Abstraction Layer。
sal 的 README 只有寥寥数行,却是整座大厦的地基。rtl 负责平台无关的字符串,osl 管线程、动态加载、进程间通信这些脏活。注意它只导出 C API,C++ 只留少量内联方法。为什么,因为 C ABI 是所有平台上唯一稳定的东西,字符串和线程这种要跨四十年的模块,必须建在最不会变的东西上。
再往上是 VCL,Visual Class Library,管所有控件和基础渲染。它的 README 特意声明别跟 Borland 的同名缩写搞混,顺手把后端目录摊了一排,win、osx、ios、android、qt5、qt6、unx、headless,外加 macOS 和 iOS 共用的 quartz。同一个抽象,十个左右的落地点。
你想想看这个设计在今天意味着什么。2010 年 fork 的时候没有 iPhone 版,没有浏览器版,可后来 iOS 版和 WASM 版都长出来了,长在 VCL 预留的插槽上,业务代码不用动。README 的平台基线写得很直白,Windows 10 起步,macOS 11 起步,iOS 只支持 LibreOfficeKit,浏览器版要 Qt 5.15 加 Emscripten。Collabora 那套在线 Office,就架在 LibreOfficeKit 这层出口上。
光靠文字讲分层有点干,我把这四十年的地基画了一张全景。
从下往上看,sal 管 C ABI,VCL 管渲染插槽,UNO 管组件边界,最上面才是 Writer、Calc 这些日常,而构建期那排设施根本不随产物运行。
新平台不触发重写,这是它没散架的第一个秘密。
UNO,一个 90 年代的梦,还在给 2026 年交货
如果说 sal 和 VCL 是务实派的抽象,UNO 就是理想派的作品。Universal Network Objects,StarDivision 在 90 年代设计的组件模型,想法是所有功能做成组件,组件之间跨语言、跨进程、跨机器调用,C++ 写的实现,Java、Python 乃至宏语言 StarBasic 都能调。
这套东西今天看是 CORBA 时代的审美,可它还在跑。你写一个 LibreOffice 宏,脚下踩的就是 UNO。跨语言调用的价格标签,明晃晃挂在仓库里,bridges/source/cpp_uno 目录下,24 套手写的 ABI 桥,按编译器和平台一字排开。
点开 gcc3_linux_x86-64 那套,12 个文件。uno2cpp.cxx 16 KB,把 UNO 侧的调用翻译成 C++ 调用,cpp2uno.cxx 20 KB,反着来,把 C++ 对象包装成 UNO 组件,except.cxx 管异常在两个世界间的翻译,rtti.cxx 对齐运行时类型信息。最狠的是 call.s,一段手写汇编,负责在虚调用时把参数按 System V ABI 摆进寄存器。
跨进程的版本还有专门协议,binaryurp 模块,Binary UNO Remote Protocol,把 UNO 调用压成二进制在线上跑。
一次调用到底走哪条路,画出来更直观。
同进程走汇编桥直转,跨进程压成 binaryurp 报文跨过进程边界,两条路最后都落到同一个组件模型上,调用方感知不到差别。
坦白讲,这套东西的成本肉眼可见。bridge 目录里躺着 gcc3_linux_alpha、gcc3_linux_hppa、gcc3_linux_m68k、gcc3_linux_ia64,这些 CPU 早就停产了,代码还留着。24 套 ABI 桥,每换一个编译器大版本就要重新核对一遍,这是当年组件梦收的税。
有意思的是 README 对自己 API 的态度。它给贡献者指了两条路,改内核代码,或者用 SDK 写扩展,然后原话承认,SDK 那条路 much less recommended,理由是脚本 API 有 arbitrary limitations。一份 README 劝你别太依赖它自己的公开 API,这份诚实我在别的仓库很少见到。
护城河不在功能列表里,在构建系统和编译器插件里
两百个模块怎么不散,答案的一半在 solenv/gbuild 里。LibreOffice 养了一套自研构建系统,早年的 dmake 撑不住,fork 之后社区重写了 gbuild,AllLangHelp.mk、ComponentTarget.mk、CompilerTest.mk,每类模块一个模板,声明式地描述构建。模块越多,这套东西越显得必要。
外部依赖的处理更极端。download.lst 一个文件,720 行,144 个压缩包,从 boost、cairo、curl 到一整排 Noto 字体,全部构建时自己拉取、自己校验 sha256。构建系统不信任你的系统库,版本全部钉死。这套做法今天会被嫌笨重,但它换来确定性,任何一台机器上构建,拉下来的都是同一份字节。连字体都自带,文档排版才不依赖用户机器装没装 Calibri,仓库里那颗 Carlito 字体,就是 Calibri 的度量兼容开源替身。
另一半答案在 compilerplugins 目录。clang 子目录下躺着 162 个 cxx 文件,一整排自研编译器插件,cstylecast 禁 C 风格强转,constparams 抓本该 const 的参数,cow_wrapper 检查写时复制的误用,includeform 连头文件用尖括号还是双引号都管。规则不写在贡献文档里靠自觉,是编进编译器,违规直接构建失败。
我一直觉得,这才是大龄代码库真正的护城河。功能谁都能加,能把 40 年的代码纪律按在每一次提交上,靠的是让编译器当守门员。
老格式的坟场,也是别人翻不过去的墙
download.lst 里最有意思的其实是几个小名字。ABW,AbiWord 的文档格式。CDR,CorelDRAW。EBOOK,一串电子书格式。还有 ETONYEK,把 Keynote 这个词倒着拼写,苹果 iWork 文档的导入库。
这就是 LibreOffice 真正的墙,格式的坟场。
WordPerfect、三十年的 .doc 和 .docx、连 StarOffice 时代自家的老文档,它都认。商业软件死了,格式就死了,只有这个代码库把每一代办公软件的遗产都背着走。
微软其实不需要这样。Word 每换一版格式,整个生态会跟着它升级。开源办公套件没有这个特权,用户拿一个 1998 年的 .xls 找你,你说打不开,他就回微软那边去了。所以兼容性在这里是承重墙级别的东西,一排过滤器库加一排测试文档,一版一版堆出来的。
别急着抄这份作业
拆到这里,很容易得出「学它」的结论。先看几个我自己挖到的坑。
协同编辑。根目录藏着一个 README.yrs,讲的是用 yrs 这个 CRDT 库给 Writer 批注做实时协同,实验性质。文档写得直白,为防崩溃,请设环境变量 EDIT_COMMENT_IN_READONLY_MODE=1 并以只读模式打开文档,两个实例之间走一根硬编码的管道通信。你琢磨一下,2026 年了,文档模型层面的原生协同还在这个阶段,Collabora 的在线协同是服务端包了一层的外挂架构,文档内核自己还没长出这个能力。
想给它提代码,流程也是老派的。这个 GitHub 仓库是只读镜像,issue 区整个关着,真实流程是 Gerrit 加邮件列表加 IRC,README 里只能靠一句「we're a friendly mob」找补。
构建成本,实话讲也劝退过不少人。144 个压缩包自己拉,Java 17 是硬依赖,Python 3.11 起步,全量构建以小时计,官方专门写了 LODE 脚本帮你配环境。想给 Writer 修个 bug,从 clone 到第一次构建跑通,一个周末就没了。
还有协议。仓库根目录躺着 COPYING、COPYING.LGPL、COPYING.MPL 三份文件,GPLv3、LGPLv3、MPLv2 三重授权。个人用户无所谓,公司拿去二次分发,这就是要法务过目的东西。
那什么场景该学它。你回到它的处境看,一份要活过每一代平台的代码,Windows 换了几个大版本,手机从不存在到 iPhone 再到 Android,浏览器从插件时代走到 WASM,每一次它都把新平台吸收成 VCL 底下的一层新后端,内核不动。我管这个叫地层架构,平台变迁不触发重写,像地质沉积一样一层层压在稳定的内核上,变化被限制在最上面那条薄薄的沉积带里。
40 年下来,它没有推倒重来过一次。同源的另一支 Apache OpenOffice 留在原地,大版本停在 4.1 系列很多年,当年出去流浪的这一支,活成了 Linux 发行版和欧洲政府桌面的默认选项。
如果你做的是生命周期三五年、只跟一个平台走的产品,这套层压结构是纯粹的负担,光 24 套 ABI 桥就能拖死一个小团队。但如果你的东西要跨过下一轮平台更替还得活着,LibreOffice 是少数被验证过的样本,把不变的东西建在 C ABI 上,把变化的东西隔离成可替换的层,再用 162 个编译器插件守住纪律。
格式的坟场背得越重,别人越翻不过来。这话放在代码库的架构上,同样成立。

评论互动