From 56764e46c822d2f05831e5fb81a5ef672d816bd4 Mon Sep 17 00:00:00 2001 From: OpenClaw Date: Sun, 10 May 2026 22:19:09 +0000 Subject: [PATCH 1/3] feat: add Intel Mac support via x86_64 cross-compilation on arm64 runner --- .github/workflows/cd-swift-cua-driver.yml | 29 ++++++++++++++++++- .../scripts/build/build-release-notarized.sh | 26 +++++++++++++---- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/.github/workflows/cd-swift-cua-driver.yml b/.github/workflows/cd-swift-cua-driver.yml index bc0ed3b3ec..39a29780f2 100644 --- a/.github/workflows/cd-swift-cua-driver.yml +++ b/.github/workflows/cd-swift-cua-driver.yml @@ -50,6 +50,10 @@ jobs: outputs: sha256_checksums: ${{ steps.generate_checksums.outputs.checksums }} version: ${{ steps.set_version.outputs.version }} + tarball_path: ${{ steps.build_notarize.outputs.tarball_path }} + pkg_path: ${{ steps.build_notarize.outputs.pkg_path }} + x86_64_tarball_path: ${{ steps.build_notarize.outputs.x86_64_tarball_path }} + x86_64_pkg_path: ${{ steps.build_notarize.outputs.x86_64_pkg_path }} steps: - uses: actions/checkout@v4 @@ -168,6 +172,9 @@ jobs: cd scripts/build LOG_LEVEL=minimal ./build-release-notarized.sh + # Cross-compile for Intel Mac (x86_64) on this arm64 runner + ARCH=x86_64 LOG_LEVEL=minimal ./build-release-notarized.sh + # Return to the cua-driver directory cd ../.. @@ -179,10 +186,14 @@ jobs: ARCH=$(uname -m) OS_IDENTIFIER="darwin-${ARCH}" - # Output paths for later use + # Output paths for arm64 (native) artifacts echo "tarball_path=.release/cua-driver-${VERSION}-${OS_IDENTIFIER}.tar.gz" >> $GITHUB_OUTPUT echo "pkg_path=.release/cua-driver-${VERSION}-${OS_IDENTIFIER}.pkg.tar.gz" >> $GITHUB_OUTPUT + # Output paths for x86_64 artifacts + echo "x86_64_tarball_path=.release/cua-driver-${VERSION}-darwin-x86_64.tar.gz" >> $GITHUB_OUTPUT + echo "x86_64_pkg_path=.release/cua-driver-${VERSION}-darwin-x86_64.pkg.tar.gz" >> $GITHUB_OUTPUT + - name: Upload build log on failure if: failure() && steps.build_notarize.outcome == 'failure' uses: actions/upload-artifact@v4 @@ -273,6 +284,20 @@ jobs: path: ./libs/cua-driver/.release/cua-driver-*-binary.tar.gz if-no-files-found: warn + - name: Upload Notarized Package x86_64 (Tarball) + uses: actions/upload-artifact@v4 + with: + name: cua-driver-notarized-tarball-x86_64 + path: ./libs/cua-driver/${{ steps.build_notarize.outputs.x86_64_tarball_path }} + if-no-files-found: error + + - name: Upload Notarized Package x86_64 (Installer) + uses: actions/upload-artifact@v4 + with: + name: cua-driver-notarized-installer-x86_64 + path: ./libs/cua-driver/${{ steps.build_notarize.outputs.x86_64_pkg_path }} + if-no-files-found: error + - name: Generate path-filtered release notes if: startsWith(github.ref, 'refs/tags/cua-driver-v') id: release-notes @@ -310,6 +335,8 @@ jobs: files: | ./libs/cua-driver/${{ steps.build_notarize.outputs.tarball_path }} ./libs/cua-driver/${{ steps.build_notarize.outputs.pkg_path }} + ./libs/cua-driver/${{ steps.build_notarize.outputs.x86_64_tarball_path }} + ./libs/cua-driver/${{ steps.build_notarize.outputs.x86_64_pkg_path }} ./libs/cua-driver/.release/cua-driver-darwin.tar.gz ./libs/cua-driver/.release/cua-driver-darwin.pkg.tar.gz ./libs/cua-driver/.release/cua-driver.tar.gz diff --git a/libs/cua-driver/scripts/build/build-release-notarized.sh b/libs/cua-driver/scripts/build/build-release-notarized.sh index 667e2e6a05..db9b25c1cb 100755 --- a/libs/cua-driver/scripts/build/build-release-notarized.sh +++ b/libs/cua-driver/scripts/build/build-release-notarized.sh @@ -60,9 +60,17 @@ cd "$CUA_DRIVER_DIR" mkdir -p .release log "normal" "Ensuring .release directory exists and is accessible" +# Get the native architecture and allow env override +NATIVE_ARCH=$(uname -m) +ARCH=${ARCH:-$(uname -m)} + # Build the release version -log "essential" "Building release version..." -swift build -c release --product cua-driver > /dev/null +log "essential" "Building release version (arch: $ARCH)..." +if [ "$ARCH" != "$NATIVE_ARCH" ]; then + swift build -c release --product cua-driver --arch "$ARCH" > /dev/null +else + swift build -c release --product cua-driver > /dev/null +fi # --- Assemble .app bundle --- log "essential" "Assembling .app bundle..." @@ -72,8 +80,15 @@ rm -rf "$APP_BUNDLE" mkdir -p "$APP_BUNDLE/Contents/MacOS" mkdir -p "$APP_BUNDLE/Contents/Resources" -# Copy the binary into the bundle -cp -f .build/release/cua-driver "$APP_BUNDLE/Contents/MacOS/cua-driver" +# Copy the binary into the bundle. +# When cross-compiling (ARCH != native), Swift places the output under +# .build//release/ instead of the usual .build/release/ symlink. +if [ "$ARCH" != "$NATIVE_ARCH" ]; then + BINARY_PATH=".build/${ARCH}-apple-macosx/release/cua-driver" +else + BINARY_PATH=".build/release/cua-driver" +fi +cp -f "$BINARY_PATH" "$APP_BUNDLE/Contents/MacOS/cua-driver" # Stamp and copy Info.plist โ€” the source plist ships with a static # `CFBundleShortVersionString` for dev builds; substitute the release @@ -218,8 +233,7 @@ fi # --- Create release archives --- -# Get architecture and create OS identifier -ARCH=$(uname -m) +# ARCH is already set above (env override or native); derive OS identifier OS_IDENTIFIER="darwin-${ARCH}" RELEASE_DIR="$(cd .release && pwd)" From 8804203f04b4287d8db94a197e7ebfbcb15b63d6 Mon Sep 17 00:00:00 2001 From: OpenClaw Date: Mon, 11 May 2026 01:34:33 +0000 Subject: [PATCH 2/3] ci: add PR pre-release binary workflow for Intel Mac testing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Builds unsigned darwin-arm64 and darwin-x86_64 binaries on every PR touching libs/cua-driver, then posts download links as a PR comment. This lets Intel Mac users test the binary before the PR is merged without requiring Apple notarization credentials. Binaries are unsigned/unnotarized โ€” users need: xattr -d com.apple.quarantine ./cua-driver --- .../workflows/ci-cua-driver-pr-binaries.yml | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 .github/workflows/ci-cua-driver-pr-binaries.yml diff --git a/.github/workflows/ci-cua-driver-pr-binaries.yml b/.github/workflows/ci-cua-driver-pr-binaries.yml new file mode 100644 index 0000000000..5bbf398558 --- /dev/null +++ b/.github/workflows/ci-cua-driver-pr-binaries.yml @@ -0,0 +1,153 @@ +name: "CI: Cua Driver โ€” PR pre-release binaries" + +# Builds unsigned pre-release binaries for both darwin-arm64 and darwin-x86_64 +# on every PR that touches libs/cua-driver, then posts download links as a +# PR comment so Intel Mac users can test before merging. +# +# Limitations: +# - Binaries are UNSIGNED and UNNOTARIZED. macOS Gatekeeper will quarantine +# them. To run: xattr -d com.apple.quarantine ./cua-driver +# - Artifact links require a GitHub login and expire after 90 days. + +on: + pull_request: + paths: + - "libs/cua-driver/**" + - ".github/workflows/ci-cua-driver-pr-binaries.yml" + +concurrency: + group: cua-driver-pr-binaries-${{ github.ref }} + cancel-in-progress: true + +permissions: + pull-requests: write # to post the comment + contents: read + +jobs: + build: + name: Build (${{ matrix.arch }}) + runs-on: macos-15 + strategy: + matrix: + arch: [arm64, x86_64] + steps: + - uses: actions/checkout@v4 + + - name: Select Xcode 16.3 + run: sudo xcode-select -s /Applications/Xcode_16.3.app + + - name: Build (${{ matrix.arch }}) + working-directory: ./libs/cua-driver + run: | + if [ "${{ matrix.arch }}" = "arm64" ]; then + swift build --configuration release --product cua-driver + BINARY=".build/release/cua-driver" + else + swift build --configuration release --product cua-driver --arch x86_64 + BINARY=".build/x86_64-apple-macosx/release/cua-driver" + fi + + # Package into a tarball alongside a thin wrapper script so users + # can just extract and run ./cua-driver without knowing the bundle layout. + TARBALL="cua-driver-pr${{ github.event.pull_request.number }}-darwin-${{ matrix.arch }}.tar.gz" + mkdir -p /tmp/cua-driver-dist + cp "$BINARY" /tmp/cua-driver-dist/cua-driver + tar -czf "/tmp/$TARBALL" -C /tmp/cua-driver-dist cua-driver + echo "TARBALL=/tmp/$TARBALL" >> "$GITHUB_ENV" + echo "TARBALL_NAME=$TARBALL" >> "$GITHUB_ENV" + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: cua-driver-pr${{ github.event.pull_request.number }}-darwin-${{ matrix.arch }} + path: ${{ env.TARBALL }} + retention-days: 90 + + comment: + name: Post PR comment with download links + runs-on: ubuntu-latest + needs: build + steps: + - name: Get artifact URLs + id: artifacts + env: + GH_TOKEN: ${{ github.token }} + run: | + # GitHub Actions artifacts aren't directly linkable without auth, + # but we can construct the deep-link to the run's artifact list. + RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + echo "run_url=$RUN_URL" >> "$GITHUB_OUTPUT" + + ARM64_NAME="cua-driver-pr${{ github.event.pull_request.number }}-darwin-arm64" + X86_NAME="cua-driver-pr${{ github.event.pull_request.number }}-darwin-x86_64" + + # Fetch artifact IDs so we can construct direct zip download URLs + ARTIFACTS=$(gh api \ + "repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts" \ + --jq '.artifacts[] | {name, id}') + + ARM64_ID=$(echo "$ARTIFACTS" | python3 -c " + import sys, json + lines = sys.stdin.read().strip().split('\n') + import json as j + for line in lines: + try: + obj = j.loads(line) + if obj['name'] == '$ARM64_NAME': + print(obj['id']) + except: pass + " 2>/dev/null || echo "") + + X86_ID=$(echo "$ARTIFACTS" | python3 -c " + import sys, json + lines = sys.stdin.read().strip().split('\n') + import json as j + for line in lines: + try: + obj = j.loads(line) + if obj['name'] == '$X86_NAME': + print(obj['id']) + except: pass + " 2>/dev/null || echo "") + + REPO="${{ github.repository }}" + if [ -n "$ARM64_ID" ]; then + echo "arm64_url=https://github.com/${REPO}/actions/runs/${{ github.run_id }}/artifacts/${ARM64_ID}" >> "$GITHUB_OUTPUT" + fi + if [ -n "$X86_ID" ]; then + echo "x86_url=https://github.com/${REPO}/actions/runs/${{ github.run_id }}/artifacts/${X86_ID}" >> "$GITHUB_OUTPUT" + fi + + - name: Find existing bot comment + id: find-comment + uses: peter-evans/find-comment@v3 + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: github-actions[bot] + body-includes: "" + + - name: Post or update PR comment + uses: peter-evans/create-or-update-comment@v4 + with: + comment-id: ${{ steps.find-comment.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + edit-mode: replace + body: | + + ## ๐Ÿ”ง Pre-release binaries (unsigned) + + Built from commit ${{ github.event.pull_request.head.sha }} ยท [CI run](${{ steps.artifacts.outputs.run_url }}) + + | Platform | Download | + |----------|----------| + | macOS โ€” Apple Silicon (arm64) | [${{ github.event.pull_request.number }}-darwin-arm64](${{ steps.artifacts.outputs.arm64_url }}) | + | macOS โ€” Intel (x86_64) | [${{ github.event.pull_request.number }}-darwin-x86_64](${{ steps.artifacts.outputs.x86_url }}) | + + > **โš ๏ธ Unsigned binary** โ€” macOS will quarantine it on first download. To run: + > ```bash + > # After extracting the tarball: + > xattr -d com.apple.quarantine ./cua-driver + > chmod +x ./cua-driver + > ./cua-driver --version + > ``` + > Artifact links require a GitHub login and expire after 90 days. From dfc590ccdffa5d275a11fc3ed01400b1df3dd180 Mon Sep 17 00:00:00 2001 From: OpenClaw Date: Mon, 11 May 2026 02:44:22 +0000 Subject: [PATCH 3/3] fix: address CodeRabbit review issues in Intel Mac cross-compile Three bugs flagged by CodeRabbit: 1. (Critical) Wildcard cleanup clobbers arm64 artifacts on x86_64 pass: rm -f cua-driver-*.tar.gz wiped the arm64 tarballs before the x86_64 pass finished. Fixed by scoping the rm to the current arch's files only. 2. (Critical) swift build failures silently produced broken app bundles: Added 'if ! swift build ...' guards and binary path existence check so a failed build exits immediately instead of packaging a broken payload. 3. (Major) Bare-binary step used uname -m (always arm64 on runner) but CuaDriver.app was x86_64 after the second build pass, causing a mislabeled bare binary. Fixed by: - Writing arch-suffixed bundles: CuaDriver-arm64.app / CuaDriver-x86_64.app - Packaging each via a temporary CuaDriver.app symlink (install.sh compat) - Iterating both arches explicitly in the bare-binary packaging step --- .github/workflows/cd-swift-cua-driver.yml | 30 +++++++++---- .../scripts/build/build-release-notarized.sh | 43 +++++++++++++++---- 2 files changed, 56 insertions(+), 17 deletions(-) diff --git a/.github/workflows/cd-swift-cua-driver.yml b/.github/workflows/cd-swift-cua-driver.yml index 39a29780f2..68a6956e52 100644 --- a/.github/workflows/cd-swift-cua-driver.yml +++ b/.github/workflows/cd-swift-cua-driver.yml @@ -249,18 +249,30 @@ jobs: working-directory: ./libs/cua-driver/.release run: | VERSION=${{ steps.set_version.outputs.version }} - ARCH=$(uname -m) - OS_IDENTIFIER="darwin-${ARCH}" + NATIVE_ARCH=$(uname -m) # The bare binary is already signed (it lives inside the signed .app). # Re-extract it and package it for embedders who build their own bundle. - BINARY="CuaDriver.app/Contents/MacOS/cua-driver" - if [ -f "$BINARY" ]; then - tar -czf "cua-driver-${VERSION}-${OS_IDENTIFIER}-binary.tar.gz" -C "CuaDriver.app/Contents/MacOS" cua-driver - ln -sf "cua-driver-${VERSION}-${OS_IDENTIFIER}-binary.tar.gz" "cua-driver-binary.tar.gz" - echo "Bare binary packaged." - else - echo "Warning: binary not found at $BINARY" + # build-release-notarized.sh now writes arch-suffixed bundles + # (CuaDriver-arm64.app, CuaDriver-x86_64.app) so we iterate over + # both arches explicitly rather than relying on uname -m, which would + # always point at the native runner arch regardless of which pass ran last. + for ARCH in arm64 x86_64; do + OS_IDENTIFIER="darwin-${ARCH}" + BUNDLE="CuaDriver-${ARCH}.app" + BINARY="${BUNDLE}/Contents/MacOS/cua-driver" + if [ -f "$BINARY" ]; then + tar -czf "cua-driver-${VERSION}-${OS_IDENTIFIER}-binary.tar.gz" \ + -C "${BUNDLE}/Contents/MacOS" cua-driver + echo "Bare binary packaged for ${ARCH}." + else + echo "Warning: binary not found at $BINARY (skipping ${ARCH})" + fi + done + # Convenience symlink points at the native-arch bare binary + NATIVE_BINARY="cua-driver-${VERSION}-darwin-${NATIVE_ARCH}-binary.tar.gz" + if [ -f "$NATIVE_BINARY" ]; then + ln -sf "$NATIVE_BINARY" "cua-driver-binary.tar.gz" fi - name: Upload Notarized Package (Tarball) diff --git a/libs/cua-driver/scripts/build/build-release-notarized.sh b/libs/cua-driver/scripts/build/build-release-notarized.sh index db9b25c1cb..8514b70607 100755 --- a/libs/cua-driver/scripts/build/build-release-notarized.sh +++ b/libs/cua-driver/scripts/build/build-release-notarized.sh @@ -67,15 +67,24 @@ ARCH=${ARCH:-$(uname -m)} # Build the release version log "essential" "Building release version (arch: $ARCH)..." if [ "$ARCH" != "$NATIVE_ARCH" ]; then - swift build -c release --product cua-driver --arch "$ARCH" > /dev/null + if ! swift build -c release --product cua-driver --arch "$ARCH" > /dev/null; then + log "error" "swift build failed for ARCH=$ARCH" + exit 1 + fi else - swift build -c release --product cua-driver > /dev/null + if ! swift build -c release --product cua-driver > /dev/null; then + log "error" "swift build failed for native ARCH=$NATIVE_ARCH" + exit 1 + fi fi # --- Assemble .app bundle --- log "essential" "Assembling .app bundle..." -APP_BUNDLE=".release/CuaDriver.app" +# Use an arch-suffixed bundle name so sequential arm64 + x86_64 invocations +# don't clobber each other's .app in .release/. The CI workflow renames/copies +# to CuaDriver.app where needed (e.g. the bare-binary packaging step). +APP_BUNDLE=".release/CuaDriver-${ARCH}.app" rm -rf "$APP_BUNDLE" mkdir -p "$APP_BUNDLE/Contents/MacOS" mkdir -p "$APP_BUNDLE/Contents/Resources" @@ -88,6 +97,10 @@ if [ "$ARCH" != "$NATIVE_ARCH" ]; then else BINARY_PATH=".build/release/cua-driver" fi +if [ ! -f "$BINARY_PATH" ]; then + log "error" "Expected binary not found at $BINARY_PATH" + exit 1 +fi cp -f "$BINARY_PATH" "$APP_BUNDLE/Contents/MacOS/cua-driver" # Stamp and copy Info.plist โ€” the source plist ships with a static @@ -240,8 +253,10 @@ RELEASE_DIR="$(cd .release && pwd)" log "essential" "Creating archives in $RELEASE_DIR..." cd "$RELEASE_DIR" -# Clean up any existing artifacts first to avoid conflicts -rm -f cua-driver-*.tar.gz cua-driver-*.pkg.tar.gz +# Clean up only this arch's artifacts โ€” a wildcard glob would wipe artifacts +# from a previous arch pass (e.g. the arm64 tarballs before x86_64 finishes). +rm -f "cua-driver-${VERSION}-${OS_IDENTIFIER}.tar.gz" \ + "cua-driver-${VERSION}-${OS_IDENTIFIER}.pkg.tar.gz" # Create a backward-compatible wrapper script at the tarball root so # extracting the tarball and running `./cua-driver ` works @@ -252,11 +267,23 @@ exec "$(dirname "$0")/CuaDriver.app/Contents/MacOS/cua-driver" "$@" WRAPPER_EOF chmod +x cua-driver -# Create version-specific archives +# Create version-specific archives. +# The arch-suffixed bundle (CuaDriver-${ARCH}.app) is packaged as CuaDriver.app +# inside the tarball so install.sh โ€” which always extracts and looks for +# CuaDriver.app โ€” works without any changes. +# We create a temporary symlink CuaDriver.app โ†’ CuaDriver-${ARCH}.app, tar it +# (tar follows symlinks to directories by default on macOS), then remove the link. log "essential" "Creating version-specific archives (${VERSION})..." -# Package the .app bundle and wrapper script -tar -czf "cua-driver-${VERSION}-${OS_IDENTIFIER}.tar.gz" cua-driver CuaDriver.app > /dev/null 2>&1 +# Temporary CuaDriver.app symlink so the tarball layout is stable for install.sh +ln -sfn "CuaDriver-${ARCH}.app" CuaDriver.app + +# Package the .app bundle (via the CuaDriver.app symlink) and wrapper script +tar -czf "cua-driver-${VERSION}-${OS_IDENTIFIER}.tar.gz" \ + cua-driver CuaDriver.app > /dev/null 2>&1 + +# Remove the symlink; the arch-suffixed bundle stays for the bare-binary step +rm -f CuaDriver.app # Package the installer tar -czf "cua-driver-${VERSION}-${OS_IDENTIFIER}.pkg.tar.gz" cua-driver.pkg > /dev/null 2>&1