Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
636 changes: 636 additions & 0 deletions .agents/docs/2026-09-26-deps-vcpkg-rules-qt-design.md

Large diffs are not rendered by default.

71 changes: 71 additions & 0 deletions .agents/docs/2026-09-26-deps-vcpkg-rules-qt-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# 实施计划:`deps-vcpkg`、`rules-qt`、`deps-cmake` 的跨仓库交付

状态:执行中 · 2026-09-26(引擎下界 2026.9.26.2) · 设计见 `2026-09-26-deps-vcpkg-rules-qt-design.md`(第 4 版)。

## 1. 第 3 轮决定(设计第 4 版据此修改)

| # | 决定 | 对设计的影响 |
|---|---|---|
| D6 | 附加模块合为一个包 | 确认 `xim:qt-addons`;按 xim 的载荷私有规则,它有**自己的前缀**,不写入 `xim:qt` 的目录 |
| D8 | 先做 mcpp / xlings 生态的通用能力;GalTranslPP 的 D 阶段只在 fork 上做初步验证 | 引擎需求 2 在 mcpp 中实现:构建程序声明运行时库目录 |
| D9 | 免装 VS 不是目标 | 删除 `toolchain::msvc_xim`;`xim:msvc` 由 mcpp 自动匹配,插件不声明 |

另有一项由实测带来的简化:vcpkg-tool 每个发布附带 `vcpkg-standalone-bundle.tar.gz`(3.4 MB,脚本与该工具
版本严格对应,`vcpkg-bundle.json` 含 `"usegitregistry": true`)。以它为 `VCPKG_ROOT` 时,`builtin-baseline`
清单经 git registry 解析,registry 落在 vcpkg 的每用户 registries 缓存。已在本机实测:对 GalTranslPP 的
baseline `ea1a7396` 执行 `vcpkg install --dry-run` 解析出 `fmt 12.2.0`,缓存 137 MB。因此设计第 3 版 §3.6 的托管克隆、
按工具检出与文件锁全部取消。

## 2. 仓库与交付物

| 仓库 | 单个 PR 的内容 | 发布 |
|---|---|---|
| mcpp-community/mcpp | SPEC-007 与 #702 的合规设计(`runtime_search_dir`、`prepare`、stamp 规则、Windows DLL 放置),由 mcpp 侧实现 | 下一个 mcpp 发布;插件 PR 等待它 |
| openxlings/xim-pkgindex | `xim:vcpkg` 2026.7.27(工具 + standalone bundle)、`xim:qt` 6.11.1、`xim:qt-addons` 6.11.1;`xim:7zip` 说明 `7z.dll` 位置;测试 | 合入即发布索引;`xlings-res/vcpkg` 资源用 `gh` 与 `gtc` 双端上传 |
| mcpp-community/mcpp-plugins | `0.13.0`:`deps-vcpkg` + `mcpp-vcpkg`、`rules-qt` + `rules-qt-xim` + `rules-qt-xim-addons`、`deps-cmake` + `mcpp-cmake`;fixtures;CI 引擎版本;README | tag `v0.13.0`,GitHub release,`gtc` 上传 `mcpp-res/mcpp-plugins` |
| mcpp-community/mcpp-index | 登记 `mcpp:plugins 0.13.0` | 合入即发布 |
| Sunrisepeak/GalTranslPP(fork) | 临时 PR 2:迁移 A–C,D 阶段初步验证 | 不合入 |

## 3. 依赖关系

```
T2 xim 包(vcpkg/qt/qt-addons)──► T2r 索引合入(xim-pkgindex#878,已合入)
T3 插件按 SPEC-007 实现(PR 已开,等待)──┐
T1 mcpp#702 实现并发布(mcpp 侧)─────────┴──► T3c 插件 CI 全绿 ──► T3r 插件发布 0.13.0 ──► T4 mcpp-index ──► T5 GalTranslPP PR 2
```

- T1、T2、T3 并行:T3 在本机用 T1 的源码构建验证 Linux 路径;Windows 与 macOS 路径只能由 CI 验证。
- T3 的 CI 依赖 T1r(`MCPP_VERSION` 指向新引擎)与 T2r(索引里有 `xim:vcpkg`、`xim:qt`)。
- T5 用索引中的 `mcpp:plugins 0.13.0`,不用路径依赖,以验证用户实际得到的东西。

