|
VLink
2.1.0
A high-performance communication middleware
|
本章面向 VLink 贡献者,覆盖从"验证一段通信代码是否正确"到"将其合入主仓库"的完整工程链路。测试与贡献合并为一章,是因为二者构成同一条质量约束的两端:仓库只接收经过验证的代码,"经过验证"的标准由前半部分的测试机制定义,"如何提交"的标准由后半部分的工程规范定义。一条 PR 的可合并性,取决于其测试是否覆盖正常、边界与异常路径,以及其分支、提交、代码风格与文档同步是否合规——前者保证改动正确,后者保证改动可评审、可回溯、可长期维护。
全章按贡献者的实际工作顺序组织:先建立测试体系(§15.1–§15.7),使每一处改动都能在单进程内被端到端验证;再给出贡献规范(§15.8–§15.19),约定分支、提交、PR、代码风格与文档同步。两部分的衔接点是测试要求(§15.15),它把测试能力固化为 PR 的硬性门槛。

VLink 的用法与 API 详见 概述 与 通信模型,本章不复述;构建开关(ENABLE_TEST、ASAN、覆盖率工具链)见 快速开始。
VLink 的测试体系建立在一个关键性质之上:intra:// 后端在单进程内提供完整的发布订阅、请求响应与状态同步语义,无需任何守护进程或网络配置。因此一对通信原语的往返验证可在普通单元测试进程内完成,并因 URL 契约而平滑迁移到跨进程、跨机后端。
通信代码的测试难点在于其执行上下文与跨端点性:回调线程随后端和模式而异,默认 intra queue 异步调度,#direct 则在 publish/invoke 调用线程同步执行;端点的就绪仍存在时序窗口。直接"发送后立即断言收到"会引入与机器负载相关的不稳定结果。
VLink 提供两类机制消除时序不确定性:
| 机制 | 接口 | 作用 |
|---|---|---|
| 就绪等待 | wait_for_subscribers / wait_for_connected / wait_for_value | 阻塞至对端连接或首个值到达,返回 bool 表示是否在超时内就绪 |
| 后端隔离 | intra:// 前缀 | 进程内传输,端点发现为同步操作,无网络抖动 |
测试的标准结构因此固定为三步:**建立端点 → wait_for_* 确认就绪 → 发送并断言**。回调中对计数或捕获状态的读写跨线程发生,须用 std::atomic 或互斥保护。此结构贯穿 §15.3 的全部用例,其稳定性依据汇总于 §15.6。
测试框架采用 doctest,以头文件形式引入,无需链接额外动态库。
| 断言 | 语义 |
|---|---|
CHECK(expr) | 失败时记录并继续执行当前用例 |
REQUIRE(expr) | 失败时中止当前用例 |
CHECK_THROWS_AS(expr, T) | 断言 expr 抛出类型为 T 的异常 |
SUBCASE("name") | 在同一 TEST_CASE 内定义独立执行的子场景 |
测试按传输后端与功能域分文件组织,每个文件以 TEST_SUITE("<后端>-<方面>") 分组,方面取 init / pubsub / method / field / qos / security / error / status 等,可由 --test-suite= 过滤(test/CMakeLists.txt 编译期为测试目标定义 DOCTEST_CONFIG_USE_STD_HEADERS,并扫描每个源文件的 TEST_SUITE(...) 名,按 suite 名拆成多个 ctest 用例)。每个传输后端单独一个文件,无依赖时由 modules/<name>/ 是否构建决定是否纳入。
以下用例均使用 intra://,可直接编译运行,均遵循 §15.1 给出的三步结构。
publish 返回 bool 指示是否成功投递。订阅回调在传输线程触发,计数器须为 std::atomic;发送后保留时间余量供回调完成。
listen的回调入参仅在回调内有效,需外带时先复制;每个订阅者只能调用一次listen。
同步 invoke(req, resp, timeout) 在超时内回填出参并返回 true;async_invoke 返回 std::future,用 wait_for 设置兜底超时以避免在故障下永久阻塞。
当服务端不返回响应(
Server<Req>)时,客户端以cli.send(req)发起单向调用。
字段模型保留最新值。set 写入后,wait_for_value 阻塞至首个值到达,get 返回 std::optional,首次写入前为 std::nullopt。
由于业务代码不感知后端,将上述任一用例的 intra:// 前缀替换为 shm://、dds:// 或 zenoh://,即可在目标传输上重复验证,源码无需改动。跨进程后端的端点发现涉及网络或共享内存初始化,耗时高于进程内传输,相应 wait_for_* 超时应放宽至 500ms–2000ms(依据见 §15.6)。
在 URL 后追加 ?qos=<档名> 即可附带 QoS 策略复测可靠性、历史深度与持久化行为。常用预置档为 event / method / field / sensor / command / static(完整档名以 QosProfile::get_available_qos_map() 为准,另含 alarm / clock / log / parameter / service 等)。
QoS 档语义与自定义见 QoS 配置;传输后端选型见 传输后端与 URL。
核心 C++ 测试目标为 vlink-test,由 ctest 统一调度。完整普通测试 还包括独立的 vlink-c-test 与三个 Python 绑定脚本;这些绑定测试未注册 到 CTest,必须分别执行。C API 测试固定使用 Fast DDS 的 dds://,运行 前将 VLINK_DDS_IP 设为回环地址以避免多网卡选择不稳定。
macOS 将上例的 LD_LIBRARY_PATH 换为 DYLD_LIBRARY_PATH。Windows 使用 .github/scripts/run-windows-ci-tests.ps1 运行 CTest,并执行同名 vlink-c-test.exe 与三个 Python 脚本。Python 绑定构建需要可被当前 解释器发现的 nanobind。
按 suite 或 case 过滤,其余 flag 见 doctest 官方文档:
SOME/IP 测试依赖外部守护进程;在其缺失的环境中以
-tse="someip-*"排除,避免用例挂起。
异步通信测试的不稳定来源于跨线程数据访问与时序假设。遵循以下约束可消除与机器负载相关的偶发失败。
| 约束 | 依据 |
|---|---|
跨线程计数与状态使用 std::atomic 或互斥保护 | 回调在传输线程触发,与断言线程并发访问 |
发送前以 wait_for_* 确认对端就绪 | 端点发现存在时序窗口,盲发可能丢失首条消息 |
| 发送后保留回调余量(进程内 ≥50ms,跨进程 500–2000ms) | 回调投递为异步,断言早于回调将误判 |
async_invoke 的 future 设置 wait_for 超时 | 防止对端故障时永久阻塞测试进程 |
注意此处"保留回调余量"指验证型示例的简化写法;提交到仓库的正式用例须以事件同步替代固定延时,见 §15.15。
仓库内置 coverage 目标,封装 gcov/lcov 数据采集与 HTML 报告生成。
覆盖率工具链与编译标志见 快速开始。
以下转入贡献规范。配套设施已落地:统一格式与检查脚本(tools/format.sh、tools/check.sh)、<tt>.github/PULL_REQUEST_TEMPLATE.md、以及 .github/workflows/ 下齐备的 CI 流水线(ci-lint.yml、ci-test.yml、ci-coverage.yml、release*.yml 等)。请以本章描述的规范为自检基准。

