Skip to content

Commit 72c3d10

Browse files
committed
0.15.0: features state mechanisms (rules-qt-xim* removed), build.mcpp-first configuration, the Qt SDK from options, QT_ROOT_DIR or the project's payload, deps actions without mcpp-deps, and docs/plugin-development.md
1 parent d7a0206 commit 72c3d10

28 files changed

Lines changed: 691 additions & 663 deletions

File tree

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# mcpp-plugins 0.15.0:职责分层、以 build.mcpp 为中心的配置与多平台运行时闭包
2+
3+
> 2026-09-26 · 第 3 版(吸收第 1、2 版评审)· 承接 `2026-09-26-deps-vcpkg-rules-qt-{design,plan}.md`
4+
5+
## 0. 总纲
6+
7+
核心体系由两部分组成:mcpp 通用引擎及其构建插件框架;官方、第三方或项目内的构建插件。`build.mcpp` 是这一机制的
8+
核心使用点:项目在其中导入插件模块、配置插件并调用它。
9+
10+
评审提出的问题:
11+
12+
1. Linux 上 vcpkg 端口与程序的 C++ 标准库不一致。
13+
2. Linux 上 QtGui 不可用。
14+
3. 插件与依赖库绑定:Qt 版本固定,运行时库写在插件的 feature 中。
15+
4. feature 应是通用机制;依赖的选择由插件与 build.mcpp 组合配置实现。
16+
5. 内部实现(`mcpp-deps`)不得成为消费方契约。
17+
6. 缺少插件开发规范;README 过长。
18+
19+
约束:
20+
- GalTranslPP 上游仅支持 Windows。Linux 与 macOS 的判据由插件仓库的 fixture 承担,GalTranslPP PR2 作为 Windows 回归。
21+
- mcpp 最新版本为 2026.9.26.2。
22+
- 0.14.1 与 0.15.0 合并为一个版本 0.15.0。每个仓库一个 PR,仅在无法同时验证时拆分(§8)。
23+
24+
## 1. 职责分层与决定
25+
26+
| 层 | 负责 | 不负责 |
27+
|---|---|---|
28+
| mcpp | 构建图、动作与戳、运行时闭包检查、部署、打包、工具链信息 | 任何具体库、SDK 或工具 |
29+
| 插件 | 机制:输入变为声明了输入输出的动作;前缀或 SDK 映射进构建 | 版本;运行时闭包;绕过引擎缺口 |
30+
| xlings 包 | 载荷及其在每个平台上的运行时闭包 | 构建逻辑 |
31+
| 项目 | 策略:库、SDK、版本、路径、选项 | — |
32+
33+
- **D1 feature 只表达机制。** feature 只能声明机制自身执行的工具(`xim:vcpkg`、`xim:cmake`),不得声明链接进程序的库或
34+
SDK。`rules-qt-xim`、`rules-qt-xim-base`、`rules-qt-xim-addons` 在 0.15.0 **直接删除**(评审决定,不保留别名)。
35+
- **D2 运行时闭包归提供它的包,逐平台成立**(§4)。
36+
- **D3 内部实现不进入契约。** 契约面只有 feature 名、`options` 字段、返回类型和文档所列的环境变量。
37+
38+
## 2. 配置:以 build.mcpp 为中心
39+
40+
每项配置取第一个给出值的层级:
41+
42+
| 级 | 位置 | 说明 |
43+
|---|---|---|
44+
| 1 | `build.mcpp` 中的 `options` | 项目配置的主要位置,值可以计算;插件的返回值(前缀、SDK 根、部署的文件)是编程扩展点 |
45+
| 2 | 约定的环境变量 | 只采用生态中已有的名字(`QT_ROOT_DIR`),变化时重新规划 |
46+
| 3 | 项目在 `[xlings]` 中声明的载荷 | 版本由项目声明 |
47+
48+
生效层级以 `mcpp::fact` 记录。第 2 版中的 `[package.metadata.<成员>]` 一级按评审意见删除:vcpkg 的库列表属于
49+
build.mcpp,而不是 mcpp.toml。
50+
51+
```toml
52+
[build-dependencies.mcpp]
53+
plugins = { version = "0.15.0", features = ["rules-qt", "deps-vcpkg"], host-module = true }
54+
55+
[target.'cfg(any(windows, linux, macos))'.xlings.workspace]
56+
"xim:qt-base" = "6.11.1"
57+
```
58+
59+
```cpp
60+
// build.mcpp
61+
import mcpp.rules.qt;
62+
import mcpp.deps.vcpkg;
63+
int main() {
64+
mcpp::deps::vcpkg::options v;
65+
v.libraries = { "fmt", "spdlog" };
66+
mcpp::rules::qt::options q;
67+
q.modules = { "Core", "Widgets" };
68+
return mcpp::deps::vcpkg::use(v) && mcpp::rules::qt::compile(q) ? 0 : 1;
69+
}
70+
```
71+
72+
rules-qt 的 SDK 查找顺序为 `options::root` → `QT_ROOT_DIR`(不采用 `QTDIR`)→ 项目声明的 `xim:qt` 或 `xim:qt-base`(附带
73+
`xim:qt-addons`)。找不到时的警告给出可直接复制的两行声明。
74+
75+
## 3. C++ 标准库对齐
76+
77+
- **deps-vcpkg**:在 Linux 的 libc++ 工具链下,默认 triplet 为生成的 `<arch>-linux-libcxx`,端口以 mcpp 的 clang 编译。
78+
- **每个 triplet 独立安装**:前缀为 `<install root>/<triplet>/<triplet>`。
79+
- **deps-cmake**:同样条件下传入 mcpp 的 clang。
80+
- **rules-qt**:在 libc++ 工具链下使用 Qt 官方 Linux 构建时给出警告。
81+
82+
以上已完成:PR #32 的这部分三平台 CI 通过,本机 `vcpkg-libcxx` 判据通过。
83+
84+
## 4. 多平台运行时闭包
85+
86+
判据:只装有操作系统的机器上,`mcpp pack` 的产物能够启动。
87+
88+
| 平台 | Qt 加载的外部库 | 0.15.0 |
89+
|---|---|---|
90+
| Linux | glib、zstd、zlib、libdbus、fontconfig、freetype、X11、xkbcommon、EGL/GL、xcb 系列 | `xim:qt` 与 `xim:qt-base` 声明这些包和 `xim:glibc`。libxpkg 的加载器谓词在安装后改写载荷中的每个 ELF:可执行文件使用 xlings 加载器,RUNPATH 为这些包与载荷自身 `lib/` 的闭包。工具与程序共用一个加载器和 libc |
91+
| Windows | 系统 DLL;VC++ 运行时(MSVCP140、VCRUNTIME140、VCRUNTIME140_1) | windows-x86_64 载荷从微软可再分发包(xlings-res/msvc 镜像,与 `xim:msvc` 14.44.35207 同一 vsix)取出 `Microsoft.VC143.CRT` 放入 `bin/`。windows-aarch64 暂缺镜像,列为后续项 |
92+
| macOS | 系统 framework 与 libc++ | 无新增 |
93+
94+
- **新增 `xim:dbus` 1.16.2**:由 conda-forge 重新打包到 xlings-res/dbus(双端逐字节一致),依赖只有 libc 与 libpthread;构建
95+
前缀已重定位到 `/`。
96+
- **后续项**:QtNetwork 所需的 krb5、brotli;windows-aarch64 的 CRT;xlings glibc 的 UTF-8 locale。
97+
98+
本机验证(手工模拟改写 RUNPATH):Linux 上 console、Widgets offscreen 两个程序均运行。Windows 的 CRT 放置由 CI 验证(V1)。
99+
100+
## 5. mcpp-deps:删除
101+
102+
它原是 deps 系列动作的命令,负责四件事:
103+
- vcpkg 的环境变量;
104+
- 安装根的锁;
105+
- 短路径的临时目录;
106+
- CMake 三步串联,以及解压前清空目录。
107+
108+
它经 `tools = ["mcpp-deps"]` 进入了消费方契约,也是 mcpp#705、#707 性能问题的来源。0.15.0 以不依赖新引擎能力的方式替代:
109+
110+
| 职责 | 替代 |
111+
|---|---|
112+
| `VCPKG_ROOT`、`VCPKG_DISABLE_METRICS` | vcpkg 参数 `--vcpkg-root`、`--disable-metrics` |
113+
| 锁 | vcpkg 自身的 `<root>/vcpkg/vcpkg-running.lock`;workspace fixture 两成员共用一个根,本机通过(V2) |
114+
| 短路径临时目录 | `--x-buildtrees-root`、`--x-packages-root`、`--downloads-root`,路径在规划时计算 |
115+
| CMake 三步、清空并解压 | 构建程序写出的脚本,由 `cmake -P` 执行;参数以方括号参数书写,保留空格与分号 |
116+
117+
- `tools/deps_main.cpp` 与 `[targets.mcpp-deps]` 直接删除,与 D1 的处理一致,不保留弃用目标。
118+
- 依赖边写有 `tools = ["mcpp-deps"]` 的消费方须删除该项,这记入 §7。
119+
- 本机 vcpkg-consumer、cmake-consumer、archive-consumer、vcpkg-workspace、vcpkg-libcxx 五个判据全部通过。
120+
121+
交给 mcpp 的只有通用能力,都不阻塞本版:
122+
- E1:动作支持环境变量与工作目录;
123+
- E2:feature 自带其所需的包内工具。`tools-embed` 的 `mcpp-embed` 仍经 `tools` 暴露,待 E2 支持后收回。
124+
125+
## 6. 规范与文档
126+
127+
- **README**:精简为约 100 行(原 1113 行),开篇陈述 §0 的总纲;各成员细节移至 `docs/<成员>.md`。
128+
- **`docs/plugin-development.md`(新增)**:包含职责分层表和十条规则:
129+
1. 机制而非策略;
130+
2. feature 只表达机制;
131+
3. build.mcpp 为先的三级配置;
132+
4. 运行时闭包归载荷;
133+
5. 内部实现不进入契约;
134+
6. 工作即动作;
135+
7. 缺失即警告;
136+
8. 引擎缺口提交 issue;
137+
9. 每条行为对应判据;
138+
10. 文档简洁。
139+
140+
第三方插件与项目内插件同样适用。
141+
142+
## 7. 兼容性(0.15.0 的破坏性变更)
143+
144+
| 变化 | 消费方需做的修改 |
145+
|---|---|
146+
| 删除 `rules-qt-xim*` | 改为 `features = ["rules-qt"]`,并在 `[xlings]` 中声明 `xim:qt-base` 或 `xim:qt` 及版本 |
147+
| 删除 `mcpp-deps` | 从依赖边删除 `tools = ["mcpp-deps"]` |
148+
| vcpkg 前缀多一层 triplet | 改用返回的 `prefix`,不硬编码 `vcpkg_installed/<triplet>`(GalTranslPP `gpp.build` 第 182 行) |
149+
| 插件不再提供 glib 目录 | 使用外部 Qt 的 Linux 项目自行处理闭包 |
150+
151+
以下变化不需要消费方修改:Linux 与 Windows 的 Qt 载荷会因 `installed()` 的标记 `runtime 1` 自动重装。
152+
153+
## 8. 任务依赖与 PR
154+
155+
```
156+
xlings-res/dbus(已发布)
157+
xim-pkgindex#885:dbus(单独拆出:CI 从已发布索引解析依赖,新包与其首个依赖方无法同时验证)
158+
│ 合入 → 索引发布
159+
xim-pkgindex#884:qt、qt-base 的 Linux 闭包与 Windows CRT
160+
│ 合入 → 索引发布
161+
mcpp-plugins#32 → 0.15.0(Linux Widgets 与 Windows CRT 判据依赖上一步)
162+
│ 三平台 CI 全绿 → tag、GitHub release、gtc 上传 GitCode
163+
mcpp-index:登记 0.15.0
164+
│
165+
GalTranslPP PR2:按 §7 迁移;Windows 回归(含 VC++ 运行时)
166+
并行:mcpp issue E1、E2
167+
```
168+
169+
## 9. 总体评审
170+
171+
| 维度 | 结论 |
172+
|---|---|
173+
| 架构 | 四层职责不重叠;插件中的平台逻辑均由引擎提供的工具链信息推导,没有写死的值 |
174+
| 稳定性 | 动作的输入输出完整;运行时闭包在规划、链接、运行、打包阶段分别由引擎与载荷保证 |
175+
| 简洁 | 删除三个 feature、一个包内工具和 metadata 一级;新增的机制只有 `cmake -P` 脚本 |
176+
| 易用性 | 配置集中在 build.mcpp;依赖边不再有 `tools`;Qt 选择是两行显式声明 |
177+
| 兼容性 | 破坏性变更集中在 §7,每项修改为一到两行;载荷自动重装 |
178+
| 跨平台 | 三个平台的闭包逐一论证;windows-aarch64 的 CRT 为已知缺口 |
179+
| 一致性 | 三级配置、fact 记录、警告优先,对所有成员相同 |
180+
| 测试 | 新增 `qt-sdk-consumer`(三个层级)、Linux Widgets 与打包、Windows CRT、`vcpkg-libcxx` |
181+
182+
遗留:
183+
- V1:Windows 的 CRT 是否被引擎放置到程序旁;
184+
- 后续项:krb5、brotli、windows-aarch64 的 CRT、UTF-8 locale。

