91e75e620b
CI: cua-driver distro-compat matrix / debian:12 (glibc 2.36) (push) Has been cancelled
CI: SPDX Headers / Check SPDX headers (warn-only) (push) Has been cancelled
CD: Docs MCP Server / build (linux/amd64) (push) Has been cancelled
CD: Docs MCP Server / build (linux/arm64) (push) Has been cancelled
CD: Docs MCP Server / merge (push) Has been cancelled
CI: cua-driver distro-compat matrix / Resolve release version (push) Has been cancelled
CI: cua-driver distro-compat matrix / fedora:41 (glibc 2.40) (push) Has been cancelled
CI: cua-driver distro-compat matrix / rockylinux:9 (glibc 2.34) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:22.04 (glibc 2.35) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:24.04 (glibc 2.39) (push) Has been cancelled
CI: cua-driver distro-compat matrix / Distro compat summary (push) Has been cancelled
CI: Rust Linux unit / Rust Linux unit and compile (push) Has been cancelled
CI: Rust Windows unit / Rust Windows unit and compile (push) Has been cancelled
CI: Nix Linux Rust source / Nix / compositor build (push) Has been cancelled
CI: Nix Linux Rust source / Nix / driver package (push) Has been cancelled
CI: Nix Linux Rust source / Nix / Rust unit tests (push) Has been cancelled
702 lines
35 KiB
YAML
702 lines
35 KiB
YAML
name: "CD: Cua Driver (cross-platform)"
|
|
|
|
# Rust implementation release workflow — cross-platform (Windows/Linux/macOS):
|
|
# - macOS: ONE universal binary tarball + bare universal binary
|
|
# (lipo arm64 + x86_64 in a single job, no separate per-arch matrix)
|
|
# - Windows: cua-driver.exe bare + .zip
|
|
# - Linux: cua-driver bare + .tar.gz
|
|
#
|
|
# Bare binaries let curlable installers do
|
|
# `curl -L .../cua-driver-rs-vN-darwin-universal-binary.tar.gz | tar -xz`
|
|
# without needing to unpack a directory structure first.
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- "cua-driver-rs-v*"
|
|
workflow_dispatch:
|
|
inputs:
|
|
version:
|
|
description: "Version to release (without v prefix)"
|
|
required: true
|
|
default: "0.1.0"
|
|
notarize:
|
|
description: "Codesign + notarize the macOS artifacts (set false to skip during iteration)"
|
|
required: false
|
|
type: boolean
|
|
default: true
|
|
|
|
permissions:
|
|
contents: write
|
|
|
|
jobs:
|
|
# ── Linux (x86_64 + arm64) ───────────────────────────────────────────────────
|
|
# One matrixed job per Linux target. x86_64 builds on the standard
|
|
# ubuntu-latest host; arm64 builds NATIVELY on GitHub's hosted ARM runner
|
|
# (ubuntu-24.04-arm) rather than cross-compiling, because the native X11 /
|
|
# Wayland C deps (libx11-dev, libwayland-dev, …) would otherwise need their
|
|
# arm64 multiarch variants + a cross linker wired up by hand. A native ARM
|
|
# runner keeps the build identical to the x86_64 path. install.sh picks the
|
|
# matching tarball at install time based on the host's `uname -m`.
|
|
build-linux:
|
|
name: linux-${{ matrix.arch }}
|
|
runs-on: ${{ matrix.runner }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- arch: x86_64
|
|
target: x86_64-unknown-linux-gnu
|
|
runner: ubuntu-latest
|
|
- arch: arm64
|
|
target: aarch64-unknown-linux-gnu
|
|
runner: ubuntu-24.04-arm
|
|
# Build INSIDE a Debian 11 (bullseye) container instead of directly on
|
|
# the host. ubuntu-latest is now Ubuntu 24.04, whose glibc is 2.39 — a
|
|
# binary linked there imports GLIBC_2.39 symbols (e.g. posix_spawn's
|
|
# pidfd_spawnp/pidfd_getpid) and refuses to even run `--version` on older
|
|
# distros:
|
|
# cua-driver: /lib/.../libc.so.6: version 'GLIBC_2.39' not found
|
|
# That broke Debian 12 (glibc 2.36), Ubuntu 22.04 (2.35), and RHEL/Rocky
|
|
# 9 (2.34). Debian 11's glibc is 2.31, so linking against it lowers the
|
|
# floor to GLIBC_2.31 — covering Debian 11+, Ubuntu 20.04+, and RHEL/
|
|
# Rocky 8+. A container (rather than the retired ubuntu-20.04 runner
|
|
# label) keeps the floor deterministic regardless of GitHub's host image
|
|
# churn. The debian:11 image is multi-arch, so the arm64 runner pulls the
|
|
# aarch64 variant automatically and the glibc floor holds on both arches.
|
|
# The C deps below all exist in bullseye, including libwayland-dev for the
|
|
# native Wayland backend (#1910).
|
|
container:
|
|
image: debian:11
|
|
steps:
|
|
# actions/checkout needs git inside the container; the bullseye image
|
|
# is bare. Install git + the build toolchain before anything else.
|
|
- name: Install base tooling (container is bare)
|
|
run: |
|
|
apt-get update
|
|
# PipeWire ScreenCast capture remains Nix/modern-build only because
|
|
# bullseye's 0.3.19 headers are too old for libspa-sys 0.8. Portal
|
|
# RemoteDesktop/libei input is pure Rust plus libxkbcommon and ships
|
|
# in these portable release binaries.
|
|
apt-get install -y --no-install-recommends \
|
|
git ca-certificates curl build-essential pkg-config \
|
|
libx11-dev libxi-dev libxtst-dev libxext-dev libwayland-dev \
|
|
libxkbcommon-dev
|
|
- uses: actions/checkout@v4
|
|
- name: Determine version
|
|
id: version
|
|
shell: bash
|
|
run: |
|
|
if [[ "$GITHUB_REF" == refs/tags/cua-driver-rs-v* ]]; then
|
|
VERSION="${GITHUB_REF#refs/tags/cua-driver-rs-v}"
|
|
else
|
|
VERSION="${{ inputs.version }}"
|
|
fi
|
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
with:
|
|
targets: ${{ matrix.target }}
|
|
- uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
~/.cargo/registry
|
|
~/.cargo/git
|
|
libs/cua-driver/rust/target
|
|
key: rust-cua-driver-rs-linux-${{ matrix.arch }}-${{ hashFiles('libs/cua-driver/rust/Cargo.lock', 'libs/cua-driver/rust/**/Cargo.toml') }}
|
|
restore-keys: |
|
|
rust-cua-driver-rs-linux-${{ matrix.arch }}-
|
|
- name: Build (release)
|
|
working-directory: libs/cua-driver/rust
|
|
run: cargo build -p cua-driver --release --features portal-input --target ${{ matrix.target }}
|
|
- name: Package
|
|
working-directory: libs/cua-driver/rust
|
|
run: |
|
|
VERSION="${{ steps.version.outputs.version }}"
|
|
LABEL="linux-${{ matrix.arch }}"
|
|
TARGET="${{ matrix.target }}"
|
|
STAGE="cua-driver-rs-${VERSION}-${LABEL}"
|
|
mkdir -p "release/${STAGE}"
|
|
cp "target/${TARGET}/release/cua-driver" "release/${STAGE}/"
|
|
cp ../../LICENSE.md "release/${STAGE}/LICENSE" 2>/dev/null || true
|
|
(cd release && tar -czf "${STAGE}.tar.gz" "${STAGE}")
|
|
# Bare binary tarball — single file (cua-driver) at the archive root,
|
|
# mirroring the Swift cua-driver `*-binary.tar.gz` convention.
|
|
tar -czf "release/${STAGE}-binary.tar.gz" -C "release/${STAGE}" cua-driver
|
|
ls -lh "release/${STAGE}.tar.gz" "release/${STAGE}-binary.tar.gz"
|
|
- uses: actions/upload-artifact@v4
|
|
with:
|
|
name: cua-driver-rs-linux-${{ matrix.arch }}
|
|
path: libs/cua-driver/rust/release/cua-driver-rs-*-linux-${{ matrix.arch }}*.tar.gz
|
|
if-no-files-found: error
|
|
|
|
# ── Windows (x86_64 + arm64) ─────────────────────────────────────────────────
|
|
# One matrixed job per Windows target. The arm64 build cross-compiles from
|
|
# the x86_64 windows-latest runner — Rust + MSVC have first-class support
|
|
# for that cross, so no separate arm64 runner is needed. install.ps1 picks
|
|
# the matching zip at install time based on the host's OS architecture.
|
|
build-windows:
|
|
name: windows-${{ matrix.arch }}
|
|
runs-on: windows-latest
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- arch: x86_64
|
|
target: x86_64-pc-windows-msvc
|
|
- arch: arm64
|
|
target: aarch64-pc-windows-msvc
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Determine version
|
|
id: version
|
|
shell: bash
|
|
run: |
|
|
if [[ "$GITHUB_REF" == refs/tags/cua-driver-rs-v* ]]; then
|
|
VERSION="${GITHUB_REF#refs/tags/cua-driver-rs-v}"
|
|
else
|
|
VERSION="${{ inputs.version }}"
|
|
fi
|
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
with:
|
|
targets: ${{ matrix.target }}
|
|
- uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
~/.cargo/registry
|
|
~/.cargo/git
|
|
libs/cua-driver/rust/target
|
|
key: rust-cua-driver-rs-windows-${{ matrix.arch }}-${{ hashFiles('libs/cua-driver/rust/Cargo.lock', 'libs/cua-driver/rust/**/Cargo.toml') }}
|
|
restore-keys: |
|
|
rust-cua-driver-rs-windows-${{ matrix.arch }}-
|
|
- name: Build (release)
|
|
working-directory: libs/cua-driver/rust
|
|
# Build both the main CLI/MCP binary AND the uiAccess'd worker.
|
|
# `cua-driver-uia.exe` is the Windows-only sibling daemon that runs
|
|
# at UIAccess integrity so SendInput / UI Automation can cross UIPI
|
|
# into UWP apps — see crates/cua-driver-uia/ and #1602.
|
|
run: cargo build -p cua-driver -p cua-driver-uia --release --target ${{ matrix.target }}
|
|
- name: Package
|
|
working-directory: libs/cua-driver/rust
|
|
shell: pwsh
|
|
run: |
|
|
$version = "${{ steps.version.outputs.version }}"
|
|
$label = "windows-${{ matrix.arch }}"
|
|
$target = "${{ matrix.target }}"
|
|
$stage = "cua-driver-rs-${version}-${label}"
|
|
New-Item -ItemType Directory -Force -Path "release/$stage" | Out-Null
|
|
Copy-Item "target/$target/release/cua-driver.exe" "release/$stage/"
|
|
Copy-Item "target/$target/release/cua-driver-uia.exe" "release/$stage/"
|
|
if (Test-Path "../../LICENSE.md") { Copy-Item "../../LICENSE.md" "release/$stage/LICENSE" }
|
|
# Zip the directory itself (no trailing /*) so the archive contains
|
|
# the top-level "$stage" wrapper dir — install.ps1 walks into
|
|
# extracted/cua-driver-rs-<v>-<arch>/cua-driver.exe to find the
|
|
# binary. With "release/$stage/*" Compress-Archive strips the
|
|
# wrapper and dumps the files at the archive root, which breaks
|
|
# the installer's path lookup.
|
|
Compress-Archive -Path "release/$stage" -DestinationPath "release/$stage.zip" -Force
|
|
# Bare-binaries zip: both exes side-by-side (no wrapper dir).
|
|
# `cua-driver-uia.exe` is currently shipped UNSIGNED in this archive
|
|
# — until #1602 wires an EV cert into CD, the worker won't elevate
|
|
# to UIAccess integrity on a default-policy machine. The main CLI/MCP
|
|
# binary stays fully functional regardless; uia is opt-in dead-weight
|
|
# until signed.
|
|
Compress-Archive -Path "release/$stage/cua-driver.exe","release/$stage/cua-driver-uia.exe" -DestinationPath "release/$stage-binary.zip" -Force
|
|
Get-ChildItem "release/$stage*.zip"
|
|
- uses: actions/upload-artifact@v4
|
|
with:
|
|
name: cua-driver-rs-windows-${{ matrix.arch }}
|
|
path: libs/cua-driver/rust/release/cua-driver-rs-*-windows-${{ matrix.arch }}*.zip
|
|
if-no-files-found: error
|
|
|
|
# ── macOS universal (arm64 + x86_64 → lipo) ─────────────────────────────────
|
|
# Mirrors cd-swift-cua-driver.yml's approach: build both arches in one job
|
|
# on macos-26, then combine into a universal binary via `lipo -create`.
|
|
# The same universal binary is used in BOTH the arm64 and x86_64 named
|
|
# tarballs (so callers that download by arch still get the universal
|
|
# binary), plus a third `darwin-universal` tarball and a bare binary.
|
|
#
|
|
# Runner pinned to macos-26 because `screencapturekit` (added in PR #1720
|
|
# for the native ScreenCaptureKit recording backend) pulls in
|
|
# `apple-metal v0.8.7`, whose Swift bridge references macOS 26 Metal
|
|
# symbols (`MTLSamplerReductionMode`, `MTLSamplerDescriptor.reductionMode`,
|
|
# `.lodBias`). The macos-15 runner ships a macOS 15 SDK that doesn't
|
|
# expose those symbols, so the transitive Swift bridge fails to compile.
|
|
build-macos-universal:
|
|
name: darwin-universal
|
|
runs-on: macos-26
|
|
# Notarize on tag push (real release); on workflow_dispatch honor the
|
|
# `notarize` input so we can iterate on the build pipeline without
|
|
# touching the Apple notary service.
|
|
env:
|
|
DO_NOTARIZE: ${{ startsWith(github.ref, 'refs/tags/cua-driver-rs-v') || inputs.notarize == true }}
|
|
KEYCHAIN_PASSWORD: temporary-cd-keychain-pwd
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Determine version
|
|
id: version
|
|
shell: bash
|
|
run: |
|
|
if [[ "$GITHUB_REF" == refs/tags/cua-driver-rs-v* ]]; then
|
|
VERSION="${GITHUB_REF#refs/tags/cua-driver-rs-v}"
|
|
else
|
|
VERSION="${{ inputs.version }}"
|
|
fi
|
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
- name: Import code-signing certificate
|
|
if: env.DO_NOTARIZE == 'true'
|
|
env:
|
|
APPLICATION_CERT_BASE64: ${{ secrets.APPLICATION_CERT_BASE64 }}
|
|
CERT_PASSWORD: ${{ secrets.CERT_PASSWORD }}
|
|
run: |
|
|
# Create a temporary keychain just for this job. The keychain is torn
|
|
# down with the runner when the job exits.
|
|
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
|
security default-keychain -s build.keychain
|
|
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
|
security set-keychain-settings -t 3600 -l build.keychain
|
|
|
|
echo "$APPLICATION_CERT_BASE64" | base64 --decode > application.p12
|
|
security import application.p12 -k build.keychain -P "$CERT_PASSWORD" \
|
|
-T /usr/bin/codesign > /dev/null 2>&1
|
|
security set-key-partition-list -S apple-tool:,apple:,codesign: \
|
|
-s -k "$KEYCHAIN_PASSWORD" build.keychain > /dev/null 2>&1
|
|
rm application.p12
|
|
|
|
CERT_COUNT=$(security find-identity -v -p codesigning build.keychain | grep -c "Developer ID Application" || echo 0)
|
|
if [ "$CERT_COUNT" -eq 0 ]; then
|
|
echo "Error: no Developer ID Application certificate imported" >&2
|
|
security find-identity -v -p codesigning build.keychain
|
|
exit 1
|
|
fi
|
|
echo "Imported $CERT_COUNT Developer ID Application certificate(s)"
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
with:
|
|
targets: aarch64-apple-darwin, x86_64-apple-darwin
|
|
- uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
~/.cargo/registry
|
|
~/.cargo/git
|
|
libs/cua-driver/rust/target
|
|
key: rust-cua-driver-rs-darwin-universal-${{ hashFiles('libs/cua-driver/rust/Cargo.lock', 'libs/cua-driver/rust/**/Cargo.toml') }}
|
|
restore-keys: |
|
|
rust-cua-driver-rs-darwin-universal-
|
|
- name: Build arm64
|
|
working-directory: libs/cua-driver/rust
|
|
run: cargo build -p cua-driver --release --target aarch64-apple-darwin
|
|
- name: Build x86_64
|
|
working-directory: libs/cua-driver/rust
|
|
run: cargo build -p cua-driver --release --target x86_64-apple-darwin
|
|
- name: Create universal binary (lipo)
|
|
working-directory: libs/cua-driver/rust
|
|
run: |
|
|
ARM64="target/aarch64-apple-darwin/release/cua-driver"
|
|
X86="target/x86_64-apple-darwin/release/cua-driver"
|
|
mkdir -p release/universal
|
|
lipo -create "$ARM64" "$X86" -output release/universal/cua-driver
|
|
lipo -info release/universal/cua-driver
|
|
- name: Codesign universal binary (hardened runtime)
|
|
if: env.DO_NOTARIZE == 'true'
|
|
working-directory: libs/cua-driver/rust
|
|
env:
|
|
DEVELOPER_NAME: ${{ secrets.DEVELOPER_NAME }}
|
|
TEAM_ID: ${{ secrets.TEAM_ID }}
|
|
run: |
|
|
IDENTITY="Developer ID Application: ${DEVELOPER_NAME} (${TEAM_ID})"
|
|
codesign --force --timestamp --options runtime \
|
|
--entitlements scripts/CuaDriver.entitlements \
|
|
--sign "$IDENTITY" release/universal/cua-driver
|
|
codesign --verify --strict --verbose=2 release/universal/cua-driver
|
|
- name: Assemble CuaDriver.app bundle
|
|
working-directory: libs/cua-driver/rust
|
|
run: |
|
|
# Copy the bundle skeleton (Info.plist) from
|
|
# scripts/CuaDriverBundle/ and drop the universal binary into
|
|
# Contents/MacOS/cua-driver. The skeleton lives under a non-
|
|
# `.app` directory so macOS LaunchServices on developer
|
|
# machines doesn't index it as a second installed app with
|
|
# the `com.trycua.driver` bundle id (which used to surface as
|
|
# a "ghost CuaDriver" entry in System Settings → Privacy &
|
|
# Security). The assembled bundle goes into every directory
|
|
# tarball so install.sh can `ditto` it to
|
|
# /Applications/CuaDriver.app for the TCC auto-relaunch path.
|
|
#
|
|
# The bundle ships unsigned (notarization happens in the next step).
|
|
# TCC keys grants on the bundle identity (com.trycua.driver).
|
|
VERSION="${{ steps.version.outputs.version }}"
|
|
mkdir -p release/CuaDriver.app
|
|
cp -R scripts/CuaDriverBundle/Contents release/CuaDriver.app/Contents
|
|
cp release/universal/cua-driver \
|
|
release/CuaDriver.app/Contents/MacOS/cua-driver
|
|
chmod +x release/CuaDriver.app/Contents/MacOS/cua-driver
|
|
# Stamp the release version into Info.plist so the bundle
|
|
# version tracks the tag instead of whatever was last
|
|
# checked in. Without this the in-tree Info.plist's
|
|
# CFBundleShortVersionString drifts from the release tag on
|
|
# every cut. CodeRabbit #4.
|
|
plutil -replace CFBundleShortVersionString -string "$VERSION" \
|
|
release/CuaDriver.app/Contents/Info.plist
|
|
plutil -replace CFBundleVersion -string "$VERSION" \
|
|
release/CuaDriver.app/Contents/Info.plist
|
|
# Remove the .gitkeep we use in source control — it's not
|
|
# part of the runtime bundle.
|
|
rm -f release/CuaDriver.app/Contents/MacOS/.gitkeep
|
|
ls -la release/CuaDriver.app/Contents/MacOS
|
|
plutil -p release/CuaDriver.app/Contents/Info.plist | grep -E 'CFBundle(Short)?Version'
|
|
- name: Codesign + notarize + staple CuaDriver.app
|
|
if: env.DO_NOTARIZE == 'true'
|
|
working-directory: libs/cua-driver/rust
|
|
env:
|
|
DEVELOPER_NAME: ${{ secrets.DEVELOPER_NAME }}
|
|
TEAM_ID: ${{ secrets.TEAM_ID }}
|
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
APP_SPECIFIC_PASSWORD: ${{ secrets.APP_SPECIFIC_PASSWORD }}
|
|
run: |
|
|
IDENTITY="Developer ID Application: ${DEVELOPER_NAME} (${TEAM_ID})"
|
|
|
|
# Sign the .app — `--deep` covers the embedded binary too, but
|
|
# since we already pre-signed the binary explicitly above this
|
|
# is essentially a no-op on the binary and a fresh signature
|
|
# on the bundle wrapper.
|
|
codesign --force --deep --timestamp --options runtime \
|
|
--entitlements scripts/CuaDriver.entitlements \
|
|
--sign "$IDENTITY" release/CuaDriver.app
|
|
codesign --verify --strict --verbose=2 release/CuaDriver.app
|
|
|
|
# notarytool wants a zip (or .dmg / .pkg). Build one next to
|
|
# the .app, submit, wait, then staple the bundle in place.
|
|
/usr/bin/ditto -c -k --keepParent release/CuaDriver.app \
|
|
release/CuaDriver.app.zip
|
|
|
|
xcrun notarytool submit release/CuaDriver.app.zip \
|
|
--apple-id "$APPLE_ID" \
|
|
--team-id "$TEAM_ID" \
|
|
--password "$APP_SPECIFIC_PASSWORD" \
|
|
--wait --timeout 20m
|
|
|
|
xcrun stapler staple release/CuaDriver.app
|
|
spctl -a -vv -t exec release/CuaDriver.app
|
|
|
|
# The zip we used to submit is throwaway; the packaging step
|
|
# below builds the final tarballs from the stapled .app.
|
|
rm -f release/CuaDriver.app.zip
|
|
- name: Package
|
|
working-directory: libs/cua-driver/rust
|
|
run: |
|
|
VERSION="${{ steps.version.outputs.version }}"
|
|
for LABEL in darwin-arm64 darwin-x86_64 darwin-universal; do
|
|
STAGE="cua-driver-rs-${VERSION}-${LABEL}"
|
|
mkdir -p "release/${STAGE}"
|
|
# Same universal binary in all three named tarballs — callers
|
|
# that download by arch still get the universal slice
|
|
# (matches the Swift `cd-swift-cua-driver.yml` convention).
|
|
cp release/universal/cua-driver "release/${STAGE}/"
|
|
# Ship the .app bundle in every macOS tarball so install.sh
|
|
# can drop it into /Applications/CuaDriver.app for TCC
|
|
# attribution (issue #1525). Tarball callers that want only
|
|
# the bare binary can grab the *-binary.tar.gz below.
|
|
cp -R release/CuaDriver.app "release/${STAGE}/CuaDriver.app"
|
|
cp ../../LICENSE.md "release/${STAGE}/LICENSE" 2>/dev/null || true
|
|
(cd release && tar -czf "${STAGE}.tar.gz" "${STAGE}")
|
|
done
|
|
# Bare universal binary — single-file tarball matching Swift's
|
|
# `cua-driver-${VERSION}-darwin-universal-binary.tar.gz`. NO
|
|
# bundle here: callers that fetch the bare tarball deliberately
|
|
# skipped the .app workflow.
|
|
tar -czf "release/cua-driver-rs-${VERSION}-darwin-universal-binary.tar.gz" \
|
|
-C release/universal cua-driver
|
|
ls -lh release/*.tar.gz
|
|
- uses: actions/upload-artifact@v4
|
|
with:
|
|
name: cua-driver-rs-darwin
|
|
path: libs/cua-driver/rust/release/cua-driver-rs-*-darwin-*.tar.gz
|
|
if-no-files-found: error
|
|
|
|
# ── Release ──────────────────────────────────────────────────────────────────
|
|
release:
|
|
needs: [build-linux, build-windows, build-macos-universal]
|
|
if: startsWith(github.ref, 'refs/tags/cua-driver-rs-v')
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Determine version
|
|
id: version
|
|
run: |
|
|
VERSION="${GITHUB_REF#refs/tags/cua-driver-rs-v}"
|
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Download all artifacts
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
path: artifacts
|
|
|
|
# Bake the new version into the working-tree install scripts BEFORE
|
|
# staging them for the release upload, so the per-tag release assets
|
|
# carry version N's baked default — not version N-1's. The same sed
|
|
# patterns run again later under `Bake version into install scripts
|
|
# (commit + push)` to land the change on main; doing the in-tree
|
|
# rewrite here is idempotent (sed on an already-baked file is a
|
|
# no-op) and ensures the GitHub Release page and main-branch commit
|
|
# stay in lockstep.
|
|
- name: Bake version into install scripts (in-tree)
|
|
if: startsWith(github.ref, 'refs/tags/cua-driver-rs-v')
|
|
env:
|
|
VERSION: ${{ steps.version.outputs.version }}
|
|
run: |
|
|
# GNU sed (ubuntu-latest). The Swift CD workflow runs on
|
|
# macos-15 and uses the BSD form (`sed -i ''`).
|
|
# The Rust install logic now lives at
|
|
# libs/cua-driver/scripts/_install-rust.sh (a private helper
|
|
# invoked by libs/cua-driver/scripts/install.sh by default).
|
|
sed -i \
|
|
"s/^CUA_DRIVER_RS_BAKED_VERSION=.*/CUA_DRIVER_RS_BAKED_VERSION=\"${VERSION}\"/" \
|
|
libs/cua-driver/scripts/_install-rust.sh
|
|
|
|
# install.ps1 lives at the canonical Swift install dir;
|
|
# PowerShell line starts with `$Script:` — escape the `$` for
|
|
# sed's BRE so it matches the literal dollar sign.
|
|
sed -i \
|
|
"s/^\\\$Script:CuaDriverRsBakedVersion = .*/\\\$Script:CuaDriverRsBakedVersion = \"${VERSION}\"/" \
|
|
libs/cua-driver/scripts/install.ps1
|
|
|
|
echo "Baked version ${VERSION} into working-tree install scripts:"
|
|
grep -E '^CUA_DRIVER_RS_BAKED_VERSION=' libs/cua-driver/scripts/_install-rust.sh
|
|
grep -E '^\$Script:CuaDriverRsBakedVersion = ' libs/cua-driver/scripts/install.ps1
|
|
|
|
- name: Stage release files
|
|
run: |
|
|
mkdir -p release-upload
|
|
find artifacts -type f \( -name "*.tar.gz" -o -name "*.zip" \) \
|
|
-exec cp {} release-upload/ \;
|
|
# Ship the installer scripts as per-tag release assets too. The
|
|
# canonical one-liners both fetch from
|
|
# raw.githubusercontent.com/trycua/cua/main/... so they always
|
|
# serve the current installer regardless of release flags
|
|
# (prerelease excludes us from the /releases/latest/ endpoint).
|
|
# The tag-pinned asset URLs below are a convenience for users who
|
|
# want to audit the exact installer that shipped with a release.
|
|
#
|
|
# The freshly-baked scripts (rewritten in the prior step) are
|
|
# what gets copied here, so the per-tag release assets carry the
|
|
# current version's baked default — not the previous version's.
|
|
# Both user-facing installers live under libs/cua-driver/scripts/:
|
|
# install.sh is the canonical macOS/Linux installer and delegates
|
|
# to the Rust _install-rust.sh helper by default. install.ps1 is
|
|
# the canonical Windows installer. The Rust install logic itself
|
|
# lives at _install-rust.sh — published as a release asset
|
|
# alongside the installers so curl|bash flows can fetch it.
|
|
cp libs/cua-driver/scripts/install.sh release-upload/install.sh
|
|
cp libs/cua-driver/scripts/install.ps1 release-upload/install.ps1
|
|
cp libs/cua-driver/scripts/_install-rust.sh release-upload/_install-rust.sh
|
|
|
|
# Uninstaller scripts — single canonical file per shell (no
|
|
# `_uninstall-rust.sh` helper). uninstall.sh handles Rust (the only
|
|
# supported backend). uninstall.ps1 is the Windows uninstaller
|
|
# and self-elevates via UAC when an -AutoStart install
|
|
# (RunLevel=Highest task) needs to be torn down.
|
|
cp libs/cua-driver/scripts/uninstall.sh release-upload/uninstall.sh
|
|
cp libs/cua-driver/scripts/uninstall.ps1 release-upload/uninstall.ps1
|
|
|
|
# Skill pack — single platform-agnostic tarball fetched by
|
|
# `cua-driver skills install`. The .md files are identical across
|
|
# OS so we publish one asset per release tag. Naming matches the
|
|
# versioned binary tarballs.
|
|
VERSION="${{ steps.version.outputs.version }}"
|
|
# Asset name keeps the `cua-driver-rs-v*` prefix for backward
|
|
# compat with the URL skills.rs constructs.
|
|
#
|
|
# Staging layout = one wrapper directory, flat .md files under
|
|
# it. extract_tar_gz strips that single wrapper and lands files
|
|
# directly in <HomeDir>/skills/cua-driver/. v0.2.19 and earlier
|
|
# had a second `cua-driver/` (or `cua-driver-rs/`) dir inside
|
|
# the wrapper — the extractor sees that and strips it too for
|
|
# backward compat with already-published releases.
|
|
SKILLS_STAGE="cua-driver-rs-v${VERSION}-skills"
|
|
if [ -d libs/cua-driver/rust/Skills/cua-driver ]; then
|
|
mkdir -p "${SKILLS_STAGE}"
|
|
cp -R libs/cua-driver/rust/Skills/cua-driver/. "${SKILLS_STAGE}/"
|
|
tar -czf "release-upload/${SKILLS_STAGE}.tar.gz" "${SKILLS_STAGE}"
|
|
echo "Packaged skill pack: release-upload/${SKILLS_STAGE}.tar.gz"
|
|
else
|
|
echo "Note: libs/cua-driver/rust/Skills/cua-driver not present; skipping skill-pack asset."
|
|
fi
|
|
|
|
echo "Release files:"
|
|
ls -lh release-upload/
|
|
|
|
- name: Generate SHA256 checksums
|
|
id: checksums
|
|
run: |
|
|
cd release-upload
|
|
{
|
|
echo "## SHA256 Checksums"
|
|
echo '```'
|
|
shasum -a 256 cua-driver-rs-*.{tar.gz,zip} 2>/dev/null | sort
|
|
echo '```'
|
|
} > checksums.txt
|
|
checksums=$(cat checksums.txt)
|
|
echo "checksums<<EOF" >> "$GITHUB_OUTPUT"
|
|
echo "$checksums" >> "$GITHUB_OUTPUT"
|
|
echo "EOF" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Generate path-filtered release notes
|
|
id: notes
|
|
run: |
|
|
PREV_TAG=$(git tag -l "cua-driver-rs-v*" --sort=-v:refname \
|
|
| grep -v "^${{ github.ref_name }}$" \
|
|
| head -n 1 || echo "")
|
|
if [ -n "$PREV_TAG" ]; then
|
|
NOTES=$(git log ${PREV_TAG}..HEAD --pretty=format:"* %s (%h) by @%an" -- "libs/cua-driver/rust" | head -50)
|
|
else
|
|
NOTES=$(git log --pretty=format:"* %s (%h) by @%an" -- "libs/cua-driver/rust" | head -50)
|
|
fi
|
|
[ -z "$NOTES" ] && NOTES="* No path-specific changes found"
|
|
{
|
|
echo "RELEASE_NOTES<<EOF"
|
|
echo "## What's Changed"
|
|
echo ""
|
|
echo "$NOTES"
|
|
echo "EOF"
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Create GitHub Release
|
|
uses: softprops/action-gh-release@v2
|
|
with:
|
|
files: release-upload/*
|
|
prerelease: true
|
|
body: |
|
|
> **cua-driver-rs**
|
|
>
|
|
> `cua-driver-rs` is the Rust implementation of
|
|
> [`cua-driver`](https://github.com/trycua/cua/tree/main/libs/cua-driver),
|
|
> shipping the same user-facing `cua-driver` binary for macOS and
|
|
> Windows, plus Linux pre-release artifacts for early testing.
|
|
>
|
|
> The Rust port and the Swift driver publish to the same GitHub repo under
|
|
> **distinct tag prefixes** (`cua-driver-rs-v*` vs `cua-driver-v*`), so their
|
|
> release artifacts do not collide.
|
|
|
|
${{ steps.notes.outputs.RELEASE_NOTES }}
|
|
|
|
${{ steps.checksums.outputs.checksums }}
|
|
|
|
### Install (macOS / Linux pre-release)
|
|
|
|
```bash
|
|
/bin/bash -c "$(curl -fsSL https://cua.ai/driver/install.sh)"
|
|
```
|
|
|
|
The shell installer covers macOS and the Linux pre-release backend
|
|
and installs the Rust implementation by default. Linux artifacts are
|
|
published for early testing and are not yet an official supported
|
|
release tier.
|
|
|
|
### Install (Windows)
|
|
|
|
```powershell
|
|
irm https://cua.ai/driver/install.ps1 | iex
|
|
```
|
|
|
|
The installer auto-detects host architecture (x86_64 / arm64) and uses
|
|
directory junctions for the install layout, so it runs without admin
|
|
and without Developer Mode.
|
|
|
|
### Artifacts
|
|
|
|
**macOS (universal — arm64 + x86_64 in one binary, like the Swift cua-driver)**
|
|
- `cua-driver-rs-${{ steps.version.outputs.version }}-darwin-universal.tar.gz` — directory tarball with LICENSE + `CuaDriver.app` bundle (install.sh expects this layout)
|
|
- `cua-driver-rs-${{ steps.version.outputs.version }}-darwin-arm64.tar.gz` — same payload, named for arm64 callers
|
|
- `cua-driver-rs-${{ steps.version.outputs.version }}-darwin-x86_64.tar.gz` — same payload, named for x86_64 callers
|
|
- `cua-driver-rs-${{ steps.version.outputs.version }}-darwin-universal-binary.tar.gz` — bare universal binary (single file at archive root; **no** .app — bypasses the TCC auto-relaunch path)
|
|
|
|
**Linux pre-release**
|
|
- `cua-driver-rs-${{ steps.version.outputs.version }}-linux-x86_64.tar.gz` — directory tarball
|
|
- `cua-driver-rs-${{ steps.version.outputs.version }}-linux-x86_64-binary.tar.gz` — bare binary
|
|
- `cua-driver-rs-${{ steps.version.outputs.version }}-linux-arm64.tar.gz` — directory tarball (Linux on ARM / aarch64)
|
|
- `cua-driver-rs-${{ steps.version.outputs.version }}-linux-arm64-binary.tar.gz` — bare binary (Linux on ARM / aarch64)
|
|
|
|
**Windows**
|
|
- `cua-driver-rs-${{ steps.version.outputs.version }}-windows-x86_64.zip` — directory zip
|
|
- `cua-driver-rs-${{ steps.version.outputs.version }}-windows-x86_64-binary.zip` — bare `cua-driver.exe`
|
|
- `cua-driver-rs-${{ steps.version.outputs.version }}-windows-arm64.zip` — directory zip (Windows on ARM)
|
|
- `cua-driver-rs-${{ steps.version.outputs.version }}-windows-arm64-binary.zip` — bare `cua-driver.exe` (Windows on ARM)
|
|
|
|
**Installer scripts**
|
|
- `install.sh` — macOS / Linux pre-release one-liner installer
|
|
- `install.ps1` — Windows one-liner installer
|
|
- `uninstall.sh` — macOS / Linux pre-release one-liner uninstaller
|
|
- `uninstall.ps1` — Windows one-liner uninstaller
|
|
generate_release_notes: false
|
|
make_latest: false
|
|
|
|
# The bump-version workflow uses this same GitHub App so its pushes
|
|
# bypass the "Changes must be made through a pull request" ruleset
|
|
# on main. The default GITHUB_TOKEN (github-actions[bot]) is NOT on
|
|
# the bypass list and gets rejected here. Mirror the Swift CD
|
|
# workflow's auth setup so the bake-version push lands.
|
|
- name: Generate GitHub App token (for bake-version push)
|
|
id: app-token
|
|
if: startsWith(github.ref, 'refs/tags/cua-driver-rs-v')
|
|
uses: actions/create-github-app-token@v1
|
|
with:
|
|
app-id: ${{ secrets.RELEASE_APP_ID }}
|
|
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
|
|
owner: ${{ github.repository_owner }}
|
|
repositories: ${{ github.event.repository.name }}
|
|
|
|
- name: Bake version into install scripts (commit + push)
|
|
if: startsWith(github.ref, 'refs/tags/cua-driver-rs-v')
|
|
env:
|
|
VERSION: ${{ steps.version.outputs.version }}
|
|
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
run: |
|
|
# Re-authenticate the origin remote with the app token so the
|
|
# push uses the bypass-enabled identity, not the default
|
|
# github-actions[bot] credentials baked in by actions/checkout.
|
|
#
|
|
# actions/checkout sets `http.https://github.com/.extraheader`
|
|
# with the default GITHUB_TOKEN; that header otherwise overrides
|
|
# the URL-embedded App token on the push (two Authorization
|
|
# headers sent → server takes the extraheader → push rejected
|
|
# by the main-branch ruleset). Drop it so only the App token's
|
|
# auth survives.
|
|
git config --unset-all "http.https://github.com/.extraheader" || true
|
|
git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git"
|
|
git fetch origin main
|
|
git checkout -B bake-version origin/main
|
|
|
|
# Replay the same sed the earlier in-tree step ran against the
|
|
# working tree — but here against origin/main, so the commit we
|
|
# push back to main has the new baked version regardless of
|
|
# whether the in-tree step ran (it's idempotent and the on-disk
|
|
# working-tree edits got discarded by the checkout above).
|
|
#
|
|
# The release job runs on ubuntu-latest, so this uses GNU sed
|
|
# syntax (`sed -i` with no empty-string arg) — the Swift CD
|
|
# workflow's bake step runs on macos-15 and uses the BSD form
|
|
# (`sed -i ''`).
|
|
# Rust install logic lives at libs/cua-driver/scripts/_install-rust.sh
|
|
# (private helper invoked by the canonical libs/cua-driver/scripts/install.sh
|
|
# by default).
|
|
sed -i \
|
|
"s/^CUA_DRIVER_RS_BAKED_VERSION=.*/CUA_DRIVER_RS_BAKED_VERSION=\"${VERSION}\"/" \
|
|
libs/cua-driver/scripts/_install-rust.sh
|
|
|
|
# install.ps1 lives at libs/cua-driver/scripts/install.ps1
|
|
# (canonical Windows entry point). The PowerShell line starts
|
|
# with `$Script:` — escape the `$` for sed's BRE so it matches
|
|
# the literal dollar sign.
|
|
sed -i \
|
|
"s/^\\\$Script:CuaDriverRsBakedVersion = .*/\\\$Script:CuaDriverRsBakedVersion = \"${VERSION}\"/" \
|
|
libs/cua-driver/scripts/install.ps1
|
|
|
|
git config user.name "trycua-release[bot]"
|
|
git config user.email "trycua-release[bot]@users.noreply.github.com"
|
|
git add libs/cua-driver/scripts/_install-rust.sh libs/cua-driver/scripts/install.ps1
|
|
git commit -m "chore(cua-driver-rs): bake version ${VERSION} into install scripts [skip ci]"
|
|
git push origin bake-version:main
|