diff --git a/.agents/docs/2026-09-26-deps-vcpkg-rules-qt-plan.md b/.agents/docs/2026-09-26-deps-vcpkg-rules-qt-plan.md index f69dd9f..c05b740 100644 --- a/.agents/docs/2026-09-26-deps-vcpkg-rules-qt-plan.md +++ b/.agents/docs/2026-09-26-deps-vcpkg-rules-qt-plan.md @@ -69,3 +69,19 @@ T1 mcpp#702 实现并发布(mcpp 侧)─────────┴──► - `path` 包的宿主工具以整棵目录树的 stamp 为键,嵌套其中的消费方写出的文件改变该键(mcpp#705)。fixture 位于插件仓库之内,因此其安装位于 `target/` 之下,检查脚本的日志写入 `target/ci/`,第二次构建以 `--profile dev` 绕过快路径,使 Linux 与 Windows、macOS 走同一条规划路径。从索引取得插件的项目不受影响。 + +## 6. 0.14.0:运行时数据与精简 Qt(GalTranslPP 验证后的第二轮) + +GalTranslPP 在 0.13.1 上构建成功后仍有两个手动步骤(解压嵌入式 Python、运行 `Release.py`),Qt 下载量为 +完整基础包。第二轮把固定步骤程序化,并补齐 windeployqt 的 Qt 翻译: + +| 交付 | 仓库 | 机制 | +|---|---|---| +| `deps-archive` | mcpp-plugins | 配置期读 zip 中央目录,一个 `artifact` action 解压并逐一命名输出(SPEC-007 R3.2),每个输出 `mcpp::deploy`;`mcpp run` 与 `mcpp pack` 均可见 | +| `deps-vcpkg` / `deps-cmake` 的 `deploy` | mcpp-plugins | 前缀中的文件由命名输出的复制 action 取出再部署;不依赖引擎「deploy 等待 prepare」 | +| `translations::qt_languages` | mcpp-plugins | `lconvert` 合并所链接模块的目录为 `qt_.qm`,即 windeployqt 的产物 | +| `rules-qt-xim-base` / `xim:qt-base` | mcpp-plugins、xim-pkgindex、xlings-res | qtbase + qttools + qttranslations + 单独重发布的 QtQml 库;下载量约为 `xim:qt` 的三分之一 | +| `libs/qtsdk.lua` | xim-pkgindex | qt、qt-base、qt-addons 共享的下载、镜像、解压与标记逻辑 | + +实测:库包中的 `artifact` 输出经 `mcpp::deploy` 传播到依赖它的程序的 `bin/` 与 `mcpp pack` 目录;依赖包中无下游 +消费者的 `artifact` action 随下游构建执行。 diff --git a/.github/scripts/check-deps-and-qt.sh b/.github/scripts/check-deps-and-qt.sh index 30d64fe..3b087be 100644 --- a/.github/scripts/check-deps-and-qt.sh +++ b/.github/scripts/check-deps-and-qt.sh @@ -65,6 +65,10 @@ vcpkg_consumer() { "$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)" + # A file of the prefix the build program named in options::deploy. + find target -path '*/bin/licenses/fmt/copyright' | grep -q . || + fail "share/fmt/copyright was not deployed beside the program" + echo "ok: a file of the prefix is deployed beside the program" if is_windows; then # The pack collects it from the runtime search directory. @@ -74,6 +78,7 @@ vcpkg_consumer() { 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" + find target/dist -path '*/licenses/fmt/copyright' | grep -q . || fail "the packed tree carries no deployed copyright" echo "ok: the packed tree carries fmt.dll" fi } @@ -128,6 +133,58 @@ cmake_consumer() { echo "ok: a file added to the subproject re-ran its installation" } +archive_consumer() { + cd "$ROOT/tests/archive-consumer" + rm -rf target + mkdir -p target/ci + # PLANNING EXTRACTS NOTHING. The listing is read from the archive's central + # directory; the extraction is an action. + "$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 extraction"; } + [ -z "$(find target -path '*deps-archive*' -type f)" ] || fail "emit build-database extracted something" + echo "ok: emit planned the archive and extracted nothing" + + "$MCPP" build 2>&1 | tee target/ci/build.log + "$MCPP" run | tee target/ci/run.log + grep -q "^archive-consumer: 'greetings from the archive', 'a file two levels down'$" target/ci/run.log || + fail "the program did not read the archive's files from beside itself" + # The C++ file among the data `#error`s if compiled; the build succeeded, + # and the file is beside the program as data. + find target -path '*/bin/runtime/bundle/not-compiled.cpp' | grep -q . || + fail "an archive member was not deployed beside the program" + echo "ok: the archive's tree is beside the program, a C++ member included, as data" + + local copy + copy=$(find target -path '*deps-archive/bundle/bundle/greeting.txt' | head -1) + [ -n "$copy" ] || fail "no extracted copy under target/" + touch -r "$copy" target/ci/before-second-build + sleep 1 + "$MCPP" build --profile dev > target/ci/second-build.log 2>&1 || + { cat target/ci/second-build.log; fail "the second build failed"; } + [ -z "$(find "$copy" -newer target/ci/before-second-build)" ] || + fail "a second build with nothing changed extracted the archive again" + echo "ok: a second build with nothing changed did not extract the archive again" + + touch assets/bundle.zip + "$MCPP" build --profile dev > target/ci/third-build.log 2>&1 || + { cat target/ci/third-build.log; fail "the build after touching the archive failed"; } + [ -n "$(find "$copy" -newer target/ci/before-second-build)" ] || + fail "a changed archive was not extracted again" + echo "ok: a changed archive was extracted again" + + "$MCPP" pack --format dir > target/ci/pack.log 2>&1 || { cat target/ci/pack.log; fail "mcpp pack failed"; } + find target/dist -path '*/runtime/bundle/nested/deep.txt' | grep -q . || + fail "the packed tree does not carry the archive's files" + # A PE program sits at the root of the packed tree, an ELF or Mach-O one + # under bin/; the deployed files are beside it either way. + local packed + packed=$(find target/dist -type f \( -name archive-consumer -o -name archive-consumer.exe \) | head -1 || true) + [ -n "$packed" ] || fail "no packed program under target/dist" + "$packed" | grep -q "^archive-consumer: 'greetings from the archive'" || + fail "the packed program did not read the archive's files" + echo "ok: the packed tree carries the archive's files, and the packed program reads them" +} + qt_consumer() { cd "$ROOT/tests/qt-consumer" rm -rf target @@ -146,6 +203,9 @@ qt_consumer() { 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" + grep -q "Qt's own 'Abbrechen'$" target/ci/run.log || + fail "Qt's own strings were not translated from the combined qt_de.qm" + echo "ok: lconvert combined Qt's catalogs into qt_de.qm, and the program loads it" "$MCPP" build --profile dev -v > target/ci/second-build.log 2>&1 || { cat target/ci/second-build.log; fail "the second build failed"; } ! grep -qE '/(lupdate|lrelease)[^ ]* ' target/ci/second-build.log || @@ -179,9 +239,10 @@ qt_widgets_consumer() { case "${1:-}" in vcpkg-consumer) vcpkg_consumer ;; + archive-consumer) archive_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 ;; + *) echo "usage: $0 vcpkg-consumer|archive-consumer|vcpkg-workspace|cmake-consumer|qt-consumer|qt-widgets-consumer"; exit 2 ;; esac diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c51511c..bda882c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1650,6 +1650,9 @@ jobs: - name: deps-vcpkg installs a manifest as an action, and planning installs nothing run: bash .github/scripts/check-deps-and-qt.sh vcpkg-consumer + - name: deps-archive extracts a zip the project keeps and deploys its tree beside the program + run: bash .github/scripts/check-deps-and-qt.sh archive-consumer + - name: deps-vcpkg in two workspace members that share no dependency run: bash .github/scripts/check-deps-and-qt.sh vcpkg-workspace @@ -1659,7 +1662,7 @@ jobs: # A console program: QtCore, which is what Linux is served for. QtGui # loads libdbus, which the ecosystem does not publish, so the Widgets # fixture runs on Windows only. - - name: rules-qt runs moc, rcc and lrelease, with the SDK from xim:qt + - name: rules-qt runs moc, rcc, lupdate and lrelease, with the SDK from xim:qt-base run: bash .github/scripts/check-deps-and-qt.sh qt-consumer # ── THE SAME RULE ON THE OTHER TWO PLATFORMS ──────────────────────────────── @@ -2325,13 +2328,16 @@ jobs: - name: deps-vcpkg installs a manifest as an action, and planning installs nothing run: bash .github/scripts/check-deps-and-qt.sh vcpkg-consumer + - name: deps-archive extracts a zip the project keeps and deploys its tree beside the program + run: bash .github/scripts/check-deps-and-qt.sh archive-consumer + - name: deps-vcpkg in two workspace members that share no dependency run: bash .github/scripts/check-deps-and-qt.sh vcpkg-workspace - name: deps-cmake builds a CMake subproject as an action and links it run: bash .github/scripts/check-deps-and-qt.sh cmake-consumer - - name: rules-qt runs moc, rcc and lrelease, with the SDK from xim:qt + - name: rules-qt runs moc, rcc, lupdate and lrelease, with the SDK from xim:qt-base run: bash .github/scripts/check-deps-and-qt.sh qt-consumer # Windows and macOS: QtGui's dependencies are the system's there. On Linux diff --git a/README.md b/README.md index 369e107..2d282cc 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ engine's own module family and is not used here. | `rules-cuda` | `mcpp.rules.cuda` | 2026.9.6.6 | `[build] accel = "cuda…"`, a constrained glob for `*.cu`; the clang route with an LLVM toolchain, the nvcc route with a GCC one | | `rules-hip` | `mcpp.rules.hip` | 2026.9.6.6 | `[build] accel = "hip, cuda12.9+{sm_89}"`, a constrained glob for `*.hip`. On the NVIDIA platform HIP is a header layer over the CUDA runtime, so the compiler is the project's own clang and there is no ROCm on the machine | | `rules-metal` | `mcpp.rules.metal` | 2026.9.8.1 | the Metal toolchain of the macOS host's Xcode, located rather than installed: Xcode is not redistributable, so no payload is declared. `.metal` sources the project names on a macOS or iOS row become one `xcrun --sdk metal` action per shader (`-MMD`, so an edited `#include` recompiles the shaders that include it) and one `xcrun --sdk metallib` action per library, placed beside the program with `mcpp::deploy` under `metallib/`, which `dist-apple` maps into the bundle's resources. `compile(shaders)` compiles one source several times with definitions of its own, one library per `shader`; `options::library` links every shader into one library (`default` is the one `newDefaultLibrary` finds). Before planning anything the rule asks `xcrun --sdk --show-sdk-path` and `--find metal` / `--find metallib`, and refuses naming the command that answered nothing, because a missing SDK and a missing compiler have different remedies (Xcode 26 installs the Metal toolchain as a separate component). A shader on any other row is refused naming the row. CI compiles the fixture on `macos-15` and checks each library's magic, and that a header edit recompiles only the shaders that include it | -| `rules-qt` | `mcpp.rules.qt` | 2026.9.26.2 (mcpp#702) | a Qt 6 SDK: `rules-qt-xim` declares `xim:qt` 6.11.1 on the target axis, `rules-qt-xim-addons` adds `xim:qt-addons` (the additional libraries) as a second prefix, and `options::root` names an SDK from elsewhere. From 0.13.0. `moc` for every header under the package root that declares `Q_OBJECT`, `Q_GADGET` or `Q_NAMESPACE` and for a source that includes its own `.moc`; `uic` for `.ui`, `rcc` for `.qrc`, `lrelease` for `.ts` (named in `[build] sources` or in the options), each a `role = "source"` action with declared inputs. The modules are linked by full path, and the SDK's `bin/` (Windows) or `lib/` is a runtime search directory: the program's run path, `mcpp run`'s load path, `mcpp pack`'s closure, and on Windows the Qt DLLs the program imports placed beside it by the engine. The plugin directories `deploy_plugins` names are deployed beside the program. See [`deps-vcpkg`, `deps-cmake` and `rules-qt`](#deps-vcpkg-deps-cmake-and-rules-qt) | +| `rules-qt` | `mcpp.rules.qt` | 2026.9.26.2 (mcpp#702) | a Qt 6 SDK: `rules-qt-xim` declares `xim:qt` 6.11.1 on the target axis, `rules-qt-xim-base` declares `xim:qt-base` instead (qtbase and qttools with the QtQml library lupdate loads, about a third of the download; 0.14.0), `rules-qt-xim-addons` adds `xim:qt-addons` (the additional libraries) as a second prefix, and `options::root` names an SDK from elsewhere. From 0.13.0. `moc` for every header under the package root that declares `Q_OBJECT`, `Q_GADGET` or `Q_NAMESPACE` and for a source that includes its own `.moc`; `uic` for `.ui`, `rcc` for `.qrc`, `lrelease` for `.ts` (named in `[build] sources` or in the options), each a `role = "source"` action with declared inputs. The modules are linked by full path, and the SDK's `bin/` (Windows) or `lib/` is a runtime search directory: the program's run path, `mcpp run`'s load path, `mcpp pack`'s closure, and on Windows the Qt DLLs the program imports placed beside it by the engine. The plugin directories `deploy_plugins` names are deployed beside the program. See [`deps-vcpkg`, `deps-cmake`, `deps-archive` and `rules-qt`](#deps-vcpkg-deps-cmake-deps-archive-and-rules-qt) | | `rules-slang` | `mcpp.rules.slang` | 2026.9.7.1 | `[build] accel = "vulkan1.2"`, a constrained glob for `*.slang`. Slang is a different language from GLSL rather than a second driver for it -- its own module system, generics, and targets beyond SPIR-V -- so it is a rule of its own. `.slang` is **not** in the engine's device-source table: this feature declares `device_extensions = [".slang"]` and `rule_module = "mcpp.rules.slang"`, and the engine routes it from there. That is the criterion for the whole arrangement -- a new device language costs no engine release. Since 0.7.0 it has the same `options::storage` axis as `rules-spirv` (header / object / sidecar), `options::extra_args` for the arguments the rule has no field for, and `options::per_file` for what one shader gets that the others do not -- a project with a `-fvk-use-gl-layout` and one shader needing `-emit-spirv-via-glsl` writes both without leaving one `compile()` call | | `rules-spirv` | `mcpp.rules.spirv` | 2026.9.6.6 | `[build] accel = "vulkan1.2"`, a constrained glob for the shader stages; compiles each shader through a `role = "source"` action and states which of the two compilers produced it | | `rules-swift` | `mcpp.rules.swift` | 2026.9.8.1 | the Swift compiler of the macOS host's Xcode or Command Line Tools, located rather than installed, as `rules-metal` locates its toolchain. From 0.12.0. The `.swift` sources a project names on a macOS or iOS row compile as one module named after the package: one whole-module `xcrun --sdk swiftc -wmo -emit-object -target ` action whose role is `object`, so the object joins every image of the package, and one `swiftc -typecheck -emit-objc-header-path` action whose role is `source`, whose directory `mcpp::include_dir` adds, so the package's C and C++ sources include `-Swift.h`. `options::bridging_header` names a C header Swift sees without an import. The link receives the toolchain's `usr/lib/swift/` and the SDK's `usr/lib/swift` as search directories and `/usr/lib/swift` as a run path through `mcpp::link_flag`. Before planning anything the rule asks `xcrun --sdk --show-sdk-path` and `--find swiftc`, and refuses naming the command that answered nothing; a Swift source on any other row is refused naming the row. Not supported: a Swift `import` of another package's module, and another package's C++ including this package's generated header, which both need an engine channel that publishes a package's interface directory to its dependents; and SwiftPM dependencies. CI builds `tests/swift-consumer` on `macos-15` -- a C++ program calling a `@_cdecl` Swift function that calls back into C -- and runs it | @@ -90,8 +90,9 @@ engine's own module family and is not used here. | `dist-apple` | `mcpp.dist.apple` | 2026.9.14.2 (0.10.0); 2026.9.11.2 (macOS) and 2026.9.12.3 (iOS) before it | the base macOS install (`ditto`, `codesign`, `hdiutil`), and `xim:macapp-run` for `mcpp run` on macOS, which this feature declares with `when = "run"`. macOS: `Contents/`-shaped, as always. iOS (`aarch64-ios-sim`, `aarch64-ios`): a flat bundle at the same call site -- no separate feature, no separate module -- with `MinimumOSVersion` from `mcpp::min_platform_version()` (#622 A11), `CFBundleSupportedPlatforms` read from `env == "sim"`, `UIDeviceFamily`, `LSRequiresIPhoneOS`, and a directory of flat PNGs listed under `CFBundleIcons` in place of macOS's single `.icns` file. Signing is skipped on the simulator row (`options::identity` is ignored, with a `mcpp::warning` naming why), and the device row signs only with an identity. The iOS row is measured end to end on `macos-15`: a real `mcpp build`, `mcpp pack --format app` and `mcpp run` against `aarch64-ios-sim`, through `xim:apple-simulator-tools`' `simctl-run`. **The macOS floor is one release higher than its siblings** and the reason is not this member: under 2026.9.11.1 `mcpp pack` staged before dispatching and let a staging failure fail the command, so on a Mach-O program -- which the built-in closure walk refuses, because it uses `LD_TRACE_LOADED_OBJECTS` and dyld answers that by running the program -- every dispatched format was unreachable, including one that reads no staged tree. 2026.9.11.2 makes staging a service to the provider. From 0.9.2 the staged tree's deployed files (`bin//...`, which the engine stages for a Mach-O program before the closure walk since the release for mcpp#630) land at the bundle's resource destination -- `Contents/Resources//...` on macOS, the bundle root on iOS -- and the launcher alone goes to the executable directory, so `CFBundleExecutable` names a file that is where it says. The iOS fixture declares `llvm.libcxx` and `llvm.compiler-rt-builtins` under `cfg(os = "ios")`, which is what an application that imports `std` on those rows declares. From 0.10.0, with mcpp 2026.9.14.2: the dylibs the engine stages beside a Mach-O program, which the stage manifest's `needs` lines name, go to `Contents/Frameworks/` (`Frameworks/` on iOS) and not to the resources; the program is linked with the rpath that finds them there (`@executable_path/../Frameworks`, `@executable_path/Frameworks` on iOS) through `mcpp::link_flag`, so no file is edited after the link; a macOS bundle without `options::identity` is signed ad hoc, frameworks first and the bundle second, which `codesign --verify --deep --strict` requires of a bundle that carries a framework; an incomplete closure is a `mcpp::warning` naming the unresolved libraries; every refusal is a `mcpp::warning` as well, because the engine discards a build program's output when it exits 0. On macOS the member supplies the runner named `app` (`macapp-run`), so `mcpp run --format app` runs the bundle's executable in the foreground and returns its status with no runner in the manifest; a manifest runner of that name wins. `--format dmg` stages the bundle beside an `Applications` link and writes a UDZO image with `hdiutil create` (`options::volume_name`, `options::dmg`); it is refused on iOS. An engine below 2026.9.14.2 stages no `needs` lines, so the bundle carries no framework, anchors the rpath to the package directory, and hands the bundle directory to the kernel under `mcpp run --format app` unless `--runner app` is typed. CI measures the bundle on `macos-15`: the load command, the signature, the program with and without its framework (exit 7, then "Library not loaded"), `mcpp run --format app` with and without `--runner app`, and `hdiutil verify` and an attached image. From 0.11.0: a project's own Info.plist entries (`options::info_plist`), an iOS device bundle's provisioning profile (`options::provisioning_profile`), and `devicectl-run` (`xim:apple-device-tools`) as the device row's runner named `app` -- see [`dist-apple`: a project's Info.plist, and an iOS device](#dist-apple-a-projects-infoplist-and-an-ios-device). From 0.12.0 `options::omit_keys` leaves out a key the member only defaults -- see [`dist-apple`: a project's Info.plist, and an iOS device](#dist-apple-a-projects-infoplist-and-an-ios-device). From 0.12.0 a package in the resolved graph contributes Info.plist entries through `[package.metadata.dist-apple]`, applied before the application's own | | `dist-web` | `mcpp.dist.web` | 2026.9.13.1, the release that carries `${mcpp.self}` and `mcpp stage`'s argument shape as an engine contract (`stage --verify content --output `) -- what lets this member's copy run on every host mcpp does, Windows included, in place of the `cp` this member used through 0.8.0 | nothing beyond mcpp: `wasm32-emscripten` only. Copies `${mcpp.stage_dir}/bin/` -- the `.js` launcher, the implicit `.wasm`, the `.data` when present, and every `mcpp::deploy`'d file, all of which #622 A5 and A4 already stage there -- to `/web/`, dropping the `bin/` prefix a browser has no use for, and writes an `index.html` rendered from a project template or a built-in default that loads the script with a plain `