‎.github/scripts/check-deps-and-qt.sh‎

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,7 @@ cmake_consumer() {
151151
"$MCPP" run | tee target/ci/run.log
152152
grep -q '^cmake-consumer: greet says 42$' target/ci/run.log || fail "the program did not call the subproject's library"
153153
assert_not_rerun "$(stamp_of deps-cmake)"
154-
# The builds after an edit are planned as well. The subproject lies inside
155-
# this repository, the tree mcpp stamps for the plugins' host tool
156-
# (mcpp#705), so an edit also rebuilds `mcpp-deps`; the build that is
157-
# expected to re-run the installation absorbs that rebuild.
154+
# The builds after an edit are planned as well.
158155
touch greet/greet.c
159156
"$MCPP" build --profile dev > target/ci/third-build.log 2>&1 || { cat target/ci/third-build.log; fail "the rebuild failed"; }
160157
# The installed library is the product of the rebuild, whatever the engine
@@ -279,7 +276,50 @@ qt_widgets_consumer() {
279276
find target/dist -ipath '*platforms/qoffscreen.dll' | grep -q . ||
280277
fail "the packed tree carries no platforms/qoffscreen.dll"
281278
echo "ok: the packed tree carries the Qt modules and the platform plugins"
279+
# The VC++ runtime Qt's DLLs import travels with them, so the program
280+
# does not depend on the target machine's VC++ Redistributable.
281+
for dll in msvcp140.dll vcruntime140.dll vcruntime140_1.dll; do
282+
find target -path '*/bin/*' -iname "$dll" | grep -q . || fail "$dll was not placed beside the program"
283+
find target/dist -iname "$dll" | grep -q . || fail "the packed tree carries no $dll"
284+
done
285+
echo "ok: the VC++ runtime is beside the program and in the packed tree"
282286
fi
287+
if ! is_windows && ! is_macos; then
288+
# QtGui's runtime closure comes from the payload: the program runs
289+
# under the ecosystem's loader, which reads no host library directory.
290+
"$MCPP" pack --format dir | tee target/ci/pack.log
291+
for so in libQt6Widgets.so.6 libdbus-1.so.3 libxkbcommon.so.0 libfontconfig.so.1; do
292+
find target/dist -name "$so*" | grep -q . || fail "the packed tree carries no $so"
293+
done
294+
echo "ok: the packed tree carries Qt and QtGui's runtime closure"
295+
fi
296+
}
297+
298+
# The SDK at each level `rules-qt` consults, read back from the fact the rule
299+
# records (`rules-qt.sdk=<level>: <root>`, in the build program's cache).
300+
qt_sdk_consumer() {
301+
cd "$ROOT/tests/qt-sdk-consumer"
302+
rm -rf target
303+
mkdir -p target/ci
304+
sdk_fact() { grep -h -o 'rules-qt\.sdk=[^"]*' target/.build-mcpp/build.mcpp.cache | tail -1; }
305+
"$MCPP" build 2>&1 | tee target/ci/build.log
306+
"$MCPP" run | tee target/ci/run.log
307+
grep -qE '^qt-sdk-consumer: Qt 6\.' target/ci/run.log || fail "the program did not load QtCore"
308+
local fact root; fact=$(sdk_fact)
309+
case "$fact" in "rules-qt.sdk=xlings: "*) ;; *) fail "the project's payload was not the SDK: $fact" ;; esac
310+
root=${fact#rules-qt.sdk=xlings: }
311+
echo "ok: the SDK is the payload the project declares ($root)"
312+
313+
# The payload's root, named for one machine, then by the build program.
314+
QT_ROOT_DIR="$root" "$MCPP" build > target/ci/env-build.log 2>&1 || { cat target/ci/env-build.log; fail "the build under QT_ROOT_DIR failed"; }
315+
fact=$(sdk_fact)
316+
[ "$fact" = "rules-qt.sdk=QT_ROOT_DIR: $root" ] || fail "QT_ROOT_DIR was not the SDK: $fact"
317+
echo "ok: QT_ROOT_DIR names the SDK, and a change re-plans the build"
318+
QT_SDK_CONSUMER_ROOT="$root" QT_ROOT_DIR=/nonexistent "$MCPP" build > target/ci/options-build.log 2>&1 ||
319+
{ cat target/ci/options-build.log; fail "the build under options::root failed"; }
320+
fact=$(sdk_fact)
321+
[ "$fact" = "rules-qt.sdk=options: $root" ] || fail "options::root was not the SDK: $fact"
322+
echo "ok: options::root names the SDK ahead of QT_ROOT_DIR"
283323
}
284324

