From 373e4b3acc7cb087fbbe209e1080f83b458da325 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 26 Sep 2026 22:00:18 +0800 Subject: [PATCH 1/5] 0.14.1: on Linux under a libc++ toolchain, deps-vcpkg and deps-cmake build with mcpp's clang, and each vcpkg triplet is its own installation --- .../2026-09-26-deps-vcpkg-rules-qt-plan.md | 17 +++++ .github/scripts/check-deps-and-qt.sh | 50 +++++++++++++- .github/workflows/ci.yml | 7 ++ README.md | 33 +++++---- deps/cmake.cppm | 21 ++++-- deps/deps.cppm | 43 ++++++++++++ deps/vcpkg.cppm | 68 +++++++++++++++---- mcpp.toml | 4 +- src/plugins.cppm | 2 +- 9 files changed, 209 insertions(+), 36 deletions(-) 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 c05b740..4dd1ca1 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 @@ -85,3 +85,20 @@ GalTranslPP 在 0.13.1 上构建成功后仍有两个手动步骤(解压嵌入 实测:库包中的 `artifact` 输出经 `mcpp::deploy` 传播到依赖它的程序的 `bin/` 与 `mcpp pack` 目录;依赖包中无下游 消费者的 `artifact` action 随下游构建执行。 + +## 7. 0.14.1:Linux 上依赖的 C++ 标准库与程序一致 + +Windows 上 MSVC ABI 的编译器共用 Microsoft 的标准库,macOS 上共用 libc++;Linux 上主机编译器使用 +libstdc++,mcpp 的 clang 使用 libc++,两者的 `std::` 符号互不链接。0.14.1 在 Linux 的 libc++ 工具链下: + +| 交付 | 机制 | +|---|---| +| `deps-vcpkg` 生成的 triplet | 默认 triplet 为 `-linux-libcxx`,经 `VCPKG_CHAINLOAD_TOOLCHAIN_FILE` 以 mcpp 的 clang 编译端口,再接 vcpkg 自身的 Linux 工具链;clang 的配置文件给出 libc++ 与 mcpp 链接的 C 库 | +| 每个 triplet 一个 vcpkg 安装 | vcpkg 的清单模式从安装中移除其余 triplet 的包;前缀改为 `//`,两个工具链的前缀并存,切换不重装 | +| `deps-cmake` | 未经 `cache_args` 指定编译器或工具链文件时,传入 mcpp 的 clang | + +判据(Linux CI `vcpkg-libcxx`):llvm 下 `libfmt.a` 含 `std::__1::` 符号且程序运行;默认工具链构建后 libc++ +前缀仍在;切回 llvm 不重跑安装;deps-cmake 子项目的 `CMAKE_CXX_COMPILER` 为 mcpp 的 clang。 + +GalTranslPP 上游仅支持 Windows(README 所述,代码直接调用 WinAPI),Linux 的判据由本仓库的 fixture 承担; +GalTranslPP PR2 作为 Windows 回归验证。 diff --git a/.github/scripts/check-deps-and-qt.sh b/.github/scripts/check-deps-and-qt.sh index 3b087be..b3d4c83 100644 --- a/.github/scripts/check-deps-and-qt.sh +++ b/.github/scripts/check-deps-and-qt.sh @@ -72,7 +72,7 @@ vcpkg_consumer() { 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" + ls target/vcpkg_installed/x64-windows/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" @@ -83,6 +83,51 @@ vcpkg_consumer() { fi } +# LINUX UNDER A libc++ TOOLCHAIN. The host compiler vcpkg and CMake find uses +# libstdc++, whose `std::` symbols a libc++ program cannot link. The default +# triplet is then the generated `x64-linux-libcxx`, whose ports build with +# mcpp's clang; fmt's interface returns `std::string`, so the link itself is the +# criterion. The prefix is then shown to survive the default toolchain's own +# installation, which vcpkg would remove if the two triplets shared one. +vcpkg_libcxx() { + local llvm="${MCPP_LLVM:-llvm@22.1.8}" gen=x64-linux-libcxx + [ "$(uname -m)" = aarch64 ] && gen=arm64-linux-libcxx + cd "$ROOT/tests/vcpkg-consumer" + rm -rf target vcpkg_installed + mkdir -p target/ci + "$MCPP" build --toolchain "$llvm" 2>&1 | tee target/ci/libcxx-build.log + "$MCPP" run --toolchain "$llvm" | tee target/ci/libcxx-run.log + grep -qE '^vcpkg-consumer: fmt [0-9]+ says 42$' target/ci/libcxx-run.log || fail "the libc++ program did not print through fmt" + local lib; lib=$(find target/vcpkg_installed -path "*/$gen/$gen/lib/libfmt.a" | head -1) + [ -n "$lib" ] || fail "no $gen prefix with libfmt.a" + grep -q 'std::__1::' <(nm -C "$lib") || fail "$lib is not built against libc++" + echo "ok: under $llvm the ports build with mcpp's clang and the program links them" + + "$MCPP" build 2>&1 | tee target/ci/default-build.log + "$MCPP" run | tee target/ci/default-run.log + grep -qE '^vcpkg-consumer: fmt [0-9]+ says 42$' target/ci/default-run.log || fail "the default toolchain's program did not print through fmt" + [ -f "$lib" ] || fail "the default toolchain's installation removed the $gen prefix" + local stamp; stamp=$(find target -path '*deps-vcpkg*' -name "$gen.stamp" | head -1) + [ -n "$stamp" ] || fail "no $gen installation stamp" + touch -r "$stamp" target/ci/before-switch-back + sleep 1 + "$MCPP" build --toolchain "$llvm" --profile dev > target/ci/switch-back.log 2>&1 || + { cat target/ci/switch-back.log; fail "the build switched back to $llvm failed"; } + [ -z "$(find "$stamp" -newer target/ci/before-switch-back)" ] || + fail "switching back to $llvm re-ran its installation" + echo "ok: the two toolchains' prefixes coexist, and switching back installs nothing" + + # deps-cmake takes the same compilers. + cd "$ROOT/tests/cmake-consumer" + rm -rf target + mkdir -p target/ci + "$MCPP" build --toolchain "$llvm" 2>&1 | tee target/ci/libcxx-build.log + "$MCPP" run --toolchain "$llvm" | grep -q '^cmake-consumer: greet says 42$' || fail "the libc++ cmake-consumer did not run" + grep -rqs 'CMAKE_CXX_COMPILER:[A-Z]*=.*xim-x-llvm.*/clang++' target --include=CMakeCache.txt || + fail "deps-cmake configured the subproject without mcpp's clang" + echo "ok: deps-cmake configures the subproject with mcpp's clang under $llvm" +} + vcpkg_workspace() { cd "$ROOT/tests/vcpkg-workspace" rm -rf target app-a/target app-b/target vcpkg_installed @@ -239,10 +284,11 @@ qt_widgets_consumer() { case "${1:-}" in vcpkg-consumer) vcpkg_consumer ;; + vcpkg-libcxx) vcpkg_libcxx ;; 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|archive-consumer|vcpkg-workspace|cmake-consumer|qt-consumer|qt-widgets-consumer"; exit 2 ;; + *) echo "usage: $0 vcpkg-consumer|vcpkg-libcxx|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 bda882c..8e1ac3d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1650,6 +1650,13 @@ 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 + # Linux has two C++ standard libraries. Under a libc++ toolchain the + # ports build with mcpp's clang, and the two toolchains' prefixes coexist. + - name: deps-vcpkg and deps-cmake build with mcpp's clang under a libc++ toolchain + run: | + "$MCPP" toolchain install llvm 22.1.8 + bash .github/scripts/check-deps-and-qt.sh vcpkg-libcxx + - 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 diff --git a/README.md b/README.md index 2d282cc..87c6267 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ 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 `