C++ language server ccls一周年

2018年4月1日我写了ccls: a fork of the C++ language server cquery,宣告ccls诞生。如今一周年,有必要记录下这一年的点点滴滴。

4月1日我fork出来,原因是我需要一个满足自己口味的C++ language server。修改cquery几个月,期间也曾学习过rtags ycmd clang_completer等clang-based工具,对如何打造language server颇有心得。由于和原作者理念差异,在cquery引入很多改动是掣肘的。因为理念不合被取消commit权限,就决定自己新建一个项目。另外cquery作者也在聊天室里提到他一直在等待clangd。而我的感觉是,clangd采用的并非最佳模型,或至少,不应该是用户唯一的选择。其实当时我也试探性想贡献一下clangd的fuzzy matcher,发现想法不合做贡献就要耽误很多时间,很多地方的设计理念也不合。所以还是决定从cquery改,看如何闯出一番天地。当时觉得cquery已经很成熟了(看着它的star涨到超过1000,在LSP、emacs-china等地被人越来越多地讨论,也会有很有成就感(虚荣心)),我另起炉灶也就是自娱自乐而已。

说干就干,从一个描述为“Resurrection of ccls”的commit开始,3月31日提交了8个描述只有一个点的commits(它们改了116个文件!)。接下来在cquery的代码库上做了大量清理工作:删除第三方库、精简代码、删除过度的抽象、合并拆得过碎的文件,当然还有改名、删除waf构建系统、删除无用blob、……自己用得舒服了再推己及人,希望别人也能用上满足自己的虚荣心。其实说穿了就是和cquery/clangd抢夺用户。精简并不能改变用户习惯,带来用户。我很快瞄准了cquery用户的一个痛点:auto-index pipeline的稳定性。最大的问题是,保存文件后容易导致重复或丢失的references。如果.h.cc没有放在同一个目录,更容易出问题。

这个组件我一直知道被设计得过分复杂,但我自己对这部分理解也不透彻。cquery把索引信息分成并行索引时用的IndexFile和加载到全局数据库里的QueryFile是个非常不错的设计。一个文件修改,触发索引,获得IndexFile,合并回主线程的全局数据库的过程其实很脆弱,出过很多问题。在交叉索引信息的基本单元(标识符,由Clang Unified Symbol Resolution表示)的表示从字符串改为8字节hash后,pipeline中的几个步骤已经没有太大意义了。query.cc用的MergeableUpdate也是有问题的。能改善一点性能(indexer threads分担少量main thread任务),但cquery的全局索引更新模型是不满足交换律的,这么做也会在一些情况下会导致索引丢失,这里还有个merge sort用的merge的bug,直到7月才修好。

五月初我把pipeline翻新了。初时内存占用会稍高,但稳定性提升很多,不会发生保存多次导致索引确实或重复的问题。到9月已经非常稳定了。

等到逐渐有人关注了,有时候不得不回答一些讨厌的问题:和cquery的区别是什么、有cquery/clangd(clang官方项目)为什么还要弄ccls。我现在仍然讨厌这类简短而内容空洞问题(除非提问者能证明自己思考过了)。在此引用eglot作者João Távora的话,当别人问起有lsp-mode为什么还要再造一个client时,他回答:“You see, I added those paragraphs in the first place because some people ask, sometimes indignantly, why I write new things and don't contribute to an existing project. Apparently "because I want to" and "to have fun" aren't acceptable answers. But for me, they are still totally great answers, so if you are having fun writing lsp-mode.el I can only wish you well.”

