什么是 Catter?
Catter 是一个构建过程拦截工具。它通过钩子拦截构建过程中的子进程创建,捕获编译和链接命令,并允许用户使用内嵌的 QuickJS 引擎编写 JavaScript 脚本来处理这些命令。
动机
Catter 是为 clice 这个新的 C++ 语言服务器而创建的。语言服务器依赖编译数据库 (CDB) 来了解每个源文件的编译方式,但获取 CDB 往往非常困难:
- 构建系统支持有限 -- CMake 仅在使用 Makefile 或 Ninja 后端时才能生成 CDB。许多其他流行的 C++ 构建系统完全没有原生 CDB 支持。
- 构建目录不可访问 -- 当 C++ 代码作为 Python 包的一部分进行构建时,构建目录由外部管理,生成的 CDB 文件无法获取。
- 代码生成器需要完整构建 -- 像 LLVM 这样的项目使用
tablegen等工具在构建时生成头文件。这些文件通过-I标志在编译命令中被引用,因此在完整构建之前,语言服务器无法正确解析 include 路径——即使你只是想阅读代码。
目前没有跨平台工具能够完整解决上述所有问题,这就是我们创建 catter 的原因。
工作原理
Catter 的方式类似于 Bear 和 scan-build。它使用平台特定的钩子(Linux 上的 LD_PRELOAD、macOS 上的 DYLD_INSERT_LIBRARIES、Windows 上的 DLL 注入)拦截子进程创建。每个被拦截的命令会发送到一个决策守护进程,该进程通过 QuickJS 运行用户提供的 JavaScript 脚本,决定如何处理每条命令。
核心功能
编译数据库生成
最主要的用例。Catter 可以跨任意构建系统捕获编译器调用,生成标准的 compile_commands.json,无需构建系统插件或特殊配置。
链接器分析
通过捕获链接器命令,catter 可以重建构建目标之间的依赖图。这对 C++20 模块支持至关重要:C++ 标准规定一个程序中同名模块最多只能有一个,而"程序"由目标(库、可执行文件)定义。这些目标信息在 CDB 标准中是缺失的,但 catter 可以从链接器命令中推断出来。
伪编译(计划中)
不将编译任务转发给真正的编译器,catter 将能够生成占位目标文件。这使得构建系统可以正常运行而无需实际编译,从而在极短的时间内生成完整的 CDB。代码生成器(如 tablegen)仍然会正常构建——catter 会分析依赖关系以确定必须真正编译的最小工具集。
构建性能分析(计划中)
捕获构建过程中的进程时间、持续时间和父子关系,可以在浏览器中可视化渲染,帮助分析构建并行度和识别性能瓶颈。
自定义脚本修补
借助内嵌的 QuickJS 引擎,用户可以编写 JavaScript 脚本来动态修改、重定向或注入构建命令的逻辑——无需修改原始构建文件。
架构概览
Catter 由三个部分组成:
- HOOK -- 注入到进程中的平台特定库,用于拦截子进程创建。
- PROXY(
catter-proxy)-- 管理钩子注入并充当编译器包装器,将被拦截的命令转发给决策守护进程。 - DECISION(
catter)-- 主守护进程。运行 JS 运行时,接收来自 proxy 的被拦截命令,并决定如何处理每条命令。
关于架构和 IPC 流程的详细介绍,请参阅设计文档。