## 4. 多角度检查项(实施与 review 共用)

| 角度 | 检查项 |
|---|---|
| 架构 | `deps-*` 家族只描述、重活在 action;插件拥有它驱动的 xim 包;运行时目录经引擎通道而非逐文件复制 |
| 稳定性 | emit 在未安装依赖时返回 0;安装边只在输入变化时重跑;各项目的 buildtrees/packages 隔离;vcpkg 自身对安装目录加锁 |
| 优雅简洁 | 零配置可用;registry 管理交给 vcpkg;一个选项结构 + 一个入口函数 |
| 用户体验 | 所有拒绝都给出下一步;缺依赖时 warning 写明 `mcpp build` 会安装它 |
| 兼容性 | 旧引擎给出明确的版本下界提示;已有 `vcpkg_installed/` 布局不变;自定义 triplet 与 overlay 照常 |
| 跨平台 | Windows x86_64 / Linux x86_64 / macOS:`deps-vcpkg` 三平台 fixture;`rules-qt` Windows + Linux(macOS 视 Qt 框架布局而定) |
| 一致性 | 选项、警告格式、`mcpp::fact` 版本记录与既有成员一致;README 成员表同格式 |
| 无感升级 | 插件次版本号前移;不改变已有成员的行为;引擎新增指令,旧程序不受影响 |
| 测试覆盖 | 每个功能一个正向 fixture + 一个拒绝/边界断言;`all-rules-compile` 覆盖新模块在三平台编译 |

## 5. 进度

| 任务 | 状态 |
|---|---|
| T0 设计第 4 版、本计划 | 完成 |
| T1 引擎 | mcpp#702 由 mcpp 侧合入,随 2026.9.26.2 发布 |
| T2 xim 包 | xim-pkgindex#878、#879 已合入;`xlings-res/vcpkg` 双端资源已校验 |
| T3 插件 | PR #29;Linux 各 fixture 在 2026.9.26.2 上通过;Windows、macOS 由 CI 验证 |
| T3r / T4 / T5 | 等待 T3c |

实测发现两项引擎缺口,均已报告:

- 主机构建不读取主机三元组的 `[target.<triple>]` 段(mcpp#704),因此 Qt fixture 在 `[build]` 中声明
`cxx_runtime = "toolchain-coupled"`。
- `path` 包的宿主工具以整棵目录树的 stamp 为键,嵌套其中的消费方写出的文件改变该键(mcpp#705)。fixture
位于插件仓库之内,因此其安装位于 `target/` 之下,检查脚本的日志写入 `target/ci/`,第二次构建以
`--profile dev` 绕过快路径,使 Linux 与 Windows、macOS 走同一条规划路径。从索引取得插件的项目不受影响。
174 changes: 174 additions & 0 deletions .github/scripts/check-deps-and-qt.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
#!/usr/bin/env bash
# The criteria for `deps-vcpkg`, `deps-cmake` and `rules-qt`, one function per
# fixture, the same on every host. A CI step names the fixture:
#
# bash .github/scripts/check-deps-and-qt.sh vcpkg-consumer
#
# Each function fails with the reason it failed. Host differences are the
# host's: a DLL is looked for on Windows, where a triplet builds fmt as one, and
# nowhere else.
set -euo pipefail

: "${MCPP:?MCPP names the mcpp under test}"
ROOT=$(cd "$(dirname "$0")/../.." && pwd)

fail() { echo "FAIL: $*"; exit 1; }

is_windows() { case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) return 0 ;; *) return 1 ;; esac; }
is_macos() { [ "$(uname -s)" = Darwin ]; }

# The stamp an installation action leaves: the file mcpp writes when a `prepare`
# action's command succeeds. Its modification time is the criterion for "the
# installation did not run again".
stamp_of() { find target -path "*$1*" -name '*.stamp' | head -1; }