ccls对于我的价值:

  • 最重要的是给了我好好学习clang的理由。2017年给llvm全家桶贡献了几个patches,到年底依然只有10个不到,后来慢慢在这个领域找到兴趣。维护ccls能逼迫我认真钻研clang工作机制,以及多灌水(commit)。到现在,在clang里灌了69个,llvm其他地方都加起来灌到400多。
  • 迫使我了解Language Server Protocol各个方面的细节。说实话,我不喜欢这个协议,很多地方设计得过于臃肿、浪费,为了所谓的VSCode兼容性做了很多让客户端、服务端都痛苦的设计。其实无可厚非,这个协议本来就是Microsoft用于VSCode的,由于用户的请求才被开源出来,这和LLVM、Rust那些社群的管理模式是很不一样的。话虽如此,比起之前几代产品还是好不少,汇聚起来了一个真正帮用户改进编程体验的松散的社群。
  • 一个展示平台。
  • 被队友、对手、主办方(??)自发关注到(尤其是DEF CON 26 CTF上)。在LLVM Dev Meeting上一些闲聊的人谈到。
  • 有厂商想给我donation但我没要。
  • 有厂商想用ccls做离线分析。
  • 被spacemacs doom-emacs采用。进了v8的.gitignore
  • 有人给AOSC、Mac OS X Homebrew、FreeBSD、Nix、Void Linux、Fedora等打包。另外还有我自己给Arch User Repository贡献的。
  • 被GNU make维护者Paul Smith使用了。help-make是我订阅过最久的邮件列表,看完过Managing Projects with GNU Make。OI比赛时我也是用GNU make编译程序、运行测试的。这个带我的感觉很不一样:一直以来写的软件影响着你的人,现在也被你影响了。
  • 有朋友真的会关心我这个东西做了怎么样,和我探讨实现。

2018年7月初从“First draft: replace libclang indexer with clangIndex”开始,用Clang C++ API重写了indexer。接下来把project.cc中用的libclang compile_commands.json解析换成clangTooling。随后用Clang C++ API重写了completion/diagnostics(从clangd和ASTUnit学了很多)。最后终于把所有libclang组件移除。

用了libclang很久,发现了它的很多问题。它在早年clang API不够稳定时确实立下了功劳,最初应该是Apple Xcode用,后来催生了clang_completer、rtags、irony-mode、iycmd clang_completer等工具。但它的限制也很明显,Clang C++ API茫茫多,libclang作为稳定的C API包不完API,因此它只实现了一些较高层次的API,某些API如(find declaration, find definition)实际上包含了很多逻辑,如果使用场景和API设计者不同,就会发现很不灵活。

10月把以前移除的textDocument/documentLink textDocument/rangeFormatting等小众功能都加了回来,在功能上也不输了。

最近几个月都挺头疼的,日均一个新增issue。不少issue提问的人随手一写,好在最近引入了GitHub issue template情况有所改观,但依然能注意到非常多不懂脑筋的人,让我很反感。我对于issue管理比较粗暴,通常回复一下就关闭,不会像一些其他项目会等一段时间,关闭时再回复一句话抚慰提问人。我不愿意浪费这些时间。别人也许会这么评价:“It did spawn a fork: https://github.com/MaskRay/ccls, which is actively developed, but the lead is explicitly not interested in supporting community requests, which IMO bodes well for its longevity.”我觉得这挺贴切。我也很感谢即时的反馈,比如某个改动造成了某某发行版编译失败或运行错误,现在都能较为迅速的得到反馈及时改正。有时用户会注意到clang bugs,调试它们的过程也挺有趣。

一些有趣的issues:

  • https://github.com/MaskRay/ccls/issues/30 -fno-rtti导致的ABI mismatch。终于查明了aur/ccls-git会segfault的原因。有个相关的commit让我成了GCC contributor:)
  • https://github.com/MaskRay/ccls/issues/71 GCC 5 dual ABI导致的ABI mismatch。
  • https://github.com/MaskRay/ccls/issues/186 调整wiki结构。新的结构确实更清晰了,不过我吐嘈刚改完的样子有些罗嗦……现在比较简洁了。很多项目的文档我都觉得太长不想读。
  • https://github.com/MaskRay/ccls/issues/355 根据lib{clang,LLVM}*.a获得.so

20180513的“Congratulations to Tea Deliverers”(恭贺Tea Delievers再度入围DEF CON CTF决赛),20180521的“Maker Faire”,20180731的“Real World CTF”(给本来宣传力度就很大的活动造势)。