# M1 · oakcommon 拆分手册 > 内容:`engine/common/`(41 文件通用工具集)+ `engine/config/`。 > 依赖:oakcore(2)。被依赖:几乎全员(node 31、render 24、 > codec 12、plugin 6、audio 5、task 4、timeline 4、undo 2)。 > 拆分顺序第 1 位(叶子)。 ## 1. 目标形态 ``` oakcommon/ include/oakcommon/ # 公共头(C ABI + 允许直引的纯头工具) src/ # 现有 .cpp 原样迁入 tests/ # oakcommon_gtest ``` - **纯头工具**(lerp.h、define.h、decibel.h、tohex.h、digit.h、 memorypool.h、threadsafemap.h、range.h 等无 .cpp 的):作为 oakcommon 公共头直接提供给其他模块 include——拆分阶段允许 (不产生链接依赖)。RIIR 阶段这些会改写成各语言自有实现。 - **config/config.h**:单头配置存取,被全模块引用(10+)。 它是 Qt 依赖(QSettings 包装),按 §2 冻结 C ABI。 ## 2. 冻结 C API(有 .cpp 实现的函数族) 命名前缀 `oakcommon_`。以下按头分组(签名机械规则见 01 §3, 此处冻结函数清单与特殊约定): ### 2.1 `oakcommon/config.h`(对应 config/config.h) ```c void oakcommon_config_set(const char *group, const char *key, const char *value_utf8); int oakcommon_config_get(const char *group, const char *key, char *buf, int buf_size); /* 两段式 */ int oakcommon_config_get_int(const char *group, const char *key, int fallback); double oakcommon_config_get_double(const char *group, const char *key, double fallback); void oakcommon_config_set_int(const char *group, const char *key, int v); ``` ### 2.2 `oakcommon/xml.h`(对应 common/xmlutils.h,8 次被引) ```c /* XMLAttributeLoop/xml_read_next_start_element 的 C 化: * 以迭代器句柄包装 QXmlStreamReader */ typedef struct OakCommonXmlReader OakCommonXmlReader; OakCommonXmlReader *oakcommon_xml_reader_init(const char *utf8, int len); void oakcommon_xml_reader_free(OakCommonXmlReader *r); int oakcommon_xml_read_next_start_element(OakCommonXmlReader *r); int oakcommon_xml_reader_name(OakCommonXmlReader *r, char *buf, int n); int oakcommon_xml_reader_attr(OakCommonXmlReader *r, const char *attr, char *buf, int n); int oakcommon_xml_reader_read_element_text(OakCommonXmlReader *r, char *buf, int n); void oakcommon_xml_reader_skip_current(OakCommonXmlReader *r); ``` 写出侧 `oakcommon_xml_writer_*`(init_to_string/write_attribute/ write_text_element/free 得字符串,两段式)。 ### 2.3 `oakcommon/files.h`(common/filefunctions.h,render 7 次) ```c int oakcommon_file_exists(const char *path); /* 1/0 */ int oakcommon_file_size(const char *path); /* -1 失败 */ int oakcommon_file_read_all(const char *path, char *buf, int n); /* 两段式 */ int oakcommon_file_write_all(const char *path, const char *data, int n); int oakcommon_dir_mkpath(const char *path); int oakcommon_get_config_path(char *buf, int n); int oakcommon_get_temp_path(char *buf, int n); ``` ### 2.4 `oakcommon/ocio.h`、`oakcommon/oii.h`、`oakcommon/ffmpeg.h` (ocioutils/oiioutils/ffmpegutils,按 01 §3 机械 POD 化; OAK/OIIO/FFmpeg 类型全部句柄化或拍平字段。) ### 2.5 `oakcommon/misc.h` `jobtime`(`double oakcommon_jobtime_now(void)`)、`current` (`oakcommon_current_get/set` 线程局部当前对象句柄)。 ## 3. 切割点(common 的 12 次反向 include,逐条) | 现状 | 处理 | |---|---| | common → render/ 7 次(播放钟/自动滚动等引 render 类型) | 涉及文件(playbackaudioclock/autoscroll 等)**上移出 oakcommon**:它们不是底层工具,归 oakrender(M7) | | common → node/ 3 次 | 同上,归 oaknode(M3) | | common → undo/ 1 次 | 同上,归 oakundo(M2) | | common → codec/ 1 次 | 同上,归 oakcodec(M5) | | common → pluginSupport/ 1 次 | 同上,归 oakplugin(M9) | 判据:切完后 `grep -rn '#include "' oakcommon/src | grep -vE '"(oakcommon|olive/core)'` 为空(只剩 oakcore 与 Qt/系统头)。 ## 4. 测试(映射 03 §2) - config:set/get 往返、int/double fallback、两段式 buf。 - xml:reader 解析样例串、attr/text 读取、skip、writer 产出解析回读。 - files:临时目录建/写/读/尺寸/删除。 - 每函数 1 正常 + 1 错误路径;`oakcommon_debug_alive_count()` 泄漏断言。 ## 实施现状(2026-08-05) M1 已落地并可独立构建、测试全绿(127 个用例:126 通过,1 个 `GTEST_SKIP`)。以下为与上文计划的实际差异。 ### 最终目录结构 - `src/common/src/` — 去 Qt 化 C++ 实现(`olive::` 命名空间),target `oakcommon`(SHARED)。 - `src/common/c_api/` — 纯 C ABI 包装,通过 `target_sources` 合并进 `oakcommon`,不单独成库。 - `src/common/tests/` — gtest,target `oakcommon-gtest`, `gtest_discover_tests`。 - `include/common/`(仓库根)— 公共 C 头:`commandlineparser.h`、 `current.h`、`debug.h`、`dropworkflowbehavior.h`、`error.h`、 `ffmpegutils.h`、`filefunctions.h`、`miscutils.h`、`ocioutils.h`、 `oiioutils.h`、`power.h`、`qtutils.h`、`xmlutils.h`。 - `src/common/standalone/CMakeLists.txt` — 独立构建 driver(见下)。 ### 独立构建与测试 ```sh cmake -S src/common/standalone -B build-oakcommon cmake --build build-oakcommon -j ctest --test-dir build-oakcommon --output-on-failure ``` driver 通过 `find_package(... CONFIG)` 使用 Homebrew 的 OCIO/OIIO/GTest (顶层 `cmake/FindOpenColorIO.cmake`/`FindOpenImageIO.cmake` 面向 `.so`,macOS 下不适用),并把 config target 映射到 oakcommon CMakeLists 消费的 `${OCIO_LIBRARIES}` 等变量。driver 中额外处理了两点: 给 `olivecore` 补 `third_party/openfx/include` 头路径(顶层靠全局 include);禁用 OpenTimelineIO(`/opt/otio` 的 dylib 用 `@loader_path` 安装名,构建树内无法加载,且 oakcommon 不需要 OTIO)。 ### 实际依赖 - 第三方:EXPAT(XML 解析)、OpenColorIO、OpenImageIO、FFmpeg(经 ffmpeg_bridge 间接)、GTest(仅测试)。 - Oak 内部库:**仍链接 `olivecore` 与 `ffmpeg_bridge`**,二者均为真实 符号依赖而非纯头文件: - `olive::core::Rational`(`core/include/olive/core/util/rational.h`) 是对 `oakcore_rational_*` C ABI 的包装,构造/析构/运算都需要 olivecore 的符号; - `FFmpegUtils::get_compatible_bridge_pixel_format` 调用 `fb_find_best_pix_fmt_of_list`(ffmpeg_bridge 导出符号)。 - `pixelformat.h`/`sampleformat.h` 本身是 header-only,只需头路径 (`core/include`、`ffmpeg_bridge/include`,因这两个 target 的 include 目录是 PRIVATE,在 oakcommon 里显式补为 PUBLIC)。 - 未按计划只链 oakcore 头;OLIVECORE_BUILD_TESTS 在独立构建中关闭。 ### 与计划的主要差异 - 接口未按 §2 冻结清单逐条实现,而是按实际使用面包装:C ABI 头放在 仓库根 `include/common/`,命名 `oakcommon_<模块>_<动词>`,两段式 buffer(先查询尺寸再拷入)约定统一。 - `xmlutils` 用 expat 实现(事件预先解析成队列);C API 的 `oakcommon_xml_reader_read_element_text` 因底层是消费型读取,在 handle 内缓存最近一次文本以兼容两段式调用。 - 被移除/上移的类(播放钟、autoscroll 等反向 include 涉及项)见 `notes.md`。 - 计划中提到的 `switch(PixelFormat)` 编译问题实际不存在: `olive::core::PixelFormat` 提供 `operator Format()`,可直接 switch。