# The program as the build left it, started without `mcpp run`: on Windows the
# engine has placed the DLLs it imports beside it (SPEC-007 R4.3), so it starts
# from the build directory as it does from the packed tree.
run_directly() {
local exe; exe=$(find target -path '*/bin/*' -name "$1.exe" | head -1)
[ -n "$exe" ] || fail "no $1.exe under target/"
"$exe"
}

# A second build with nothing changed must not run the installation again.
assert_not_rerun() {
local stamp="$1"
[ -n "$stamp" ] && [ -f "$stamp" ] || fail "no installation stamp under target/"
mkdir -p target/ci
touch -r "$stamp" target/ci/before-second-build
sleep 1
# `--profile dev` names the default profile and declines mcpp's fast path,
# so the second build is planned on every host, as it is on Windows and
# macOS without the flag, and a host tool whose key moved is rebuilt.
"$MCPP" build --profile dev > target/ci/second-build.log 2>&1 ||
{ cat target/ci/second-build.log; fail "the second build failed"; }
[ -z "$(find "$stamp" -newer target/ci/before-second-build)" ] ||
fail "the second build re-ran the installation ($stamp is newer)"
echo "ok: a second build with nothing changed did not re-run the installation"
}

vcpkg_consumer() {
cd "$ROOT/tests/vcpkg-consumer"
rm -rf target vcpkg_installed
# PLANNING NEVER INSTALLS, AND STILL STATES THE PATHS. An editor asks for
# the build database on machines that never built; the build program must
# succeed there and name the include directory the build will fill.
mkdir -p target/ci
"$MCPP" emit build-database --format json > target/ci/db.json 2> target/ci/emit.log ||
{ cat target/ci/emit.log; fail "emit build-database failed before any installation"; }
grep -q 'vcpkg_installed' target/ci/db.json || fail "the database names no vcpkg_installed include directory"
[ ! -d target/vcpkg_installed ] || fail "emit build-database installed something"
echo "ok: emit succeeded before the installation and named its include directory"

"$MCPP" build 2>&1 | tee target/ci/build.log
"$MCPP" run | tee target/ci/run.log
grep -qE '^vcpkg-consumer: fmt [0-9]+ says 42$' target/ci/run.log || fail "the program did not print through fmt"
assert_not_rerun "$(stamp_of deps-vcpkg)"

if is_windows; then
# The pack collects it from the runtime search directory.
ls target/vcpkg_installed/x64-windows/bin/fmt.dll > /dev/null || fail "x64-windows built no fmt.dll"
run_directly vcpkg-consumer | tee target/ci/direct.log
grep -qE '^vcpkg-consumer: fmt [0-9]+ says 42$' target/ci/direct.log ||
fail "started from the build directory, the program did not find fmt.dll"
"$MCPP" pack --format dir | tee target/ci/pack.log
find target/dist -iname 'fmt.dll' | grep -q . || fail "the packed tree carries no fmt.dll"
echo "ok: the packed tree carries fmt.dll"
fi
}

vcpkg_workspace() {
cd "$ROOT/tests/vcpkg-workspace"
rm -rf target app-a/target app-b/target vcpkg_installed
mkdir -p target/ci
"$MCPP" build 2>&1 | tee target/ci/build.log
"$MCPP" run -p app-a | tee target/ci/run-a.log
"$MCPP" run -p app-b | tee target/ci/run-b.log
grep -qE '^app-a: fmt [0-9]+$' target/ci/run-a.log || fail "app-a did not run"
grep -qE '^app-b: fmt [0-9]+$' target/ci/run-b.log || fail "app-b did not run"
# Each member builds into its own target/ and declares its own installation.
[ "$(find . -path '*/target/*' -path '*deps-vcpkg*' -name '*.stamp' | wc -l)" -ge 2 ] ||
fail "each member did not declare its own installation"
echo "ok: two members that share no dependency both installed and linked one prefix"
}

