Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 49 additions & 10 deletions .github/workflows/cd-swift-cua-driver.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Comment on lines +175 to +176

@coderabbitai coderabbitai Bot May 10, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Second pass overwrites .release/CuaDriver.app, which can mislabel the bare-binary artifact.

After Line 176, .release/CuaDriver.app is x86_64, but the later bare-binary step names output using native uname -m (arm64 on this runner). That can publish a wrongly labeled binary tarball.

One practical approach
 LOG_LEVEL=minimal ./build-release-notarized.sh
+cp -R .release/CuaDriver.app .release/CuaDriver-arm64.app
 
 # Cross-compile for Intel Mac (x86_64) on this arm64 runner
 ARCH=x86_64 LOG_LEVEL=minimal ./build-release-notarized.sh

Then point the bare-binary packaging step to .release/CuaDriver-arm64.app/... for the native artifact (or produce explicitly arch-suffixed bare binaries from each app).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/cd-swift-cua-driver.yml around lines 175 - 176, The
workflow overwrites .release/CuaDriver.app when cross-compiling x86_64
(ARCH=x86_64) which later causes the bare-binary packaging to pick up the
wrong-arch app using native `uname -m`; update the build/packaging steps so each
arch writes an arch-suffixed app directory (e.g. .release/CuaDriver-x86_64.app
and .release/CuaDriver-arm64.app) or make the bare-binary step explicitly point
to the correct arched app (e.g. use .release/CuaDriver-arm64.app when packaging
the native runner) instead of relying on the unsuffixed .release/CuaDriver.app;
adjust calls around build-release-notarized.sh, the ARCH variable usage, and the
bare-binary packaging invocation to reference the suffixed app name.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

worth addressing @r33drichards

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!


# Return to the cua-driver directory
cd ../..

Expand All @@ -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
Expand Down Expand Up @@ -238,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)
Expand All @@ -273,6 +296,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
Expand Down Expand Up @@ -310,6 +347,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
Expand Down
153 changes: 153 additions & 0 deletions .github/workflows/ci-cua-driver-pr-binaries.yml
Original file line number Diff line number Diff line change
@@ -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: "<!-- cua-driver-pr-binaries -->"

- 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: |
<!-- cua-driver-pr-binaries -->
## 🔧 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.
65 changes: 53 additions & 12 deletions libs/cua-driver/scripts/build/build-release-notarized.sh
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,48 @@ 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
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
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
Comment on lines +69 to +79

@coderabbitai coderabbitai Bot May 10, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fail fast if ARCH-specific build output is missing.

Right now, a failed swift build or missing $BINARY_PATH won’t stop the script immediately, which can produce a signed/packageable but broken app payload.

Suggested hardening
 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
@@
 if [ "$ARCH" != "$NATIVE_ARCH" ]; then
   BINARY_PATH=".build/${ARCH}-apple-macosx/release/cua-driver"
 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"

Also applies to: 83-91

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/cua-driver/scripts/build/build-release-notarized.sh` around lines 69 -
73, The script runs swift build for ARCH-specific and native builds without
failing immediately on build errors or missing artifacts; update the blocks that
invoke swift build (the conditional using ARCH and NATIVE_ARCH and the later
similar block at 83-91) to check the exit status of the swift build command and
verify that the expected $BINARY_PATH exists and is non-empty right after each
build, and if either check fails call echo with a descriptive error mentioning
the ARCH and exit with a non-zero status so the script hard-fails instead of
producing a broken payload.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

worth addressing @r33drichards

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!


# --- 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"

# 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/<triple>/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
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
# `CFBundleShortVersionString` for dev builds; substitute the release
Expand Down Expand Up @@ -218,16 +246,17 @@ 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}"
Comment on lines +249 to 250

@coderabbitai coderabbitai Bot May 10, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Scope archive cleanup to the current architecture.

With dual invocations, the wildcard cleanup on Line 244 deletes artifacts from the first pass (native) before the x86_64 pass finishes, so native upload paths can disappear.

Suggested fix
-# 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 to keep outputs from other arch passes
+rm -f "cua-driver-${VERSION}-${OS_IDENTIFIER}.tar.gz" \
+      "cua-driver-${VERSION}-${OS_IDENTIFIER}.pkg.tar.gz"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/cua-driver/scripts/build/build-release-notarized.sh` around lines 236 -
237, The cleanup step currently uses a wildcard that removes artifacts from
other-architecture runs; modify the cleanup to only remove archives for the
current architecture by using the OS_IDENTIFIER (set to "darwin-${ARCH}") or
ARCH variable when matching archive filenames in build-release-notarized.sh so
each invocation only deletes its own artifacts (scope the glob/pattern to
${OS_IDENTIFIER} or ${ARCH} instead of a broad wildcard).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

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 <tool>` works
Expand All @@ -238,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
Expand Down
Loading