Skip to content
Merged
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
22 changes: 14 additions & 8 deletions .github/workflows/cli-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@ on:
platform:
description: Platforms to build without publishing
type: choice
options: [all, linux-arm64]
options: [all, linux]
default: all
validate_sdk:
description: Validate the ARM64 package with a private Apple SDK runner
description: Validate the Linux package with a private Apple SDK runner
type: boolean
default: false
sdk_package_arch:
description: Host architecture of the private SDK runner
type: choice
options: [arm64, x64]
default: arm64
sdk_runner_label:
description: Label of the provisioned private SDK runner
type: string
Expand All @@ -28,22 +33,23 @@ permissions:

name: Release CLI
jobs:
linux-arm64:
name: Linux ARM64 release package
linux:
name: Linux release packages
uses: ./.github/workflows/linux-arm64-package.yaml
sdk-validation:
name: Private SDK install validation
if: github.event_name == 'workflow_dispatch' && inputs.validate_sdk
needs: linux-arm64
needs: linux
permissions:
contents: read
actions: read
uses: ./.github/workflows/linux-apple-build.yaml
with:
artifact_run_id: ${{ github.run_id }}
runner_label: ${{ inputs.sdk_runner_label }}
package_arch: ${{ inputs.sdk_package_arch }}
release:
if: inputs.platform != 'linux-arm64'
if: inputs.platform != 'linux' && inputs.platform != 'linux-arm64'
outputs:
version: ${{ steps.version.outputs.version }}
name: Build, sign, notarize CLI (macOS)
Expand Down Expand Up @@ -176,7 +182,7 @@ jobs:

