diff --git a/docs/guides/plugins.md b/docs/guides/plugins.md index 7afc46cf7..a880d07bf 100644 --- a/docs/guides/plugins.md +++ b/docs/guides/plugins.md @@ -215,3 +215,7 @@ Kernel also uses typed construction while retaining its public `*_impl` and `ker Consul follows the same construction pattern while retaining all three public headers and types. Its private factory is not installed; the module keeps its interface RTTI key and disabled updates. Rebuild Consul and consumers together because its public implementation/plugin layouts change. See [Consul construction migration](../reference/directory-structure.md#typed-consul-construction) for test entry points and dependency conditions. Etcd Client also retains all five public headers and types while using typed bases, a private interface-returning factory and explicit entry/exit functions. Its interface RTTI key and disabled updates are unchanged. Rebuild the plugin and consumers together because the public implementation/plugin layouts change. The [Etcd Client construction migration](../reference/directory-structure.md#typed-etcd-client-construction) describes optional JSON dependencies and tests without an Etcd service. + +OSS SDK uses the same typed bases and private interface-returning factory while retaining all three public headers/types, its interface RTTI key and disabled updates. Rebuild the plugin and consumers together for the public layout change. Typed unregistration replaces its undefined legacy deregistration call; the implementation remains a TODO scaffold. See [OSS SDK construction migration](../reference/directory-structure.md#typed-oss-sdk-construction) for build and test boundaries. + +Kafka retains all four public headers and both RdKafka callback bases while adopting typed construction and explicit entries. Its interface key and enabled frame updates remain unchanged, and the runtime plugin stays off by default. Rebuild Kafka and consumers together after the public layout change. See [Kafka construction migration](../reference/directory-structure.md#typed-kafka-construction) for driver requirements and tests without a broker connection. diff --git a/docs/guides/plugins_CN.md b/docs/guides/plugins_CN.md index ef5d6defb..b29239be1 100644 --- a/docs/guides/plugins_CN.md +++ b/docs/guides/plugins_CN.md @@ -202,3 +202,7 @@ Kernel 同样使用类型化构造,并保留公开 `*_impl`、`kernel_plugin` Consul 使用相同构造方式,保留全部三个公开头与类型。私有工厂不安装;模块保留接口 RTTI 键和禁用 update 的选择。公开实现/插件布局发生变化,Consul 与消费者必须一起重建。测试入口与依赖条件见 [Consul 构造迁移](../reference/directory-structure_CN.md#consul-类型化构造)。 Etcd Client 使用类型化基类、返回接口的私有工厂与显式入口/出口,保留全部五个公开头与类型。接口 RTTI 键和禁用 update 的选择不变。公开实现/插件布局发生变化,插件与消费者必须一起重建。可选 JSON 依赖与无需 Etcd 服务的测试见 [Etcd Client 构造迁移](../reference/directory-structure_CN.md#etcd-client-类型化构造)。 + +OSS SDK 使用相同的类型化基类和返回接口的私有工厂,保留全部三个公开头/类型、接口 RTTI 键和禁用 update 的选择。公开布局发生变化,插件与消费者必须一起重建。类型化注销替代未定义的旧注销调用;实现仍为 TODO 骨架。构建与测试边界见 [OSS SDK 构造迁移](../reference/directory-structure_CN.md#oss-sdk-类型化构造)。 + +Kafka 使用类型化构造与显式入口,保留原有四个公开头和两个 RdKafka 回调基类。接口键与启用逐帧 update 的选择不变,运行时插件继续默认关闭。公开布局发生变化,Kafka 与消费者必须一起重建。驱动要求及无需连接 broker 的测试见 [Kafka 构造迁移](../reference/directory-structure_CN.md#kafka-类型化构造)。 diff --git a/docs/project-layout.md b/docs/project-layout.md index 6ce9b31b3..b17dafa02 100644 --- a/docs/project-layout.md +++ b/docs/project-layout.md @@ -133,4 +133,6 @@ cmake -S tests/consumer/etcd_client -B etcd-client-headers -G Ninja \ cmake --build etcd-client-headers --target check-etcd-client-headers --parallel 2 ``` +OSS SDK retains its three public headers and types through typed construction; rebuild the plugin and consumers together because public class layouts change. See [OSS SDK construction migration](reference/directory-structure.md#typed-oss-sdk-construction) for optional build conditions and construction/export checks. The implementation remains a TODO scaffold. + Header mapping does not establish Consul, Kafka or OSS SDK runtime compatibility. Physic and `physics_scene` still require the unsupported legacy PhysX 3 integration and retain their explicit configuration failure; no passing PhysX build is claimed. diff --git a/docs/project-layout_CN.md b/docs/project-layout_CN.md index 82a8ee230..00faadbad 100644 --- a/docs/project-layout_CN.md +++ b/docs/project-layout_CN.md @@ -123,4 +123,6 @@ cmake -S tests/consumer/etcd_client -B etcd-client-headers -G Ninja \ cmake --build etcd-client-headers --target check-etcd-client-headers --parallel 2 ``` +OSS SDK 使用类型化构造,保留全部三个公开头和类型;公开类布局发生变化,插件与消费者必须一起重建。可选构建条件及构造/导出检查见 [OSS SDK 构造迁移](reference/directory-structure_CN.md#oss-sdk-类型化构造)。实现仍为 TODO 骨架。 + 头文件映射检查不代表 Consul、Kafka、OSS SDK 的运行时兼容性已经验证。Physic 与 `physics_scene` 仍依赖未受支持的旧版 PhysX 3 集成,保留明确的配置失败;此处没有宣称 PhysX 构建通过。 diff --git a/docs/reference/build-options.md b/docs/reference/build-options.md index 1825ad741..b7196649c 100644 --- a/docs/reference/build-options.md +++ b/docs/reference/build-options.md @@ -39,10 +39,12 @@ cmake -S . -B build -DARK_BUILD_APPLICATIONS=ON \ cmake --build build --target ark_mysql_plugin ark_redis_plugin ``` -All official plugin public headers are copied from explicit lists at configuration time into each build tree's generated plugin header mapping. Each `ark::` API target carries that include root even with runtime plugins disabled. MySQL, Redis, Utility, Net, Bus and HTTP exclude private assembly and factories. Kernel retains all 38 existing headers, including `kernel_plugin.hpp`; Consul, Etcd Client, Kafka, OSS SDK and Physic retain all 22 existing public headers, including implementation and assembly headers. Registration keys, public type visibility and optional plugin policies are retained; [Kernel typed construction](kernel.md#typed-kernel-construction), [Consul typed construction](directory-structure.md#typed-consul-construction) and [Etcd Client typed construction](directory-structure.md#typed-etcd-client-construction) change public class layouts and require rebuilding consumers. Their private factory headers are excluded from mapping and installation; header mapping does not validate optional driver integrations. Each public header has one co-located source; its generated include mapping belongs to the selected build tree, rather than a second source layout. +All official plugin public headers are copied from explicit lists at configuration time into each build tree's generated plugin header mapping. Each `ark::` API target carries that include root even with runtime plugins disabled. MySQL, Redis, Utility, Net, Bus and HTTP exclude private assembly and factories. Kernel retains all 38 existing headers, including `kernel_plugin.hpp`; Consul, Etcd Client, Kafka, OSS SDK and Physic retain all 22 existing public headers, including implementation and assembly headers. Registration keys, public type visibility and optional plugin policies are retained; [Kernel typed construction](kernel.md#typed-kernel-construction), [Consul typed construction](directory-structure.md#typed-consul-construction), [Etcd Client typed construction](directory-structure.md#typed-etcd-client-construction), [OSS SDK typed construction](directory-structure.md#typed-oss-sdk-construction) and [Kafka typed construction](directory-structure.md#typed-kafka-construction) change public class layouts and require rebuilding consumers. Their private factory headers are excluded from mapping and installation; header mapping does not validate optional driver integrations. Each public header has one co-located source; its generated include mapping belongs to the selected build tree, rather than a second source layout. Applications CI explicitly enables Consul and Etcd Client on Linux, macOS and Windows. Its `check-core` run includes their construction, DLL lifecycle and production export checks; these checks require no Consul or Etcd service and do not establish service interoperability. Etcd Client keeps its existing default `ON` selection and can be disabled with `ARK_BUILD_PLUGIN_ETCD_CLIENT=OFF`. Building it requires the existing `nlohmann_json` package (`etcd-client` vcpkg feature); disabling it does not add that JSON dependency to core tests. +OSS SDK remains optional and off by default (`ARK_BUILD_PLUGIN_OSSSDK=OFF`). Core tests include its real factory/assembly and controlled DSO checks without Protobuf or a runtime plugin. With the runtime plugin selected, `check-oss-sdk-plugin` also loads the actual DSO and checks its exports; `check-core` includes the applicable checks. The implementation is still a TODO scaffold, with no object-storage client or service acceptance. See [OSS SDK construction migration](directory-structure.md#typed-oss-sdk-construction). + Link a consumer to `ark::mysql` and/or `ark::redis` for the public headers and usage requirements. These public interface targets do not load a runtime library. Deploy the selected libraries with the launcher and add their stems to the application's manifest: ```toml @@ -68,3 +70,5 @@ Core headers live under `src/core/`; `ark::core` supplies public header usage re `ARK_INSTALL` defaults to `ON` for the top-level project and `OFF` for embedded builds. It enables explicit public-header installation, relocatable CMake exports, `check-install` and TGZ/ZIP CPack archives. `ARK_USE_INSTALLED_ARKNET=ON` resolves a separately installed arknet package and takes precedence over the source/revision overrides. It defaults to `OFF`, preserving source dependency builds. See [installation and package consumption](../get-started/installation.md#install-and-consume-the-cmake-package). Packaging does not enable optional runtime plugins or validate their external drivers. Net's 18 public headers exclude `http_client_module_impl.hpp`; use `` and public interface lookup. This removal changes source/ABI compatibility; see [Net migration](network.md#api-and-private-construction). Private factory declarations are not installed. + +Kafka remains off by default (`ARK_BUILD_PLUGIN_KAFKA=OFF`). With the existing driver dependencies, applications, Protobuf and core tests enabled, `ARK_BUILD_PLUGIN_KAFKA=ON` adds `check-kafka-construction` and `check-kafka-plugin` to `check-core`. The first exercises actual factory/assembly plus controlled DSO lifecycle and enabled updates; the second also checks production DSO exports. No broker is contacted. Disabling Kafka avoids Kafka dependency discovery in core tests. See [Kafka construction migration](directory-structure.md#typed-kafka-construction) for rebuild requirements and verification boundaries. diff --git a/docs/reference/build-options_CN.md b/docs/reference/build-options_CN.md index d9d1cd082..46f81810c 100644 --- a/docs/reference/build-options_CN.md +++ b/docs/reference/build-options_CN.md @@ -39,10 +39,12 @@ cmake -S . -B build -DARK_BUILD_APPLICATIONS=ON \ cmake --build build --target ark_mysql_plugin ark_redis_plugin ``` -全部官方插件的公开头通过显式清单在配置阶段复制到各构建树生成的插件公开头映射。各 `ark::` API target 传递该目录,插件关闭时仍可使用。MySQL、Redis、Utility、Net、Bus、HTTP 的私有装配头和工厂不进入映射;Kernel 保留包括 `kernel_plugin.hpp` 在内的原有 38 个头;Consul、Etcd Client、Kafka、OSS SDK、Physic 保留包括实现头与装配头在内的原有 22 个公开头。注册键、公开类型可见性和可选插件策略继续保留;[Kernel 类型化构造](kernel_CN.md#kernel-类型化构造)、[Consul 类型化构造](directory-structure_CN.md#consul-类型化构造)与 [Etcd Client 类型化构造](directory-structure_CN.md#etcd-client-类型化构造)改变公开类布局,需要重建消费者。这些私有工厂头均不映射、不安装;头文件映射不代表可选驱动集成已经验证。每个公共头仅有一份共置源码;生成的头文件映射属于选定构建树,不构成第二棵源码目录。 +全部官方插件的公开头通过显式清单在配置阶段复制到各构建树生成的插件公开头映射。各 `ark::` API target 传递该目录,插件关闭时仍可使用。MySQL、Redis、Utility、Net、Bus、HTTP 的私有装配头和工厂不进入映射;Kernel 保留包括 `kernel_plugin.hpp` 在内的原有 38 个头;Consul、Etcd Client、Kafka、OSS SDK、Physic 保留包括实现头与装配头在内的原有 22 个公开头。注册键、公开类型可见性和可选插件策略继续保留;[Kernel 类型化构造](kernel_CN.md#kernel-类型化构造)、[Consul 类型化构造](directory-structure_CN.md#consul-类型化构造)、[Etcd Client 类型化构造](directory-structure_CN.md#etcd-client-类型化构造)、[OSS SDK 类型化构造](directory-structure_CN.md#oss-sdk-类型化构造)与 [Kafka 类型化构造](directory-structure_CN.md#kafka-类型化构造)改变公开类布局,需要重建消费者。这些私有工厂头均不映射、不安装;头文件映射不代表可选驱动集成已经验证。每个公共头仅有一份共置源码;生成的头文件映射属于选定构建树,不构成第二棵源码目录。 应用 CI 在 Linux、macOS、Windows 显式启用 Consul 与 Etcd Client,`check-core` 包含两者的构造、动态库生命周期与生产库导出检查。这些检查不需要 Consul 或 Etcd 服务,也不代表服务互通验收。Etcd Client 保留现有默认 `ON` 选择,可通过 `ARK_BUILD_PLUGIN_ETCD_CLIENT=OFF` 关闭。构建它需要现有 `nlohmann_json` 软件包(vcpkg 的 `etcd-client` feature);关闭它不会向核心测试增加该 JSON 依赖。 +OSS SDK 保持可选且默认关闭(`ARK_BUILD_PLUGIN_OSSSDK=OFF`)。核心测试包含真实工厂/装配与受控 DSO 检查,无需 Protobuf 或运行时插件。选中运行时插件后,`check-oss-sdk-plugin` 还加载真实 DSO 并检查导出;`check-core` 包含当前条件下可用的检查。实现仍为 TODO 骨架,没有对象存储客户端或服务验收。参见 [OSS SDK 构造迁移](directory-structure_CN.md#oss-sdk-类型化构造)。 + 业务插件链接 `ark::mysql` 和/或 `ark::redis`,以获取公开头文件和编译要求;公共接口目标不会加载运行时动态库。将所需动态库与启动器一起部署,并在应用清单中填写库名称: ```toml @@ -68,3 +70,5 @@ config = "" `ARK_INSTALL` 在顶层项目中默认 `ON`,嵌入构建时默认 `OFF`;启用显式公开头安装、可迁移 CMake 导出、`check-install` 与 TGZ/ZIP CPack 归档。`ARK_USE_INSTALLED_ARKNET=ON` 发现单独安装的 arknet 包,优先于源码与版本覆盖;默认 `OFF`,保留源码依赖构建。具体操作见[安装与包消费](../get-started/installation_CN.md#安装并消费-cmake-包)。打包不会启用可选运行时插件,也不代表外部驱动已经验收。 Net 的 18 个公开头不再包含 `http_client_module_impl.hpp`;使用 `` 并查找公开接口。此移除属于源码/ABI 兼容性变化,详见 [Net 迁移](network_CN.md#api-与私有创建)。私有工厂声明不安装。 + +Kafka 继续默认关闭(`ARK_BUILD_PLUGIN_KAFKA=OFF`)。准备原有驱动依赖并启用应用、Protobuf 和核心测试后,`ARK_BUILD_PLUGIN_KAFKA=ON` 会将 `check-kafka-construction`、`check-kafka-plugin` 加入 `check-core`。前者验证真实工厂/装配、受控动态库生命周期及启用 update;后者另检查生产动态库导出。测试不连接 broker。关闭 Kafka 时,核心测试不发现 Kafka 依赖。重建要求与验收边界见 [Kafka 构造迁移](directory-structure_CN.md#kafka-类型化构造)。 diff --git a/docs/reference/directory-structure.md b/docs/reference/directory-structure.md index 56ac10a5c..8df5530d6 100644 --- a/docs/reference/directory-structure.md +++ b/docs/reference/directory-structure.md @@ -152,6 +152,8 @@ cmake -S tests/consumer/etcd_client -B etcd-client-headers -G Ninja \ cmake --build etcd-client-headers --target check-etcd-client-headers --parallel 2 ``` +OSS SDK retains its three public headers and types through typed construction; rebuild the plugin and consumers together because public class layouts change. See [OSS SDK construction migration](directory-structure.md#typed-oss-sdk-construction) for optional build conditions and construction/export checks. The implementation remains a TODO scaffold. + Header mapping does not establish Consul, Kafka or OSS SDK runtime compatibility. Physic and `physics_scene` still require the unsupported legacy PhysX 3 integration and retain their explicit configuration failure; no passing PhysX build is claimed. @@ -196,3 +198,41 @@ cmake --build build --target check-etcd-client-plugin --parallel 1 This target includes `check-etcd-client-construction`: actual factory/assembly doctest cases, plus a controlled-factory DSO using the production entry/exit source to check configuration handoff, interface metadata, disabled updates, exactly-once ownership and repeated stop. A separate process loads the actual production plugin, checks its entry/exit exports and unloads it. `check-core` includes both checks when the runtime plugin is selected; disabled-plugin core builds do not acquire the JSON dependency. Applications CI explicitly enables the plugin and runs these checks through its existing `check-core` job. These tests do not run an Etcd service or exercise production HTTP/Etcd interoperability. They establish construction and DSO boundaries, not a completed Registry or arbitrary hot-update safety. + +## Typed OSS SDK construction + +**Breaking ABI/layout change:** `oss_sdk_module_impl` now inherits `module_base` and `oss_sdk_plugin` inherits `plugin_base`. Rebuild OSS SDK and consumers together. All three public headers, their `` paths and public types remain available through `ark::osssdk`; the `oss_sdk_module` interface is unchanged. The C++ plugin boundary does not promise compiler-independent ABI compatibility. + +The private `oss_sdk_factory.hpp` returns `std::unique_ptr` and is excluded from public mappings and installation. Explicit `DllEntryPlugin` / `DllExitPlugin` retain platform exports and logger initialization. Installation retains the original `oss_sdk_module` interface RTTI key and disabled updates. Typed unregistration replaces the undefined `ARK_DEREGISTER_MODULE` invocation and destroys the owned module. Configuration storage and the inherited default update method are preserved. The implementation remains a TODO scaffold: no OSS client, request API or object-storage protocol is added. + +With `ARK_BUILD_CORE_TESTS=ON`, `check-oss-sdk-construction` compiles the real factory and assembly into doctest cases and uses the production entry/exit source with a counted controlled factory in a DSO. Checks cover retained public bases/types, factory metadata, interface registration, duplicate rejection, disabled updates, configuration handoff, exactly-once destruction and repeated stop. This target requires only the normal core/arknet dependencies; it needs no runtime plugin, Protobuf, JSON package or object-storage service: + +```sh +cmake --build build --target check-oss-sdk-construction --parallel 1 +``` + +The runtime plugin remains off by default. Enable `ARK_BUILD_APPLICATIONS=ON`, `ARK_BUILD_PLUGIN_OSSSDK=ON`, `ARK_BUILD_PROTO=ON` and `ARK_BUILD_CORE_TESTS=ON` to additionally build the actual `ark_oss_sdk_plugin` DSO and inspect its entry/exit exports in a separate process: + +```sh +cmake --build build --target check-oss-sdk-plugin --parallel 1 +``` + +Application builds retain their existing Protobuf, configuration and Boost.Asio requirements. `check-core` includes the applicable OSS SDK construction/export checks. No external OSS SDK dependency is introduced. These tests establish construction and DSO boundaries only; they do not establish OSS client functionality, service interoperability or arbitrary hot-update safety. + +## Typed Kafka construction + +**Breaking ABI/layout change:** `kafka_module_impl` now inherits `module_base` and `kafka_plugin` inherits `plugin_base`. Rebuild Kafka and consumers together. All four public headers, their `` paths and public types remain available through `ark::kafka`. The module interface, producer/consumer wrappers and both `RdKafka::DeliveryReportCb` / `RdKafka::EventCb` bases are retained. C++ plugin boundaries do not promise compiler-independent ABI compatibility. + +The private `kafka_factory.hpp` returns `std::unique_ptr` and is excluded from public mappings and installation. Explicit `DllEntryPlugin` / `DllExitPlugin` retain platform exports and logger setup. Assembly retains the interface RTTI registration key and explicitly selects `module_update::enabled`, preserving periodic producer polling. Unregistration destroys the owned module. Producer storage and ownership, producer creation, delivery/event callbacks and message behavior are unchanged. + +Kafka remains optional and **off by default**. Its existing dependency discovery and linking are unchanged: enable `ARK_BUILD_APPLICATIONS=ON`, `ARK_BUILD_PROTO=ON`, `ARK_BUILD_CORE_TESTS=ON` and `ARK_BUILD_PLUGIN_KAFKA=ON`, with the platform's Kafka driver dependencies available. On Unix this includes librdkafka C/C++, OpenSSL, Zstandard, LZ4 and zlib. With that build configured, run: + +```sh +cmake --build build --target check-kafka-plugin --parallel 1 +``` + +`check-kafka-plugin` includes `check-kafka-construction`: actual factory/assembly doctest cases and a controlled-factory DSO using the production entry/exit source. They check typed bases, interface metadata, duplicate registration, enabled updates, configuration handoff, exactly-once ownership and repeated stop. A separate process loads the actual production DSO, checks its entry/exit exports and unloads it. `check-core` includes these checks only when the Kafka runtime target is selected; disabled-plugin core builds do not discover Kafka dependencies. + +Callback logs explicitly convert RdKafka error, severity and event-type enums to integers for current fmt compatibility, retaining their numeric log values. This does not change producer behavior, callback ordering or dependency discovery. + +These checks create no producer and require no broker connection. They establish construction and DSO boundaries, not Kafka delivery, flush behavior, service interoperability or arbitrary hot-update safety. The optional driver build still requires independent platform acceptance. diff --git a/docs/reference/directory-structure_CN.md b/docs/reference/directory-structure_CN.md index 25d79a69f..baf4885d8 100644 --- a/docs/reference/directory-structure_CN.md +++ b/docs/reference/directory-structure_CN.md @@ -142,6 +142,8 @@ cmake -S tests/consumer/etcd_client -B etcd-client-headers -G Ninja \ cmake --build etcd-client-headers --target check-etcd-client-headers --parallel 2 ``` +OSS SDK 使用类型化构造,保留全部三个公开头和类型;公开类布局发生变化,插件与消费者必须一起重建。可选构建条件及构造/导出检查见 [OSS SDK 构造迁移](directory-structure_CN.md#oss-sdk-类型化构造)。实现仍为 TODO 骨架。 + 头文件映射检查不代表 Consul、Kafka、OSS SDK 的运行时兼容性已经验证。Physic 与 `physics_scene` 仍依赖未受支持的旧版 PhysX 3 集成,保留明确的配置失败;此处没有宣称 PhysX 构建通过。 ## 安装目录 @@ -186,3 +188,41 @@ cmake --build build --target check-etcd-client-plugin --parallel 1 该目标包含 `check-etcd-client-construction`:真实工厂/装配的 doctest 用例,以及使用生产入口/出口源码与受控工厂的 DSO 测试,覆盖配置传递、接口元数据、禁用 update、所有权恰好销毁一次和重复 stop。另一个独立进程加载真实生产插件,检查入口/出口导出后卸载。选中运行时插件时,`check-core` 包含这两项检查;关闭插件的核心构建不增加 JSON 依赖。应用 CI 显式启用该插件,通过现有 `check-core` 任务执行检查。 这些测试不启动 Etcd 服务,不执行生产 HTTP/Etcd 互通验收,只验证构造与 DSO 边界,不代表 Registry 已完成,也不证明任意热更新安全。 + +## OSS SDK 类型化构造 + +**ABI/布局不兼容变更:** `oss_sdk_module_impl` 现在继承 `module_base`,`oss_sdk_plugin` 继承 `plugin_base`。OSS SDK 与消费者必须一起重建。全部三个公开头、`` 路径和公开类型继续通过 `ark::osssdk` 提供,`oss_sdk_module` 接口不变。C++ 插件边界不承诺跨编译器的 ABI 兼容性。 + +私有 `oss_sdk_factory.hpp` 返回 `std::unique_ptr`,不映射、不安装。显式 `DllEntryPlugin`/`DllExitPlugin` 保留平台导出和日志初始化。安装时沿用 `oss_sdk_module` 接口 RTTI 键,继续禁用 update。类型化注销替代未定义的 `ARK_DEREGISTER_MODULE` 调用,销毁拥有的模块。配置存储和继承的默认 update 方法保持不变。实现仍为 TODO 骨架,不增加 OSS 客户端、请求 API 或对象存储协议。 + +开启 `ARK_BUILD_CORE_TESTS=ON` 后,`check-oss-sdk-construction` 将真实工厂与装配编入 doctest 用例,并使用生产入口/出口源码与带计数的受控工厂构建 DSO。检查覆盖保留的公开基类/类型、工厂元数据、接口注册、重复注册拒绝、禁用 update、配置传递、恰好销毁一次和重复 stop。该目标仅需要通常的 core/arknet 依赖,无需运行时插件、Protobuf、JSON 软件包或对象存储服务: + +```sh +cmake --build build --target check-oss-sdk-construction --parallel 1 +``` + +运行时插件默认保持关闭。开启 `ARK_BUILD_APPLICATIONS=ON`、`ARK_BUILD_PLUGIN_OSSSDK=ON`、`ARK_BUILD_PROTO=ON` 与 `ARK_BUILD_CORE_TESTS=ON` 后,还可构建真实 `ark_oss_sdk_plugin` DSO,并在独立进程中检查入口/出口导出: + +```sh +cmake --build build --target check-oss-sdk-plugin --parallel 1 +``` + +应用构建仍需要现有 Protobuf、配置模块与 Boost.Asio。`check-core` 包含当前条件下可用的 OSS SDK 构造/导出检查,不增加外部 OSS SDK 依赖。这些测试只验证构造与 DSO 边界,不代表 OSS 客户端功能、服务互通或任意热更新安全已经通过验收。 + +## Kafka 类型化构造 + +**ABI/布局不兼容变更:**`kafka_module_impl` 改为继承 `module_base`,`kafka_plugin` 改为继承 `plugin_base`。Kafka 与消费者必须一起重建。原有四个公开头、`` 路径和公开类型继续通过 `ark::kafka` 提供。模块接口、生产者/消费者封装及 `RdKafka::DeliveryReportCb`、`RdKafka::EventCb` 两个基类均保留。C++ 插件边界不承诺跨编译器 ABI 兼容。 + +私有 `kafka_factory.hpp` 返回 `std::unique_ptr`,不进入公开映射或安装。显式 `DllEntryPlugin`/`DllExitPlugin` 保留平台导出与日志初始化。装配保留接口 RTTI 注册键,并显式选择 `module_update::enabled`,继续定期轮询生产者。注销会销毁所属模块。生产者存储与所有权、生产者创建、投递/事件回调及消息行为均不变。 + +Kafka 继续作为可选插件,**默认关闭**。原有依赖发现与链接方式不变:启用 `ARK_BUILD_APPLICATIONS=ON`、`ARK_BUILD_PROTO=ON`、`ARK_BUILD_CORE_TESTS=ON`、`ARK_BUILD_PLUGIN_KAFKA=ON`,并准备对应平台的 Kafka 驱动依赖。Unix 平台包括 librdkafka C/C++、OpenSSL、Zstandard、LZ4 和 zlib。完成配置后运行: + +```sh +cmake --build build --target check-kafka-plugin --parallel 1 +``` + +`check-kafka-plugin` 包含 `check-kafka-construction`:真实工厂/装配 doctest,以及使用生产入口/出口源码、替换私有工厂的受控动态库。检查覆盖类型化基类、接口元数据、重复注册、启用 update、配置传递、仅销毁一次的所有权与重复停机。独立进程加载真实生产动态库,检查入口/出口导出后卸载。仅选择 Kafka 运行时目标时,`check-core` 才纳入这些检查;关闭插件的核心测试不发现 Kafka 依赖。 + +回调日志将 RdKafka 错误码、严重等级和事件类型枚举显式转换为整数,以兼容当前 fmt,并保留原有数值信息。生产者行为、回调顺序与依赖发现方式均不变。 + +这些检查不创建生产者,也不连接 broker。它们验证构造与动态库边界,不代表 Kafka 消息投递、flush 行为、服务互通或任意热更新安全性。可选驱动构建仍需单独进行各平台验收。 diff --git a/src/plugins/README.md b/src/plugins/README.md index bb41192b6..24ba933db 100755 --- a/src/plugins/README.md +++ b/src/plugins/README.md @@ -28,4 +28,6 @@ Kernel co-locates its 38 existing headers with its cpp files in `src/plugins/ker Consul retains its three public headers and implementation/plugin types while using typed construction, a private owning factory and explicit entry points. Rebuild Consul and consumers together because the public class layouts change. The single interface registration key, disabled updates and request behavior are preserved. See [Consul construction migration](../../docs/reference/directory-structure.md#typed-consul-construction) for build conditions and service-free construction checks. -The other four legacy plugin families retain 19 byte-identical headers. Mapping checks do not validate Consul, Kafka or OSS SDK runtime integrations. Physic retains the unsupported PhysX 3 configuration failure; see [optional legacy dependencies](../../docs/dependencies.md#optional-legacy-integrations). +Kafka and Physic retain their original public headers. Mapping checks do not validate Consul, Kafka or OSS SDK runtime integrations. Physic retains the unsupported PhysX 3 configuration failure; see [optional legacy dependencies](../../docs/dependencies.md#optional-legacy-integrations). + +OSS SDK retains all three public headers and types while using typed construction with a private factory and explicit entry points. Rebuild the plugin and consumers together for the public layout change. It remains off by default and retains its TODO scaffold; construction/export checks add no object-storage client capability. See [OSS SDK construction migration](../../docs/reference/directory-structure.md#typed-oss-sdk-construction). diff --git a/src/plugins/README_CN.md b/src/plugins/README_CN.md index bed2c6eb9..97e8ded49 100644 --- a/src/plugins/README_CN.md +++ b/src/plugins/README_CN.md @@ -28,4 +28,6 @@ Kernel 的原有 38 个头与 cpp 共置于 `src/plugins/kernel/`,通过 `ark: Consul 保留原有三个公开头及实现/插件类型,同时使用类型化构造、私有拥有型工厂和显式入口。公开类布局发生变化,Consul 与消费者必须一起重建。唯一接口注册键、禁用 update 与请求行为保持不变。构建条件及无需服务的构造检查见 [Consul 构造迁移](../../docs/reference/directory-structure_CN.md#consul-类型化构造)。 -其他四类旧插件保留原有 19 个头的全部字节。映射检查不代表 Consul、Kafka、OSS SDK 的运行时集成已经验证。Physic 保留未受支持的 PhysX 3 配置失败;参见[可选旧集成依赖](../../docs/dependencies_CN.md)。 +Kafka 与 Physic 保留原有公开头。映射检查不代表 Consul、Kafka、OSS SDK 的运行时集成已经验证。Physic 保留未受支持的 PhysX 3 配置失败;参见[可选旧集成依赖](../../docs/dependencies_CN.md)。 + +OSS SDK 使用类型化构造、私有工厂与显式入口,保留全部三个公开头和类型。公开布局发生变化,插件与消费者必须一起重建。插件默认保持关闭,实现仍为 TODO 骨架;构造/导出检查不增加对象存储客户端能力。参见 [OSS SDK 构造迁移](../../docs/reference/directory-structure_CN.md#oss-sdk-类型化构造)。 diff --git a/src/plugins/kafka/kafka_factory.hpp b/src/plugins/kafka/kafka_factory.hpp new file mode 100644 index 000000000..c9f6118ed --- /dev/null +++ b/src/plugins/kafka/kafka_factory.hpp @@ -0,0 +1,13 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#pragma once + +#include + +#include "ark/kafka/kafka_module.hpp" + +// Plugin-private construction; deliberately absent from public header mappings. +namespace ark::detail { + +std::unique_ptr make_kafka_module(); + +} // namespace ark::detail diff --git a/src/plugins/kafka/kafka_module_impl.cpp b/src/plugins/kafka/kafka_module_impl.cpp index 9035e5b28..ee2e13227 100644 --- a/src/plugins/kafka/kafka_module_impl.cpp +++ b/src/plugins/kafka/kafka_module_impl.cpp @@ -1,9 +1,14 @@ #include "ark/kafka/kafka_module_impl.hpp" #include "ark/kafka/kafka_plugin.hpp" #include "ark/logger.hpp" +#include "kafka_factory.hpp" namespace ark { +std::unique_ptr detail::make_kafka_module() { + return std::make_unique(); +} + bool kafka_module_impl::init() { return true; } @@ -63,20 +68,23 @@ void kafka_module_impl::event_cb(RdKafka::Event& event) { switch (event.type()) { case RdKafka::Event::EVENT_ERROR: - ARK_LOG_WARN("Kafka error! err=[{}]:{} msg={}", event.err(), RdKafka::err2str(event.err()), event.str()); + ARK_LOG_WARN("Kafka error! err=[{}]:{} msg={}", static_cast(event.err()), RdKafka::err2str(event.err()), + event.str()); break; case RdKafka::Event::EVENT_STATS: ARK_LOG_INFO("Kafka stats: {}", event.str()); break; case RdKafka::Event::EVENT_LOG: - ARK_LOG_INFO("Kafka event log! severity={} facility={} msg={}", event.severity(), event.fac(), event.str()); + ARK_LOG_INFO("Kafka event log! severity={} facility={} msg={}", static_cast(event.severity()), event.fac(), + event.str()); break; case RdKafka::Event::EVENT_THROTTLE: ARK_LOG_INFO("Kafka throttled time={}ms by {} ! brokerID={}", event.throttle_time(), event.broker_name(), event.broker_id()); break; default: - ARK_LOG_INFO("Kafka event. type={} err=[{}] msg={}", event.type(), RdKafka::err2str(event.err()), event.str()); + ARK_LOG_INFO("Kafka event. type={} err=[{}] msg={}", static_cast(event.type()), + RdKafka::err2str(event.err()), event.str()); break; } } diff --git a/src/plugins/kafka/kafka_module_impl.hpp b/src/plugins/kafka/kafka_module_impl.hpp index a843df2ba..83cbb5fc0 100644 --- a/src/plugins/kafka/kafka_module_impl.hpp +++ b/src/plugins/kafka/kafka_module_impl.hpp @@ -3,16 +3,15 @@ #include "ark/container/array_map.hpp" #include "ark/kafka/kafka.hpp" #include "ark/kafka/kafka_module.hpp" -#include "ark/plugin_manager.hpp" +#include "ark/module_base.hpp" namespace ark { class kafka_module_impl - : public kafka_module + : public module_base , public RdKafka::DeliveryReportCb , public RdKafka::EventCb { - ARK_DECLARE_MODULE_FUNCTIONS public: bool init() override; bool update() override; @@ -28,4 +27,4 @@ class kafka_module_impl array_map producers_; }; -} // namespace ark \ No newline at end of file +} // namespace ark diff --git a/src/plugins/kafka/kafka_plugin.cpp b/src/plugins/kafka/kafka_plugin.cpp index 3a3900e74..37a6ea18a 100644 --- a/src/plugins/kafka/kafka_plugin.cpp +++ b/src/plugins/kafka/kafka_plugin.cpp @@ -1,16 +1,31 @@ -#include "ark/kafka/kafka_module_impl.hpp" +#include + #include "ark/kafka/kafka_plugin.hpp" +#include "kafka_factory.hpp" namespace ark { -ARK_DECLARE_PLUGIN_DLL_FUNCTION(kafka_plugin) +ARK_EXPORT_FUNC void DllEntryPlugin(plugin_manager* manager, const std::string& name, logger* log) { + logger::init(log); + manager->register_plugin(name); +} + +ARK_EXPORT_FUNC void DllExitPlugin(plugin_manager* manager) { + manager->unregister_plugin(); +} void kafka_plugin::install() { - ARK_REGISTER_MODULE(kafka_module, kafka_module_impl); + if (!register_module(detail::make_kafka_module(), module_update::enabled)) + { + throw std::runtime_error("Kafka module registration rejected"); + } } void kafka_plugin::uninstall() { - ARK_UNREGISTER_MODULE(kafka_module, kafka_module_impl); + if (!unregister_module()) + { + throw std::runtime_error("Kafka module unregistration rejected"); + } } } // namespace ark diff --git a/src/plugins/kafka/kafka_plugin.hpp b/src/plugins/kafka/kafka_plugin.hpp index cee80bcd2..db562c284 100644 --- a/src/plugins/kafka/kafka_plugin.hpp +++ b/src/plugins/kafka/kafka_plugin.hpp @@ -1,8 +1,14 @@ -#include "ark/plugin.hpp" -#include "ark/plugin_manager.hpp" +#pragma once + +#include "ark/plugin_base.hpp" namespace ark { -ARK_DECLARE_PLUGIN(kafka_plugin) +class kafka_plugin final : public plugin_base +{ +public: + void install() override; + void uninstall() override; +}; } // namespace ark diff --git a/src/plugins/osssdk/oss_sdk_factory.hpp b/src/plugins/osssdk/oss_sdk_factory.hpp new file mode 100644 index 000000000..c4c6fba0a --- /dev/null +++ b/src/plugins/osssdk/oss_sdk_factory.hpp @@ -0,0 +1,13 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#pragma once + +#include + +#include "ark/osssdk/oss_sdk_module.hpp" + +// Plugin-private construction; deliberately absent from public header mappings. +namespace ark::detail { + +std::unique_ptr make_oss_sdk_module(); + +} // namespace ark::detail diff --git a/src/plugins/osssdk/oss_sdk_module_impl.cpp b/src/plugins/osssdk/oss_sdk_module_impl.cpp index 061bfa3a8..c5619a994 100644 --- a/src/plugins/osssdk/oss_sdk_module_impl.cpp +++ b/src/plugins/osssdk/oss_sdk_module_impl.cpp @@ -19,9 +19,12 @@ */ #include "ark/osssdk/oss_sdk_module_impl.hpp" +#include "oss_sdk_factory.hpp" namespace ark { -//method realization +std::unique_ptr detail::make_oss_sdk_module() { + return std::make_unique(); +} } // namespace ark diff --git a/src/plugins/osssdk/oss_sdk_module_impl.hpp b/src/plugins/osssdk/oss_sdk_module_impl.hpp index fad7a70e5..734e3b0d3 100644 --- a/src/plugins/osssdk/oss_sdk_module_impl.hpp +++ b/src/plugins/osssdk/oss_sdk_module_impl.hpp @@ -20,14 +20,13 @@ #pragma once +#include "ark/module_base.hpp" #include "ark/osssdk/oss_sdk_module.hpp" -#include "ark/plugin_manager.hpp" namespace ark { -class oss_sdk_module_impl final : public oss_sdk_module +class oss_sdk_module_impl final : public module_base { - ARK_DECLARE_MODULE_FUNCTIONS public: // TODO diff --git a/src/plugins/osssdk/oss_sdk_plugin.cpp b/src/plugins/osssdk/oss_sdk_plugin.cpp index df8d665b8..d20d4321e 100644 --- a/src/plugins/osssdk/oss_sdk_plugin.cpp +++ b/src/plugins/osssdk/oss_sdk_plugin.cpp @@ -18,21 +18,34 @@ * */ -#include "ark/osssdk/oss_sdk_module_impl.hpp" +#include + #include "ark/osssdk/oss_sdk_plugin.hpp" +#include "oss_sdk_factory.hpp" namespace ark { -ARK_DECLARE_PLUGIN_DLL_FUNCTION(oss_sdk_plugin) +ARK_EXPORT_FUNC void DllEntryPlugin(plugin_manager* manager, const std::string& name, logger* log) { + logger::init(log); + manager->register_plugin(name); +} + +ARK_EXPORT_FUNC void DllExitPlugin(plugin_manager* manager) { + manager->unregister_plugin(); +} void oss_sdk_plugin::install() { - // install module - ARK_REGISTER_MODULE(oss_sdk_module, oss_sdk_module_impl); + if (!register_module(detail::make_oss_sdk_module(), module_update::disabled)) + { + throw std::runtime_error("OSS SDK module registration rejected"); + } } void oss_sdk_plugin::uninstall() { - // uninstall module - ARK_DEREGISTER_MODULE(oss_sdk_module, oss_sdk_module_impl); + if (!unregister_module()) + { + throw std::runtime_error("OSS SDK module unregistration rejected"); + } } } // namespace ark diff --git a/src/plugins/osssdk/oss_sdk_plugin.hpp b/src/plugins/osssdk/oss_sdk_plugin.hpp index 500bf3167..349ad0c5a 100644 --- a/src/plugins/osssdk/oss_sdk_plugin.hpp +++ b/src/plugins/osssdk/oss_sdk_plugin.hpp @@ -20,11 +20,15 @@ #pragma once -#include "ark/plugin.hpp" -#include "ark/plugin_manager.hpp" +#include "ark/plugin_base.hpp" namespace ark { -ARK_DECLARE_PLUGIN(oss_sdk_plugin) +class oss_sdk_plugin final : public plugin_base +{ +public: + void install() override; + void uninstall() override; +}; } // namespace ark \ No newline at end of file diff --git a/tests/ci/install-package.test.mjs b/tests/ci/install-package.test.mjs index 43eb8591d..e91f348b5 100644 --- a/tests/ci/install-package.test.mjs +++ b/tests/ci/install-package.test.mjs @@ -72,10 +72,10 @@ include(cmake/ark_install.cmake) const text = readFileSync(file, 'utf8'); for (const forbidden of [root, producer, build, prefix]) assert.ok(!text.includes(forbidden), `${file}: ${forbidden}`); } - for (const name of ['mysql/mysql_module.hpp', 'kernel/kernel_plugin.hpp', 'etcd_client/etcd_client_plugin.hpp', 'etcd_client/etcd_client_module_impl.hpp', 'physic/physic_module_impl.hpp', 'application_manifest.hpp']) { + for (const name of ['mysql/mysql_module.hpp', 'kernel/kernel_plugin.hpp', 'etcd_client/etcd_client_plugin.hpp', 'etcd_client/etcd_client_module_impl.hpp', 'kafka/kafka.hpp', 'kafka/kafka_module.hpp', 'kafka/kafka_module_impl.hpp', 'kafka/kafka_plugin.hpp', 'physic/physic_module_impl.hpp', 'application_manifest.hpp']) { assert.ok(existsSync(join(relocated, 'include/ark', name)), name); } - for (const name of ['mysql/mysql_plugin.hpp', 'bus/bus_config_parser.hpp', 'http/http_plugin.hpp', 'net/http_client_module_impl.hpp', 'net/http_client_factory.hpp', 'kernel/kernel_factory.hpp', 'etcd_client/etcd_client_factory.hpp', 'configuration_reader.hpp']) { + for (const name of ['mysql/mysql_plugin.hpp', 'bus/bus_config_parser.hpp', 'http/http_plugin.hpp', 'net/http_client_module_impl.hpp', 'net/http_client_factory.hpp', 'kernel/kernel_factory.hpp', 'etcd_client/etcd_client_factory.hpp', 'kafka/kafka_factory.hpp', 'configuration_reader.hpp']) { assert.equal(existsSync(join(relocated, 'include/ark', name)), false, name); } rmSync(producer, { recursive: true }); diff --git a/tests/ci/package-dependencies.test.mjs b/tests/ci/package-dependencies.test.mjs index 840a16bc9..58061aa9f 100644 --- a/tests/ci/package-dependencies.test.mjs +++ b/tests/ci/package-dependencies.test.mjs @@ -104,3 +104,18 @@ test('Applications CI explicitly enables Etcd Client ownership and production DS assert.match(plugin, /DllExitPlugin/); assert.doesNotMatch(plugin, /ARK_DECLARE_PLUGIN|ARK_REGISTER_MODULE|ARK_UNREGISTER_MODULE/); }); + +test('Optional Kafka construction preserves update selection without public factories', () => { + const core = read('tests/core/CMakeLists.txt'); + assert.match(core, /if\(TARGET ark_kafka_plugin\)[\s\S]*add_subdirectory\(\.\.\/kafka/); + const checks = read('tests/kafka/CMakeLists.txt'); + assert.match(checks, /add_dependencies\(check-core check-kafka-plugin check-kafka-construction\)/); + assert.match(checks, /kafka_module_impl\.cpp/); + const mappings = read('src/plugins/CMakeLists.txt').split('set(ark_kafka_public_headers')[1].split('\n)')[0]; + assert.doesNotMatch(mappings, /kafka_factory/); + const plugin = read('src/plugins/kafka/kafka_plugin.cpp'); + assert.match(plugin, /module_update::enabled/); + assert.match(plugin, /DllEntryPlugin/); + assert.match(plugin, /DllExitPlugin/); + assert.doesNotMatch(plugin, /ARK_DECLARE_PLUGIN|ARK_REGISTER_MODULE|ARK_UNREGISTER_MODULE/); +}); diff --git a/tests/core/CMakeLists.txt b/tests/core/CMakeLists.txt index 927446e32..b162013ad 100644 --- a/tests/core/CMakeLists.txt +++ b/tests/core/CMakeLists.txt @@ -224,6 +224,8 @@ if(ARK_BUILD_PROTO) add_dependencies(check-core check-kernel-initialization) endif() +add_subdirectory(../osssdk "${CMAKE_CURRENT_BINARY_DIR}/osssdk") + if(ARK_BUILD_PROTO AND ARK_BUILD_CONFIG) add_subdirectory(../consul "${CMAKE_CURRENT_BINARY_DIR}/consul") endif() @@ -233,6 +235,11 @@ if(TARGET ark_etcd_client_plugin) add_subdirectory(../etcd_client "${CMAKE_CURRENT_BINARY_DIR}/etcd_client") endif() +# Keep Kafka driver discovery optional: checks follow its selected runtime plugin. +if(TARGET ark_kafka_plugin) + add_subdirectory(../kafka "${CMAKE_CURRENT_BINARY_DIR}/kafka") +endif() + if(ARK_BUILD_PROTO AND TARGET ark_kernel_plugin AND TARGET ark_utility_plugin) add_executable(ark_kernel_plugin_tests ../app/kernel_plugin.cpp) set_property(TARGET ark_kernel_plugin_tests PROPERTY LINK_LIBRARIES ark::kernel) diff --git a/tests/core/README.md b/tests/core/README.md index bfe66a033..748362bb4 100644 --- a/tests/core/README.md +++ b/tests/core/README.md @@ -159,3 +159,13 @@ With Protobuf and the Kernel/Utility runtime targets enabled, `check-kernel-plug The XXTEA cases cover exact-size short-key allocations, 16-byte and embedded-zero keys, binary round trips and output appending. See the [key contract](../../docs/reference/xxtea.md). + +With the optional Kafka runtime enabled (`ARK_BUILD_APPLICATIONS=ON`, `ARK_BUILD_PROTO=ON`, +`ARK_BUILD_CORE_TESTS=ON`, `ARK_BUILD_PLUGIN_KAFKA=ON`) and its existing driver dependencies, +`check-core` includes `check-kafka-plugin` and `check-kafka-construction`. The focused checks +compile the real factory/assembly, verify interface RTTI metadata and enabled updates, run +production entry/exit code with a controlled factory, and load the actual production DSO to +inspect exports. They create no producer or broker connection. Kafka stays off by default; +disabled-plugin core builds do not discover its dependencies. See the +[construction migration](../../docs/reference/directory-structure.md#typed-kafka-construction) +for the public-layout rebuild requirement and service-validation limits. diff --git a/tests/core/README_CN.md b/tests/core/README_CN.md index 56005e362..eb8d5ddb2 100644 --- a/tests/core/README_CN.md +++ b/tests/core/README_CN.md @@ -136,3 +136,11 @@ Net 注册的 HTTP 客户端现在由私有接口工厂创建。`check-net-plugi 开启 Protobuf 且存在 Kernel/Utility 运行时 target 时,`check-kernel-plugin` 通过独立公开接口 runner 加载真实 DSO,检查原有四个接口键与管理器指针、元数据/静态记录加载、地图/实体注册和回调、管理器更新、停机销毁与重复 stop。它与现有直接初始化/实体默认值测试一起纳入 `check-core`;不在卸载后检查注册表,也不证明任意热更新安全。Kernel 公开头消费者检查全部保留类型的基类和 update 声明。 XXTEA 用例覆盖精确长度分配的短密钥、16 字节与内嵌零字节密钥、二进制往返及输出追加,参见[密钥约定](../../docs/reference/xxtea_CN.md)。 + +启用可选 Kafka 运行时(`ARK_BUILD_APPLICATIONS=ON`、`ARK_BUILD_PROTO=ON`、 +`ARK_BUILD_CORE_TESTS=ON`、`ARK_BUILD_PLUGIN_KAFKA=ON`)并准备原有驱动依赖后, +`check-core` 包含 `check-kafka-plugin` 与 `check-kafka-construction`。专项检查编译真实 +工厂/装配,验证接口 RTTI 元数据与启用 update,通过受控工厂执行生产入口/出口, +并加载真实生产动态库检查导出。测试不创建生产者、不连接 broker。Kafka 继续默认关闭; +关闭插件的核心测试不发现其依赖。公开布局重建要求与服务验收边界见 +[构造迁移](../../docs/reference/directory-structure_CN.md#kafka-类型化构造)。 diff --git a/tests/kafka/CMakeLists.txt b/tests/kafka/CMakeLists.txt new file mode 100644 index 000000000..85bcebba8 --- /dev/null +++ b/tests/kafka/CMakeLists.txt @@ -0,0 +1,35 @@ +# Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +# Match production's Unix-only discovery; Windows keeps its existing external setup. +if(NOT WIN32) + find_package(librdkafka REQUIRED) +endif() +set(kafka_source "${PROJECT_SOURCE_DIR}/src/plugins/kafka") + +# Compile real assembly/factory too: private C++ symbols are not DLL exports. +add_executable(ark_kafka_construction_tests ../core/main.cpp construction.cpp + "${kafka_source}/kafka_plugin.cpp" "${kafka_source}/kafka_module_impl.cpp") +set_property(TARGET ark_kafka_construction_tests PROPERTY LINK_LIBRARIES + ark::kafka "${LIBRDKAFKA_CPP_LIBRARIES}" "${LIBRDKAFKA_LIBRARIES}") +target_include_directories(ark_kafka_construction_tests PRIVATE "${kafka_source}" "${LIBRDKAFKA_INCLUDE_DIR}") + +# The controlled module exercises production entry/exit without a producer or broker. +add_library(ark_kafka_entry_probe SHARED entry_factory.cpp "${kafka_source}/kafka_plugin.cpp") +set_property(TARGET ark_kafka_entry_probe PROPERTY LINK_LIBRARIES ark::kafka) +target_include_directories(ark_kafka_entry_probe PRIVATE "${kafka_source}") +set_target_properties(ark_kafka_entry_probe PROPERTIES PREFIX "") +add_executable(ark_kafka_entry_tests entry.cpp) +set_property(TARGET ark_kafka_entry_tests PROPERTY LINK_LIBRARIES ark::kafka) + +add_custom_target(check-kafka-construction + COMMAND $ + COMMAND $ "$/" "ark_kafka_entry_probe" + DEPENDS ark_kafka_construction_tests ark_kafka_entry_tests ark_kafka_entry_probe + USES_TERMINAL VERBATIM) + +add_executable(ark_kafka_exports_tests exports.cpp) +set_property(TARGET ark_kafka_exports_tests PROPERTY LINK_LIBRARIES ark::core) +add_custom_target(check-kafka-plugin + COMMAND $ "$/" "ark_kafka_plugin" + DEPENDS ark_kafka_exports_tests ark_kafka_plugin check-kafka-construction + USES_TERMINAL VERBATIM) +add_dependencies(check-core check-kafka-plugin check-kafka-construction) diff --git a/tests/kafka/construction.cpp b/tests/kafka/construction.cpp new file mode 100644 index 000000000..02be84f74 --- /dev/null +++ b/tests/kafka/construction.cpp @@ -0,0 +1,61 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#include +#include +#include + +#include "../network/doctest.h" + +#include +#include +#include "kafka_factory.hpp" + +#if __has_include() +#error "Kafka public mappings must exclude the private factory" +#endif + +static_assert(std::is_base_of_v, ark::kafka_module_impl>); +static_assert(std::is_base_of_v, ark::kafka_plugin>); +static_assert(std::is_base_of_v); +static_assert(std::is_base_of_v); +static_assert(!std::is_same_v); +static_assert(std::is_same_v>); + +TEST_CASE("Kafka real factory retains its public implementation and unpublished metadata") { + auto candidate = ark::detail::make_kafka_module(); + REQUIRE(candidate != nullptr); + const auto* instance = candidate.get(); + CHECK(typeid(*instance) == typeid(ark::kafka_module_impl)); + CHECK(candidate->get_plugin_manager() == nullptr); + CHECK(candidate->get_name().empty()); +} + +TEST_CASE("Kafka assembly retains the interface key, enabled updates and explicit ownership") { + ark::plugin_manager manager; + ark::kafka_plugin owner; + owner.set_plugin_manager(&manager); + CHECK(owner.get_plugin_version() == 0); + CHECK(owner.get_plugin_name() == typeid(ark::kafka_plugin).name()); + owner.set_plugin_conf("kafka-construction.toml"); + CHECK(ark::kafka_plugin::get_plugin_conf() == "kafka-construction.toml"); + owner.install(); + auto* instance = manager.find_module(); + REQUIRE(instance != nullptr); + CHECK(typeid(*instance) == typeid(ark::kafka_module_impl)); + CHECK(instance->get_plugin_manager() == &manager); + CHECK(instance->get_name() == typeid(ark::kafka_module).name()); + // Duplicate update insertion fails only when assembly already selected updates. + CHECK_FALSE(manager.add_update_module(instance)); + CHECK(instance->init()); + CHECK(manager.update(1)); + // Empty parameters avoid creating a producer or connecting to any broker. + CHECK_FALSE(instance->init_producer("", "construction")); + CHECK_FALSE(instance->init_producer("unused", "")); + CHECK_THROWS_AS(owner.install(), std::runtime_error); + CHECK(manager.find_module() == instance); + owner.uninstall(); + // Reinstallation proves the public registration key and ownership were released. + CHECK_NOTHROW(owner.install()); + CHECK_NOTHROW(owner.uninstall()); + CHECK(manager.update(1)); + CHECK_THROWS_AS(owner.uninstall(), std::runtime_error); +} diff --git a/tests/kafka/entry.cpp b/tests/kafka/entry.cpp new file mode 100644 index 000000000..a72464d22 --- /dev/null +++ b/tests/kafka/entry.cpp @@ -0,0 +1,45 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#include +#include +#include + +#include +#include +#include + +namespace { + +void require(bool condition, const char* message) { + if (!condition) + { + std::cerr << message << std::endl; + std::_Exit(1); + } +} + +} // namespace + +int main(int argc, char* argv[]) { + require(argc == 3, "Expected Kafka entry-probe directory and library stem"); + ark::logger log; + ark::logger::init(&log); + log.set_log_func([](spdlog::level::level_enum, const std::string&) {}); + ark::dynamic_library library(argv[2]); + require(library.load(argv[1]), "Cannot load Kafka entry-probe library"); + auto events = reinterpret_cast(library.get_symbol("kafka_entry_events")); + require(events && events(0) == 0 && events(1) == 0, "Entry-probe state is not fresh"); + require(library.get_symbol("DllEntryPlugin") && library.get_symbol("DllExitPlugin"), + "Kafka explicit entry/exit exports are missing"); + ark::plugin_manager manager; + require(manager.start(0, argv[1], "conf/", {{argv[2], "entry-probe.toml"}}), "Kafka explicit entry failed"); + auto* instance = manager.find_module(); + require(instance && instance->get_plugin_manager() == &manager && + instance->get_name() == typeid(ark::kafka_module).name(), + "Kafka explicit entry lost interface registration metadata"); + require(events(0) == 1 && events(1) == 0, "Kafka entry did not transfer factory ownership"); + require(manager.update(1) && events(2) == 1, "Kafka entry lost enabled update selection"); + require(manager.stop(2) && events(1) == 1, "Kafka explicit exit did not destroy its module exactly once"); + require(manager.stop(3) && events(1) == 1, "Repeated stop invoked Kafka exit twice"); + require(library.unload(), "Cannot release Kafka observation handle"); + std::cout << "Kafka real assembly entry/exit, configuration, interface metadata and ownership passed\n"; +} diff --git a/tests/kafka/entry_factory.cpp b/tests/kafka/entry_factory.cpp new file mode 100644 index 000000000..3c6c3a35f --- /dev/null +++ b/tests/kafka/entry_factory.cpp @@ -0,0 +1,63 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#include + +#include +#include +#include "kafka_factory.hpp" + +namespace { + +unsigned created{}; +unsigned destroyed{}; +unsigned updated{}; + +class entry_kafka final : public ark::module_base +{ +public: + entry_kafka() { + ++created; + } + + ~entry_kafka() override { + ++destroyed; + } + + bool init() override { + return ark::kafka_plugin::get_plugin_conf() == "conf/entry-probe.toml"; + } + + bool update() override { + ++updated; + return true; + } + + bool init_producer(const std::string&, const std::string&) override { + return false; + } + + int produce(const std::string&, const std::string&, void*) override { + return 0; + } +}; + +} // namespace + +namespace ark::detail { + +std::unique_ptr make_kafka_module() { + return std::make_unique(); +} + +} // namespace ark::detail + +ARK_EXPORT_FUNC unsigned kafka_entry_events(unsigned event) { + switch (event) + { + case 0: + return created; + case 1: + return destroyed; + default: + return updated; + } +} diff --git a/tests/kafka/exports.cpp b/tests/kafka/exports.cpp new file mode 100644 index 000000000..238f8f035 --- /dev/null +++ b/tests/kafka/exports.cpp @@ -0,0 +1,31 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#include +#include +#include + +#include + +namespace { + +void require(bool condition, const char* message) { + if (!condition) + { + std::cerr << message << std::endl; + std::_Exit(1); + } +} + +} // namespace + +int main(int argc, char* argv[]) { + require(argc == 3, "Expected plugin directory and library stem"); + ark::logger log; + ark::logger::init(&log); + log.set_log_func([](spdlog::level::level_enum, const std::string&) {}); + ark::dynamic_library library(argv[2]); + require(library.load(argv[1]), "Cannot load actual Kafka shared library"); + require(library.get_symbol("DllEntryPlugin") != nullptr, "Missing explicit DllEntryPlugin export"); + require(library.get_symbol("DllExitPlugin") != nullptr, "Missing explicit DllExitPlugin export"); + require(library.unload(), "Cannot unload Kafka shared library"); + std::cout << argv[2] << ": actual DSO load, explicit entry/exit exports and unload passed\n"; +} diff --git a/tests/osssdk/CMakeLists.txt b/tests/osssdk/CMakeLists.txt new file mode 100644 index 000000000..cbf91e03f --- /dev/null +++ b/tests/osssdk/CMakeLists.txt @@ -0,0 +1,36 @@ +# Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +set(oss_sdk_source "${PROJECT_SOURCE_DIR}/src/plugins/osssdk") + +# Compile real assembly/factory locally too: private C++ symbols are not DLL exports. +add_executable(ark_oss_sdk_construction_tests ../core/main.cpp construction.cpp + "${oss_sdk_source}/oss_sdk_plugin.cpp" "${oss_sdk_source}/oss_sdk_module_impl.cpp") +set_property(TARGET ark_oss_sdk_construction_tests PROPERTY LINK_LIBRARIES + ark::osssdk) +target_include_directories(ark_oss_sdk_construction_tests PRIVATE "${oss_sdk_source}") + +# A controlled module exercises the actual entry/exit functions without object-storage services. +add_library(ark_oss_sdk_entry_probe SHARED entry_factory.cpp "${oss_sdk_source}/oss_sdk_plugin.cpp") +set_property(TARGET ark_oss_sdk_entry_probe PROPERTY LINK_LIBRARIES ark::osssdk) +target_include_directories(ark_oss_sdk_entry_probe PRIVATE "${oss_sdk_source}") +set_target_properties(ark_oss_sdk_entry_probe PROPERTIES PREFIX "") +add_executable(ark_oss_sdk_entry_tests entry.cpp) +set_property(TARGET ark_oss_sdk_entry_tests PROPERTY LINK_LIBRARIES ark::osssdk) + +add_custom_target(check-oss-sdk-construction + COMMAND $ + COMMAND $ "$/" "ark_oss_sdk_entry_probe" + DEPENDS ark_oss_sdk_construction_tests ark_oss_sdk_entry_tests ark_oss_sdk_entry_probe + USES_TERMINAL VERBATIM) + +if(TARGET ark_oss_sdk_plugin) + # Keep real DSO export inspection separate from the host linking static AFProto. + add_executable(ark_oss_sdk_exports_tests exports.cpp) + set_property(TARGET ark_oss_sdk_exports_tests PROPERTY LINK_LIBRARIES ark::core) + add_custom_target(check-oss-sdk-plugin + COMMAND $ "$/" "ark_oss_sdk_plugin" + DEPENDS ark_oss_sdk_exports_tests ark_oss_sdk_plugin check-oss-sdk-construction + USES_TERMINAL VERBATIM) + add_dependencies(check-core check-oss-sdk-plugin) +else() + add_dependencies(check-core check-oss-sdk-construction) +endif() diff --git a/tests/osssdk/construction.cpp b/tests/osssdk/construction.cpp new file mode 100644 index 000000000..66f268649 --- /dev/null +++ b/tests/osssdk/construction.cpp @@ -0,0 +1,55 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#include +#include +#include + +#include "../network/doctest.h" + +#include +#include +#include "oss_sdk_factory.hpp" + +#if __has_include() +#error "OSS SDK public mappings must exclude the private factory" +#endif + +static_assert(std::is_base_of_v, ark::oss_sdk_module_impl>); +static_assert(std::is_base_of_v, ark::oss_sdk_plugin>); +static_assert(std::is_same_v); +static_assert(std::is_same_v>); + +TEST_CASE("OSS SDK real factory retains its public implementation and unpublished metadata") { + auto candidate = ark::detail::make_oss_sdk_module(); + REQUIRE(candidate != nullptr); + const auto* instance = candidate.get(); + CHECK(typeid(*instance) == typeid(ark::oss_sdk_module_impl)); + CHECK(candidate->get_plugin_manager() == nullptr); + CHECK(candidate->get_name().empty()); +} + +TEST_CASE("OSS SDK assembly retains the interface key, disabled updates and explicit ownership") { + ark::plugin_manager manager; + ark::oss_sdk_plugin owner; + owner.set_plugin_manager(&manager); + CHECK(owner.get_plugin_version() == 0); + CHECK(owner.get_plugin_name() == typeid(ark::oss_sdk_plugin).name()); + owner.set_plugin_conf("oss-sdk-construction.toml"); + CHECK(ark::oss_sdk_plugin::get_plugin_conf() == "oss-sdk-construction.toml"); + owner.install(); + auto* instance = manager.find_module(); + REQUIRE(instance != nullptr); + CHECK(typeid(*instance) == typeid(ark::oss_sdk_module_impl)); + CHECK(instance->get_plugin_manager() == &manager); + CHECK(instance->get_name() == typeid(ark::oss_sdk_module).name()); + // Insertion succeeds only when assembly left the module out of the update set. + CHECK(manager.add_update_module(instance)); + manager.remove_update_module(instance->get_name()); + CHECK_THROWS_AS(owner.install(), std::runtime_error); + CHECK(manager.find_module() == instance); + owner.uninstall(); + // Reinstallation proves the public registration key and ownership were released. + CHECK_NOTHROW(owner.install()); + CHECK_NOTHROW(owner.uninstall()); + CHECK(manager.update(1)); + CHECK_THROWS_AS(owner.uninstall(), std::runtime_error); +} diff --git a/tests/osssdk/entry.cpp b/tests/osssdk/entry.cpp new file mode 100644 index 000000000..d32165f9a --- /dev/null +++ b/tests/osssdk/entry.cpp @@ -0,0 +1,45 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#include +#include +#include + +#include +#include +#include + +namespace { + +void require(bool condition, const char* message) { + if (!condition) + { + std::cerr << message << std::endl; + std::_Exit(1); + } +} + +} // namespace + +int main(int argc, char* argv[]) { + require(argc == 3, "Expected OSS SDK entry-probe directory and library stem"); + ark::logger log; + ark::logger::init(&log); + log.set_log_func([](spdlog::level::level_enum, const std::string&) {}); + ark::dynamic_library library(argv[2]); + require(library.load(argv[1]), "Cannot load OSS SDK entry-probe library"); + auto events = reinterpret_cast(library.get_symbol("oss_sdk_entry_events")); + require(events && events(0) == 0 && events(1) == 0, "Entry-probe state is not fresh"); + require(library.get_symbol("DllEntryPlugin") && library.get_symbol("DllExitPlugin"), + "OSS SDK explicit entry/exit exports are missing"); + ark::plugin_manager manager; + require(manager.start(0, argv[1], "conf/", {{argv[2], "entry-probe.toml"}}), "OSS SDK explicit entry failed"); + auto* instance = manager.find_module(); + require(instance && instance->get_plugin_manager() == &manager && + instance->get_name() == typeid(ark::oss_sdk_module).name(), + "OSS SDK explicit entry lost interface registration metadata"); + require(events(0) == 1 && events(1) == 0, "OSS SDK entry did not transfer factory ownership"); + require(manager.update(1) && events(2) == 0, "OSS SDK entry changed disabled update selection"); + require(manager.stop(2) && events(1) == 1, "OSS SDK explicit exit did not destroy its module exactly once"); + require(manager.stop(3) && events(1) == 1, "Repeated stop invoked OSS SDK exit twice"); + require(library.unload(), "Cannot release OSS SDK observation handle"); + std::cout << "OSS SDK real assembly entry/exit, configuration, interface metadata and ownership passed\n"; +} diff --git a/tests/osssdk/entry_factory.cpp b/tests/osssdk/entry_factory.cpp new file mode 100644 index 000000000..b51dad2e7 --- /dev/null +++ b/tests/osssdk/entry_factory.cpp @@ -0,0 +1,55 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#include + +#include +#include +#include "oss_sdk_factory.hpp" + +namespace { + +unsigned created{}; +unsigned destroyed{}; +unsigned updated{}; + +class entry_oss_sdk final : public ark::module_base +{ +public: + entry_oss_sdk() { + ++created; + } + + ~entry_oss_sdk() override { + ++destroyed; + } + + bool init() override { + return ark::oss_sdk_plugin::get_plugin_conf() == "conf/entry-probe.toml"; + } + + bool update() override { + ++updated; + return true; + } +}; + +} // namespace + +namespace ark::detail { + +std::unique_ptr make_oss_sdk_module() { + return std::make_unique(); +} + +} // namespace ark::detail + +ARK_EXPORT_FUNC unsigned oss_sdk_entry_events(unsigned event) { + switch (event) + { + case 0: + return created; + case 1: + return destroyed; + default: + return updated; + } +} diff --git a/tests/osssdk/exports.cpp b/tests/osssdk/exports.cpp new file mode 100644 index 000000000..118770b0b --- /dev/null +++ b/tests/osssdk/exports.cpp @@ -0,0 +1,31 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#include +#include +#include + +#include + +namespace { + +void require(bool condition, const char* message) { + if (!condition) + { + std::cerr << message << std::endl; + std::_Exit(1); + } +} + +} // namespace + +int main(int argc, char* argv[]) { + require(argc == 3, "Expected plugin directory and library stem"); + ark::logger log; + ark::logger::init(&log); + log.set_log_func([](spdlog::level::level_enum, const std::string&) {}); + ark::dynamic_library library(argv[2]); + require(library.load(argv[1]), "Cannot load actual OSS SDK shared library"); + require(library.get_symbol("DllEntryPlugin") != nullptr, "Missing explicit DllEntryPlugin export"); + require(library.get_symbol("DllExitPlugin") != nullptr, "Missing explicit DllExitPlugin export"); + require(library.unload(), "Cannot unload OSS SDK shared library"); + std::cout << argv[2] << ": actual DSO load, explicit entry/exit exports and unload passed\n"; +}