## 背景 有一类常见的需求:**程序的入口由框架提供**,应用只写回调,或者干脆只有资源和脚本。 - **gtest_main**:测试程序不写 `main`,由 gtest 提供。 - **SDL3 main callbacks**(`SDL_MAIN_USE_CALLBACKS`):应用实现 `SDL_AppInit`、`SDL_AppIterate`、`SDL_AppEvent`、`SDL_AppQuit`,`main` 由 SDL 提供。 - **Qt**:在 Windows 上提供 `qtmain` / WinMain。 - **openmui**(一个基于 mcpp 的 UI 框架,正在设计中):纯 TS / Python / Lua 应用没有任何 C++ 源文件,入口是框架的“播放器”,它读取清单,再加载视图和脚本;C++ 应用也可以选择只实现 `omui_app()` 回调,由框架提供 `main`。 另外,`kind = "app"` 在 `*-linux-android` 上本来就是“由平台约定入口”的共享库,所以“入口不一定由当前包写出”这件事在 mcpp 里已经有先例了。 ## 现状 `kind = "app"`(2026.9.12.3 起)和 `kind = "bin"` 一样,要求 `main = "<本包内的文件>"`(`docs/04-mcpp-toml.md`,`kind = "app"` 一节)。所以框架只能让模板生成一个用户自己维护的 `src/main.cpp`: ```cpp import openmui.player; int main(int c, char** v) { return omui::player::run(c, v); } ``` 这个办法能用,但有几个问题: - 每个应用都要带一个样板文件; - 框架升级时如果入口的签名变了,就要改每个应用; - 对纯脚本应用来说,“工程里必须有一个 .cpp 文件”很难理解。 ## 提议:入口可以由依赖的某个特性提供 使用方: ```toml [dependencies] openmui = { version = "0.1", features = ["entry-player"] } [targets.notes] kind = "app" entry = "openmui" # 入口由依赖 openmui 的某个已启用特性提供;和 main 互斥 ``` 提供方(框架包): ```toml [features.entry-player] entry = "src/entry/player.cpp" # 声明:这个特性提供入口翻译单元 [features.entry-app] entry = "src/entry/app.cpp" # 另一种入口:调用应用实现的回调 ``` 建议的语义: 1. 一个目标里 `main` 和 `entry` 只能写一个。 2. `entry = "<依赖>"` 指向的依赖**必须恰好有一个已启用的特性声明了 `entry`**。没有或者有多个时报错,并列出候选的特性。 3. 入口翻译单元**作为使用方目标的入口来编译和链接**:它可以 import 提供方的模块,也可以引用使用方定义的符号(例如框架声明、应用实现的 `extern "C++" omui::AppSpec omui_app();`)。 4. 在所有平台上的效果和现在写 `main` 完全一样:`bin` / `app` 的可执行文件形式、Android 上的共享库形式、`windows_subsystem` / `windows_entry`、`mcpp run`、`mcpp pack`、`mcpp test`。 5. 诊断信息要写明入口来自哪个依赖的哪个特性,方便排查。 ## 不改变的部分 - 现有写 `main` 的工程完全不受影响。 - 模板机制不变:模板仍然可以选择生成一个可见的 `main.cpp`(作为过渡)。 ## 备选方案(不太理想) - 由规则包生成入口源文件(`role = "source"`):但 `main` 字段需要的是一个写死的路径,而且入口不应该依赖构建插件。 - 由库导出 `main` 符号(gtest_main 那种做法):可移植性差(Android 和 Windows 的入口形式不同),mcpp 也无法给出清楚的诊断。 如果方向可以,openmui 这边可以先用模板生成 `main.cpp` 的办法过渡,等这个能力落地后再切换过去。
Activity
a program's beginning can be a dependency's entry target, and a frees…