From 27a022a4b98f154b7370c5264a6f19660a8f4774 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Fri, 5 Jun 2026 01:18:32 +0800 Subject: [PATCH 01/10] =?UTF-8?q?test:=20temp=20CI=20=E2=80=94=20macOS=201?= =?UTF-8?q?4=20support=20experiment=20(DO=20NOT=20MERGE)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes all other workflows on this branch and adds a two-job experiment: build mcpp@HEAD + xlings@main on macos-15 with MACOSX_DEPLOYMENT_TARGET=14.0 (assert LC_BUILD_VERSION minos=14.0, probe libc++ availability gating at compile time), then run the artifacts end-to-end on a macos-14 runner (control: released minos15 binaries must be refused by dyld). --- .github/workflows/bootstrap-macos.yml | 146 ------ .github/workflows/ci-fresh-install.yml | 192 -------- .github/workflows/ci-linux.yml | 168 ------- .github/workflows/ci-macos.yml | 358 -------------- .github/workflows/ci-windows.yml | 257 ---------- .github/workflows/release.yml | 545 --------------------- .github/workflows/temp-macos14-support.yml | 199 ++++++++ 7 files changed, 199 insertions(+), 1666 deletions(-) delete mode 100644 .github/workflows/bootstrap-macos.yml delete mode 100644 .github/workflows/ci-fresh-install.yml delete mode 100644 .github/workflows/ci-linux.yml delete mode 100644 .github/workflows/ci-macos.yml delete mode 100644 .github/workflows/ci-windows.yml delete mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/temp-macos14-support.yml diff --git a/.github/workflows/bootstrap-macos.yml b/.github/workflows/bootstrap-macos.yml deleted file mode 100644 index 33fffc4d..00000000 --- a/.github/workflows/bootstrap-macos.yml +++ /dev/null @@ -1,146 +0,0 @@ -name: bootstrap-macos - -# One-shot workflow to produce the first macOS mcpp binary. -# Uses xmake + xlings LLVM to compile mcpp from source. -# Once a macOS binary exists, mcpp can self-host for future releases. - -on: - workflow_dispatch: - -jobs: - bootstrap: - name: Bootstrap mcpp (macOS ARM64) - runs-on: macos-15 - timeout-minutes: 30 - env: - XLINGS_NON_INTERACTIVE: '1' - XLINGS_VERSION: '0.4.30' - steps: - - uses: actions/checkout@v4 - - - name: System info - run: | - uname -a - sw_vers - xcrun --show-sdk-path - - - name: Install xlings - run: | - WORK=$(mktemp -d) - tarball="xlings-${XLINGS_VERSION}-macosx-arm64.tar.gz" - curl -fsSL -o "${WORK}/${tarball}" \ - "https://github.com/d2learn/xlings/releases/download/v${XLINGS_VERSION}/${tarball}" - tar -xzf "${WORK}/${tarball}" -C "${WORK}" - "${WORK}/xlings-${XLINGS_VERSION}-macosx-arm64/subos/default/bin/xlings" self install - echo "$HOME/.xlings/subos/default/bin" >> "$GITHUB_PATH" - echo "$HOME/.xlings/bin" >> "$GITHUB_PATH" - - - name: Install LLVM + xmake - run: | - xlings install llvm -y || xlings install llvm@20.1.7 -y - brew install xmake - LLVM_ROOT=$(find "$HOME/.xlings" -path "*/xpkgs/xim-x-llvm/*/bin/clang++" | head -1 | xargs dirname | xargs dirname) - echo "LLVM_ROOT=$LLVM_ROOT" >> "$GITHUB_ENV" - "$LLVM_ROOT/bin/clang++" --version - xmake --version - - - name: Build mcpp with xmake - run: | - # Generate xmake.lua if not present - if [ ! -f xmake.lua ]; then - cat > xmake.lua << 'EOF' - add_rules("mode.release") - set_languages("c++23") - - package("cmdline") - set_homepage("https://github.com/mcpplibs/cmdline") - set_description("Modern C++ command-line parsing library") - set_license("Apache-2.0") - add_urls("https://github.com/mcpplibs/cmdline/archive/refs/tags/$(version).tar.gz") - add_versions("0.0.1", "3fb2f5495c1a144485b3cbb2e43e27059151633460f702af0f3851cbff387ef0") - on_install(function (package) - import("package.tools.xmake").install(package) - end) - package_end() - - add_requires("cmdline 0.0.1") - - target("mcpp") - set_kind("binary") - add_files("src/main.cpp") - add_files("src/**.cppm") - add_packages("cmdline") - add_includedirs("src/libs/json") - set_policy("build.c++.modules", true) - -- Static link libc++ for minimal runtime dependencies - add_ldflags("-static-libstdc++", {force = true}) - add_cxxflags("-stdlib=libc++", {force = true}) - add_ldflags("-stdlib=libc++", {force = true}) - EOF - fi - - # Configure with xlings LLVM - xmake f -y -m release --toolchain=llvm --sdk="$LLVM_ROOT" - # Build - xmake build -y mcpp - - - name: Verify built binary - run: | - MCPP=$(find build -name mcpp -type f -perm +111 | head -1) - test -x "$MCPP" - echo "=== file ===" - file "$MCPP" - echo "=== otool -L (dynamic deps) ===" - otool -L "$MCPP" - echo "=== version ===" - "$MCPP" --version - echo "MCPP=$MCPP" >> "$GITHUB_ENV" - - - name: Package - id: package - run: | - VERSION=$(awk -F '"' '/^version[[:space:]]*=/{print $2; exit}' mcpp.toml) - TARBALL="mcpp-${VERSION}-macosx-arm64.tar.gz" - WRAPPER="mcpp-${VERSION}-macosx-arm64" - - mkdir -p "dist/$WRAPPER/bin" - cp "$MCPP" "dist/$WRAPPER/bin/mcpp" - strip "dist/$WRAPPER/bin/mcpp" 2>/dev/null || true - cp LICENSE "dist/$WRAPPER/" 2>/dev/null || true - cp README.md "dist/$WRAPPER/" 2>/dev/null || true - - cat > "dist/$WRAPPER/mcpp" << 'LAUNCHER' - #!/bin/sh - exec "$(dirname "$0")/bin/mcpp" "$@" - LAUNCHER - chmod +x "dist/$WRAPPER/mcpp" - - # Bundle xlings - XLINGS_BIN="$HOME/.xlings/subos/default/bin/xlings" - if [ -x "$XLINGS_BIN" ]; then - mkdir -p "dist/$WRAPPER/registry/bin" - cp "$XLINGS_BIN" "dist/$WRAPPER/registry/bin/xlings" - fi - - (cd dist && tar -czf "$TARBALL" "$WRAPPER") - (cd dist && shasum -a 256 "$TARBALL" > "$TARBALL.sha256") - - echo "tarball=dist/$TARBALL" >> "$GITHUB_OUTPUT" - echo "version=$VERSION" >> "$GITHUB_OUTPUT" - ls -la dist/ - - - name: Smoke test - run: | - SMOKE=$(mktemp -d) - tar -xzf "${{ steps.package.outputs.tarball }}" -C "$SMOKE" - VERSION="${{ steps.package.outputs.version }}" - "$SMOKE/mcpp-${VERSION}-macosx-arm64/bin/mcpp" --version - "$SMOKE/mcpp-${VERSION}-macosx-arm64/mcpp" --version - - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: mcpp-macosx-arm64 - path: | - dist/mcpp-*-macosx-arm64.tar.gz - dist/mcpp-*-macosx-arm64.tar.gz.sha256 diff --git a/.github/workflows/ci-fresh-install.yml b/.github/workflows/ci-fresh-install.yml deleted file mode 100644 index d5cf6ca5..00000000 --- a/.github/workflows/ci-fresh-install.yml +++ /dev/null @@ -1,192 +0,0 @@ -name: ci-fresh-install - -# Fresh install CI — validates the released mcpp binary via xlings. -# Simulates a real first-time user on a clean machine (no caches). -# -# For each platform, tests every supported toolchain: -# 1. mcpp new hello → mcpp run (basic project) -# 2. mcpp build (build mcpp itself from source) -# -# This workflow tests released mcpp, not PR code. -# It runs on release publish, manual trigger, and daily schedule. - -on: - release: - types: [ published ] - workflow_dispatch: - schedule: - # Run daily at 06:00 UTC to catch issues from xlings/runner updates - - cron: '0 6 * * *' - -concurrency: - group: ci-fresh-install - cancel-in-progress: false # use false to test in PRs, true to only test released mcpp - -jobs: - # ────────────────────────────────────────────────────────────────── - # Linux: gcc@16.1.0, musl-gcc@15.1.0, llvm@20.1.7 - # ────────────────────────────────────────────────────────────────── - linux-fresh: - name: Linux fresh install - runs-on: ubuntu-24.04 - timeout-minutes: 60 - steps: - - uses: actions/checkout@v4 - - - name: Install xlings + mcpp - env: - XLINGS_NON_INTERACTIVE: '1' - run: | - curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v0.4.38 - echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH" - - - name: Install mcpp and config mirror - run: | - xlings install mcpp -y -g # install to global - mcpp --version - mcpp self config --mirror GLOBAL - - echo "mcpp debug info:" - which mcpp - cat $HOME/.xlings/.xlings.json - - - name: "Default: mcpp new → run" - run: | - cd "$(mktemp -d)" - mcpp new hello_gcc - cd hello_gcc - mcpp run - - - name: "Default: build mcpp" - run: | - mcpp clean - mcpp run - - - name: "musl-gcc: mcpp new → run" - run: | - mcpp toolchain install gcc 15.1.0-musl - mcpp toolchain default gcc@15.1.0-musl - cd "$(mktemp -d)" - mcpp new hello_musl - cd hello_musl - mcpp run - - - name: "musl-gcc: build mcpp" - run: | - mcpp toolchain default gcc@15.1.0-musl - mcpp clean - mcpp run - - - name: "gcc 16: mcpp new → run" - run: | - mcpp toolchain install gcc 16.1.0 - mcpp toolchain default gcc@16.1.0 - cd "$(mktemp -d)" - mcpp new hello_gcc16 - cd hello_gcc16 - mcpp run - - - name: "gcc 16: build mcpp" - run: | - mcpp toolchain default gcc@16.1.0 - mcpp clean - mcpp run - - - name: "LLVM: mcpp new → run" - run: | - mcpp toolchain install llvm 20.1.7 - mcpp toolchain default llvm@20.1.7 - cd "$(mktemp -d)" - mcpp new hello_llvm - cd hello_llvm - mcpp run - - - name: "LLVM: build mcpp" - run: | - mcpp toolchain default llvm@20.1.7 - mcpp clean - mcpp run - - # ────────────────────────────────────────────────────────────────── - # macOS: llvm@20.1.7 - # ────────────────────────────────────────────────────────────────── - macos-fresh: - name: macOS fresh install - runs-on: macos-15 - timeout-minutes: 30 - steps: - - uses: actions/checkout@v4 - - - name: Install xlings - env: - XLINGS_NON_INTERACTIVE: '1' - run: | - curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v0.4.38 - echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH" - - - name: Install mcpp and config mirror - run: | - xlings install mcpp -y -g # install to global - mcpp --version - mcpp self config --mirror GLOBAL - - echo "mcpp debug info:" - which mcpp - cat $HOME/.xlings/.xlings.json - - - name: "LLVM: mcpp new → run" - run: | - cd "$(mktemp -d)" - mcpp new hello_mac - cd hello_mac - mcpp run - - - name: "LLVM: build mcpp" - run: | - mcpp clean - mcpp run - - # ────────────────────────────────────────────────────────────────── - # Windows: llvm@20.1.7 + MSVC STL - # ────────────────────────────────────────────────────────────────── - windows-fresh: - name: Windows fresh install - runs-on: windows-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v4 - - - name: Install xlings - shell: pwsh - env: - XLINGS_NON_INTERACTIVE: '1' - run: | - irm https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.ps1 | iex - - $xlingsbin = "$env:USERPROFILE\.xlings\subos\current\bin" - $env:PATH = "$xlingsbin;$env:PATH" - $xlingsbin | Out-File -Append -FilePath $env:GITHUB_PATH -Encoding utf8 - - - name: Install mcpp and config mirror - shell: pwsh - run: | - xlings install mcpp -y -g --verbose - - cat "$env:USERPROFILE\.xlings\.xlings.json" - mcpp --version - mcpp self config --mirror GLOBAL - - - name: "LLVM: mcpp new → run" - shell: pwsh - run: | - $tmp = New-TemporaryFile | ForEach-Object { Remove-Item $_; New-Item -ItemType Directory -Path $_ } - Set-Location $tmp - mcpp new hello_win - Set-Location hello_win - mcpp run - - - name: "LLVM: build mcpp" - shell: pwsh - run: | - mcpp clean - mcpp run diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml deleted file mode 100644 index fbd75d87..00000000 --- a/.github/workflows/ci-linux.yml +++ /dev/null @@ -1,168 +0,0 @@ -name: ci-linux - -# Self-host CI on Linux: mcpp builds mcpp. The bootstrap mcpp comes from -# `xlings install mcpp` (xim:mcpp in the xlings package index), so this -# workflow no longer depends on a previous-release tarball — the -# chicken-and-egg now lives upstream in the xlings index. -# -# Paired workflows: ci-macos.yml, ci-windows.yml. - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - workflow_dispatch: - -concurrency: - group: ci-${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - build-and-test: - name: build + test (linux x86_64, self-host) - runs-on: ubuntu-24.04 - timeout-minutes: 60 - env: - # MCPP_HOME pinned so the cache key below restores into the - # same path mcpp resolves at runtime. - MCPP_HOME: /home/runner/.mcpp - steps: - - uses: actions/checkout@v4 - - # Cache mcpp's sandbox: the toolchain (musl-gcc + binutils + - # glibc + linux-headers + patchelf + ninja) takes minutes to - # install on a cold runner. Key on the workspace manifest so a - # toolchain change in mcpp.toml refreshes the cache; restore-keys - # provide a layered fallback so near-misses still skip the slow - # toolchain installs. - - name: Cache mcpp sandbox - uses: actions/cache@v4 - with: - path: ~/.mcpp - key: mcpp-sandbox-${{ runner.os }}-${{ hashFiles('mcpp.toml', '.xlings.json') }} - restore-keys: | - mcpp-sandbox-${{ runner.os }}- - - # Cache xlings + its locally installed packages (xim:mcpp etc.). - # Saves the xlings bootstrap roundtrip + the mcpp xpkg download - # on hot runs. - - name: Cache xlings - uses: actions/cache@v4 - with: - path: ~/.xlings - key: xlings-${{ runner.os }}-v2-${{ hashFiles('.xlings.json') }} - restore-keys: | - xlings-${{ runner.os }}-v2- - - - name: Bootstrap mcpp via xlings - env: - XLINGS_NON_INTERACTIVE: '1' - XLINGS_VERSION: '0.4.30' - run: | - # Always install the pinned version — cache may hold an older - # xlings whose sysroot/packages are incompatible. - tarball="xlings-${XLINGS_VERSION}-linux-x86_64.tar.gz" - curl -fsSL -o "/tmp/${tarball}" \ - "https://github.com/d2learn/xlings/releases/download/v${XLINGS_VERSION}/${tarball}" - tar -xzf "/tmp/${tarball}" -C /tmp - "/tmp/xlings-${XLINGS_VERSION}-linux-x86_64/subos/default/bin/xlings" self install - export PATH="$HOME/.xlings/subos/default/bin:$PATH" - xlings --version - # xim:mcpp — `xlings install` is idempotent so cache hits skip - # the download. - xlings install mcpp -y - MCPP="$HOME/.xlings/subos/default/bin/mcpp" - test -x "$MCPP" - "$MCPP" --version - echo "MCPP=$MCPP" >> "$GITHUB_ENV" - echo "XLINGS_BIN=$HOME/.xlings/subos/default/bin/xlings" >> "$GITHUB_ENV" - - # Cache the build directory: precise key on src/ + manifest - # so a no-source-change run lands on a full hit. Layered - # restore-keys let mid-run partial hits keep BMI/dyndep state - # for proper incremental builds. - - name: Cache target/ (build artifacts + BMIs) - uses: actions/cache@v4 - with: - path: target - key: mcpp-target-${{ runner.os }}-${{ hashFiles('src/**', 'tests/**', 'mcpp.toml', 'mcpp.lock') }} - restore-keys: | - mcpp-target-${{ runner.os }}- - - - name: Configure mirror + Build mcpp from source (self-host) - run: | - export MCPP_VENDORED_XLINGS="$XLINGS_BIN" - # Set GLOBAL mirror via xlings directly (bootstrap mcpp may lack --mirror flag) - "$XLINGS_BIN" config --mirror GLOBAL 2>/dev/null || true - "$MCPP" self config --mirror GLOBAL 2>/dev/null || true - "$MCPP" build - - - name: Unit + integration tests via `mcpp test` - run: | - # Use freshly-built mcpp for test (it has --mirror support) - MCPP_FRESH=$(realpath "$(find target -type f -name mcpp -printf '%T@ %p\n' | sort -rn | head -1 | cut -d' ' -f2)") - "$MCPP_FRESH" self config --mirror GLOBAL - "$MCPP_FRESH" test - - - name: E2E suite - # Step-level guard: a single hung test (historically 10_env_command.sh - # on slow xlings/network) used to eat the full 60-min job budget. - # Cap the suite at 25 min so a hang fails fast and we still have room - # for the downstream toolchain steps. Per-test 600s timeout lives in - # tests/e2e/run_all.sh and identifies WHICH test hung. - timeout-minutes: 25 - run: | - # Point the e2e runner at the freshly-built binary, not the - # bootstrap one. Tests cd into mktemp -d, so $MCPP must be - # absolute or the relative path breaks under the temp cwd. - MCPP=$(realpath "$(find target -type f -name mcpp -printf '%T@ %p\n' | sort -rn | head -1 | cut -d' ' -f2)") - test -x "$MCPP" - export MCPP - # Tests that set MCPP_HOME to a fresh tmpdir need an xlings - # to bootstrap from; surface the xlings binary installed - # above so they don't have to reinstall the sandbox. - export MCPP_VENDORED_XLINGS="$XLINGS_BIN" - test -x "$MCPP_VENDORED_XLINGS" - # GitHub-hosted runners are outside CN; keep CI toolchain downloads on - # the global mirror while mcpp's default remains CN for fresh local - # sandboxes. E2E tests with their own MCPP_HOME read this variable. - export MCPP_E2E_TOOLCHAIN_MIRROR=GLOBAL - "$MCPP" self config --mirror "$MCPP_E2E_TOOLCHAIN_MIRROR" - "$MCPP" self config - # Pin the global default so test 28 (which exercises the - # default-toolchain path) gets a deterministic GNU answer - # instead of whatever auto-install picks on a fresh sandbox. - "$MCPP" toolchain default gcc@16.1.0 - # Warm musl once in the persistent sandbox. Fresh-home e2e tests - # inherit this payload, and the later --target musl job reuses it - # instead of downloading a second copy into another home. - "$MCPP" toolchain install gcc 15.1.0-musl - bash tests/e2e/run_all.sh - - - name: Save freshly-built mcpp for toolchain tests - run: | - MCPP=$(realpath "$(find target -type f -name mcpp -printf '%T@ %p\n' | sort -rn | head -1 | cut -d' ' -f2)") - cp "$MCPP" /tmp/mcpp-fresh - echo "MCPP=/tmp/mcpp-fresh" >> "$GITHUB_ENV" - - - name: "Toolchain: GCC — build mcpp + test" - run: | - "$MCPP" clean - "$MCPP" build 2>&1 | tee build.log; grep -q "Resolved gcc@16.1.0" build.log - "$MCPP" test - - - name: "Toolchain: musl-gcc — build mcpp (--target)" - run: | - "$MCPP" clean - "$MCPP" build --target x86_64-linux-musl 2>&1 | tee build.log; grep -q "Resolved gcc@15.1.0-musl" build.log - - - name: "Toolchain: LLVM — build mcpp" - run: | - "$MCPP" toolchain install llvm 20.1.7 - # Override project toolchain to use LLVM for this build - sed -i 's/^default = "gcc@16.1.0"/default = "llvm@20.1.7"/' mcpp.toml - "$MCPP" clean - "$MCPP" build 2>&1 | tee build.log; grep -q "Resolved llvm@20.1.7" build.log - # Restore - sed -i 's/^default = "llvm@20.1.7"/default = "gcc@16.1.0"/' mcpp.toml diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml deleted file mode 100644 index 4aa5e60c..00000000 --- a/.github/workflows/ci-macos.yml +++ /dev/null @@ -1,358 +0,0 @@ -name: ci-macos - -# macOS CI for mcpp — validates LLVM/Clang as the default macOS toolchain. -# Tests the full xlings → LLVM → C++23 import std pipeline on macOS ARM64. - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - workflow_dispatch: - -concurrency: - group: ci-macos-${{ github.ref }} - cancel-in-progress: true - -jobs: - macos-xlings-llvm: - name: macOS ARM64 — xlings LLVM end-to-end - runs-on: macos-15 - timeout-minutes: 30 - steps: - - uses: actions/checkout@v4 - - - name: System info - run: | - uname -a - sw_vers - xcrun --show-sdk-path - echo "SDK: $(xcrun --show-sdk-version)" - - - name: Cache xlings - uses: actions/cache@v4 - with: - path: ~/.xlings - key: xlings-macos15-arm64-v3-${{ hashFiles('.xlings.json') }} - restore-keys: | - xlings-macos15-arm64-v3- - - - name: Bootstrap xlings - env: - XLINGS_NON_INTERACTIVE: '1' - XLINGS_VERSION: '0.4.30' - run: | - WORK=$(mktemp -d) - tarball="xlings-${XLINGS_VERSION}-macosx-arm64.tar.gz" - curl -fsSL -o "${WORK}/${tarball}" \ - "https://github.com/d2learn/xlings/releases/download/v${XLINGS_VERSION}/${tarball}" - tar -xzf "${WORK}/${tarball}" -C "${WORK}" - XLINGS_DIR="${WORK}/xlings-${XLINGS_VERSION}-macosx-arm64" - "$XLINGS_DIR/subos/default/bin/xlings" self install - export PATH="$HOME/.xlings/subos/default/bin:$PATH" - xlings --version - echo "PATH=$HOME/.xlings/subos/default/bin:$PATH" >> "$GITHUB_ENV" - - - name: Install LLVM via xlings - run: | - xlings install llvm -y || xlings install llvm@20.1.7 -y - # Verify clang++ is available - LLVM_ROOT=$(find "$HOME/.xlings" -path "*/xpkgs/xim-x-llvm/*/bin/clang++" | head -1 | xargs dirname | xargs dirname) - echo "LLVM_ROOT=$LLVM_ROOT" - ls "$LLVM_ROOT/bin/clang++" - "$LLVM_ROOT/bin/clang++" --version - echo "LLVM_ROOT=$LLVM_ROOT" >> "$GITHUB_ENV" - echo "CXX=$LLVM_ROOT/bin/clang++" >> "$GITHUB_ENV" - - - name: Inspect LLVM package structure - run: | - echo "=== bin/ ===" - ls "$LLVM_ROOT/bin/" | grep -E "^(clang|llvm|lld|ld)" | head -20 - echo "=== lib/ ===" - ls "$LLVM_ROOT/lib/" 2>/dev/null | head -10 - echo "=== share/libc++/ ===" - find "$LLVM_ROOT" -name "std.cppm" -o -name "std.compat.cppm" 2>/dev/null - echo "=== clang++.cfg ===" - cat "$LLVM_ROOT/bin/clang++.cfg" 2>/dev/null || echo "(no cfg file)" - echo "=== Target triple ===" - "$CXX" -dumpmachine - echo "=== Module manifest ===" - "$CXX" -print-library-module-manifest-path 2>/dev/null || echo "(not available)" - - - name: Test — non-module C++23 compilation - run: | - WORK=$(mktemp -d) - cd "$WORK" - cat > main.cpp << 'EOF' - #include - #include - int main() { - std::cout << std::format("Hello from LLVM on macOS! clang {}", __clang_version__) << std::endl; - return 0; - } - EOF - "$CXX" -std=c++23 -o hello main.cpp - ./hello - - - name: Test — import std (two-stage module compilation) - run: | - WORK=$(mktemp -d) - cd "$WORK" - - # Find std.cppm - STD_CPPM=$(find "$LLVM_ROOT" -name "std.cppm" -path "*/libc++/*" | head -1) - if [ -z "$STD_CPPM" ]; then - echo "::error::std.cppm not found in LLVM package" - find "$LLVM_ROOT" -name "*.cppm" 2>/dev/null - exit 1 - fi - echo "std.cppm at: $STD_CPPM" - - echo "=== Step 1: Precompile std module ===" - mkdir -p pcm.cache - "$CXX" -std=c++23 -Wno-reserved-module-identifier \ - --precompile "$STD_CPPM" -o pcm.cache/std.pcm - - echo "=== Step 2: Compile std.pcm → std.o ===" - "$CXX" -std=c++23 -Wno-reserved-module-identifier \ - pcm.cache/std.pcm -c -o std.o - - echo "=== Step 3: Compile main.cpp with import std ===" - cat > main.cpp << 'EOF' - import std; - int main() { - std::println("C++23 import std works on macOS via xlings LLVM!"); - return 0; - } - EOF - "$CXX" -std=c++23 -fmodule-file=std=pcm.cache/std.pcm -c main.cpp -o main.o - - echo "=== Step 4: Link ===" - "$CXX" main.o std.o -o hello_modules - echo "=== Step 5: Run ===" - ./hello_modules - - - name: Test — import std.compat - run: | - WORK=$(mktemp -d) - cd "$WORK" - - STD_CPPM=$(find "$LLVM_ROOT" -name "std.cppm" -path "*/libc++/*" | head -1) - STD_COMPAT_CPPM=$(find "$LLVM_ROOT" -name "std.compat.cppm" -path "*/libc++/*" | head -1) - - if [ -z "$STD_COMPAT_CPPM" ]; then - echo "::warning::std.compat.cppm not found, skipping" - exit 0 - fi - echo "std.compat.cppm at: $STD_COMPAT_CPPM" - - mkdir -p pcm.cache - # Build std first - "$CXX" -std=c++23 -Wno-reserved-module-identifier \ - --precompile "$STD_CPPM" -o pcm.cache/std.pcm - "$CXX" -std=c++23 -Wno-reserved-module-identifier \ - pcm.cache/std.pcm -c -o std.o - - # Build std.compat (depends on std) - "$CXX" -std=c++23 -Wno-reserved-module-identifier \ - -fmodule-file=std=pcm.cache/std.pcm \ - --precompile "$STD_COMPAT_CPPM" -o pcm.cache/std.compat.pcm - "$CXX" -std=c++23 -Wno-reserved-module-identifier \ - -fmodule-file=std=pcm.cache/std.pcm \ - pcm.cache/std.compat.pcm -c -o std.compat.o - - cat > main.cpp << 'EOF' - import std.compat; - #include - int main() { - printf("std.compat works on macOS! %s\n", "success"); - return 0; - } - EOF - "$CXX" -std=c++23 \ - -fmodule-file=std=pcm.cache/std.pcm \ - -fmodule-file=std.compat=pcm.cache/std.compat.pcm \ - -c main.cpp -o main.o - "$CXX" main.o std.o std.compat.o -o compat_test - ./compat_test - - - name: Test — multi-module project - run: | - WORK=$(mktemp -d) - cd "$WORK" - - STD_CPPM=$(find "$LLVM_ROOT" -name "std.cppm" -path "*/libc++/*" | head -1) - mkdir -p pcm.cache - - # Build std - "$CXX" -std=c++23 -Wno-reserved-module-identifier \ - --precompile "$STD_CPPM" -o pcm.cache/std.pcm - "$CXX" -std=c++23 -Wno-reserved-module-identifier \ - pcm.cache/std.pcm -c -o std.o - - # User module: greeter - cat > greeter.cppm << 'EOF' - export module greeter; - import std; - export namespace greeter { - std::string hello(std::string_view name) { - return std::format("Hello, {}! (from macOS module)", name); - } - } - EOF - "$CXX" -std=c++23 -fmodule-file=std=pcm.cache/std.pcm \ - --precompile greeter.cppm -o pcm.cache/greeter.pcm - "$CXX" -std=c++23 -fmodule-file=std=pcm.cache/std.pcm \ - pcm.cache/greeter.pcm -c -o greeter.o - - # Main - cat > main.cpp << 'EOF' - import std; - import greeter; - int main() { - std::println("{}", greeter::hello("mcpp")); - return 0; - } - EOF - "$CXX" -std=c++23 \ - -fmodule-file=std=pcm.cache/std.pcm \ - -fmodule-file=greeter=pcm.cache/greeter.pcm \ - -c main.cpp -o main.o - "$CXX" main.o greeter.o std.o -o multimod - ./multimod - - - name: Validate mcpp probe logic expectations - run: | - echo "=== Verifying mcpp's assumptions ===" - echo "1. -print-sysroot returns empty (mcpp falls back to xcrun):" - result=$("$CXX" -print-sysroot 2>/dev/null || true) - if [ -z "$result" ]; then - echo " PASS: empty (xcrun fallback needed)" - else - echo " INFO: $result" - fi - - echo "2. xcrun --show-sdk-path works:" - xcrun --show-sdk-path && echo " PASS" - - echo "3. -dumpmachine returns darwin triple:" - triple=$("$CXX" -dumpmachine) - echo " $triple" - echo "$triple" | grep -q "darwin" && echo " PASS: contains 'darwin'" - - echo "4. libc++ module manifest discoverable:" - manifest=$("$CXX" -print-library-module-manifest-path 2>/dev/null || true) - if [ -n "$manifest" ] && [ -f "$manifest" ]; then - echo " PASS: $manifest" - echo " Content:" - cat "$manifest" | head -20 - else - echo " INFO: manifest not via flag, using fallback path" - find "$LLVM_ROOT/share/libc++" -name "*.cppm" 2>/dev/null && echo " PASS: fallback exists" - fi - - echo "5. llvm-ar available:" - ls "$LLVM_ROOT/bin/llvm-ar" && echo " PASS" - - echo "6. clang-scan-deps available:" - ls "$LLVM_ROOT/bin/clang-scan-deps" && echo " PASS" || echo " WARN: not found" - - - name: Validate install.sh platform detection - run: | - uname_s=$(uname -s) - uname_m=$(uname -m) - echo "Platform: ${uname_s}-${uname_m}" - case "${uname_s}-${uname_m}" in - Darwin-arm64) echo "PASS: would select darwin-arm64" ;; - Darwin-x86_64) echo "PASS: would select darwin-x86_64" ;; - *) echo "FAIL: unexpected platform"; exit 1 ;; - esac - - - name: Bootstrap mcpp via xlings - run: | - # Same pattern as Linux CI: xlings install mcpp - xlings install mcpp -y - MCPP="$HOME/.xlings/subos/default/bin/mcpp" - test -x "$MCPP" - "$MCPP" --version - echo "MCPP=$MCPP" >> "$GITHUB_ENV" - echo "XLINGS_BIN=$HOME/.xlings/subos/default/bin/xlings" >> "$GITHUB_ENV" - - - name: Configure dev mcpp sandbox to reuse xlings LLVM - run: | - LLVM_PKG="$HOME/.xlings/data/xpkgs/xim-x-llvm/20.1.7" - MCPP_LLVM_LINK="$HOME/.mcpp/registry/data/xpkgs/xim-x-llvm/20.1.7" - test -d "$LLVM_PKG" - printf '1\n' > "$LLVM_PKG/.mcpp_ok" - - mkdir -p "$HOME/.mcpp/registry/data/xpkgs/xim-x-llvm" - rm -rf "$MCPP_LLVM_LINK" - ln -s "$LLVM_PKG" "$MCPP_LLVM_LINK" - - mkdir -p "$HOME/.mcpp" - cat > "$HOME/.mcpp/config.toml" <> "$GITHUB_PATH" - xlings.exe --version - xlings.exe install mcpp -y - echo "=== Searching for mcpp binary ===" - find "$USERPROFILE/.xlings" -name "mcpp.exe" -o -name "mcpp" 2>/dev/null | head -10 - MCPP=$(find "$USERPROFILE/.xlings" -name "mcpp.exe" -path "*/bin/*" 2>/dev/null | head -1) - if [ -z "$MCPP" ]; then - MCPP=$(find "$USERPROFILE/.xlings" -name "mcpp" -path "*/bin/*" 2>/dev/null | head -1) - fi - test -n "$MCPP" || { echo "FAIL: mcpp not found after xlings install"; exit 1; } - echo "Found mcpp at: $MCPP" - "$MCPP" --version - echo "MCPP=$MCPP" >> "$GITHUB_ENV" - XLINGS_BIN=$(cygpath -w "$USERPROFILE/.xlings/subos/default/bin/xlings.exe") - echo "XLINGS_BIN=$XLINGS_BIN" >> "$GITHUB_ENV" - - - name: Build mcpp from source (self-host) - shell: bash - run: | - export MCPP_VENDORED_XLINGS="$XLINGS_BIN" - - "$MCPP" build - - MCPP_SELF=$(find target -name "mcpp.exe" -path "*/bin/*" | head -1) - test -n "$MCPP_SELF" || { echo "FAIL: no mcpp.exe"; exit 1; } - MCPP_SELF=$(cd "$(dirname "$MCPP_SELF")" && pwd)/$(basename "$MCPP_SELF") - echo "Self-hosted binary: $MCPP_SELF" - "$MCPP_SELF" --version - echo "MCPP_SELF=$MCPP_SELF" >> "$GITHUB_ENV" - - - name: Unit + integration tests via mcpp test - shell: bash - run: | - export MCPP_VENDORED_XLINGS=$(cygpath -w "$USERPROFILE/.xlings/subos/default/bin/xlings.exe") - "$MCPP_SELF" test - - # Regression test for the Windows first-run "press Enter to advance" hang. - # Launches mcpp with an OPEN, EMPTY, never-closing stdin pipe. Without - # seal_stdin's Windows fix, any grandchild that reads stdin would inherit - # our pipe and block forever — caught by the timeout below. With the fix, - # every subprocess stdin is redirected from NUL → no possibility of hang. - - name: "Regression: mcpp survives open-empty-stdin (Windows hang fix)" - shell: pwsh - timeout-minutes: 15 - env: - MCPP_VENDORED_XLINGS: ${{ env.XLINGS_BIN }} - run: | - $ErrorActionPreference = 'Stop' - - # MCPP_SELF was set in a bash step as an MSYS-style path - # (e.g. /d/a/mcpp/...). PowerShell can't exec that — convert it - # to a native Windows path via the git-bash cygpath that ships - # on the runner. - $mcppExe = (& 'C:\Program Files\Git\usr\bin\cygpath.exe' -w $env:MCPP_SELF).Trim() - Write-Host "Resolved MCPP_SELF (Windows form): $mcppExe" - if (-not (Test-Path $mcppExe)) { - throw "MCPP_SELF after cygpath not found: $mcppExe" - } - - $tmp = Join-Path $env:RUNNER_TEMP ("stdin-hang-test-" + [guid]::NewGuid().ToString('N')) - New-Item -ItemType Directory -Path $tmp | Out-Null - Set-Location $tmp - & $mcppExe new hello_stdin - Set-Location hello_stdin - - function Invoke-McppWithOpenStdin { - param([string]$McppPath, [string]$McppArgs, [int]$TimeoutSeconds = 300) - - $psi = [System.Diagnostics.ProcessStartInfo]::new() - $psi.FileName = $McppPath - $psi.Arguments = $McppArgs - $psi.WorkingDirectory = (Get-Location).Path - $psi.UseShellExecute = $false - $psi.RedirectStandardInput = $true # parent holds child's stdin - $psi.RedirectStandardOutput = $true - $psi.RedirectStandardError = $true - # By default the child inherits the parent's env (we did not - # touch $psi.Environment) so MCPP_VENDORED_XLINGS / PATH / etc. - # propagate. - - $p = [System.Diagnostics.Process]::Start($psi) - - # Async-drain stdout/stderr so a full output buffer doesn't - # itself deadlock the child (separate failure mode from the - # stdin hang we're testing). - $stdoutTask = $p.StandardOutput.ReadToEndAsync() - $stderrTask = $p.StandardError.ReadToEndAsync() - - # NEVER write or close $p.StandardInput — the pipe stays open - # and empty for the lifetime of the child. Any grandchild that - # reads stdin will block on this pipe → caught by WaitForExit. - - if (-not $p.WaitForExit($TimeoutSeconds * 1000)) { - try { $p.Kill($true) } catch {} - Write-Host "----- captured stdout -----" - Write-Host $stdoutTask.Result - Write-Host "----- captured stderr -----" - Write-Host $stderrTask.Result - throw "REGRESSION: 'mcpp $McppArgs' HUNG with open-empty stdin after ${TimeoutSeconds}s. The Windows seal_stdin fix is not effective." - } - - Write-Host "----- stdout -----" - Write-Host $stdoutTask.Result - Write-Host "----- stderr -----" - Write-Host $stderrTask.Result - - if ($p.ExitCode -ne 0) { - throw "'mcpp $McppArgs' exited with code $($p.ExitCode) (no hang, but failed)." - } - } - - Write-Host '=== T1: mcpp --version (sanity, fast path) ===' - Invoke-McppWithOpenStdin -McppPath $mcppExe -McppArgs '--version' -TimeoutSeconds 30 - - Write-Host '=== T2: mcpp build (full bootstrap + toolchain + dep resolve + compile) ===' - Invoke-McppWithOpenStdin -McppPath $mcppExe -McppArgs 'build' -TimeoutSeconds 600 - - Write-Host '=== T3: mcpp run (post-build run path) ===' - Invoke-McppWithOpenStdin -McppPath $mcppExe -McppArgs 'run' -TimeoutSeconds 120 - - Write-Host 'SUCCESS: mcpp completes with open-empty stdin → Windows seal_stdin fix verified.' - - - name: E2E suite - shell: bash - # See ci-linux.yml — fail-fast on hung tests instead of burning the - # whole job budget. Per-test 600s timeout lives in run_all.sh. - timeout-minutes: 25 - run: | - export MCPP="$MCPP_SELF" - export MCPP_VENDORED_XLINGS="$XLINGS_BIN" - export MCPP_E2E_TOOLCHAIN_MIRROR=GLOBAL - "$MCPP_SELF" self config --mirror GLOBAL - "$MCPP_SELF" toolchain default llvm@20.1.7 - bash tests/e2e/run_all.sh - - - name: "Toolchain: LLVM — mcpp new → run" - shell: bash - run: | - export MCPP_VENDORED_XLINGS="$XLINGS_BIN" - TMP=$(mktemp -d) - cd "$TMP" - "$MCPP_SELF" new hello_win - cd hello_win - "$MCPP_SELF" run - - - name: "Toolchain: LLVM — build mcpp (self-host)" - shell: bash - run: | - export MCPP_VENDORED_XLINGS="$XLINGS_BIN" - cp "$MCPP_SELF" /tmp/mcpp-fresh.exe - MCPP=/tmp/mcpp-fresh.exe - "$MCPP" toolchain default llvm@20.1.7 - "$MCPP" clean --bmi-cache - "$MCPP" build 2>&1 | tee build.log; grep -q "Resolved llvm@20.1.7" build.log - - - name: Package Windows release zip - id: package - shell: bash - run: | - VERSION=$(awk -F '"' '/^version[[:space:]]*=/{print $2; exit}' mcpp.toml) - WRAPPER="mcpp-${VERSION}-windows-x86_64" - ZIPNAME="${WRAPPER}.zip" - - MCPP_BIN=$(find target -name "mcpp.exe" -path "*/bin/*" | head -1) - test -n "$MCPP_BIN" || { echo "FAIL: no mcpp.exe in target/"; exit 1; } - - STAGING=$(mktemp -d) - mkdir -p "$STAGING/$WRAPPER/bin" "$STAGING/$WRAPPER/registry/bin" - cp "$MCPP_BIN" "$STAGING/$WRAPPER/bin/mcpp.exe" - printf '@echo off\r\n"%%~dp0bin\\mcpp.exe" %%*\r\n' > "$STAGING/$WRAPPER/mcpp.bat" - cp README.md "$STAGING/$WRAPPER/" 2>/dev/null || true - cp LICENSE "$STAGING/$WRAPPER/" 2>/dev/null || true - XLINGS_EXE="$USERPROFILE/.xlings/subos/default/bin/xlings.exe" - [ -f "$XLINGS_EXE" ] && cp "$XLINGS_EXE" "$STAGING/$WRAPPER/registry/bin/xlings.exe" - - mkdir -p dist - (cd "$STAGING" && 7z a -tzip "$ZIPNAME" "$WRAPPER") - cp "$STAGING/$ZIPNAME" "dist/$ZIPNAME" - (cd dist && sha256sum "$ZIPNAME" > "$ZIPNAME.sha256") - echo "zipname=$ZIPNAME" >> "$GITHUB_OUTPUT" - ls -la dist/ - - - name: Smoke-test the packaged zip - shell: bash - run: | - ZIPNAME="${{ steps.package.outputs.zipname }}" - WRAPPER="${ZIPNAME%.zip}" - SMOKE=$(mktemp -d) - (cd "$SMOKE" && unzip -q "$GITHUB_WORKSPACE/dist/$ZIPNAME") - "$SMOKE/$WRAPPER/bin/mcpp.exe" --version - test -f "$SMOKE/$WRAPPER/registry/bin/xlings.exe" - test -f "$SMOKE/$WRAPPER/mcpp.bat" - echo "Smoke-test passed" - - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: mcpp-windows-x86_64 - path: | - dist/*.zip - dist/*.sha256 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index a398ade1..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,545 +0,0 @@ -name: release - -# Self-host release: bootstrap mcpp from xlings (xim:mcpp), build the -# musl-static artefact via `mcpp pack --target x86_64-linux-musl -o ...`, -# inject xlings into the produced tarball for install.sh consumers, -# smoke-test, upload. - -on: - push: - tags: [ 'v*' ] - workflow_dispatch: - inputs: - tag: - description: 'tag to (re)build — leave blank to derive `v` from mcpp.toml and create the tag automatically' - required: false - -jobs: - build-release: - name: build + upload (linux / x86_64) - runs-on: ubuntu-24.04 - permissions: - contents: write # required to create releases + push tags - timeout-minutes: 60 - env: - # mcpp resolves MCPP_HOME from the binary's location by default, - # but here we want to share toolchains with the bootstrap sandbox, - # so we pin to a known path. - MCPP_HOME: /home/runner/.mcpp - steps: - # fetch-depth: 0 instead of fetch-tags: true — actions/checkout@v4 - # fails on push-tag triggers when both the ref'd tag and - # `fetch-tags: true` are set: - # "Cannot fetch both and refs/tags/vX.Y.Z to refs/tags/vX.Y.Z" - # Full-history fetch covers the resolve-tag step's needs without - # that contention. - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Resolve target tag + commit - id: resolve - # Three trigger shapes converge here: - # 1. push: refs/tags/vX.Y.Z → use that tag, build at its commit - # 2. workflow_dispatch with `tag` input set: - # - tag exists on remote → check it out (rebuild scenario) - # - tag doesn't exist → use current HEAD; gh-release - # creates the tag at that commit on upload - # 3. workflow_dispatch with no input → derive `v` from - # mcpp.toml's [package].version, build at current HEAD; - # gh-release creates the tag. - run: | - if [ "${{ github.event_name }}" = "push" ]; then - TAG="${{ github.ref_name }}" - elif [ -n "${{ github.event.inputs.tag }}" ]; then - TAG="${{ github.event.inputs.tag }}" - else - VER=$(awk -F '"' '/^version[[:space:]]*=/{print $2; exit}' mcpp.toml) - test -n "$VER" || { echo 'failed to read [package].version from mcpp.toml'; exit 1; } - TAG="v$VER" - fi - echo "tag=$TAG" >> "$GITHUB_OUTPUT" - echo "version=${TAG#v}" >> "$GITHUB_OUTPUT" - # If the tag exists on remote AND we're on workflow_dispatch, - # check it out so we rebuild that exact commit. push-tag runs - # already start at the tag commit. - if [ "${{ github.event_name }}" = "workflow_dispatch" ] \ - && git rev-parse --verify "refs/tags/$TAG" >/dev/null 2>&1; then - git checkout --detach "refs/tags/$TAG" - fi - echo "Resolved tag: $TAG (commit $(git rev-parse --short HEAD))" - - # Cache mcpp's sandbox: musl-gcc 15.1 + binutils + glibc + linux-headers - # + patchelf + ninja is ~800 MB on disk; without this every release - # rebuilds from cold install. Key on the workspace manifest so a - # toolchain change in mcpp.toml refreshes the cache. - - name: Cache mcpp sandbox - uses: actions/cache@v4 - with: - path: ~/.mcpp - key: mcpp-sandbox-${{ runner.os }}-release-${{ hashFiles('mcpp.toml', '.xlings.json') }} - restore-keys: | - mcpp-sandbox-${{ runner.os }}-release- - mcpp-sandbox-${{ runner.os }}- - - # Cache xlings + xim:mcpp install. - - name: Cache xlings - uses: actions/cache@v4 - with: - path: ~/.xlings - key: xlings-${{ runner.os }}-release-xl0448-${{ hashFiles('.xlings.json') }} - restore-keys: | - xlings-${{ runner.os }}-release-xl0448- - - - name: Bootstrap mcpp via xlings - env: - XLINGS_NON_INTERACTIVE: '1' - # Pin xlings to a known-good version. The upstream install - # script always grabs `latest` (no version override), so we - # download + self-install manually to avoid broken releases. - XLINGS_VERSION: '0.4.48' - run: | - if [ ! -x "$HOME/.xlings/subos/default/bin/xlings" ]; then - tarball="xlings-${XLINGS_VERSION}-linux-x86_64.tar.gz" - curl -fsSL -o "/tmp/${tarball}" \ - "https://github.com/openxlings/xlings/releases/download/v${XLINGS_VERSION}/${tarball}" - tar -xzf "/tmp/${tarball}" -C /tmp - "/tmp/xlings-${XLINGS_VERSION}-linux-x86_64/subos/default/bin/xlings" self install - fi - export PATH="$HOME/.xlings/subos/default/bin:$PATH" - xlings --version - xlings install mcpp -y - MCPP="$HOME/.xlings/subos/default/bin/mcpp" - test -x "$MCPP" - "$MCPP" --version - echo "MCPP=$MCPP" >> "$GITHUB_ENV" - echo "XLINGS_BIN=$HOME/.xlings/subos/default/bin/xlings" >> "$GITHUB_ENV" - - - name: Build + pack release artefact (musl static) - id: stage - # Build for the musl-static target, strip the produced ELF, then - # let `mcpp pack` assemble the tarball (binary + top-level wrapper - # + README + LICENSE, contents at archive root). Inject xlings - # afterwards so install.sh consumers get a single self-contained - # bundle. - run: | - TAG="${{ steps.resolve.outputs.tag }}" - VERSION="${{ steps.resolve.outputs.version }}" - TARBALL_NAME="mcpp-${VERSION}-linux-x86_64.tar.gz" - - # Build first so we can strip the ELF before pack copies it. - export MCPP_VENDORED_XLINGS="$XLINGS_BIN" - "$MCPP" build --target x86_64-linux-musl - ARTIFACT=$(find target/x86_64-linux-musl -type f -name mcpp | head -1) - test -n "$ARTIFACT" - file "$ARTIFACT" | grep -q 'statically linked' - # Strip — debug info on a static ELF balloons it ~7×. - strip "$ARTIFACT" - - # Pack with the freshly-built mcpp (not the bootstrap) so any - # fixes to the pack code path are exercised in the same release - # they ship in. MCPP_HOME is forced so the new binary uses the - # pinned sandbox instead of resolving relative to its own - # location under target/. - MCPP_HOME="$MCPP_HOME" "$ARTIFACT" pack \ - --target x86_64-linux-musl \ - --mode static \ - -o "${TARBALL_NAME}" - - # Inject xlings: extract → add registry/bin/xlings to the wrapper - # dir → re-tar preserving the wrapper. Since 0.0.4 the bundled - # xlings lives at /registry/bin/xlings (= /bin/xlings). - TARBALL="target/dist/${TARBALL_NAME}" - WRAPPER="${TARBALL_NAME%.tar.gz}" - test -f "$TARBALL" - INJECT=$(mktemp -d) - tar -xzf "$TARBALL" -C "$INJECT" - mkdir -p "$INJECT/$WRAPPER/registry/bin" - cp "$XLINGS_BIN" "$INJECT/$WRAPPER/registry/bin/xlings" - chmod +x "$INJECT/$WRAPPER/registry/bin/xlings" - (cd "$INJECT" && tar -czf "$GITHUB_WORKSPACE/${TARBALL}" "$WRAPPER") - rm -rf "$INJECT" - - # Stage final dist/ (tarball + sidecars) for upload. - mkdir -p dist - cp "$TARBALL" "dist/${TARBALL_NAME}" - (cd dist && cp "${TARBALL_NAME}" "mcpp-linux-x86_64.tar.gz") - (cd dist && sha256sum "${TARBALL_NAME}" "mcpp-linux-x86_64.tar.gz" > SHA256SUMS) - (cd dist && sha256sum "${TARBALL_NAME}" > "${TARBALL_NAME}.sha256") - (cd dist && sha256sum "mcpp-linux-x86_64.tar.gz" > "mcpp-linux-x86_64.tar.gz.sha256") - - # Top-level install.sh — fetched by `curl | bash`. - cp install.sh dist/install.sh - chmod +x dist/install.sh - - echo "tag=$TAG" >> $GITHUB_OUTPUT - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "tarball=${TARBALL_NAME}" >> $GITHUB_OUTPUT - ls -la dist/ - - - name: Smoke-test the bundled tarball - # Extract to a scratch dir and run mcpp from there with MCPP_HOME - # unset — proves the release artefact is genuinely self-contained. - run: | - VERSION="${{ steps.stage.outputs.version }}" - TARBALL_NAME="${{ steps.stage.outputs.tarball }}" - # Wrapper dir inside the tarball matches its stem (mcpp pack - # ties the two together). - WRAPPER="${TARBALL_NAME%.tar.gz}" - SMOKE=$(mktemp -d) - tar -xzf "dist/${TARBALL_NAME}" -C "$SMOKE" - ROOT="$SMOKE/$WRAPPER" - test -x "$ROOT/bin/mcpp" - test -x "$ROOT/registry/bin/xlings" - test -x "$ROOT/mcpp" - file "$ROOT/bin/mcpp" | grep -q 'statically linked' - env -u MCPP_HOME "$ROOT/bin/mcpp" --version - env -u MCPP_HOME "$ROOT/bin/mcpp" --help | head -10 - # Top-level wrapper reports the same version we're shipping. - env -u MCPP_HOME "$ROOT/mcpp" --version | grep -q "$VERSION" - # MCPP_HOME should auto-resolve to the extracted root. - out=$(env -u MCPP_HOME "$ROOT/bin/mcpp" self env) - echo "$out" | grep -q "MCPP_HOME *= *$ROOT" - - - name: Generate source tarball + xpkg.lua via mcpp publish - # Use the freshly-built mcpp to produce the source tarball + xpkg - # descriptor for mcpp-index. The release tarball wraps its - # contents in a `/` directory so the extract path - # is $PUB/$WRAPPER/bin/mcpp. - run: | - VERSION="${{ steps.stage.outputs.version }}" - TARBALL_NAME="${{ steps.stage.outputs.tarball }}" - WRAPPER="${TARBALL_NAME%.tar.gz}" - PUB=$(mktemp -d) - tar -xzf "dist/${TARBALL_NAME}" -C "$PUB" - MCPP_BIN="$PUB/$WRAPPER/bin/mcpp" - env -u MCPP_HOME "$MCPP_BIN" publish --dry-run --allow-dirty - test -f "target/dist/mcpp-${VERSION}.tar.gz" - test -f "target/dist/mcpp.lua" - cp "target/dist/mcpp-${VERSION}.tar.gz" dist/ - cp "target/dist/mcpp.lua" dist/ - ls -la dist/ - - - name: Extract release notes from CHANGELOG - id: notes - run: | - TAG="${{ steps.stage.outputs.tag }}" - VERSION="${{ steps.stage.outputs.version }}" - awk -v v="$VERSION" ' - /^## \[/ { - if (in_section) exit - if ($0 ~ "\\[" v "\\]") { in_section=1; next } - } - in_section { print } - ' CHANGELOG.md > dist/RELEASE_NOTES.md || true - if [ ! -s dist/RELEASE_NOTES.md ]; then - echo "(no CHANGELOG entry found for $VERSION)" > dist/RELEASE_NOTES.md - fi - echo "--- RELEASE_NOTES.md ---" - cat dist/RELEASE_NOTES.md - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ steps.stage.outputs.tag }} - name: ${{ steps.stage.outputs.tag }} - body_path: dist/RELEASE_NOTES.md - draft: false - prerelease: false - files: | - dist/mcpp-${{ steps.stage.outputs.version }}-linux-x86_64.tar.gz - dist/mcpp-${{ steps.stage.outputs.version }}-linux-x86_64.tar.gz.sha256 - dist/mcpp-linux-x86_64.tar.gz - dist/mcpp-linux-x86_64.tar.gz.sha256 - dist/install.sh - dist/SHA256SUMS - dist/mcpp-${{ steps.stage.outputs.version }}.tar.gz - dist/mcpp.lua - - build-macos: - name: build (macOS / ARM64) - runs-on: macos-15 - needs: build-release - permissions: - contents: write - timeout-minutes: 30 - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Resolve tag - id: resolve - run: | - if [ "${{ github.event_name }}" = "push" ]; then - TAG="${{ github.ref_name }}" - elif [ -n "${{ github.event.inputs.tag }}" ]; then - TAG="${{ github.event.inputs.tag }}" - else - VER=$(awk -F '"' '/^version[[:space:]]*=/{print $2; exit}' mcpp.toml) - TAG="v$VER" - fi - echo "tag=$TAG" >> "$GITHUB_OUTPUT" - echo "version=${TAG#v}" >> "$GITHUB_OUTPUT" - if [ "${{ github.event_name }}" = "workflow_dispatch" ] \ - && git rev-parse --verify "refs/tags/$TAG" >/dev/null 2>&1; then - git checkout --detach "refs/tags/$TAG" - fi - - - name: Cache xlings - uses: actions/cache@v4 - with: - path: ~/.xlings - key: xlings-macos15-release-xl0448-${{ hashFiles('.xlings.json') }} - restore-keys: | - xlings-macos15-release-xl0448- - - - name: Bootstrap mcpp via xlings - env: - XLINGS_NON_INTERACTIVE: '1' - XLINGS_VERSION: '0.4.48' - run: | - if [ ! -x "$HOME/.xlings/subos/default/bin/xlings" ]; then - WORK=$(mktemp -d) - tarball="xlings-${XLINGS_VERSION}-macosx-arm64.tar.gz" - curl -fsSL -o "${WORK}/${tarball}" \ - "https://github.com/openxlings/xlings/releases/download/v${XLINGS_VERSION}/${tarball}" - tar -xzf "${WORK}/${tarball}" -C "${WORK}" - "${WORK}/xlings-${XLINGS_VERSION}-macosx-arm64/subos/default/bin/xlings" self install - fi - export PATH="$HOME/.xlings/subos/default/bin:$PATH" - xlings --version - xlings install mcpp -y - MCPP="$HOME/.xlings/subos/default/bin/mcpp" - test -x "$MCPP" - "$MCPP" --version - echo "MCPP=$MCPP" >> "$GITHUB_ENV" - echo "XLINGS_BIN=$HOME/.xlings/subos/default/bin/xlings" >> "$GITHUB_ENV" - - - name: Build mcpp from source (self-host) - run: | - export PATH="$HOME/.xlings/subos/default/bin:$PATH" - export MCPP_VENDORED_XLINGS="$XLINGS_BIN" - "$MCPP" build - MCPP_BIN=$(find target -path "*/bin/mcpp" | head -1) - MCPP_BIN=$(cd "$(dirname "$MCPP_BIN")" && pwd)/$(basename "$MCPP_BIN") - test -x "$MCPP_BIN" - file "$MCPP_BIN" - otool -L "$MCPP_BIN" - "$MCPP_BIN" --version - echo "MCPP_BIN=$MCPP_BIN" >> "$GITHUB_ENV" - - - name: Package macOS release - id: stage - run: | - VERSION="${{ steps.resolve.outputs.version }}" - TARBALL_NAME="mcpp-${VERSION}-macosx-arm64.tar.gz" - WRAPPER="mcpp-${VERSION}-macosx-arm64" - - # Create release layout - STAGING=$(mktemp -d) - mkdir -p "$STAGING/$WRAPPER/bin" - cp "$MCPP_BIN" "$STAGING/$WRAPPER/bin/mcpp" - # Strip (Mach-O) - strip "$STAGING/$WRAPPER/bin/mcpp" 2>/dev/null || true - # Copy metadata - cp LICENSE "$STAGING/$WRAPPER/" 2>/dev/null || true - cp README.md "$STAGING/$WRAPPER/" 2>/dev/null || true - - # Shell launcher (same as Linux) - cat > "$STAGING/$WRAPPER/mcpp" << 'LAUNCHER' - #!/bin/sh - exec "$(dirname "$0")/bin/mcpp" "$@" - LAUNCHER - chmod +x "$STAGING/$WRAPPER/mcpp" - - # Bundle xlings for install.sh consumers - XLINGS_BIN="$HOME/.xlings/subos/default/bin/xlings" - if [ -x "$XLINGS_BIN" ]; then - mkdir -p "$STAGING/$WRAPPER/registry/bin" - cp "$XLINGS_BIN" "$STAGING/$WRAPPER/registry/bin/xlings" - chmod +x "$STAGING/$WRAPPER/registry/bin/xlings" - fi - - # Create tarball - mkdir -p dist - (cd "$STAGING" && tar -czf "$GITHUB_WORKSPACE/dist/${TARBALL_NAME}" "$WRAPPER") - # Versionless alias - cp "dist/${TARBALL_NAME}" "dist/mcpp-macosx-arm64.tar.gz" - # SHA256 - (cd dist && shasum -a 256 "${TARBALL_NAME}" > "${TARBALL_NAME}.sha256") - (cd dist && shasum -a 256 "mcpp-macosx-arm64.tar.gz" > "mcpp-macosx-arm64.tar.gz.sha256") - - echo "tarball=${TARBALL_NAME}" >> "$GITHUB_OUTPUT" - ls -la dist/ - - - name: Smoke-test the tarball - run: | - VERSION="${{ steps.resolve.outputs.version }}" - TARBALL_NAME="${{ steps.stage.outputs.tarball }}" - WRAPPER="${TARBALL_NAME%.tar.gz}" - SMOKE=$(mktemp -d) - tar -xzf "dist/${TARBALL_NAME}" -C "$SMOKE" - "$SMOKE/$WRAPPER/bin/mcpp" --version - "$SMOKE/$WRAPPER/mcpp" --version | grep -q "$VERSION" - - - name: Upload macOS artifacts to release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ steps.resolve.outputs.tag }} - files: | - dist/mcpp-${{ steps.resolve.outputs.version }}-macosx-arm64.tar.gz - dist/mcpp-${{ steps.resolve.outputs.version }}-macosx-arm64.tar.gz.sha256 - dist/mcpp-macosx-arm64.tar.gz - dist/mcpp-macosx-arm64.tar.gz.sha256 - - build-windows: - name: build (Windows / x86_64) - runs-on: windows-latest - needs: build-release - permissions: - contents: write - timeout-minutes: 45 - env: - MCPP_HOME: C:\Users\runneradmin\.mcpp - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Resolve tag - id: resolve - shell: bash - run: | - if [ "${{ github.event_name }}" = "push" ]; then - TAG="${{ github.ref_name }}" - elif [ -n "${{ github.event.inputs.tag }}" ]; then - TAG="${{ github.event.inputs.tag }}" - else - VER=$(awk -F '"' '/^version[[:space:]]*=/{print $2; exit}' mcpp.toml) - TAG="v$VER" - fi - echo "tag=$TAG" >> "$GITHUB_OUTPUT" - echo "version=${TAG#v}" >> "$GITHUB_OUTPUT" - if [ "${{ github.event_name }}" = "workflow_dispatch" ] \ - && git rev-parse --verify "refs/tags/$TAG" >/dev/null 2>&1; then - git checkout --detach "refs/tags/$TAG" - fi - - - name: Cache mcpp sandbox - uses: actions/cache@v4 - with: - path: ~\.mcpp - key: mcpp-sandbox-${{ runner.os }}-release-${{ hashFiles('mcpp.toml', '.xlings.json') }} - restore-keys: | - mcpp-sandbox-${{ runner.os }}-release- - mcpp-sandbox-${{ runner.os }}- - - - name: Cache xlings - uses: actions/cache@v4 - with: - path: ~\.xlings - key: xlings-${{ runner.os }}-release-xl0448-${{ hashFiles('.xlings.json') }} - restore-keys: | - xlings-${{ runner.os }}-release-xl0448- - - - name: Bootstrap mcpp via xlings - shell: bash - env: - XLINGS_NON_INTERACTIVE: '1' - XLINGS_VERSION: '0.4.48' - run: | - WORK=$(mktemp -d) - zipfile="xlings-${XLINGS_VERSION}-windows-x86_64.zip" - curl -fsSL -o "${WORK}/${zipfile}" \ - "https://github.com/openxlings/xlings/releases/download/v${XLINGS_VERSION}/${zipfile}" - cd "${WORK}" - unzip -q "${zipfile}" - "$WORK/xlings-${XLINGS_VERSION}-windows-x86_64/subos/default/bin/xlings.exe" self install - export PATH="$USERPROFILE/.xlings/subos/default/bin:$PATH" - echo "$USERPROFILE/.xlings/subos/default/bin" >> "$GITHUB_PATH" - xlings.exe --version - xlings.exe install mcpp -y - MCPP=$(find "$USERPROFILE/.xlings" -name "mcpp.exe" -path "*/bin/*" 2>/dev/null | head -1) - if [ -z "$MCPP" ]; then - MCPP=$(find "$USERPROFILE/.xlings" -name "mcpp" -path "*/bin/*" 2>/dev/null | head -1) - fi - test -n "$MCPP" || { echo "FAIL: mcpp not found after xlings install"; exit 1; } - "$MCPP" --version - echo "MCPP=$MCPP" >> "$GITHUB_ENV" - XLINGS_BIN=$(cygpath -w "$USERPROFILE/.xlings/subos/default/bin/xlings.exe") - echo "XLINGS_BIN=$XLINGS_BIN" >> "$GITHUB_ENV" - echo "XLINGS_BIN_UNIX=$USERPROFILE/.xlings/subos/default/bin/xlings.exe" >> "$GITHUB_ENV" - echo "XLINGS_XPKGS=$USERPROFILE/.xlings/data/xpkgs" >> "$GITHUB_ENV" - - - name: Build mcpp from source (self-host) - shell: bash - run: | - export MCPP_VENDORED_XLINGS="$XLINGS_BIN" - - "$MCPP" build - - MCPP_BIN=$(find target -name "mcpp.exe" -path "*/bin/*" | head -1) - test -n "$MCPP_BIN" || { echo "FAIL: no mcpp.exe in target/"; exit 1; } - MCPP_BIN=$(cd "$(dirname "$MCPP_BIN")" && pwd)/$(basename "$MCPP_BIN") - echo "Self-hosted binary: $MCPP_BIN" - "$MCPP_BIN" --version - echo "MCPP_BIN=$MCPP_BIN" >> "$GITHUB_ENV" - - - name: Package Windows release zip - id: stage - shell: bash - run: | - VERSION="${{ steps.resolve.outputs.version }}" - WRAPPER="mcpp-${VERSION}-windows-x86_64" - ZIPNAME="${WRAPPER}.zip" - - STAGING=$(mktemp -d) - mkdir -p "$STAGING/$WRAPPER/bin" "$STAGING/$WRAPPER/registry/bin" - cp "$MCPP_BIN" "$STAGING/$WRAPPER/bin/mcpp.exe" - - # Windows batch launcher - printf '@echo off\r\n"%%~dp0bin\\mcpp.exe" %%*\r\n' > "$STAGING/$WRAPPER/mcpp.bat" - cp README.md "$STAGING/$WRAPPER/" 2>/dev/null || true - cp LICENSE "$STAGING/$WRAPPER/" 2>/dev/null || true - - # Bundle xlings.exe for install consumers - if [ -f "$XLINGS_BIN_UNIX" ]; then - cp "$XLINGS_BIN_UNIX" "$STAGING/$WRAPPER/registry/bin/xlings.exe" - fi - - # Pack with 7z (available on windows-latest) - mkdir -p dist - (cd "$STAGING" && 7z a -tzip "$ZIPNAME" "$WRAPPER") - cp "$STAGING/$ZIPNAME" "dist/$ZIPNAME" - # Versionless alias - cp "dist/$ZIPNAME" "dist/mcpp-windows-x86_64.zip" - # SHA256 - (cd dist && sha256sum "$ZIPNAME" > "$ZIPNAME.sha256") - (cd dist && sha256sum "mcpp-windows-x86_64.zip" > "mcpp-windows-x86_64.zip.sha256") - - echo "zipname=$ZIPNAME" >> "$GITHUB_OUTPUT" - ls -la dist/ - - - name: Smoke-test the packaged zip - shell: bash - run: | - ZIPNAME="${{ steps.stage.outputs.zipname }}" - WRAPPER="${ZIPNAME%.zip}" - SMOKE=$(mktemp -d) - (cd "$SMOKE" && unzip -q "$GITHUB_WORKSPACE/dist/$ZIPNAME") - "$SMOKE/$WRAPPER/bin/mcpp.exe" --version - "$SMOKE/$WRAPPER/bin/mcpp.exe" --help | head -5 - test -f "$SMOKE/$WRAPPER/registry/bin/xlings.exe" - test -f "$SMOKE/$WRAPPER/mcpp.bat" - echo "Smoke-test passed" - - - name: Upload Windows artifacts to release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ steps.resolve.outputs.tag }} - files: | - dist/mcpp-${{ steps.resolve.outputs.version }}-windows-x86_64.zip - dist/mcpp-${{ steps.resolve.outputs.version }}-windows-x86_64.zip.sha256 - dist/mcpp-windows-x86_64.zip - dist/mcpp-windows-x86_64.zip.sha256 diff --git a/.github/workflows/temp-macos14-support.yml b/.github/workflows/temp-macos14-support.yml new file mode 100644 index 00000000..ec191ff9 --- /dev/null +++ b/.github/workflows/temp-macos14-support.yml @@ -0,0 +1,199 @@ +# TEMPORARY workflow — macOS 14 support experiment for mcpp + xlings. +# +# Current state: released macosx-arm64 binaries carry LC_BUILD_VERSION +# minos=15.0 (built on macos-15 runners with no deployment target), so +# dyld on macOS 14 refuses to load them. Both binaries dynamically link +# the SYSTEM /usr/lib/libc++.1.dylib, so lowering the target also has to +# survive libc++ availability gating at compile time and missing-symbol +# risk at runtime — which is exactly what this experiment verifies. +# +# job 1 (macos-15): bootstrap mcpp, build mcpp@HEAD and xlings@main +# with MACOSX_DEPLOYMENT_TARGET=14.0, assert +# minos=14.0 via otool, smoke on 15. +# job 2 (macos-14): control-check the released 0.0.49 binary is refused, +# then run the minos14 artifacts end-to-end: +# xlings self install + mcpp new/build/run (installs +# the llvm/ninja toolchain packages on macOS 14 too). +# +# DO NOT MERGE. Delete branch after the investigation. +name: temp-macos14-support + +on: + pull_request: + branches: [main] + workflow_dispatch: + +jobs: + build-minos14: + name: "build mcpp+xlings with MACOSX_DEPLOYMENT_TARGET=14.0 (macos-15)" + runs-on: macos-15 + timeout-minutes: 60 + steps: + - uses: actions/checkout@v4 + + - uses: actions/checkout@v4 + with: + repository: openxlings/xlings + path: xlings-src + + - name: Cache xlings (bootstrap) + uses: actions/cache@v4 + with: + path: ~/.xlings + key: xlings-macos15-m14test-${{ hashFiles('.xlings.json') }} + restore-keys: | + xlings-macos15-m14test- + + - name: Cache mcpp sandbox + uses: actions/cache@v4 + with: + path: ~/.mcpp + key: mcpp-macos15-m14test-${{ hashFiles('mcpp.toml') }} + restore-keys: | + mcpp-macos15-m14test- + + - name: Bootstrap mcpp via xlings + env: + XLINGS_NON_INTERACTIVE: '1' + XLINGS_VERSION: '0.4.49' + run: | + if [ ! -x "$HOME/.xlings/subos/default/bin/xlings" ]; then + WORK=$(mktemp -d) + tarball="xlings-${XLINGS_VERSION}-macosx-arm64.tar.gz" + curl -fsSL -o "${WORK}/${tarball}" \ + "https://github.com/openxlings/xlings/releases/download/v${XLINGS_VERSION}/${tarball}" + tar -xzf "${WORK}/${tarball}" -C "${WORK}" + "${WORK}/xlings-${XLINGS_VERSION}-macosx-arm64/subos/default/bin/xlings" self install + fi + export PATH="$HOME/.xlings/subos/default/bin:$PATH" + xlings --version + xlings install mcpp -y + MCPP="$HOME/.xlings/subos/default/bin/mcpp" + test -x "$MCPP" + "$MCPP" --version + echo "MCPP=$MCPP" >> "$GITHUB_ENV" + echo "$HOME/.xlings/subos/default/bin" >> "$GITHUB_PATH" + + - name: "Control: bootstrap mcpp binary is minos 15" + run: | + BIN=$(readlink -f "$(find "$HOME/.xlings/data/xpkgs" -path '*-x-mcpp/*/bin/mcpp' | head -1)") + otool -l "$BIN" | grep -A4 LC_BUILD_VERSION | head -6 + + - name: Build mcpp@HEAD with deployment target 14.0 + env: + MACOSX_DEPLOYMENT_TARGET: '14.0' + MCPP_HOME: /Users/runner/.mcpp + run: | + "$MCPP" build + ART=$(find target -type f -path '*/bin/mcpp' | head -1) + test -n "$ART" + echo "ART=$ART" >> "$GITHUB_ENV" + echo "=== LC_BUILD_VERSION of freshly built mcpp ===" + otool -l "$ART" | grep -A4 LC_BUILD_VERSION | head -6 + otool -l "$ART" | grep -A4 LC_BUILD_VERSION | grep -q "minos 14.0" \ + || { echo "FAIL: expected minos 14.0"; exit 1; } + echo "=== smoke on macos-15 ===" + "$ART" --version + + - name: Build xlings@main with deployment target 14.0 (using minos14 mcpp) + env: + MACOSX_DEPLOYMENT_TARGET: '14.0' + MCPP_HOME: /Users/runner/.mcpp + run: | + cd xlings-src + "$GITHUB_WORKSPACE/$ART" build + XART=$(find target -type f -path '*/bin/xlings' | head -1) + test -n "$XART" + echo "XART=xlings-src/$XART" >> "$GITHUB_ENV" + echo "=== LC_BUILD_VERSION of freshly built xlings ===" + otool -l "$XART" | grep -A4 LC_BUILD_VERSION | head -6 + otool -l "$XART" | grep -A4 LC_BUILD_VERSION | grep -q "minos 14.0" \ + || { echo "FAIL: expected minos 14.0"; exit 1; } + echo "=== smoke on macos-15 ===" + "$XART" --version + + - name: Upload minos14 binaries + uses: actions/upload-artifact@v4 + with: + name: minos14-binaries + path: | + ${{ env.ART }} + ${{ env.XART }} + + smoke-macos14: + name: "run minos14 mcpp+xlings end-to-end on macos-14" + needs: build-minos14 + runs-on: macos-14 + timeout-minutes: 45 + steps: + - name: "Runner info" + run: sw_vers; uname -m; xcodebuild -version | head -2 || true + + - name: "Control: released 0.0.49 (minos 15) must be refused by dyld" + run: | + WORK=$(mktemp -d) + curl -fsSL -o "$WORK/m.tar.gz" \ + "https://github.com/mcpp-community/mcpp/releases/download/v0.0.49/mcpp-0.0.49-macosx-arm64.tar.gz" + tar -xzf "$WORK/m.tar.gz" -C "$WORK" + if "$WORK/mcpp-0.0.49-macosx-arm64/bin/mcpp" --version 2>err.txt; then + echo "UNEXPECTED: minos15 binary ran on macOS 14" + else + echo "confirmed refusal:"; cat err.txt | head -3 + fi + + - name: Download minos14 binaries + uses: actions/download-artifact@v4 + with: + name: minos14-binaries + path: art + + - name: "minos14 binaries start on macOS 14" + run: | + find art -type f | head; chmod +x $(find art -type f) + MCPP=$(find art -path '*bin/mcpp' -type f | head -1) + XLINGS=$(find art -path '*bin/xlings' -type f | head -1) + echo "MCPP=$PWD/$MCPP" >> "$GITHUB_ENV" + echo "XLINGS=$PWD/$XLINGS" >> "$GITHUB_ENV" + "$MCPP" --version + "$XLINGS" --version + + - name: "Assemble xlings home on macOS 14 (release layout + minos14 binary)" + env: + XLINGS_NON_INTERACTIVE: '1' + run: | + WORK=$(mktemp -d) + tarball="xlings-0.4.49-macosx-arm64.tar.gz" + curl -fsSL -o "$WORK/$tarball" \ + "https://github.com/openxlings/xlings/releases/download/v0.4.49/$tarball" + tar -xzf "$WORK/$tarball" -C "$WORK" + ROOT="$WORK/xlings-0.4.49-macosx-arm64" + # Swap in the minos14 binary everywhere the layout carries one. + for p in "$ROOT/bin/xlings" "$ROOT/subos/default/bin/xlings"; do + if [ -e "$p" ]; then cp "$XLINGS" "$p"; chmod +x "$p"; fi + done + "$ROOT/subos/default/bin/xlings" self install + export PATH="$HOME/.xlings/subos/default/bin:$PATH" + echo "$HOME/.xlings/subos/default/bin" >> "$GITHUB_PATH" + xlings --version + + - name: "mcpp end-to-end on macOS 14: new + build + run (installs llvm/ninja)" + env: + MCPP_HOME: /Users/runner/.mcpp + run: | + "$MCPP" new hello + cd hello + "$MCPP" build + "$MCPP" run | head -3 + echo "=== built hello LC_BUILD_VERSION (built natively on 14) ===" + BIN=$(find target -type f -path '*/bin/hello' | head -1) + otool -l "$BIN" | grep -A4 LC_BUILD_VERSION | head -6 + + - name: "Summary" + if: always() + run: | + { + echo "## macOS 14 support experiment" + echo "" + echo "- minos14 mcpp/xlings binaries built on macos-15 with MACOSX_DEPLOYMENT_TARGET=14.0" + echo "- end-to-end on macos-14: see step outcomes above" + } >> "$GITHUB_STEP_SUMMARY" From b8271113c0c6642ab1fa0e81331c9a2e811f201a Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Fri, 5 Jun 2026 01:31:24 +0800 Subject: [PATCH 02/10] =?UTF-8?q?test:=20run=20B=20=E2=80=94=20target=2011?= =?UTF-8?q?.0=20+=20static=20LLVM=20libc++=20via=20[build]=20ldflags=20inj?= =?UTF-8?q?ection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Run A evidence: MACOSX_DEPLOYMENT_TARGET=14.0 with the status-quo dynamic system-libc++ linkage compiles clean (availability gating does not reject) and asserts minos=14.0, but dies at launch on macOS 14.8.7: dyld: Symbol not found: __ZNSt3__119__is_posix_terminalEP7__sFILE (std::print support symbol; macOS 14's system libc++ predates LLVM 18) Lowering minos alone is not viable — run B tests the shipping route: deployment target 11.0 + statically linked LLVM libc++/libc++abi, injected via [build] ldflags (honored by the released bootstrap mcpp, no code change needed for the experiment). --- .github/workflows/temp-macos14-support.yml | 133 +++++++++++++-------- 1 file changed, 82 insertions(+), 51 deletions(-) diff --git a/.github/workflows/temp-macos14-support.yml b/.github/workflows/temp-macos14-support.yml index ec191ff9..4fea8998 100644 --- a/.github/workflows/temp-macos14-support.yml +++ b/.github/workflows/temp-macos14-support.yml @@ -1,19 +1,18 @@ -# TEMPORARY workflow — macOS 14 support experiment for mcpp + xlings. +# TEMPORARY workflow — macOS min-version support experiment (run B). # -# Current state: released macosx-arm64 binaries carry LC_BUILD_VERSION -# minos=15.0 (built on macos-15 runners with no deployment target), so -# dyld on macOS 14 refuses to load them. Both binaries dynamically link -# the SYSTEM /usr/lib/libc++.1.dylib, so lowering the target also has to -# survive libc++ availability gating at compile time and missing-symbol -# risk at runtime — which is exactly what this experiment verifies. +# Run A (previous commit) tested MACOSX_DEPLOYMENT_TARGET=14.0 with the +# status-quo dynamic system-libc++ linkage. Run B tests the proposed end +# state: deployment target 11.0 + statically linked LLVM libc++/libc++abi +# (injected via `[build] ldflags`, which the released bootstrap mcpp +# already honors — no mcpp code change needed for the experiment). # -# job 1 (macos-15): bootstrap mcpp, build mcpp@HEAD and xlings@main -# with MACOSX_DEPLOYMENT_TARGET=14.0, assert -# minos=14.0 via otool, smoke on 15. -# job 2 (macos-14): control-check the released 0.0.49 binary is refused, -# then run the minos14 artifacts end-to-end: -# xlings self install + mcpp new/build/run (installs -# the llvm/ninja toolchain packages on macOS 14 too). +# job 1 (macos-15): bootstrap → warm toolchain → assert llvm payload +# ships libc++.a/libc++abi.a → inject static ldflags +# into both manifests → rebuild with target 11.0 → +# assert minos=11.0 + no libc++ dylib dep → smoke. +# job 2 (macos-14): control-check released minos15 binary is refused, +# then run the minos11 artifacts end-to-end +# (xlings self install + mcpp new/build/run). # # DO NOT MERGE. Delete branch after the investigation. name: temp-macos14-support @@ -24,8 +23,8 @@ on: workflow_dispatch: jobs: - build-minos14: - name: "build mcpp+xlings with MACOSX_DEPLOYMENT_TARGET=14.0 (macos-15)" + build-minos11: + name: "build mcpp+xlings: target 11.0 + static LLVM libc++ (macos-15)" runs-on: macos-15 timeout-minutes: 60 steps: @@ -74,60 +73,95 @@ jobs: echo "MCPP=$MCPP" >> "$GITHUB_ENV" echo "$HOME/.xlings/subos/default/bin" >> "$GITHUB_PATH" - - name: "Control: bootstrap mcpp binary is minos 15" + - name: Warm toolchain (installs llvm/ninja into the sandbox) + env: + MCPP_HOME: /Users/runner/.mcpp run: | - BIN=$(readlink -f "$(find "$HOME/.xlings/data/xpkgs" -path '*-x-mcpp/*/bin/mcpp' | head -1)") - otool -l "$BIN" | grep -A4 LC_BUILD_VERSION | head -6 + "$MCPP" build + echo "warm build done (linkage/minos not asserted here)" - - name: Build mcpp@HEAD with deployment target 14.0 + - name: "Assert llvm payload ships static libc++ archives" + run: | + LLVM_ROOT=$(find "$HOME/.mcpp/registry/data/xpkgs/xim-x-llvm" -maxdepth 1 -mindepth 1 -type d | head -1) + test -n "$LLVM_ROOT" + echo "LLVM_ROOT=$LLVM_ROOT" >> "$GITHUB_ENV" + ls "$LLVM_ROOT/lib" | grep -E '^libc\+\+' || true + test -f "$LLVM_ROOT/lib/libc++.a" || { echo "FAIL: libc++.a missing"; exit 1; } + test -f "$LLVM_ROOT/lib/libc++abi.a" || { echo "FAIL: libc++abi.a missing"; exit 1; } + + - name: "Inject static-libc++ ldflags into both manifests" + run: | + cat > /tmp/inject.py <<'PYEOF' + import sys + llvm = sys.argv[1] + flags = 'ldflags = ["-nostdlib++", "%s/lib/libc++.a", "%s/lib/libc++abi.a"]' % (llvm, llvm) + for path in sys.argv[2:]: + text = open(path).read() + assert "[build]" in text, path + assert "ldflags" not in text, path + text = text.replace("[build]", "[build]\n" + flags, 1) + open(path, "w").write(text) + print("injected into " + path) + PYEOF + python3 /tmp/inject.py "$LLVM_ROOT" mcpp.toml xlings-src/mcpp.toml + grep -n "ldflags" mcpp.toml xlings-src/mcpp.toml + + - name: Build mcpp@HEAD (target 11.0, static libc++) env: - MACOSX_DEPLOYMENT_TARGET: '14.0' + MACOSX_DEPLOYMENT_TARGET: '11.0' MCPP_HOME: /Users/runner/.mcpp run: | - "$MCPP" build + "$MCPP" build --no-cache ART=$(find target -type f -path '*/bin/mcpp' | head -1) test -n "$ART" echo "ART=$ART" >> "$GITHUB_ENV" - echo "=== LC_BUILD_VERSION of freshly built mcpp ===" + echo "=== LC_BUILD_VERSION ===" otool -l "$ART" | grep -A4 LC_BUILD_VERSION | head -6 - otool -l "$ART" | grep -A4 LC_BUILD_VERSION | grep -q "minos 14.0" \ - || { echo "FAIL: expected minos 14.0"; exit 1; } - echo "=== smoke on macos-15 ===" + otool -l "$ART" | grep -A4 LC_BUILD_VERSION | grep -q "minos 11.0" \ + || { echo "FAIL: expected minos 11.0"; exit 1; } + echo "=== dylib deps (must NOT contain libc++) ===" + otool -L "$ART" + if otool -L "$ART" | grep -q "libc++"; then + echo "FAIL: still linked against system libc++"; exit 1 + fi + echo "=== smoke on macos-15 (checks static-dtor abort too) ===" "$ART" --version + echo "exit: $?" - - name: Build xlings@main with deployment target 14.0 (using minos14 mcpp) + - name: Build xlings@main (target 11.0, static libc++, via minos11 mcpp) env: - MACOSX_DEPLOYMENT_TARGET: '14.0' + MACOSX_DEPLOYMENT_TARGET: '11.0' MCPP_HOME: /Users/runner/.mcpp run: | cd xlings-src - "$GITHUB_WORKSPACE/$ART" build + "$GITHUB_WORKSPACE/$ART" build --no-cache XART=$(find target -type f -path '*/bin/xlings' | head -1) test -n "$XART" echo "XART=xlings-src/$XART" >> "$GITHUB_ENV" - echo "=== LC_BUILD_VERSION of freshly built xlings ===" otool -l "$XART" | grep -A4 LC_BUILD_VERSION | head -6 - otool -l "$XART" | grep -A4 LC_BUILD_VERSION | grep -q "minos 14.0" \ - || { echo "FAIL: expected minos 14.0"; exit 1; } - echo "=== smoke on macos-15 ===" + otool -l "$XART" | grep -A4 LC_BUILD_VERSION | grep -q "minos 11.0" \ + || { echo "FAIL: expected minos 11.0"; exit 1; } + if otool -L "$XART" | grep -q "libc++"; then + echo "FAIL: still linked against system libc++"; exit 1 + fi "$XART" --version - - name: Upload minos14 binaries + - name: Upload minos11 binaries uses: actions/upload-artifact@v4 with: - name: minos14-binaries + name: minos11-binaries path: | ${{ env.ART }} ${{ env.XART }} smoke-macos14: - name: "run minos14 mcpp+xlings end-to-end on macos-14" - needs: build-minos14 + name: "run minos11 mcpp+xlings end-to-end on macos-14" + needs: build-minos11 runs-on: macos-14 timeout-minutes: 45 steps: - name: "Runner info" - run: sw_vers; uname -m; xcodebuild -version | head -2 || true + run: sw_vers; uname -m - name: "Control: released 0.0.49 (minos 15) must be refused by dyld" run: | @@ -138,18 +172,18 @@ jobs: if "$WORK/mcpp-0.0.49-macosx-arm64/bin/mcpp" --version 2>err.txt; then echo "UNEXPECTED: minos15 binary ran on macOS 14" else - echo "confirmed refusal:"; cat err.txt | head -3 + echo "confirmed refusal:"; head -3 err.txt fi - - name: Download minos14 binaries + - name: Download minos11 binaries uses: actions/download-artifact@v4 with: - name: minos14-binaries + name: minos11-binaries path: art - - name: "minos14 binaries start on macOS 14" + - name: "minos11 binaries start on macOS 14" run: | - find art -type f | head; chmod +x $(find art -type f) + chmod +x $(find art -type f) MCPP=$(find art -path '*bin/mcpp' -type f | head -1) XLINGS=$(find art -path '*bin/xlings' -type f | head -1) echo "MCPP=$PWD/$MCPP" >> "$GITHUB_ENV" @@ -157,7 +191,7 @@ jobs: "$MCPP" --version "$XLINGS" --version - - name: "Assemble xlings home on macOS 14 (release layout + minos14 binary)" + - name: "Assemble xlings home on macOS 14 (release layout + minos11 binary)" env: XLINGS_NON_INTERACTIVE: '1' run: | @@ -167,13 +201,12 @@ jobs: "https://github.com/openxlings/xlings/releases/download/v0.4.49/$tarball" tar -xzf "$WORK/$tarball" -C "$WORK" ROOT="$WORK/xlings-0.4.49-macosx-arm64" - # Swap in the minos14 binary everywhere the layout carries one. for p in "$ROOT/bin/xlings" "$ROOT/subos/default/bin/xlings"; do if [ -e "$p" ]; then cp "$XLINGS" "$p"; chmod +x "$p"; fi done "$ROOT/subos/default/bin/xlings" self install - export PATH="$HOME/.xlings/subos/default/bin:$PATH" echo "$HOME/.xlings/subos/default/bin" >> "$GITHUB_PATH" + export PATH="$HOME/.xlings/subos/default/bin:$PATH" xlings --version - name: "mcpp end-to-end on macOS 14: new + build + run (installs llvm/ninja)" @@ -184,16 +217,14 @@ jobs: cd hello "$MCPP" build "$MCPP" run | head -3 - echo "=== built hello LC_BUILD_VERSION (built natively on 14) ===" BIN=$(find target -type f -path '*/bin/hello' | head -1) + echo "=== hello built natively on macOS 14 ===" otool -l "$BIN" | grep -A4 LC_BUILD_VERSION | head -6 - name: "Summary" if: always() run: | { - echo "## macOS 14 support experiment" - echo "" - echo "- minos14 mcpp/xlings binaries built on macos-15 with MACOSX_DEPLOYMENT_TARGET=14.0" - echo "- end-to-end on macos-14: see step outcomes above" + echo "## macOS min-version experiment (run B: 11.0 + static libc++)" + echo "- artifacts built on macos-15, executed end-to-end on macos-14" } >> "$GITHUB_STEP_SUMMARY" From 52182d439aaa441be9daa0513e9c1f7dfb4f3baa Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Fri, 5 Jun 2026 01:36:13 +0800 Subject: [PATCH 03/10] =?UTF-8?q?test:=20run=20B=20fix=20=E2=80=94=20consi?= =?UTF-8?q?stent=20deployment=20target=20across=20builds=20+=20cache=20sal?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Run B attempt 1 failed with std.pcm config-mismatch: the warm build (no deployment env -> target macosx15) reused run A's cached std.pcm (compiled for macosx14). mcpp's BMI fingerprint does not include the deployment target — real product bug, fix tracked for 0.0.50. --- .github/workflows/temp-macos14-support.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/temp-macos14-support.yml b/.github/workflows/temp-macos14-support.yml index 4fea8998..982af44a 100644 --- a/.github/workflows/temp-macos14-support.yml +++ b/.github/workflows/temp-macos14-support.yml @@ -39,17 +39,17 @@ jobs: uses: actions/cache@v4 with: path: ~/.xlings - key: xlings-macos15-m14test-${{ hashFiles('.xlings.json') }} + key: xlings-macos15-m14test-v2-${{ hashFiles('.xlings.json') }} restore-keys: | - xlings-macos15-m14test- + xlings-macos15-m14test-v2- - name: Cache mcpp sandbox uses: actions/cache@v4 with: path: ~/.mcpp - key: mcpp-macos15-m14test-${{ hashFiles('mcpp.toml') }} + key: mcpp-macos15-m14test-v2-${{ hashFiles('mcpp.toml') }} restore-keys: | - mcpp-macos15-m14test- + mcpp-macos15-m14test-v2- - name: Bootstrap mcpp via xlings env: @@ -76,6 +76,12 @@ jobs: - name: Warm toolchain (installs llvm/ninja into the sandbox) env: MCPP_HOME: /Users/runner/.mcpp + # Same target as the real build below: mcpp's BMI fingerprint + # does not (yet) include the deployment target, so mixing + # targets within one sandbox produces std.pcm config-mismatch + # errors (observed in run B attempt 1; product fix tracked for + # mcpp 0.0.50's toolchain fingerprint). + MACOSX_DEPLOYMENT_TARGET: '11.0' run: | "$MCPP" build echo "warm build done (linkage/minos not asserted here)" From d8fdbe5f012743c0329e9041dbbf8bc5d93e6aac Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Fri, 5 Jun 2026 01:47:28 +0800 Subject: [PATCH 04/10] =?UTF-8?q?feat(0.0.50):=20macOS=20min-version=20sup?= =?UTF-8?q?port=20=E2=80=94=20static=20LLVM=20libc++=20+=20explicit=20depl?= =?UTF-8?q?oyment=20target?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Released macosx-arm64 binaries carried LC_BUILD_VERSION minos=15.0 and dynamically linked the SYSTEM /usr/lib/libc++.1.dylib, so they only ran on macOS 15: dyld refuses older minos, and lowering minos alone dies at launch on macOS 14 with a missing libc++ symbol (__ZNSt3__119__is_posix_terminalEP7__sFILE — std::print support added in LLVM-18-era libc++; verified on macos-14 CI). - flags.cppm: implement staticStdlib (the manifest default, previously silently ignored on the clang route) for the macOS link path — link LLVM's own libc++.a/libc++abi.a via -nostdlib++ instead of -lc++, falling back to -lc++ when the archives are absent. Mirror MACOSX_DEPLOYMENT_TARGET onto compile and link command lines so ninja commands don't depend on env propagation. - cli.cppm: fold MACOSX_DEPLOYMENT_TARGET into the BMI fingerprint — the deployment target changes the effective compile triple (arm64-apple-macosxNN), and a std.pcm built for one target cannot be loaded by a TU compiled for another (config-mismatch observed on CI). - main.cpp: __APPLE__ exit guard (_Exit after stream flush) — static libc++'s static destruction can SIGABRT on exit; same guard xlings uses. - release.yml (macos job): MACOSX_DEPLOYMENT_TARGET=11.0 + staged-archive ldflags injection for the self-build (the bootstrap mcpp predates this change), with minos/no-dylib assertions. - version 0.0.50. Design: xlings .agents/docs/2026-06-05-macos-min-version-support.md --- .github/workflows/release.yml | 32 ++++++++++++++++++++ mcpp.toml | 2 +- src/build/flags.cppm | 55 +++++++++++++++++++++++++++++++++- src/cli.cppm | 11 +++++++ src/main.cpp | 14 ++++++++- src/toolchain/fingerprint.cppm | 2 +- 6 files changed, 112 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a398ade1..2b0e4c4a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -317,16 +317,48 @@ jobs: echo "XLINGS_BIN=$HOME/.xlings/subos/default/bin/xlings" >> "$GITHUB_ENV" - name: Build mcpp from source (self-host) + env: + # macOS min-version support: target the first arm64 macOS so the + # release runs on 11.0+ instead of only the runner's OS (15). + # Requires static LLVM libc++ (injected below) — the system + # libc++ on older macOS lacks LLVM-20-era C++23 symbols + # (std::print's __is_posix_terminal etc.; verified on macos-14 + # CI). See xlings .agents/docs/2026-06-05-macos-min-version-support.md. + MACOSX_DEPLOYMENT_TARGET: '11.0' run: | export PATH="$HOME/.xlings/subos/default/bin:$PATH" export MCPP_VENDORED_XLINGS="$XLINGS_BIN" + + # Warm the toolchain so the llvm payload exists, then inject the + # static libc++ archives via [build] ldflags. The bootstrap mcpp + # predates the flags.cppm staticStdlib/clang implementation + # shipped in this very release — from the next release on, the + # injection is redundant (but harmless). "$MCPP" build + LLVM_ROOT=$(find "$HOME/.mcpp/registry/data/xpkgs/xim-x-llvm" -maxdepth 1 -mindepth 1 -type d | head -1) + test -f "$LLVM_ROOT/lib/libc++.a" + test -f "$LLVM_ROOT/lib/libc++abi.a" + if ! grep -q '^ldflags' mcpp.toml; then + sed -i '' "s|^\[build\]|[build]\nldflags = [\"-nostdlib++\", \"$LLVM_ROOT/lib/libc++.a\", \"$LLVM_ROOT/lib/libc++abi.a\"]|" mcpp.toml + fi + grep -n 'ldflags' mcpp.toml + + "$MCPP" build --no-cache MCPP_BIN=$(find target -path "*/bin/mcpp" | head -1) MCPP_BIN=$(cd "$(dirname "$MCPP_BIN")" && pwd)/$(basename "$MCPP_BIN") test -x "$MCPP_BIN" file "$MCPP_BIN" otool -L "$MCPP_BIN" + echo "=== LC_BUILD_VERSION (must be minos 11.0) ===" + otool -l "$MCPP_BIN" | grep -A4 LC_BUILD_VERSION | head -6 + otool -l "$MCPP_BIN" | grep -A4 LC_BUILD_VERSION | grep -q "minos 11.0" \ + || { echo "FAIL: expected minos 11.0"; exit 1; } + if otool -L "$MCPP_BIN" | grep -q "libc++"; then + echo "FAIL: still linked against system libc++"; exit 1 + fi "$MCPP_BIN" --version + # Restore the manifest so packaging sees a clean tree. + git checkout -- mcpp.toml echo "MCPP_BIN=$MCPP_BIN" >> "$GITHUB_ENV" - name: Package macOS release diff --git a/mcpp.toml b/mcpp.toml index f94556d3..c514a62b 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,6 +1,6 @@ [package] name = "mcpp" -version = "0.0.49" +version = "0.0.50" description = "Modern C++ build & package management tool" license = "Apache-2.0" authors = ["mcpp-community"] diff --git a/src/build/flags.cppm b/src/build/flags.cppm index 234955cf..5e42ca3c 100644 --- a/src/build/flags.cppm +++ b/src/build/flags.cppm @@ -6,6 +6,9 @@ // // See .agents/docs/2026-05-12-compile-commands-design.md. +module; +#include + export module mcpp.build.flags; import std; @@ -120,6 +123,9 @@ CompileFlags compute_flags(const BuildPlan& plan) { std::string link_toolchain_flags; bool isClangWithCfg = false; std::filesystem::path cfgPath; + // LLVM root of a clang-with-cfg toolchain — used by the macOS link + // path below to locate libc++.a/libc++abi.a for staticStdlib. + std::filesystem::path llvmRootForStdlib; if (mcpp::toolchain::is_clang(plan.toolchain)) { cfgPath = plan.toolchain.binaryPath.parent_path() / (plan.toolchain.binaryPath.stem().string() + ".cfg"); @@ -131,6 +137,22 @@ CompileFlags compute_flags(const BuildPlan& plan) { auto llvmRoot = plan.toolchain.binaryPath.parent_path().parent_path(); auto libcxxInclude = llvmRoot / "include" / "c++" / "v1"; compile_toolchain_flags = " --no-default-config -nostdinc++"; + // macOS deployment target: make MACOSX_DEPLOYMENT_TARGET explicit + // on the command line so (a) the ninja commands don't depend on + // env propagation and (b) the value participates in the BMI + // fingerprint via canonical flags — mixing targets in one sandbox + // otherwise reuses a std.pcm built for a different + // arm64-apple-macosxNN triple and dies with a config mismatch + // (observed on macos CI). The link side is added to f.ld below + // (the macOS link path doesn't consume link_toolchain_flags). + if (mcpp::platform::is_macos) { + if (const char* dt = std::getenv("MACOSX_DEPLOYMENT_TARGET"); + dt && *dt) { + compile_toolchain_flags += + std::string(" -mmacosx-version-min=") + dt; + } + } + llvmRootForStdlib = llvmRoot; // libc++ headers compile_toolchain_flags += " -isystem" + escape_path(libcxxInclude); if (!plan.toolchain.targetTriple.empty()) { @@ -309,7 +331,38 @@ CompileFlags compute_flags(const BuildPlan& plan) { if constexpr (mcpp::platform::is_windows) { f.ld = user_ldflags + link_extra; } else if constexpr (mcpp::platform::needs_explicit_libcxx) { - f.ld = std::format("{}{}{} -lc++{}{}", full_static, static_stdlib, b_flag, user_ldflags, link_extra); + // macOS. Two min-version concerns (see xlings + // .agents/docs/2026-06-05-macos-min-version-support.md): + // + // 1. stdlib linkage — `-lc++` resolves to the SYSTEM + // /usr/lib/libc++.1.dylib, which caps the deployment floor at + // the build host's OS: e.g. std::print's __is_posix_terminal + // support symbol only exists in macOS 15's libc++, so a + // minos-14 binary dies at launch on 14 (dyld missing-symbol + // abort; verified on macos-14 CI). With staticStdlib (the + // manifest default — previously silently ignored on the clang + // route), link LLVM's own libc++.a/libc++abi.a instead: + // runtime deps shrink to libSystem and the floor drops to + // 11.0 (first arm64 macOS). Falls back to -lc++ when the + // archives are absent. + // 2. deployment target — mirror MACOSX_DEPLOYMENT_TARGET onto the + // link command line so it doesn't depend on env propagation. + std::string stdlib_link = " -lc++"; + if (f.staticStdlib && !llvmRootForStdlib.empty()) { + auto libcxxA = llvmRootForStdlib / "lib" / "libc++.a"; + auto libcxxAbiA = llvmRootForStdlib / "lib" / "libc++abi.a"; + if (std::filesystem::exists(libcxxA) + && std::filesystem::exists(libcxxAbiA)) { + stdlib_link = " -nostdlib++ " + escape_path(libcxxA) + + " " + escape_path(libcxxAbiA); + } + } + std::string version_min; + if (const char* dt = std::getenv("MACOSX_DEPLOYMENT_TARGET"); dt && *dt) { + version_min = std::string(" -mmacosx-version-min=") + dt; + } + f.ld = std::format("{}{}{}{}{}{}{}", full_static, static_stdlib, b_flag, + version_min, stdlib_link, user_ldflags, link_extra); } else { f.ld = std::format("{}{}{}{}{}{}{}{}", full_static, static_stdlib, link_toolchain_flags, b_flag, runtime_dirs, payload_ld, user_ldflags, link_extra); diff --git a/src/cli.cppm b/src/cli.cppm index 346fb8e8..782f0c73 100644 --- a/src/cli.cppm +++ b/src/cli.cppm @@ -587,6 +587,17 @@ std::string canonical_compile_flags(const mcpp::manifest::Manifest& m) { std::string s; s += "-std="; s += m.package.standard; s += " -fmodules"; + // macOS deployment target changes the effective compile triple + // (arm64-apple-macosxNN) — a std.pcm built for one target cannot be + // loaded by a TU compiled for another. Fold it into the fingerprint + // so switching MACOSX_DEPLOYMENT_TARGET rebuilds the BMI cache + // instead of dying with a module config mismatch. + if constexpr (mcpp::platform::is_macos) { + if (const char* dt = std::getenv("MACOSX_DEPLOYMENT_TARGET"); dt && *dt) { + s += " macos_deployment_target="; + s += dt; + } + } if (!m.buildConfig.cStandard.empty()) { s += " c_standard="; s += m.buildConfig.cStandard; diff --git a/src/main.cpp b/src/main.cpp index 8dc40daa..b62c9b3f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,5 +5,17 @@ import std; import mcpp.cli; int main(int argc, char* argv[]) { - return mcpp::cli::run(argc, argv); + int rc = mcpp::cli::run(argc, argv); +#ifdef __APPLE__ + // With statically linked libc++ (the macOS release linkage since + // 0.0.50), static destruction can SIGABRT on exit — same issue xlings + // guards against. A CLI tool needs no destructor-based cleanup; skip + // static dtors entirely. _Exit bypasses atexit handlers too, so flush + // the standard streams explicitly first. + std::cout.flush(); + std::cerr.flush(); + std::_Exit(rc); +#else + return rc; +#endif } diff --git a/src/toolchain/fingerprint.cppm b/src/toolchain/fingerprint.cppm index 234b4877..13eaee5c 100644 --- a/src/toolchain/fingerprint.cppm +++ b/src/toolchain/fingerprint.cppm @@ -18,7 +18,7 @@ import mcpp.toolchain.detect; export namespace mcpp::toolchain { -inline constexpr std::string_view MCPP_VERSION = "0.0.49"; +inline constexpr std::string_view MCPP_VERSION = "0.0.50"; struct FingerprintInputs { Toolchain toolchain; From d03000f434226045a27f6f3d0590c823e84be4e0 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Fri, 5 Jun 2026 01:48:41 +0800 Subject: [PATCH 05/10] ci(release): macos self-build static-libc++ injection v2 (staged -L dir) Run B2 evidence: -nostdlib++ in user ldflags cannot remove the hardcoded -lc++ that precedes it in the bootstrap mcpp's link line. Stage the archives in a dylib-free dir instead and -L it so -lc++ resolves to libc++.a. --- .github/workflows/release.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2b0e4c4a..e667b9df 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -329,17 +329,23 @@ jobs: export PATH="$HOME/.xlings/subos/default/bin:$PATH" export MCPP_VENDORED_XLINGS="$XLINGS_BIN" - # Warm the toolchain so the llvm payload exists, then inject the - # static libc++ archives via [build] ldflags. The bootstrap mcpp - # predates the flags.cppm staticStdlib/clang implementation - # shipped in this very release — from the next release on, the - # injection is redundant (but harmless). + # Warm the toolchain so the llvm payload exists, then stage the + # static libc++ archives in a dylib-free directory and inject + # `-L -lc++abi` via [build] ldflags: the bootstrap + # mcpp's macOS link line hardcodes ` -lc++` BEFORE user ldflags, + # and the staged -L path makes it resolve to libc++.a (no dylib + # competes there). The bootstrap predates the flags.cppm + # staticStdlib implementation shipped in this very release — + # from the next release on, the injection is redundant. "$MCPP" build LLVM_ROOT=$(find "$HOME/.mcpp/registry/data/xpkgs/xim-x-llvm" -maxdepth 1 -mindepth 1 -type d | head -1) test -f "$LLVM_ROOT/lib/libc++.a" test -f "$LLVM_ROOT/lib/libc++abi.a" + STATICDIR="$RUNNER_TEMP/static-libcxx" + mkdir -p "$STATICDIR" + cp "$LLVM_ROOT/lib/libc++.a" "$LLVM_ROOT/lib/libc++abi.a" "$STATICDIR/" if ! grep -q '^ldflags' mcpp.toml; then - sed -i '' "s|^\[build\]|[build]\nldflags = [\"-nostdlib++\", \"$LLVM_ROOT/lib/libc++.a\", \"$LLVM_ROOT/lib/libc++abi.a\"]|" mcpp.toml + sed -i '' "s|^\[build\]|[build]\nldflags = [\"-L$STATICDIR\", \"-lc++abi\"]|" mcpp.toml fi grep -n 'ldflags' mcpp.toml From 53189b246fcd393cfa891e38176595117df6c767 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Fri, 5 Jun 2026 01:57:26 +0800 Subject: [PATCH 06/10] =?UTF-8?q?test:=20run=20B4=20=E2=80=94=20two-stage?= =?UTF-8?q?=20self-host=20instead=20of=20ldflags=20injection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Run B3 post-mortem: the v2 injection never actually ran (a merge conflict broke the command chain), so B3 re-tested B2's injection. But injection is the wrong tool anyway — the bootstrap's hardcoded -lc++ precedes user ldflags. Two-stage self-host removes the problem: stage 1 (bootstrap-built, dynamic, runs on the build host) rebuilds itself as stage 2 with flags.cppm's native staticStdlib implementation. --- .github/workflows/temp-macos14-support.yml | 32 +++++++++------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/.github/workflows/temp-macos14-support.yml b/.github/workflows/temp-macos14-support.yml index 982af44a..6b92e5fa 100644 --- a/.github/workflows/temp-macos14-support.yml +++ b/.github/workflows/temp-macos14-support.yml @@ -95,29 +95,21 @@ jobs: test -f "$LLVM_ROOT/lib/libc++.a" || { echo "FAIL: libc++.a missing"; exit 1; } test -f "$LLVM_ROOT/lib/libc++abi.a" || { echo "FAIL: libc++abi.a missing"; exit 1; } - - name: "Inject static-libc++ ldflags into both manifests" - run: | - cat > /tmp/inject.py <<'PYEOF' - import sys - llvm = sys.argv[1] - flags = 'ldflags = ["-nostdlib++", "%s/lib/libc++.a", "%s/lib/libc++abi.a"]' % (llvm, llvm) - for path in sys.argv[2:]: - text = open(path).read() - assert "[build]" in text, path - assert "ldflags" not in text, path - text = text.replace("[build]", "[build]\n" + flags, 1) - open(path, "w").write(text) - print("injected into " + path) - PYEOF - python3 /tmp/inject.py "$LLVM_ROOT" mcpp.toml xlings-src/mcpp.toml - grep -n "ldflags" mcpp.toml xlings-src/mcpp.toml - - - name: Build mcpp@HEAD (target 11.0, static libc++) + - name: "Build mcpp@HEAD (two-stage self-host: target 11.0, static libc++)" env: MACOSX_DEPLOYMENT_TARGET: '11.0' MCPP_HOME: /Users/runner/.mcpp run: | - "$MCPP" build --no-cache + # Stage 1: bootstrap (0.0.49) builds mcpp@HEAD. Its macOS link + # path hardcodes -lc++, so stage 1 is dynamically linked — fine, + # it only needs to RUN on this runner. + "$MCPP" build + STAGE1=$(find target -type f -path '*/bin/mcpp' | head -1) + STAGE1="$PWD/$STAGE1" + "$STAGE1" --version + # Stage 2: mcpp@HEAD rebuilds itself — its flags.cppm implements + # staticStdlib for the macOS link path natively. + "$STAGE1" build --no-cache ART=$(find target -type f -path '*/bin/mcpp' | head -1) test -n "$ART" echo "ART=$ART" >> "$GITHUB_ENV" @@ -130,6 +122,8 @@ jobs: if otool -L "$ART" | grep -q "libc++"; then echo "FAIL: still linked against system libc++"; exit 1 fi + echo "=== build.ninja ldflags (evidence) ===" + grep -h "^ldflags" target/*/*/build.ninja | head -2 || true echo "=== smoke on macos-15 (checks static-dtor abort too) ===" "$ART" --version echo "exit: $?" From af0d07548d6c15e25bb7ead596a6c78117985d7f Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Fri, 5 Jun 2026 01:58:10 +0800 Subject: [PATCH 07/10] ci(release): macos self-build via two-stage self-host (drop ldflags injection) The bootstrap's hardcoded -lc++ precedes user ldflags, so manifest injection cannot produce the static link (runs B2/B3). Stage 1 (bootstrap-built, dynamic) rebuilds itself as stage 2 with the native staticStdlib implementation from this release. --- .github/workflows/release.yml | 43 +++++++++++++---------------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e667b9df..4596fb2a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -316,40 +316,31 @@ jobs: echo "MCPP=$MCPP" >> "$GITHUB_ENV" echo "XLINGS_BIN=$HOME/.xlings/subos/default/bin/xlings" >> "$GITHUB_ENV" - - name: Build mcpp from source (self-host) + - name: Build mcpp from source (two-stage self-host) env: # macOS min-version support: target the first arm64 macOS so the - # release runs on 11.0+ instead of only the runner's OS (15). - # Requires static LLVM libc++ (injected below) — the system - # libc++ on older macOS lacks LLVM-20-era C++23 symbols - # (std::print's __is_posix_terminal etc.; verified on macos-14 - # CI). See xlings .agents/docs/2026-06-05-macos-min-version-support.md. + # release runs on 11.0+ instead of only the runner's OS. Needs + # static LLVM libc++ — the system libc++ on older macOS lacks + # LLVM-20-era C++23 symbols (std::print's __is_posix_terminal + # etc.; minos-14 + dynamic libc++ dies at launch on macos-14 CI). + # See xlings .agents/docs/2026-06-05-macos-min-version-support.md. MACOSX_DEPLOYMENT_TARGET: '11.0' run: | export PATH="$HOME/.xlings/subos/default/bin:$PATH" export MCPP_VENDORED_XLINGS="$XLINGS_BIN" - # Warm the toolchain so the llvm payload exists, then stage the - # static libc++ archives in a dylib-free directory and inject - # `-L -lc++abi` via [build] ldflags: the bootstrap - # mcpp's macOS link line hardcodes ` -lc++` BEFORE user ldflags, - # and the staged -L path makes it resolve to libc++.a (no dylib - # competes there). The bootstrap predates the flags.cppm - # staticStdlib implementation shipped in this very release — - # from the next release on, the injection is redundant. + # Stage 1: the bootstrap mcpp builds this release's source. The + # bootstrap's macOS link path predates the staticStdlib + # implementation (hardcoded -lc++), so stage 1 links the system + # libc++ — fine, it only needs to RUN on this runner. "$MCPP" build - LLVM_ROOT=$(find "$HOME/.mcpp/registry/data/xpkgs/xim-x-llvm" -maxdepth 1 -mindepth 1 -type d | head -1) - test -f "$LLVM_ROOT/lib/libc++.a" - test -f "$LLVM_ROOT/lib/libc++abi.a" - STATICDIR="$RUNNER_TEMP/static-libcxx" - mkdir -p "$STATICDIR" - cp "$LLVM_ROOT/lib/libc++.a" "$LLVM_ROOT/lib/libc++abi.a" "$STATICDIR/" - if ! grep -q '^ldflags' mcpp.toml; then - sed -i '' "s|^\[build\]|[build]\nldflags = [\"-L$STATICDIR\", \"-lc++abi\"]|" mcpp.toml - fi - grep -n 'ldflags' mcpp.toml + STAGE1=$(find target -path "*/bin/mcpp" | head -1) + STAGE1=$(cd "$(dirname "$STAGE1")" && pwd)/$(basename "$STAGE1") + "$STAGE1" --version - "$MCPP" build --no-cache + # Stage 2: this release's mcpp rebuilds itself — flags.cppm's + # native staticStdlib link produces the static minos-11 binary. + "$STAGE1" build --no-cache MCPP_BIN=$(find target -path "*/bin/mcpp" | head -1) MCPP_BIN=$(cd "$(dirname "$MCPP_BIN")" && pwd)/$(basename "$MCPP_BIN") test -x "$MCPP_BIN" @@ -363,8 +354,6 @@ jobs: echo "FAIL: still linked against system libc++"; exit 1 fi "$MCPP_BIN" --version - # Restore the manifest so packaging sees a clean tree. - git checkout -- mcpp.toml echo "MCPP_BIN=$MCPP_BIN" >> "$GITHUB_ENV" - name: Package macOS release From e5b877a7899556b54362226a1e93c5a5d57d3c61 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Fri, 5 Jun 2026 02:19:17 +0800 Subject: [PATCH 08/10] feat: macOS links via lld (same as the Linux clang path) Xcode's system ld floats with the host: on macos-14 CI, Xcode 15.4's ld aborted at launch (dyld resolved its libc++ against the LLVM payload's libc++.1.0.dylib, missing __ZdaPv). lld ships with the exact LLVM toolchain doing the compile and removes the host-Xcode dependency from the link step. --- src/build/flags.cppm | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/build/flags.cppm b/src/build/flags.cppm index 5e42ca3c..6e2f017d 100644 --- a/src/build/flags.cppm +++ b/src/build/flags.cppm @@ -347,6 +347,11 @@ CompileFlags compute_flags(const BuildPlan& plan) { // archives are absent. // 2. deployment target — mirror MACOSX_DEPLOYMENT_TARGET onto the // link command line so it doesn't depend on env propagation. + // 3. linker — use LLVM's own lld (same as the Linux clang path) + // instead of Xcode's ld: the system ld's version floats with + // the host Xcode (observed: Xcode 15.4's ld aborting at launch + // on macos-14 CI when its libc++ resolution was diverted), and + // lld ships with the exact toolchain doing the compile. std::string stdlib_link = " -lc++"; if (f.staticStdlib && !llvmRootForStdlib.empty()) { auto libcxxA = llvmRootForStdlib / "lib" / "libc++.a"; @@ -361,8 +366,8 @@ CompileFlags compute_flags(const BuildPlan& plan) { if (const char* dt = std::getenv("MACOSX_DEPLOYMENT_TARGET"); dt && *dt) { version_min = std::string(" -mmacosx-version-min=") + dt; } - f.ld = std::format("{}{}{}{}{}{}{}", full_static, static_stdlib, b_flag, - version_min, stdlib_link, user_ldflags, link_extra); + f.ld = std::format("{}{}{} -fuse-ld=lld{}{}{}{}", full_static, static_stdlib, + b_flag, version_min, stdlib_link, user_ldflags, link_extra); } else { f.ld = std::format("{}{}{}{}{}{}{}{}", full_static, static_stdlib, link_toolchain_flags, b_flag, runtime_dirs, payload_ld, user_ldflags, link_extra); From c8fe61c2ccb5b2a1e7cfc46db3438c3fd4bbf650 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Fri, 5 Jun 2026 02:19:35 +0800 Subject: [PATCH 09/10] =?UTF-8?q?test:=20run=20B5=20=E2=80=94=20macOS=20li?= =?UTF-8?q?nk=20via=20lld=20+=20DYLD=20forensics=20on=20macos-14=20e2e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/temp-macos14-support.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/temp-macos14-support.yml b/.github/workflows/temp-macos14-support.yml index 6b92e5fa..fda63ee3 100644 --- a/.github/workflows/temp-macos14-support.yml +++ b/.github/workflows/temp-macos14-support.yml @@ -213,6 +213,8 @@ jobs: env: MCPP_HOME: /Users/runner/.mcpp run: | + echo "=== DYLD/forensics: env ===" + env | grep -i -E "dyld|library_path" || echo "(no dyld vars in env)" "$MCPP" new hello cd hello "$MCPP" build From 047bbe2e76cce568ee2942daf5f3a261f8a31da6 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Fri, 5 Jun 2026 17:29:59 +0800 Subject: [PATCH 10/10] =?UTF-8?q?test:=20final=20release=20verification=20?= =?UTF-8?q?=E2=80=94=20v0.0.50=20+=20v0.4.50=20on=20macos-15/14=20incl.=20?= =?UTF-8?q?split-brain=20coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/temp-macos14-support.yml | 260 ++++++--------------- 1 file changed, 72 insertions(+), 188 deletions(-) diff --git a/.github/workflows/temp-macos14-support.yml b/.github/workflows/temp-macos14-support.yml index fda63ee3..6ec58208 100644 --- a/.github/workflows/temp-macos14-support.yml +++ b/.github/workflows/temp-macos14-support.yml @@ -1,20 +1,11 @@ -# TEMPORARY workflow — macOS min-version support experiment (run B). -# -# Run A (previous commit) tested MACOSX_DEPLOYMENT_TARGET=14.0 with the -# status-quo dynamic system-libc++ linkage. Run B tests the proposed end -# state: deployment target 11.0 + statically linked LLVM libc++/libc++abi -# (injected via `[build] ldflags`, which the released bootstrap mcpp -# already honors — no mcpp code change needed for the experiment). -# -# job 1 (macos-15): bootstrap → warm toolchain → assert llvm payload -# ships libc++.a/libc++abi.a → inject static ldflags -# into both manifests → rebuild with target 11.0 → -# assert minos=11.0 + no libc++ dylib dep → smoke. -# job 2 (macos-14): control-check released minos15 binary is refused, -# then run the minos11 artifacts end-to-end -# (xlings self install + mcpp new/build/run). -# -# DO NOT MERGE. Delete branch after the investigation. +# TEMPORARY — final verification of the shipped macOS min-version support +# against the REAL release artifacts (mcpp v0.0.50 + xlings v0.4.50): +# - control: released 0.0.49 (minos 15, dynamic libc++) refused on macOS 14 +# - new artifacts start on both macos-15 and macos-14 +# - full user flow on macOS 14: xlings self install -> mcpp new/build/run +# - split-brain libc++ coverage: a `cout << int` program built with a +# declared floor (static libc++ via -Wl,-load_hidden) runs clean +# DO NOT MERGE. Close PR + delete branch after verification. name: temp-macos14-support on: @@ -23,210 +14,103 @@ on: workflow_dispatch: jobs: - build-minos11: - name: "build mcpp+xlings: target 11.0 + static LLVM libc++ (macos-15)" - runs-on: macos-15 - timeout-minutes: 60 - steps: - - uses: actions/checkout@v4 - - - uses: actions/checkout@v4 - with: - repository: openxlings/xlings - path: xlings-src - - - name: Cache xlings (bootstrap) - uses: actions/cache@v4 - with: - path: ~/.xlings - key: xlings-macos15-m14test-v2-${{ hashFiles('.xlings.json') }} - restore-keys: | - xlings-macos15-m14test-v2- - - - name: Cache mcpp sandbox - uses: actions/cache@v4 - with: - path: ~/.mcpp - key: mcpp-macos15-m14test-v2-${{ hashFiles('mcpp.toml') }} - restore-keys: | - mcpp-macos15-m14test-v2- - - - name: Bootstrap mcpp via xlings - env: - XLINGS_NON_INTERACTIVE: '1' - XLINGS_VERSION: '0.4.49' - run: | - if [ ! -x "$HOME/.xlings/subos/default/bin/xlings" ]; then - WORK=$(mktemp -d) - tarball="xlings-${XLINGS_VERSION}-macosx-arm64.tar.gz" - curl -fsSL -o "${WORK}/${tarball}" \ - "https://github.com/openxlings/xlings/releases/download/v${XLINGS_VERSION}/${tarball}" - tar -xzf "${WORK}/${tarball}" -C "${WORK}" - "${WORK}/xlings-${XLINGS_VERSION}-macosx-arm64/subos/default/bin/xlings" self install - fi - export PATH="$HOME/.xlings/subos/default/bin:$PATH" - xlings --version - xlings install mcpp -y - MCPP="$HOME/.xlings/subos/default/bin/mcpp" - test -x "$MCPP" - "$MCPP" --version - echo "MCPP=$MCPP" >> "$GITHUB_ENV" - echo "$HOME/.xlings/subos/default/bin" >> "$GITHUB_PATH" - - - name: Warm toolchain (installs llvm/ninja into the sandbox) - env: - MCPP_HOME: /Users/runner/.mcpp - # Same target as the real build below: mcpp's BMI fingerprint - # does not (yet) include the deployment target, so mixing - # targets within one sandbox produces std.pcm config-mismatch - # errors (observed in run B attempt 1; product fix tracked for - # mcpp 0.0.50's toolchain fingerprint). - MACOSX_DEPLOYMENT_TARGET: '11.0' - run: | - "$MCPP" build - echo "warm build done (linkage/minos not asserted here)" - - - name: "Assert llvm payload ships static libc++ archives" - run: | - LLVM_ROOT=$(find "$HOME/.mcpp/registry/data/xpkgs/xim-x-llvm" -maxdepth 1 -mindepth 1 -type d | head -1) - test -n "$LLVM_ROOT" - echo "LLVM_ROOT=$LLVM_ROOT" >> "$GITHUB_ENV" - ls "$LLVM_ROOT/lib" | grep -E '^libc\+\+' || true - test -f "$LLVM_ROOT/lib/libc++.a" || { echo "FAIL: libc++.a missing"; exit 1; } - test -f "$LLVM_ROOT/lib/libc++abi.a" || { echo "FAIL: libc++abi.a missing"; exit 1; } - - - name: "Build mcpp@HEAD (two-stage self-host: target 11.0, static libc++)" - env: - MACOSX_DEPLOYMENT_TARGET: '11.0' - MCPP_HOME: /Users/runner/.mcpp - run: | - # Stage 1: bootstrap (0.0.49) builds mcpp@HEAD. Its macOS link - # path hardcodes -lc++, so stage 1 is dynamically linked — fine, - # it only needs to RUN on this runner. - "$MCPP" build - STAGE1=$(find target -type f -path '*/bin/mcpp' | head -1) - STAGE1="$PWD/$STAGE1" - "$STAGE1" --version - # Stage 2: mcpp@HEAD rebuilds itself — its flags.cppm implements - # staticStdlib for the macOS link path natively. - "$STAGE1" build --no-cache - ART=$(find target -type f -path '*/bin/mcpp' | head -1) - test -n "$ART" - echo "ART=$ART" >> "$GITHUB_ENV" - echo "=== LC_BUILD_VERSION ===" - otool -l "$ART" | grep -A4 LC_BUILD_VERSION | head -6 - otool -l "$ART" | grep -A4 LC_BUILD_VERSION | grep -q "minos 11.0" \ - || { echo "FAIL: expected minos 11.0"; exit 1; } - echo "=== dylib deps (must NOT contain libc++) ===" - otool -L "$ART" - if otool -L "$ART" | grep -q "libc++"; then - echo "FAIL: still linked against system libc++"; exit 1 - fi - echo "=== build.ninja ldflags (evidence) ===" - grep -h "^ldflags" target/*/*/build.ninja | head -2 || true - echo "=== smoke on macos-15 (checks static-dtor abort too) ===" - "$ART" --version - echo "exit: $?" - - - name: Build xlings@main (target 11.0, static libc++, via minos11 mcpp) - env: - MACOSX_DEPLOYMENT_TARGET: '11.0' - MCPP_HOME: /Users/runner/.mcpp - run: | - cd xlings-src - "$GITHUB_WORKSPACE/$ART" build --no-cache - XART=$(find target -type f -path '*/bin/xlings' | head -1) - test -n "$XART" - echo "XART=xlings-src/$XART" >> "$GITHUB_ENV" - otool -l "$XART" | grep -A4 LC_BUILD_VERSION | head -6 - otool -l "$XART" | grep -A4 LC_BUILD_VERSION | grep -q "minos 11.0" \ - || { echo "FAIL: expected minos 11.0"; exit 1; } - if otool -L "$XART" | grep -q "libc++"; then - echo "FAIL: still linked against system libc++"; exit 1 - fi - "$XART" --version - - - name: Upload minos11 binaries - uses: actions/upload-artifact@v4 - with: - name: minos11-binaries - path: | - ${{ env.ART }} - ${{ env.XART }} - - smoke-macos14: - name: "run minos11 mcpp+xlings end-to-end on macos-14" - needs: build-minos11 - runs-on: macos-14 + verify: + strategy: + fail-fast: false + matrix: + os: [macos-15, macos-14] + name: "release verification (${{ matrix.os }})" + runs-on: ${{ matrix.os }} timeout-minutes: 45 steps: - - name: "Runner info" - run: sw_vers; uname -m + - name: Runner info + run: sw_vers - - name: "Control: released 0.0.49 (minos 15) must be refused by dyld" + - name: "Control: released 0.0.49 (minos 15) behavior" run: | WORK=$(mktemp -d) curl -fsSL -o "$WORK/m.tar.gz" \ "https://github.com/mcpp-community/mcpp/releases/download/v0.0.49/mcpp-0.0.49-macosx-arm64.tar.gz" tar -xzf "$WORK/m.tar.gz" -C "$WORK" if "$WORK/mcpp-0.0.49-macosx-arm64/bin/mcpp" --version 2>err.txt; then - echo "UNEXPECTED: minos15 binary ran on macOS 14" + echo "0.0.49 runs here (expected on macos-15)" else - echo "confirmed refusal:"; head -3 err.txt + echo "0.0.49 refused (expected on macos-14):"; head -3 err.txt fi - - name: Download minos11 binaries - uses: actions/download-artifact@v4 - with: - name: minos11-binaries - path: art - - - name: "minos11 binaries start on macOS 14" - run: | - chmod +x $(find art -type f) - MCPP=$(find art -path '*bin/mcpp' -type f | head -1) - XLINGS=$(find art -path '*bin/xlings' -type f | head -1) - echo "MCPP=$PWD/$MCPP" >> "$GITHUB_ENV" - echo "XLINGS=$PWD/$XLINGS" >> "$GITHUB_ENV" - "$MCPP" --version - "$XLINGS" --version - - - name: "Assemble xlings home on macOS 14 (release layout + minos11 binary)" + - name: "Install xlings v0.4.50 (release tarball self install)" env: XLINGS_NON_INTERACTIVE: '1' run: | WORK=$(mktemp -d) - tarball="xlings-0.4.49-macosx-arm64.tar.gz" + tarball="xlings-0.4.50-macosx-arm64.tar.gz" curl -fsSL -o "$WORK/$tarball" \ - "https://github.com/openxlings/xlings/releases/download/v0.4.49/$tarball" + "https://github.com/openxlings/xlings/releases/download/v0.4.50/$tarball" tar -xzf "$WORK/$tarball" -C "$WORK" - ROOT="$WORK/xlings-0.4.49-macosx-arm64" - for p in "$ROOT/bin/xlings" "$ROOT/subos/default/bin/xlings"; do - if [ -e "$p" ]; then cp "$XLINGS" "$p"; chmod +x "$p"; fi - done - "$ROOT/subos/default/bin/xlings" self install + BIN="$WORK/xlings-0.4.50-macosx-arm64/bin/xlings" + [ -x "$BIN" ] || BIN="$WORK/xlings-0.4.50-macosx-arm64/subos/default/bin/xlings" + echo "=== shipped xlings LC_BUILD_VERSION ===" + otool -l "$BIN" | grep -A4 LC_BUILD_VERSION | head -6 + otool -L "$BIN" + "$BIN" self install echo "$HOME/.xlings/subos/default/bin" >> "$GITHUB_PATH" export PATH="$HOME/.xlings/subos/default/bin:$PATH" xlings --version - - name: "mcpp end-to-end on macOS 14: new + build + run (installs llvm/ninja)" + - name: "Install mcpp v0.0.50 (release tarball)" + run: | + WORK=$(mktemp -d) + curl -fsSL -o "$WORK/m.tar.gz" \ + "https://github.com/mcpp-community/mcpp/releases/download/v0.0.50/mcpp-0.0.50-macosx-arm64.tar.gz" + tar -xzf "$WORK/m.tar.gz" -C "$WORK" + MCPP_ROOT="$WORK/mcpp-0.0.50-macosx-arm64" + echo "=== shipped mcpp LC_BUILD_VERSION ===" + otool -l "$MCPP_ROOT/bin/mcpp" | grep -A4 LC_BUILD_VERSION | head -6 + otool -L "$MCPP_ROOT/bin/mcpp" + "$MCPP_ROOT/bin/mcpp" --version + echo "MCPP=$MCPP_ROOT/bin/mcpp" >> "$GITHUB_ENV" + + - name: "User flow: mcpp new hello + build + run" env: MCPP_HOME: /Users/runner/.mcpp run: | - echo "=== DYLD/forensics: env ===" - env | grep -i -E "dyld|library_path" || echo "(no dyld vars in env)" "$MCPP" new hello cd hello "$MCPP" build "$MCPP" run | head -3 BIN=$(find target -type f -path '*/bin/hello' | head -1) - echo "=== hello built natively on macOS 14 ===" + echo "=== hello (host default floor) ===" otool -l "$BIN" | grep -A4 LC_BUILD_VERSION | head -6 - - name: "Summary" + - name: "Split-brain coverage: cout< src/main.cpp <<'CEOF' + #include + int answer() { return 42; } + int main() { std::cout << "int says " << answer() << std::endl; return 0; } + CEOF + "$MCPP" build + BIN=$(ls -t $(find target -type f -path '*/bin/intprog') | head -1) + echo "=== intprog (declared floor 14.0 -> static libc++) ===" + otool -l "$BIN" | grep -A4 LC_BUILD_VERSION | head -6 + otool -L "$BIN" + if otool -L "$BIN" | grep -q "libc++"; then + echo "FAIL: declared-floor build still links system libc++"; exit 1 + fi + OUT=$("$BIN") + echo "output: $OUT exit=$?" + [ "$OUT" = "int says 42" ] || { echo "FAIL: wrong output"; exit 1; } + echo "SPLIT-BRAIN COVERAGE: OK" + + - name: Summary if: always() run: | { - echo "## macOS min-version experiment (run B: 11.0 + static libc++)" - echo "- artifacts built on macos-15, executed end-to-end on macos-14" + echo "## macOS min-version release verification (${{ matrix.os }})" + echo "- mcpp v0.0.50 + xlings v0.4.50 release artifacts" } >> "$GITHUB_STEP_SUMMARY"