285325
case "${1:-}" in
@@ -290,5 +330,6 @@ case "${1:-}" in
290330
cmake-consumer) cmake_consumer ;;
291331
qt-consumer) qt_consumer ;;
292332
qt-widgets-consumer) qt_widgets_consumer ;;
293-
*) echo "usage: $0 vcpkg-consumer|vcpkg-libcxx|archive-consumer|vcpkg-workspace|cmake-consumer|qt-consumer|qt-widgets-consumer"; exit 2 ;;
333+
qt-sdk-consumer) qt_sdk_consumer ;;
334+
*) echo "usage: $0 vcpkg-consumer|vcpkg-libcxx|archive-consumer|vcpkg-workspace|cmake-consumer|qt-consumer|qt-widgets-consumer|qt-sdk-consumer"; exit 2 ;;
294335
esac

‎.github/workflows/ci.yml‎

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1453,7 +1453,7 @@ jobs:
14531453
# catch, so it must not introduce one.
14541454
# THE MEMBERS THAT CARRY SOURCES, READ OUT OF THE MANIFEST. From 0.13.0 a
14551455
# member family may hold features that add a payload and no source
1456-
# (`rules-qt-xim`, `dist-apk-kotlin`): they compile nothing this fixture
1456+
# (`dist-apk-kotlin`): they compile nothing this fixture
14571457
# could miss, and naming them here would download an SDK on every host.
14581458
# So the requirement is one-sided -- every source-carrying member of the
14591459
# four families is named -- and the other side only checks spelling:
@@ -1638,7 +1638,7 @@ jobs:
16381638
#
16391639
# vcpkg keeps everything it reuses in one per-user directory -- its
16401640
# binary cache (`archives/`), its registry cache (`registries/`) and,
1641-
# through `mcpp-deps`, its downloads -- so caching that directory is what
1641+
# through the member's `--downloads-root`, its downloads -- so caching that directory is what
16421642
# makes the second run of a manifest a restore rather than a compile.
16431643
- name: Cache vcpkg's per-user directory
16441644
uses: actions/cache@v4
@@ -1666,12 +1666,17 @@ jobs:
16661666
- name: deps-cmake builds a CMake subproject as an action and links it
16671667
run: bash .github/scripts/check-deps-and-qt.sh cmake-consumer
16681668

