diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index a785a3f..1795d39 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -45,10 +45,79 @@ jobs: fi xcodebuild -version + - name: Fetch the engine bundle from the private engine repo + # Makes a nightly behave like an official release: installer.sh finds the + # engine inside Resources and sets it up offline instead of building or + # downloading wine itself. Installing = replacing deps/wine-unified + # wholesale, which is what installer.sh already does (rm -rf then extract). + # + # WINE_ENGINE_TOKEN is a fine-grained PAT with Contents: read-only on the + # engine repo -- it is private, so the workflow's own GITHUB_TOKEN cannot + # see it. Absent secret = launcher-only build rather than a hard failure, + # so a fork without the secret still produces a usable nightly. + id: payload + env: + GH_TOKEN: ${{ secrets.WINE_ENGINE_TOKEN }} + ENGINE_REPO: mont127/MacNdCheese-WineEngine-PRIVATE + run: | + set -euo pipefail + if [ -z "${GH_TOKEN:-}" ]; then + echo "::warning::WINE_ENGINE_TOKEN not set - building launcher-only (no bundled engine)" + echo "bundled=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + mkdir -p payload + + # Newest release carrying an engine asset, chosen by ASSET NAME rather than + # `--latest`: engine builds are often prereleases and the repo also publishes + # d3d-pack-only releases, either of which would make --latest pick the wrong one. + TAG="$(gh release list --repo "$ENGINE_REPO" --limit 100 --json tagName \ + --jq '.[].tagName' | while read -r t; do + if gh api "repos/$ENGINE_REPO/releases/tags/$t" \ + --jq '.assets[].name' 2>/dev/null | grep -q 'unified-osx64\.tar\.xz'; then + echo "$t"; break + fi + done)" + [ -n "$TAG" ] || { echo "::error::no unified engine asset found in $ENGINE_REPO"; exit 1; } + echo "engine release: $TAG" + + gh release download "$TAG" --repo "$ENGINE_REPO" \ + --pattern '*unified-osx64.tar.xz' --dir payload + mv payload/*unified-osx64.tar.xz payload/wine-unified-bundle.tar.xz + + # The engine bundle carries mnc-d3d/ itself, so nothing else to fetch. Verify + # it is complete: a pack missing either half of the D3DMetal runtime loads + # fine and then dies on "Failed to dlopen D3DMetal" with no hint a file is + # missing, so fail here rather than shipping it. + for req in mnc-d3d/d3d11_dxmt.dll mnc-d3d/dxgi_dxmt.dll \ + mnc-d3d/libd3dshared.dylib mnc-d3d/D3DMetal.framework \ + loader/wine server/wineserver; do + tar -tJf payload/wine-unified-bundle.tar.xz "wine-unified/$req" >/dev/null 2>&1 \ + || { echo "::error::engine bundle incomplete: $req missing"; exit 1; } + done + + echo "engine_tag=$TAG" >> "$GITHUB_OUTPUT" + echo "bundled=true" >> "$GITHUB_OUTPUT" + ls -lh payload/ + - name: Build universal DMG + env: + MNC_PAYLOAD_DIR: payload run: | chmod +x buildapp.sh bash buildapp.sh universal + ls -la *.dmg + + - name: Fail if the DMG exceeds GitHub's 2 GB asset limit + # A bundled nightly is ~1.9 GB; uploads over 2 GB are rejected outright and + # the failure surfaces late and confusingly, so check it up front. + run: | + SZ=$(stat -f%z "MacNdCheese Launcher.dmg" 2>/dev/null || echo 0) + echo "DMG size: $((SZ/1048576)) MB" + if [ "$SZ" -gt 2147483648 ]; then + echo "::error::DMG is $((SZ/1048576)) MB, over GitHub's 2048 MB asset limit" + exit 1 + fi - name: Compute tag and release notes id: tag diff --git a/buildapp.sh b/buildapp.sh index 20e6f00..6476225 100644 --- a/buildapp.sh +++ b/buildapp.sh @@ -123,6 +123,29 @@ for img in Steam.png Wine.png Setting.png Add.png icon.png; do fi done +# Engine payloads (wine-unified-bundle.zip, mnc-d3d/, redist/, ...). An official +# release ships these inside Resources -- that is what makes installer.sh able to +# set the engine up offline instead of building or downloading wine itself. They +# are far too large for git, so they are supplied out of band: point +# MNC_PAYLOAD_DIR at a directory containing them (the nightly workflow fills it +# from the engine repo's releases). +# +# Whatever is in that directory is copied verbatim, so a payload set can grow +# without touching this script. Absent = a launcher-only build, which is the +# pre-existing behaviour and still perfectly usable -- installer.sh just falls +# back to its other sources. +PAYLOAD_DIR="${MNC_PAYLOAD_DIR:-payload}" +if [ -d "$PAYLOAD_DIR" ] && [ -n "$(ls -A "$PAYLOAD_DIR" 2>/dev/null)" ]; then + echo "Bundling engine payloads from $PAYLOAD_DIR:" + for item in "$PAYLOAD_DIR"/*; do + [ -e "$item" ] || continue + cp -R "$item" "$RESOURCES/" + echo " + $(basename "$item") ($(du -sh "$item" 2>/dev/null | cut -f1))" + done +else + echo "No engine payloads ($PAYLOAD_DIR absent/empty) — building launcher-only." +fi + # Extract App Intents metadata so Siri/Apple Intelligence can discover shortcuts. # App Intents definitions don't vary by CPU arch, so for a universal build one # representative triple (arm64) is enough — this only affects Siri phrase diff --git a/installer.sh b/installer.sh index 4de54f8..80a2009 100644 --- a/installer.sh +++ b/installer.sh @@ -2233,13 +2233,21 @@ locate_wine_unified_bundle() { script_path="$0" case "$script_path" in /*) ;; *) script_path="$PWD/$script_path" ;; esac script_dir="$(cd "$(dirname "$script_path")" 2>/dev/null && pwd)" || script_dir="" + # .tar.xz is what the engine repo publishes (and what the nightly bundles); + # .zip is the historical hand-made bundle. Both are accepted. candidates=" ${WINE_UNIFIED_BUNDLE_PATH:-} +${RESOURCES_DIR:-}/wine-unified-bundle.tar.xz ${RESOURCES_DIR:-}/wine-unified-bundle.zip +${script_dir}/wine-unified-bundle.tar.xz ${script_dir}/wine-unified-bundle.zip +${script_dir}/../Resources/wine-unified-bundle.tar.xz ${script_dir}/../Resources/wine-unified-bundle.zip +${script_dir}/../../Resources/wine-unified-bundle.tar.xz ${script_dir}/../../Resources/wine-unified-bundle.zip +$HOME/macndcheese/wine-unified-bundle.tar.xz $HOME/macndcheese/wine-unified-bundle.zip +$HOME/Library/Application Support/MacNCheese/wine-unified-bundle.tar.xz $HOME/Library/Application Support/MacNCheese/wine-unified-bundle.zip " while IFS= read -r c; do @@ -2250,7 +2258,7 @@ $candidates EOF for root in /Applications "$HOME/Applications" "$HOME/Downloads"; do [ -d "$root" ] || continue - found="$(find "$root" -maxdepth 5 -name 'wine-unified-bundle.zip' -type f 2>/dev/null | head -n1)" + found="$(find "$root" -maxdepth 5 \( -name 'wine-unified-bundle.tar.xz' -o -name 'wine-unified-bundle.zip' \) -type f 2>/dev/null | head -n1)" [ -n "$found" ] && [ -f "$found" ] && { printf '%s' "$found"; return 0; } done return 1 @@ -2309,16 +2317,32 @@ install_wine_unified() { echo "Using unified wine bundle: $bundle" rm -rf "$dst" mkdir -p "$dst" - if command -v unzip >/dev/null 2>&1; then - unzip -q "$bundle" -d "$dst" || { echo "Failed to unzip unified wine bundle"; exit 1; } - elif [ -x "$SEVENZ_BIN" ]; then - "$SEVENZ_BIN" x -y -o"$dst" "$bundle" >/dev/null || { echo "Failed to extract unified wine bundle"; exit 1; } - else - echo "Neither unzip nor 7z available to extract the bundle"; exit 1 - fi + case "$bundle" in + *.tar.xz|*.txz) + # The tarball wraps everything in one top-level dir (wine-unified/), but + # $dst IS that dir -- strip a level or it ends up nested twice. + tar -xJf "$bundle" -C "$dst" --strip-components=1 \ + || { echo "Failed to untar unified wine bundle"; exit 1; } + ;; + *) + if command -v unzip >/dev/null 2>&1; then + unzip -q "$bundle" -d "$dst" || { echo "Failed to unzip unified wine bundle"; exit 1; } + elif [ -x "$SEVENZ_BIN" ]; then + "$SEVENZ_BIN" x -y -o"$dst" "$bundle" >/dev/null || { echo "Failed to extract unified wine bundle"; exit 1; } + else + echo "Neither unzip nor 7z available to extract the bundle"; exit 1 + fi + ;; + esac find "$dst" -name 'wine' -type f -exec chmod +x {} \; 2>/dev/null || true xattr -dr com.apple.quarantine "$dst" 2>/dev/null || true - stage_unified_d3d_pack "$dst" + # The engine bundle now ships mnc-d3d/ itself; only stage from Resources when + # it didn't (older hand-made .zip bundles). + if [ -f "$dst/mnc-d3d/d3d11.dll" ]; then + echo "install_wine_unified: d3d pack came with the bundle ($(ls "$dst/mnc-d3d" 2>/dev/null | wc -l | tr -d ' ') entries)" + else + stage_unified_d3d_pack "$dst" + fi stage_redist_pack sign_unified_wine "$dst" echo "install_wine_unified: done ($(du -sh "$dst" 2>/dev/null | cut -f1))"