cmake_consumer() {
cd "$ROOT/tests/cmake-consumer"
rm -rf target
mkdir -p target/ci
"$MCPP" build 2>&1 | tee target/ci/build.log
"$MCPP" run | tee target/ci/run.log
grep -q '^cmake-consumer: greet says 42$' target/ci/run.log || fail "the program did not call the subproject's library"
assert_not_rerun "$(stamp_of deps-cmake)"
# The builds after an edit are planned as well. The subproject lies inside
# this repository, the tree mcpp stamps for the plugins' host tool
# (mcpp#705), so an edit also rebuilds `mcpp-deps`; the build that is
# expected to re-run the installation absorbs that rebuild.
touch greet/greet.c
"$MCPP" build --profile dev > target/ci/third-build.log 2>&1 || { cat target/ci/third-build.log; fail "the rebuild failed"; }
# The installed library is the product of the rebuild, whatever the engine
# does with the action's stamp.
[ -n "$(find target -path '*deps-cmake*/install/*' -name '*greet*' -newer target/ci/before-second-build)" ] ||
fail "an edited subproject source did not rebuild the subproject"
echo "ok: an edited subproject source rebuilt the subproject"
# And not again: the engine moves the stamp past the input that changed
# (mcpp's SPEC-007 R3.5).
assert_not_rerun "$(stamp_of deps-cmake)"

# A file ADDED to the subproject is an input too: the build program watches
# the tree, so the next plan names it and the installation runs again.
trap 'rm -f "$ROOT/tests/cmake-consumer/greet/added.txt"' RETURN
touch -r "$(stamp_of deps-cmake)" target/ci/before-added-file
sleep 1
echo added > greet/added.txt
"$MCPP" build --profile dev > target/ci/added-build.log 2>&1 || { cat target/ci/added-build.log; fail "the build after adding a file failed"; }
[ -n "$(find "$(stamp_of deps-cmake)" -newer target/ci/before-added-file)" ] ||
fail "a file added to the subproject did not re-run its installation"
echo "ok: a file added to the subproject re-ran its installation"
}

qt_consumer() {
cd "$ROOT/tests/qt-consumer"
rm -rf target
mkdir -p target/ci
"$MCPP" build 2>&1 | tee target/ci/build.log
"$MCPP" run | tee target/ci/run.log
grep -qE "^qt-consumer: signal 42, resource 'greetings from rcc', translation 'hallo', Qt 6\." target/ci/run.log ||
fail "moc, rcc or lrelease did not reach the program"
find target -name 'qt_consumer_de.qm' | grep -q . || fail "no .qm was produced"
echo "ok: moc (header and inline), rcc and lrelease reached the program"
}

qt_widgets_consumer() {
cd "$ROOT/tests/qt-widgets-consumer"
rm -rf target
mkdir -p target/ci
"$MCPP" build 2>&1 | tee target/ci/build.log
QT_QPA_PLATFORM=offscreen "$MCPP" run | tee target/ci/run.log
grep -q "^qt-widgets-consumer: platform offscreen, label 'made by uic'$" target/ci/run.log ||
fail "the platform plugin or the uic form did not reach the program"
if is_macos; then
find target -path '*/bin/platforms/libqoffscreen.dylib' | grep -q . ||
fail "no platforms/libqoffscreen.dylib was deployed beside the program"
fi
if is_windows; then
QT_QPA_PLATFORM=offscreen run_directly qt-widgets-consumer | tee target/ci/direct.log
grep -q "^qt-widgets-consumer: platform offscreen" target/ci/direct.log ||
fail "started from the build directory, the program did not find the Qt DLLs"
"$MCPP" pack --format dir | tee target/ci/pack.log
find target/dist -iname 'Qt6Widgets.dll' | grep -q . || fail "the packed tree carries no Qt6Widgets.dll"
find target/dist -ipath '*platforms/qoffscreen.dll' | grep -q . ||
fail "the packed tree carries no platforms/qoffscreen.dll"
echo "ok: the packed tree carries the Qt modules and the platform plugins"
fi
}

case "${1:-}" in
vcpkg-consumer) vcpkg_consumer ;;
vcpkg-workspace) vcpkg_workspace ;;
cmake-consumer) cmake_consumer ;;
qt-consumer) qt_consumer ;;
qt-widgets-consumer) qt_widgets_consumer ;;
*) echo "usage: $0 vcpkg-consumer|vcpkg-workspace|cmake-consumer|qt-consumer|qt-widgets-consumer"; exit 2 ;;
esac
Loading
Loading