publish:
name: Publish verified macOS and Linux packages
needs: [release, linux-arm64]
needs: [release, linux]
if: startsWith(github.ref, 'refs/tags/cli-v')
runs-on: ubuntu-24.04
env:
Expand Down Expand Up @@ -215,7 +221,7 @@ jobs:
echo
echo " mise use -g 'github:sweetpad-dev/sweetpad[version_prefix=cli-v]'"
echo
echo 'Linux ARM64 (experimental, Ubuntu 24.04): download the linux-arm64 tarball and its .sha256 file, verify it, then run the included install.sh.'
echo 'Linux ARM64 and x64 (experimental, Ubuntu 24.04): download the linux-arm64 or linux-x64 tarball for your host and its .sha256 file, verify it, then run the included install.sh.'
echo 'The archive includes public toolchain setup sources. Apple SDKs and signing material are supplied separately.'
echo
echo '## Changes'
Expand Down
52 changes: 40 additions & 12 deletions .github/workflows/linux-apple-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ on:
workflow_dispatch:
inputs:
artifact_run_id:
description: Optional run with a verified cli-tarball-linux-arm64 artifact
description: Optional run with verified Linux package artifacts
type: string
default: ''
package_arch:
description: Package host architecture (arm64 or x64)
type: string
default: arm64
runner_label:
description: Label of the provisioned private SDK runner
type: string
Expand All @@ -15,6 +19,9 @@ on:
artifact_run_id:
type: string
default: ''
package_arch:
type: string
default: arm64
runner_label:
type: string
default: sweetpad-linux
Expand All @@ -25,7 +32,7 @@ jobs:
hybrid-builds:
# The Apple SDK stays on a provisioned Apple-hardware Linux runner.
# Never upload or distribute it as a workflow artifact.
runs-on: [self-hosted, Linux, ARM64, '${{ inputs.runner_label }}']
runs-on: [self-hosted, Linux, "${{ inputs.package_arch == 'x64' && 'X64' || 'ARM64' }}", '${{ inputs.runner_label }}']
timeout-minutes: 120
env:
CARGO_INCREMENTAL: "0"
Expand All @@ -34,11 +41,19 @@ jobs:
- uses: actions/checkout@v7
with:
persist-credentials: false
- name: Download verified ARM64 package
- name: Read pinned compiler version
run: |
python3 - <<'PY' >> "$GITHUB_ENV"
import json
from pathlib import Path
pins = json.loads(Path('sweetpad-lib/ci/linux-toolchain.json').read_text())
print('SWEETPAD_SWIFT_VERSION=' + pins['swift'])
PY
- name: Download verified Linux package
if: inputs.artifact_run_id != ''
uses: actions/download-artifact@v8
with:
name: cli-tarball-linux-arm64
name: cli-tarball-linux-${{ inputs.package_arch }}
run-id: ${{ inputs.artifact_run_id }}
github-token: ${{ github.token }}
path: ${{ runner.temp }}/release-package
Expand All @@ -47,31 +62,40 @@ jobs:
env:
PACKAGE_ROOT: ${{ runner.temp }}/release-package
run: |
export PATH="$HOME/bin:$HOME/swift/swift-6.4.2-RELEASE/usr/bin:$PATH"
export PATH="$HOME/bin:$HOME/swift/swift-${SWEETPAD_SWIFT_VERSION}-RELEASE/usr/bin:$PATH"
MACROS="$HOME/bin/sweetpad-macros.bin"
if [ ! -e "$MACROS" ]; then MACROS="$HOME/bin/sweetpad-macros"; fi
python3 sweetpad-cli/ci/check-linux-sdk-install.py \
--archive "$(find "$PACKAGE_ROOT" -maxdepth 1 -name '*.tar.gz')" \
--out "$RUNNER_TEMP/sdk-package-install" \
--swift "$HOME/swift/swift-6.4.2-RELEASE" \
--swbuild-products "$HOME/src/swift-build/.build/out/Products/Release-linux-aarch64" \
--swift "$HOME/swift/swift-${SWEETPAD_SWIFT_VERSION}-RELEASE" \
--swbuild-products "$(dirname "$(readlink -f "$HOME/bin/swbuild")")" \
--macros "$(readlink -f "$MACROS")" \
--sdk "$HOME/.swiftpm/swift-sdks/darwin.artifactbundle"
- name: Build CLI
run: |
export PATH="$HOME/.cargo/bin:$HOME/bin:$HOME/swift/swift-6.4.2-RELEASE/usr/bin:$PATH"
export PATH="$HOME/.cargo/bin:$HOME/bin:$HOME/swift/swift-${SWEETPAD_SWIFT_VERSION}-RELEASE/usr/bin:$PATH"
CARGO_TARGET_DIR="$HOME/sweetpad-target" cargo build --locked -p sweetpad-cli --bin sweetpad
- name: Install frozen framework tools
if: inputs.package_arch == 'arm64'
run: bash sweetpad-lib/ci/linux-framework-tools.sh
- name: Check script, clean, and Linux app routing regressions
run: |
export PATH="$HOME/bin:$HOME/swift/swift-${SWEETPAD_SWIFT_VERSION}-RELEASE/usr/bin:$PATH"
python3 sweetpad-cli/ci/check-linux-gaps.py \
--binary "$HOME/sweetpad-target/debug/sweetpad" \
--out "$RUNNER_TEMP/linux-gaps"
- name: Fresh Linux dependency and Release build checks
if: inputs.package_arch == 'arm64'
run: python3 -B sweetpad-lib/ci/linux-clean-checks.py --out "$RUNNER_TEMP/linux-clean"
- name: Generate manifests for Linux XCTest and Swift Testing products
run: |
export PATH="$HOME/bin:$HOME/swift/swift-6.4.2-RELEASE/usr/bin:$PATH"
export PATH="$HOME/bin:$HOME/swift/swift-${SWEETPAD_SWIFT_VERSION}-RELEASE/usr/bin:$PATH"
mkdir -p "$RUNNER_TEMP/linux-clean/reports"
python3 -B sweetpad-lib/ci/linux-exec-audit.py --out "$RUNNER_TEMP/linux-clean/reports/test-build-audit" -- \
"$HOME/sweetpad-target/debug/sweetpad" test build \
--project "$PWD/sweetpad-lib/ci/fixture-test-manifest/ManifestProbe.xcodeproj" \
--scheme ManifestProbe --configuration Debug --destination 'generic/platform=iOS Simulator' \
--scheme ManifestProbe --configuration Debug --destination 'generic/platform=iOS Simulator,arch=arm64' \
--json --non-interactive -- -derivedDataPath "$RUNNER_TEMP/manifest-products" \
ARCHS=arm64 CODE_SIGNING_ALLOWED=NO > "$RUNNER_TEMP/linux-clean/reports/test-build.json"
python3 - "$RUNNER_TEMP/linux-clean/reports/test-build.json" <<'PYCODE'
Expand All @@ -85,10 +109,10 @@ jobs:
PYCODE
- name: Generate mixed UI and logic test-plan manifests
run: |
export PATH="$HOME/bin:$HOME/swift/swift-6.4.2-RELEASE/usr/bin:$PATH"
export PATH="$HOME/bin:$HOME/swift/swift-${SWEETPAD_SWIFT_VERSION}-RELEASE/usr/bin:$PATH"
"$HOME/sweetpad-target/debug/sweetpad" test build \
--project "$PWD/sweetpad-lib/ci/fixture-test-plans/TestPlanProbe.xcodeproj" \
--scheme TestPlanProbe --destination 'generic/platform=iOS Simulator' --json --non-interactive \
--scheme TestPlanProbe --destination 'generic/platform=iOS Simulator,arch=arm64' --json --non-interactive \
-- -derivedDataPath "$RUNNER_TEMP/plan-products" ARCHS=arm64 CODE_SIGNING_ALLOWED=NO \
> "$RUNNER_TEMP/linux-clean/reports/test-plan-build.json"
python3 - "$RUNNER_TEMP/linux-clean/reports/test-plan-build.json" <<'PYCODE'
Expand All @@ -111,6 +135,10 @@ jobs:
with:
name: linux-execution-and-build-reports
path: |
${{ runner.temp }}/linux-gaps/summary.json
${{ runner.temp }}/linux-gaps/*.stdout
${{ runner.temp }}/linux-gaps/*.stderr
${{ runner.temp }}/linux-gaps/scheme-actions.ndjson
${{ runner.temp }}/linux-clean/summary.json
${{ runner.temp }}/linux-clean/reports/
${{ runner.temp }}/sdk-package-install/summary.json
Expand Down
48 changes: 40 additions & 8 deletions .github/workflows/linux-arm64-package.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
name: Linux ARM64 package
name: Linux packages
on:
push:
branches: [main]
paths:
- "sweetpad-cli/**"
- "sweetpad-core/**"
- "sweetpad-lib/**"
- "Cargo.toml"
- "Cargo.lock"
- "rust-toolchain.toml"
- ".github/workflows/linux-arm64-package.yaml"
- ".github/workflows/cli-release.yaml"
pull_request:
paths:
- "sweetpad-cli/**"
Expand All @@ -16,8 +27,18 @@ permissions:
contents: read
jobs:
package:
name: ARM64 archive and clean Ubuntu install
runs-on: ubuntu-24.04-arm
name: ${{ matrix.arch }} archive and clean Ubuntu install
strategy:
fail-fast: false
matrix:
include:
- arch: arm64
runner: ubuntu-24.04-arm
docker_arch: arm64
- arch: x64
runner: ubuntu-24.04
docker_arch: amd64
runs-on: ${{ matrix.runner }}
timeout-minutes: 45
env:
CARGO_INCREMENTAL: "0"
Expand All @@ -31,18 +52,29 @@ jobs:
run: sudo apt-get update -qq && sudo apt-get install -y libssl-dev pkg-config
- name: Install pinned Rust
run: rustup show
- name: Validate SDK archive safety and bootstrap syntax
run: |
python3 -m unittest discover -s sweetpad-lib/ci -p test_linux_sdk.py
python3 -m unittest discover -s sweetpad-lib/ci -p test_linux_release.py
python3 -m unittest discover -s sweetpad-cli/ci -p test_linux_package.py
python3 -m unittest discover -s sweetpad-cli/ci -p test_device_discovery.py
bash -n sweetpad-lib/ci/linux-setup.sh
- uses: Swatinem/rust-cache@v2
- name: Build ARM64 release CLI
- name: Check host setup and SDK diagnostics
run: |
cargo test --locked -p sweetpad-cli --lib cli::commands::setup::tests
cargo test --locked -p sweetpad-cli --lib cli::commands::doctor::tests
- name: Build Linux release CLI
run: cargo build --release --locked -p sweetpad-cli --bin sweetpad -j 4
- name: Package public sources and binary
run: |
VERSION="$(python3 sweetpad-cli/ci/release-version.py)"
export SOURCE_DATE_EPOCH="$(git log -1 --format=%ct)"
python3 sweetpad-cli/ci/package-linux.py --binary target/release/sweetpad --version "$VERSION" --out dist
- name: Install in a clean Ubuntu 24.04 ARM64 container
- name: Install in a clean Ubuntu 24.04 container
run: |
mkdir -p "$RUNNER_TEMP/package-smoke"
docker run --rm --platform linux/arm64 \
docker run --rm --platform linux/${{ matrix.docker_arch }} \
-v "$PWD/dist:/artifacts:ro" \
-v "$PWD/sweetpad-cli/ci/check-linux-package.py:/check.py:ro" \
-v "$RUNNER_TEMP/package-smoke:/reports" ubuntu:24.04 bash -euc '
Expand All @@ -54,13 +86,13 @@ jobs:
if: always()
uses: actions/upload-artifact@v7
with:
name: linux-arm64-install-report
name: linux-${{ matrix.arch }}-install-report
path: ${{ runner.temp }}/package-smoke/install/*.json
if-no-files-found: warn
- name: Upload verified release package
uses: actions/upload-artifact@v7
with:
name: cli-tarball-linux-arm64
name: cli-tarball-linux-${{ matrix.arch }}
path: |
dist/*.tar.gz
dist/*.sha256
Expand Down
27 changes: 23 additions & 4 deletions sweetpad-cli/CLI_DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -4550,11 +4550,18 @@ alone.

**The engine follows the host.** `Engine::for_host` picks xcodebuild on
macOS and Swift Build everywhere else, so the same command line builds on
both. A hidden `--engine swift-build` (`SWEETPAD_ENGINE`) runs Swift Build on
a Mac too, which is how a build is checked against xcodebuild's; it stays
hidden because nothing else needs it. `build --show-command` prints the
both. `--engine swift-build` (`SWEETPAD_ENGINE`) runs Swift Build on
a Mac too, which is how a build is checked against xcodebuild's. `build --show-command` prints the
`swbuild` command line.

SDK setup uses `sdk export` on a Mac and `setup --sdk` on Ubuntu ARM64 or x86_64.
Export contains private Apple inputs and localized App Intents resources, without signing identities.
Import verifies checksums and stages the SDK before replacement. It adds the Linux compiler's Clang resource headers.
Setup discovers installed, extracted-archive, mise, or explicit source layouts and builds a sample to validate the result.
Linux doctor uses the engine's macro lookup, checks SDK and compiler tools, and separates optional capabilities from required prerequisites.
Generated shell configuration preserves an existing CI-selected CLI.
Swift Build's dependency revisions are frozen in the shipped `swift-build-Package.resolved`.

**Swift Build gets a PIF and a build request, never a target name.**
`sweetpad-lib`'s emitter translates the workspace into Swift Build's PIF,
with GUIDs as `xcodebuild -dumpPIF` derives them. The packages come from
Expand Down Expand Up @@ -4690,7 +4697,7 @@ the VM, and `ci/linux-sweep.py` builds every scheme of the corpus on Linux,
and with Xcode where Linux fails.
`ci/linux-projects.py` adds pinned real apps, including Flutter, React Native, and Capacitor.
`ci/linux-project-checks.py` compares bundles and checks both products in a Mac simulator.
`ci/LINUX_PROJECTS.md` records setup, results, fixture patches, and the remaining MMKV interop gap.
`ci/LINUX_PROJECTS.md` records setup, results, and fixture patches. The generated-header correction closes the validated MMKV interop case without its former shim.

## 10. Testing

Expand Down Expand Up @@ -4749,3 +4756,15 @@ quiet. `parse_line` is pure and unit-tested without Xcode.
(`--device`/`--mac` don't apply; library packages have nothing to run).
- Whether the extension actually adopts the CLI as its engine.
- (Declined for now: `tools` resource, `config`/`state` subcommands.)

### Linux x64 packages (issue #349)

Linux packages select the host architecture from the ELF header: ARM64 uses
`linux-arm64`, and x86_64 uses `linux-x64`. Setup selects the matching Swift
compiler and pinned xtool checksum. SDK receipts record the host architecture
so a transferred SDK cannot silently reuse another host's tools.

iPhone device destinations use ARM64 independently of the Linux host.
Swift Build and compiler macros run on the host. CI builds both packages and
checks installation on matching Ubuntu 24.04 runners. Private SDK integration
can select either host architecture without publishing SDK data.
14 changes: 10 additions & 4 deletions sweetpad-cli/LINUX_DEVICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,21 @@ The CLI supports physical iPhones through local Linux USB or direct Wi-Fi.
Both paths use userspace RSD tunnels and require `pymobiledevice3==11.50.0`.
App operations authenticate the device UDID before installation, launch, or termination.
They do not require a Mac relay or access to its login keychain.
Discovery checks devices independently. An unreachable device produces a warning when another device is available.
If every configured device fails, the command reports those failures instead of returning an empty list.

`SWEETPAD_DEVICE_DISCOVERY_TIMEOUT` sets the per-device discovery timeout (default: 30 seconds).
`SWEETPAD_DEVICE_TIMEOUT` sets the timeout for finite actions such as installation (default: 180 seconds).
Both values must be positive numbers of at most 3600 seconds.
Live logs and console streams retain their streaming behavior. Use the CLI's bounded log options for automation.

## Python setup

Create a private Python environment and install the pinned dependency:

```bash
uv venv --python 3.12 "$HOME/.local/share/sweetpad/device-tools"
uv pip install --python "$HOME/.local/share/sweetpad/device-tools/bin/python" \
pymobiledevice3==11.50.0
python3 -m venv "$HOME/.local/share/sweetpad/device-tools"
"$HOME/.local/share/sweetpad/device-tools/bin/python" -m pip install pymobiledevice3==11.50.0
export SWEETPAD_DEVICE_PYTHON="$HOME/.local/share/sweetpad/device-tools/bin/python"
```

Expand Down Expand Up @@ -123,4 +129,4 @@ Automatic Wi-Fi discovery succeeds without a configured phone hostname or port.
The VM uses the DNS-SD fallback because raw LAN multicast does not cross its bridge.
USB checks use physical passthrough and local Linux `usbmuxd`, with no network configuration or host relay.

Local reports are under `sweetpad-lib/corpus/_linux-followups/validation/transport-2026-11-08/`.
The validation reports remain private and are not part of the installed CLI package.
Loading
Loading