- 开发工具
- CLI
- 代码质量
【免费下载链接】cangjie_tools
仓颉编程语言命令行工具,包括仓颉包管理工具、仓颉格式化工具、仓颉多语言桥接工具及仓颉语言服务。
本文基于仓颉模块演进兼容性规范(版本号 0.1.4,发布时间 2025-03-25,见 Chapter_00_Cover.md)的"概述"章节,解析仓颉模块(仓颉最小发布单元)在演进时对下游使用者的 API 兼容与 ABI 兼容承诺边界、两项明确的"例外豁免"、以宏展开与类型推导完成后的代码为基准的判定原则,并结合cjcompat(Cangjie Compatible)工具在开源仓库中的源码实现,说明该规范是如何被落地的、如何在本地执行一次真实的兼容性检查。
规范背景与文档结构
本规范定义的核心问题:对一个仓颉模块,在符合哪些条件的代码修改情况下,能对下游使用者提供兼容性承诺。它同时作为仓颉工具链中配套工具CJCOMPAT的实现规格文档(Specification)——即仓库中 cjcompat 子项目所依据的设计蓝本。
规范采用书册式结构,目录见 SUMMARY.md:
- Chapter_02_Overview.md:概述,即本文核心内容——目标、适用范围、豁免边界与判定基准;
- Chapter_03_Rules.md:兼容性规则正文,按"全局变量 / 全局函数 / struct / enum / class / interface / extend / import / typeAlias / package"逐类给出 API 与 ABI 两列的兼容/不兼容判定表;
- Chapter_01_Change_Log.md:修订记录,当前唯一一条记录为 2025/03/25 的 0.1.4 版(增加修订记录;区分 struct/class 中不同可见性的成员变量规则)。
参考本规范前,规范要求读者具有相应的仓颉语言基础知识。
兼容性承诺的两层定义
规范将兼容承诺分为两个递进层级,两者都以"下游使用者"的构建结果为承诺对象:
| 层级 | 定义 | 承诺强度 | |-|-|-| |API 兼容| 改动后保证下游代码重新编译能正常完成 | 源码级兼容,允许重新编译下游 | |ABI 兼容| 在 API 兼容的基础上,进一步保证下游代码在不重新编译的情况下可以直接重新链接 | 二进制级兼容,下游产物无需重编 |
理解这一分层的关键在于:API 兼容只承诺"源码还能编过去",而 ABI 兼容要求下游的二进制产物可以原封不动地与新版模块链接。任何一项兼容性破坏都会同时向上波及(ABI 破坏必伴随 API 破坏),因此规则表中会出现大量"API 不兼容 / ABI 不兼容"同时成立的条目,以及少量"API 兼容但 ABI 不兼容"的条目。
规范明确豁免的三类场景
在参考业界标准及实践的基础上,概述章节特别划定了三类不属于兼容性破坏的边界场景。这些豁免条款直接决定了cjcompat检查器哪些变更应该被忽略,对库作者规划变更、对工具使用者理解检查结果都有实际意义:
新增 API 引发的符号冲突 / 重载决议失败不属于 API 兼容破坏。具体包括:新增全局变量、新增全局函数、新增自定义类型、新增扩展、已有类型新增父类和父接口、删除和放宽已有扩展中的泛型约束。这些操作虽然是"新增",但在特定条件下可能与下游代码产生同名符号冲突,或导致函数重载决议失败——规范明确将其排除在 API 兼容破坏之外。
FFI foreign 函数名称冲突不属于 ABI 兼容破坏。仓颉支持 FFI 操作,若新版本新增了对 foreign 函数的依赖,而该函数名恰好与其他模块依赖的 foreign 函数重名导致链接失败,这不属于 ABI 兼容破坏。
判定基准的特殊性(第 3、4 条"特别说明"):
- 由于仓颉支持宏和条件编译,二者会导致实际参与编译的代码与源码有差别,因此文中介绍的兼容性规则均以完成宏展开和条件编译处理后的代码为判定基准;
- 由于仓颉支持自动类型推导,文中涉及的类型均以自动类型推导完成后的类型结果为判定基准。
这两条基准意味着:阅读规则表时看到的"类型""声明",指的都是编译前端处理后(宏已展开、条件编译已求值、类型已推导完成)的形态,而非源码书写形态。
CJCOMPAT:规范的可执行实现
规范概述中声明"本规范也同时作为 CJCOMPAT(仓颉工具链中提供的兼容性检查配套工具)的实现规格文档"。在开源仓库中,这一角色由cjcompat子项目承担,其目录与职责见 开发者指南:CheckersAndRules(检查规则)、CjoLoader(cjo 文件解析)、Diff(AST 差异识别)、Dsl(公共模块)、Logger(检查结果)、Option(命令行选项)。
命令行用法与参数
cjcompat通过对比一个仓颉库两个版本的.cjo文件(cjc 编译生成的包文件)来判定兼容性是否变更。基本调用格式(cjcompat -h帮助输出):
Usage: cjcompat --old /path/to/old.cjo --new /path/to/new.cjo [--api] [--abi] [--import-path /path] Options: -h Show usage eg: cjcompat -h --old <value> Path to the .cjo file corresponding to the old version of a Cangjie library. --new <value> Path to the .cjo file corresponding to the new version of a Cangjie library. eg: cjcompat --old filePathOld --new filePathNew --import-path <value> Add .cjo search path. --api Enforces rules for API compatibility. --abi Enforces rules for ABI compatibility. eg: cjcompat --old filePathOld --new filePathNew --api三种典型用法:
# 默认:API 与 ABI 规则都检查 cjcompat --old old.cjo --new new.cjo # 仅检查 API 兼容规则 cjcompat --old old.cjo --new new.cjo --api # 仅检查 ABI 兼容规则 cjcompat --old old.cjo --new new.cjo --abi使用前提与约束(来自 开发者指南 与源码):
--old和--new必须指定且仅允许出现一次,路径必须以.cjo结尾,否则报错退出;- 两个
.cjo必须是兼容版本内cjc编译生成的,否则可能因版本不兼容出现解析错误; - 运行前需设置
CANGJIE_HOME指向 SDK 位置(源码中CheckCangjieHome会强制校验该环境变量,缺失则报Can not find CANGJIE_HOME, please setup CANGJIE_HOME); - 若报
Cannot load file old.cjo,说明包还依赖其他.cjo文件,可通过CANGJIE_PATH环境变量或--import-path指定依赖.cjo的搜索路径; -v可打印cjcompat自身版本及其依赖的 Cangjie 编译器版本;可执行文件依赖cangjie-lsp动态库,Linux 下需将其加入动态库搜索路径(如LD_LIBRARY_PATH)。
从源码看检查流水线
cjcompat的执行入口在 main.cpp,参数解析完成后调用 Cjcompat.cpp 中的CJcompat(),流水线分为五步,与规范的两层承诺一一对应:
- 解析命令行与环境。Option.cpp 中的
HandleOptions()逐一遍历参数:--old/--new/--import-path属于"带参选项",同一选项重复使用会报Argument: xx, used more than once!;--api/--abi属于无参开关。CJCOMPATProcessFlag()中可以看到概述所对应的默认行为——未指定任何规则类型时,enableAPI与enableABI同时置为 true,即 API 和 ABI 规则默认都检查。此外SetCangjieModulesPath()会在CANGJIE_HOME/modules下寻找模块目录,并解析CANGJIE_PATH作为额外搜索路径。 - 加载两个版本的包。
CjoLoader(见 CjoLoader.h)对old.cjo与new.cjo分别调用Load();其内部复用了仓颉编译器前端的Cangjie::CompilerInstance、TypeManager与SourceManager,将.cjo还原为 AST 的Package节点。加载失败时输出Cannot load file并提示检查CANGJIE_PATH/CANGJIE_HOME或--import-path。 - 计算 AST 差异。Cjcompat.cpp 中
Diff diff(*pkg1, *pkg2, ...); diff.CalculateDiff();对两个包做差异识别,产出后续检查器消费的变更集。 - 按优先级执行检查器。
AllCheckers(见 Checker.h)在构造时通过CheckersList.inc中的 X-Macro 展开出全部检查器,RunAll()先按precedence稳定排序,优先级数值小者优先执行,且高优先级检查器一旦判定失败,后续低优先级检查器直接停止,结果交由Logger聚合。 - 输出报告。
Logger{enableAPI, enableABI}只记录命中且属于所启用承诺层级(--api/--abi)的规则条目,最后由logger.Print(...)输出。
规则与检查器的规模
规范概述中的"兼容性规则"在实现中被形式化为两级结构:
- 规则(Rule):Rule.h 中的
RuleKind枚举由RulesList.inc展开定义,共 310 余条规则,每条规则在清单中显式标注它属于 API 层还是 ABI 层(IS_API/IS_ABI标志),这正是概述中"API 兼容 / ABI 兼容两列判定表"的代码化体现; - 检查器(Checker):CheckersList.inc 中注册了 94 个检查器(如
PackageDeclChanged、VarDeleted、FuncParametersChanged、ClassInstanceMemberFuncParamsOrRetTypeChanged等),每个检查器可覆盖多条规则,实现分布在 src/CheckersAndRules 的 10 个编译单元中; - 特例佐证豁免条款:
PackageDeclChanged检查器(优先级 50,全局最高)的注释明确指出"修改包名会导致所有声明的 mangled name 不同,破坏新旧版本声明的对应关系"——这类跨声明关联的判定,正体现了概述中"以完成类型推导后的类型为判定基准"的原则所要求的深度比对。
与规则章节的衔接
概述章节给出的是承诺的定义、豁免的边界与判定的基准;具体的"何种变更属于破坏"则全部列在 Chapter_03_Rules.md 的判定表中,按声明类别组织:全局变量变更、全局函数变更、struct、enum、class、interface、extend、import 语句、typeAlias 语句与 package 声明语句,每类再细分"新增和删除场景 / 修改场景 / 修改定义位置",修改场景进一步下钻到注解、编译标记(如@Deprecated)、修饰符、泛型参数与约束、成员变量、成员函数、构造函数等子节。阅读建议:先以本概述建立"承诺—豁免—基准"三层框架,再对照 Chapter 03 的表格理解具体场景,最后可用cjcompat对真实库的两个版本.cjo做一次实测,观察Logger报告中的规则命中情况。
小结
- 兼容性承诺分两层:API 兼容保证下游可重新编译,ABI 兼容在此之上保证下游产物不重编即可重新链接;
- 新增 API 导致的符号冲突/重载失败、FFI foreign 函数名冲突,均被规范明确豁免,不属于兼容性破坏;
- 一切判定以宏展开、条件编译处理和类型推导完成后的代码为基准;
cjcompat是该规范的可执行实现:--old/--new定位两个版本.cjo,--api/--abi选择承诺层级(默认双检),底层由CjoLoader → Diff → Checkers → Logger流水线完成检查,规则与检查器的完整清单分别见RulesList.inc与CheckersList.inc。
- 开发工具
- CLI
- 代码质量
【免费下载链接】cangjie_tools
仓颉编程语言命令行工具,包括仓颉包管理工具、仓颉格式化工具、仓颉多语言桥接工具及仓颉语言服务。
相关推荐
cangjie_tools 中的 cjcompat:基于 Library Evolution 规范的仓颉 ABI/API 兼容性检查工具详解
cangjie_tools 中的 cjcompat:基于 Library Evolution 规范的仓颉 ABI/API 兼容性检查工具详解 cjcompat
开发工具CLI代码质量Kronos金融预测模型实战指南:双路径部署深度解析与性能调优
Kronos金融预测模型实战指南:双路径部署深度解析与性能调优 在金融科技快速发展的今天,量化交易团队面临着模型部署的多重挑战。Kronos作为专为金融市场设计
人工智能大模型基础模型预训练金融科技lx-music-desktop 更新日志解读:版本规范、发布流水线与兼容性承诺
lx music desktop 更新日志解读:版本规范、发布流水线与兼容性承诺 CHANGELOG.md https://link.gitcode.com/i
桌面应用音视频前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考