一次 PR 的最小路径如下;各步骤的约束在后续各节展开。
发起 PR 前对照 §15.9 提交前核对清单逐项确认;PR 描述按 §15.12.2 模板填写。
本清单是本章贡献规范唯一权威核对项,其余各节为其展开。未通过的 PR 将被关闭并要求整改。
代码
clang-format、clang-tidy、cpplint、cmake-format、actionlint 五道关均通过、零告警(§15.14);仅本地跑 clang-format/clang-tidy 不足以通过 CI@brief 必需,按实体签名补适用的 @tparam / @param / @return)TODO/FIXME/XXX、注释掉的代码或占位文本.cc 不新增说明性注释;公共头文件 Doxygen 使用英文,examples 教学说明写入对应 README构建
cmake -B build -DCMAKE_BUILD_TYPE=Debug -DENABLE_TEST=ON -DENABLE_TEST_WARN=ON -DENABLE_C_API=ON -DENABLE_PYTHON_API=ON 通过(ENABLE_TEST_WARN 给 vlink 库目标加 PUBLIC 的 -Wall -Wpedantic -Wextra -Werror,并经依赖传播到测试目标;需同时开 ENABLE_TEST 才会编译测试)cmake --build build -j 全量通过;改动 module/CLI 的,对应 ENABLE_* 开关在 ON/OFF 两种状态下均通过build/ 目录与任何二进制产物测试
ctest --test-dir build --output-on-failure 全部通过(§15.15)vlink-c-test 及 languages/python_api/test/ 下的基础、完整、覆盖三个绑定脚本分别通过(§15.5)文档
doc/images/*.drawio 文字过期时,同步更新 .drawio 源并重新导出同名 .png兼容性
提交
| 组件 | 最低版本 | 约束来源 |
|---|---|---|
| CMake | 3.15 | 根 CMakeLists.txt 与项目构建基线 |
| C++ 编译器 | GCC 9 / Clang 10 / MSVC 2019 / Apple Clang 12 | 必须支持 C++17 |
| clang-format / clang-tidy | 14+ | 低版本对 .clang-format 选项兼容性不一致 |
| Git | 2.25 | worktree / sparse-checkout |
| drawio (CLI) | 24.x | headless 导出 PNG(§15.19.1) |
一次性准备(构建细节见 快速开始):
约束项:不对无关文件做全仓格式化,只格式化本次改动文件;不提交 .idea//.vscode/ 等个人配置;不提交 .log/.so/.exe/.a/.pdb 等构建产物。图片、字体等二进制资产仅在产品或文档确实使用时纳入版本控制。
VLink 采用 master + 功能分支模型:master 为保护分支,禁止直推与 force-push;功能分支从 master 起,合并后 30 天内删除;发布分支 release/vX.Y.Z 打完 tag 即删。
维护者另保留长期集成分支 dev,通过专用 /pr 流程合入 master;普通功能分支仍按上述模型提交。
分支名格式 type/summary-issue?:全小写、连字符分词、summary ≤ 50 字符、尽量附 issue 号。
| 类型 | 用途 | 示例 |
|---|---|---|
feat/ | 新功能 / 新 API / 新 module | feat/add-websocket-transport-42 |
fix/ | bug 修复 | fix/shm-loan-leak-103 |
refactor/ | 不改功能的重构 | refactor/split-proxy-server |
perf/ | 性能优化 | perf/intra-queue-lockfree-77 |
docs/ | 仅文档 / drawio | docs/fix-cli-count-88 |
test/ build/ chore/ | 测试 / 构建系统 / 杂项 | build/add-qnx-toolchain |
禁止 tmp/、test-1/、人名前缀等不规范命名。
feat fix refactor perf docs test build ci chore style revertcore base extension proxy viewer webviz cli-NAME module-NAME examples docs cmake;跨多 scope 可省略或用 *update、adjust、misc fixes、cleanup 等无信息量措辞BREAKING CHANGE: <描述 + 迁移提示>;关联 issue 写 Closes: #123 / Refs: #98合规示例:
不合规示例:wip / update / fix bug / misc fixes / cleanup / ‘Merge branch 'master’ into ...`(PR 内不应有 merge commit,用 rebase 替代)。
PR 标题取本次最重要那条 commit 的 subject,使用简体中文并遵循相同规则(含 type/scope、≤ 72 字符):
仓库已提供 .github/PULL_REQUEST_TEMPLATE.md,发起 PR 时会自动套用。 以该文件为准,依次填写 Summary、Type of change、Related issues、 How was this tested 与 Checklist;不适用的说明项可删除。
stale。以追加 commit 推进评审,不以 force-push 覆盖历史。Squash and merge(合成 1 条 commit);多个逻辑独立且各自有单测覆盖的 commit 可由维护者 Rebase and merge;禁止 Create a merge commit。基线为 Google C++ Style Guide 与项目 .clang-format(BasedOnStyle: Google、ColumnLimit: 120):
snake_case.{h,cc}、类 PascalCase、函数/方法 snake_case()、成员 snake_case_、常量 kPascalCase、宏 SCREAMING_SNAKE_CASE、命名空间 snake_case、模板类型形参 PascalCase 加 T 后缀(MsgT、SecT)。#pragma once。using namespace、C 风格强制转换、NULL(用 nullptr)。普通所有权代码避免裸 new/delete;内存资源、placement new 或第三方 ABI 等低层场景沿用相邻实现并明确所有权。if / else if / else 分支体必须使用大括号;if 的语法、初始化语句与条件表达式保持 C++17 可编译,不依赖 C++20 专属语法、类型或 API。catch 或吞掉原始失败。VLOG_*,复杂嵌入格式推荐 CLOG_*,MLOG_* 只沿用相邻模块的 {} 格式;Info 只记录低频正常状态,Warn/Error/Fatal 分别对应可恢复异常、当前操作失败和无法安全继续。.cc 不新增说明性注释、横幅、注释掉的代码或临时 TODO,逻辑通过命名与结构表达;许可证头、NOLINT、LCOV_EXCL_*、GCOVR_EXCL_* 等工具必需指令保留。公共头文件 Doxygen 及其他语言确有必要的代码注释使用英文;examples 教学说明写入对应 README。中文用于 doc/**/*.md、README.md、CHANGELOG.md、examples/**/README.md 等文档资产。当前任务未触及的历史注释不得为统一风格机械清理。
公共入口头文件的导出类、函数与枚举均需 Doxygen 注释;include/vlink/internal/ 与 include/vlink/impl/ 是实现路径,但进入公开签名或供用户配置的符号仍按公开契约维护。按实体使用适用的 @brief/@tparam/@param[in,out]/@return/@note/@see 等标记;代码块用 @code{.cpp} ... @endcode。不写"参数 a:第一个参数"这类零信息量内容。
第一方且采用 VLink 模板的新建 C/C++ 源文件以完整的 22 行 Apache 2.0 header(含 ASCII logo)开头;年份与作者由维护者确认后从相邻 VLink 源文件复制。第三方、生成代码、保留上游版权的文件及其他语言文件沿用各自相邻文件,不机械转写该模板。
每个 PR 必须无条件通过五道静态检查关:除 clang-format、clang-tidy 外,CI(见 .github/scripts/ci-lint.sh)还强制 cpplint(tools/check.sh,--counting=detailed --linelength=120)、cmake-format(tools/format.sh,依仓库根 .cmake-format)与 actionlint(校验 .github/workflows/*.yml)。项目 .clang-tidy 设定 ‘WarningsAsErrors: ’*',任何新增告警都会导致 CI 失败、PR 无法合并——这是硬门槛而非倾向。注意:仅在本地跑clang-format/clang-tidy` 并不足以通过 CI,还需保证上述其余三关干净。
推送前在本地执行下列增量便捷命令(仅扫描本次改动,覆盖最常踩的 clang-format/clang-tidy 两关);其余 cpplint/cmake-format/actionlint 可直接跑对应工具或 tools/format.sh、tools/check.sh 验证。注意本地命令与 CI 范围不同:CI 的格式化是全树扫描(tools/format.sh 对整个仓库 find 全部文件,非 diff 增量),clang-tidy 经 .github/scripts/ci-tidy.sh 以 -DCMAKE_CXX_CLANG_TIDY 整目标构建(非逐改动文件 -p):
具体何种写法触发 tidy 告警属 clang-tidy 通用知识,以仓库根 .clang-tidy 配置为准,本章不展开。确属误报或有意写法时,按相邻代码使用带具体 check 名的行级 NOLINT;宏区、生成式代码块与 test/ 全文件豁免沿用仓库既有边界,详见 .agents/languages/CPP.md §11。
PR 门槛为两条硬规则;测试框架细节(doctest、断言宏、TEST_CASE/SUBCASE 结构、命名约定)见本章前半部分(§15.1 至 §15.3),此处不重复。
禁用的测试写法:
wait_for / vlink::ConditionVariable / std::future);确需固定延时测试时间行为时,断言必须容忍调度抖动,不依赖精确睡眠时长。部分计数型事实横跨多个文档,修改时必须一次性改全,违例 PR 将被拒。
| 事实 | 必须同步的位置 |
|---|---|
| CLI 工具数量 | 根 README(中文 + README.en.md)/ 白皮书 / 概述 / 快速开始 ENABLE_CLI_* 列表 / CLI 工具 / 速查参考 / cli-tools-overview.drawio、overview-architecture.drawio、foreword-*.drawio + PNG / .github/wiki/(index.html + assets/js/i18n.js 三语)/ CHANGELOG.md |
| transport 模块数量 | 同上 + 传输后端与 URL |
| 序列化类型数量 | 消息序列化 / 根 README / 速查参考 |
| QoS 预设数量 | QoS 配置 / 速查参考 / CHANGELOG.md |
| base 库组件数量 | 基础库 表格 / 概述相应表 |
| 示例数量 / 类别 | 快速开始 / 各 examples/CATEGORY/README.md |
| 环境变量 | 集成 / 速查参考 |
| CMake 选项 | 快速开始 §1.4 / 第一方面向用户的 option() 与 cache 配置 |
计数本身易过期,以源码与专题 doc 为准,不在多处转抄数字。
cli/TOOL/etc/completions/vlink-TOOL.bash 与 .zsh 两份补全脚本,二者子命令集合必须一致。doc/;不将 Doxygen 内容大段复制进教程 doc(存在过期风险),教程中引一个示例并链接到头文件即可。VLink 遵循 Semantic Versioning 2.0:**MAJOR** 可含破坏性改动(须在 CHANGELOG 明列);**MINOR** 只增不改、向后兼容;**PATCH** 只修 bug,不改 API/ABI。
| 破坏性变更(走 MAJOR) | 兼容变更(可走 MINOR) |
|---|---|
删除或 rename 任何 include/vlink/** 公共符号 | 新增 public symbol |
| 改现有公共函数/方法签名(入参 / 返回值 / 默认值) | 新增 enum 值(放末尾,不动已有值) |
| 改已有 enum 值;增删、重排或改变公开 struct 字段(破 ABI;zerocopy 线格式禁止变更) | — |
| 改 URL scheme 或 query 参数名 | 新增 URL query 参数 |
| 改 CLI 子命令 / 顶层 flag 名 | 新增 CLI 子命令 |
| 改 CMake 导出目标名 / C API 返回码 / env 变量名 | 新增 CMake 选项(默认值对存量构建无影响) |
弃用但暂不删除的 API 用 [[deprecated("use new_api() since v2.5.0")]] 标注,至少保留至下一个 MAJOR 版本方可删除。
<>;条件编译集中声明,不散落进函数体;路径分隔符只用 /。MessageLoop、Timer、ThreadPool 及 include/vlink/base/ 的并发抽象;没有匹配抽象时再沿用相邻代码使用标准库。时间统一使用 std::chrono。<cstdint> 定宽类型;普通计数、索引与平台 API 参数按语义和相邻接口选型,不机械替换 int/long。epoll/signalfd/eventfd;Android 不链接 glibc-only 符号、不用 pthread_cancel;二者在 CMake 中以 target_compile_definitions 区分。vlink-bench 的同一预设、同一环境对比快路径基线;明显下降必须分析原因并说明取舍。ThreadPool;热路径日志用可按级别关闭的项目日志宏。vlink::Security 或业务回调,禁止自行实现 AES/RSA 或散落调用 OpenSSL 低层 API。src/extension/security.cc、处理加密 payload 的模块、密钥/证书/签名逻辑或认证握手流程的 PR,必须打 security-review 标签走安全评审。新增 VLink 专属构件时的通用要求;目录结构与计数位置以各专题 doc 为准(传输后端与 URL / CLI 工具 / 快速开始 / 集成)。
**新增 Transport**(例如 ros2://)
modules/NAME/ 下仿照 modules/mqtt/ 结构实现六原语(Publisher/Subscriber/Client/Server/Setter/Getter)工厂;URL 解析非法时抛 Exception::RuntimeErrormodules/NAME/CMakeLists.txt 定义 SKIP_NAME 选项与 VLINK_SUPPORT_NAME 宏,缺依赖时 return() 而非 FATAL_ERROR;新增 CMake target vlink::NAMEexamples/url_guide/url_NAME/ 示例 + READMEtransport-decision-tree.drawio + PNG / CHANGELOG新增 CLI 工具
cli/TOOLNAME/ 目录含 TOOL.cc + CMakeLists.txt;根 CMake 加 option(ENABLE_CLI_TOOLNAME) + add_subdirectoryvlink-TOOLNAME;支持 -h/--help 与 -v/--version;提供 .bash + .zsh 补全脚本cli-tools-overview.drawio + PNG;文档中至少一个完整调用示例新增 Example
examples/CATEGORY/NAME/ 含 CMakeLists.txt(target_link_libraries(... PRIVATE vlink::...))+ README.md(目标 / 用到的 API / 运行方式 / 预期输出)add_subdirectory 与 快速开始 计数新增 Plugin 接口
include/vlink/extension/NAME_plugin_interface.h 定义抽象基类:业务纯虚函数 + 虚析构(仿 bag_plugin_interface.h / trigger_plugin_interface.h;版本由 VLINK_PLUGIN_DECLARE 的 major/minor 版本门承担,虚表 ABI 变更时必须提升 major),经 vlink::Plugin 框架按 ABI 契约加载examples/plugin/plugin_NAME/;更新 集成 的插件类型说明doc/images/TOPIC.drawio 与导出 doc/images/TOPIC.png 必须同名成对;仅改 PNG 不改 drawio 的 PR 将被拒。PNG 统一使用白色不透明背景,导出命令 drawio --export --format png --scale 2 --output TOPIC.png TOPIC.drawio(不要加透明选项 -t;headless 遇沙箱报错时加 --no-sandbox)。| 语义 | 配色(填充 / 描边) |
|---|---|
| 主体模块 | #e3f2fd 或 #bbdefb / #1976d2 |
| 正常流 / 数据 | #c8e6c9 / #388e3c |
| 警示 / 特殊路径 | #ffe082 / #ef6c00 |
| 外部 / 底层 | #eceff1 / #607d8b |
| 容器 / 分组框 | #ffffff 或无填充 / 相邻语义色 |
images/NAME.png);改代码若影响图面信息(数量、枚举、流程)须同步改源并重生 PNG;无引用的孤儿 drawio 会被周期清理。从评审者视角逐项核对:
x/tmp/data2)?<tt>.cc 是否没有新增说明性注释、注释掉的代码或无信息横幅?函数是否职责单一,复杂分支与嵌套是否已按相邻代码合理收敛?所有项通过后维护者 "Approve";有疑虑用 "Request changes" 并明确列出阻塞项。
仅维护者操作,贡献者了解即可。版本号按 §15.17 的 SemVer 选取(MAJOR/MINOR/PATCH)。
release/vX.Y.Z 分支,使用 tools/update_version.sh X.Y.Z 同步版本镜像并在 CHANGELOG.md 插入正式版本段mastergit tag -a vX.Y.Z -m "Release vX.Y.Z" 并 git push origin vX.Y.Zrelease: published 事件会触发 workflow 构建并上传发布产物。完成后删除 release/ 分支紧急 patch:从最后一个 tag 起 hotfix/vX.Y.Z+1 分支,只收 fix 类 commit,打 tag 后 cherry-pick 回 master。
以下行为直接拒绝:
master 上直接 push 或 force-pushWIP/fix/update 等无信息量内容build/、<tt>.so、<tt>.exe、<tt>.a).h 中用 using namespace,或在头文件放函数定义(template/constexpr/inline 除外)std::regex / std::stringstream / boost::format.txt 文档不受此限)ENABLE_TESTvlink-bench quick 预设;vlink-test 为测试可执行文件,经 ctest 调度,不属 CLI 工具)规则有歧义或缺失场景,提 issue 标签
governance,由维护者更新本章。本文件本身也遵循 PR 规范。