1669-
# A console program: QtCore, which is what Linux is served for. QtGui
1670-
# loads libdbus, which the ecosystem does not publish, so the Widgets
1671-
# fixture runs on Windows only.
1672-
- name: rules-qt runs moc, rcc, lupdate and lrelease, with the SDK from xim:qt-base
1669+
- name: rules-qt runs moc, rcc, lupdate and lrelease, with the SDK the project declares
16731670
run: bash .github/scripts/check-deps-and-qt.sh qt-consumer
16741671

1672+
# The payload carries QtGui's runtime closure (libdbus, fontconfig, xcb,
1673+
# ...), so a Widgets program links, runs offscreen and packs on Linux.
1674+
- name: rules-qt builds a Widgets program with a .ui form, runs it offscreen and packs it
1675+
run: bash .github/scripts/check-deps-and-qt.sh qt-widgets-consumer
1676+
1677+
- name: rules-qt takes the SDK from the project's payload, QT_ROOT_DIR or options::root
1678+
run: bash .github/scripts/check-deps-and-qt.sh qt-sdk-consumer
1679+
16751680
# ── THE SAME RULE ON THE OTHER TWO PLATFORMS ────────────────────────────────
16761681
#
16771682
# `rules-spirv` is the only member whose payload this ecosystem publishes for
@@ -1844,7 +1849,7 @@ jobs:
18441849
# three hosts is the class of difference this job exists to catch.
18451850
# THE MEMBERS THAT CARRY SOURCES, READ OUT OF THE MANIFEST. From 0.13.0 a
18461851
# member family may hold features that add a payload and no source
1847-
# (`rules-qt-xim`, `dist-apk-kotlin`): they compile nothing this fixture
1852+
# (`dist-apk-kotlin`): they compile nothing this fixture
18481853
# could miss, and naming them here would download an SDK on every host.
18491854
# So the requirement is one-sided -- every source-carrying member of the
18501855
# four families is named -- and the other side only checks spelling:
@@ -2344,11 +2349,14 @@ jobs:
23442349
- name: deps-cmake builds a CMake subproject as an action and links it
23452350
run: bash .github/scripts/check-deps-and-qt.sh cmake-consumer
23462351

2347-
- name: rules-qt runs moc, rcc, lupdate and lrelease, with the SDK from xim:qt-base
2352+
- name: rules-qt runs moc, rcc, lupdate and lrelease, with the SDK the project declares
23482353
run: bash .github/scripts/check-deps-and-qt.sh qt-consumer
23492354

2350-
# Windows and macOS: QtGui's dependencies are the system's there. On Linux
2351-
# QtGui loads libdbus, which the ecosystem does not publish, so the Linux
2352-
# job runs the console fixture only.
2353-
- name: rules-qt builds a Widgets program with a .ui form, and it runs offscreen
2355+
# Windows: the payload carries the VC++ runtime Qt's DLLs import, so the
2356+
# program starts on a machine without the VC++ Redistributable. macOS:
2357+
# Qt loads system frameworks only.
2358+
- name: rules-qt builds a Widgets program with a .ui form, runs it offscreen and packs it
23542359
run: bash .github/scripts/check-deps-and-qt.sh qt-widgets-consumer
2360+
2361+
- name: rules-qt takes the SDK from the project's payload, QT_ROOT_DIR or options::root
2362+
run: bash .github/scripts/check-deps-and-qt.sh qt-sdk-consumer

0 commit comments

Comments
 (0)