Build, run, and hot-reload Flutter iOS apps natively from Windows or Linux.
No Mac. No Xcode. No WSL.
Install • Quick start • Commands • IDE • FAQ • Under the hood
xcross reimplements the Flutter iOS build pipeline and the iOS 17+ CoreDevice launch protocol in pure Dart. It compiles your Flutter app with the official Swift and LLVM toolchains, signs it with your Apple ID or an App Store Connect API key, installs it on a physical iPhone, launches it, and gives you full hot reload - all from a Windows or Linux machine.
| Feature | Details |
|---|---|
| Native Windows & Linux | Runs directly on the host with official Swift and LLVM toolchains - no VM, no WSL, no macOS anywhere |
| Hot reload & hot restart | Full r / R workflow on a real iPhone over an iOS 17+ RSD tunnel |
| Native signing & install | Apple ID (free account works) or App Store Connect API key; signing runs in-process |
| SwiftPM plugins | Swift Package Manager iOS plugins compile on both Windows and Linux |
| IDE debugging | One command sets up VS Code (F5, breakpoints, DevTools) or JetBrains IDEs via DAP |
| Direct build pipeline | frontend_server → clang → ld64.lld → .app / .ipa - no Xcode build system involved |
Important
xcross produces debug (JIT) device builds. Release/AOT builds still require Flutter's macOS build tooling. Launching with xcross requires iOS 17 or later on the device.
Both platforms need the same five ingredients:
| Requirement | Purpose |
|---|---|
| Flutter | Your app's SDK; xcross reuses its engine artifacts |
| Swift toolchain | Compiles SwiftPM plugins and runner glue code |
LLVM (clang, clang++, llvm-ar, ld64.lld on PATH) |
Compiles and links the iOS Mach-O binaries |
Python 3 + pymobiledevice3 |
Device communication and the iOS 17+ RSD tunnel |
A complete Xcode.xip (xcodereleases.com) |
Processed once by xcross sdk install into a private Darwin Swift SDK |
Note
Download the Xcode archive from xcodereleases.com (requires an Apple ID). It is only used as SDK input - neither Xcode nor macOS is ever installed or executed. xcross extracts the iOS SDK and frameworks from the archive with its own pure-Dart xar/pbzx/cpio readers. Don't redistribute the extracted Apple SDK.
Both installers download the latest release, install it, add xcross to your PATH, verify the binary, and print any missing prerequisites with install hints.
-
One-line install (PowerShell):
irm https://raw.githubusercontent.com/arxdeus/xcross/main/install.ps1 | iex
-
Install Swift and LLVM from an Administrator PowerShell (the installer tells you if they're missing):
winget install --id Swift.Toolchain --exact winget install --id LLVM.LLVM --exact
-
Install Flutter, Python 3, and
pymobiledevice3, then finish setup:py -m pip install -U pymobiledevice3 xcross setup xcross sdk install C:\Downloads\Xcode.xip # once, takes a while
-
One-line install:
curl -fsSL https://raw.githubusercontent.com/arxdeus/xcross/main/install.sh | sh -
Let xcross install its apt dependencies and
pymobiledevice3, and build the Darwin SDK:xcross setup xcross sdk install ~/Downloads/Xcode.xip # once, takes a while
xcross setupalso installsusbmuxd,usbutils, andlibimobiledevice-utilsfor USB device access and diagnostics.
Every release archive is built by release.yml and signed with a SLSA build provenance attestation that binds the archive's digest to the commit it was built from. The release job refuses to publish an artifact whose provenance does not match, so a bundle produced outside this repository can never reach the release page.
To check an archive yourself with the gh CLI (SHA256SUMS.txt is published alongside the archives for plain integrity checks):
gh attestation verify xcross-linux-x64.tar.gz \
--repo arxdeus/xcross \
--signer-workflow arxdeus/xcross/.github/workflows/release.yml \
--source-digest <commit-sha-from-the-release-notes>xcross talks to Apple's Developer Services directly. Two options:
xcross auth --apple-id you@example.comThe command prompts for your password and 2FA code, then stores only the resulting Developer Services session - never your password.
Machine attestation uses Android ADI libraries (libCoreADI.so, libstoreservicescore.so):
- Windows x64 / Linux x86_64 - downloaded automatically from the Apple Music APK into
%APPDATA%\xcross\adi-libs(Windows) or~/.config/xcross/adi-libs(Linux) on first use. - Other architectures - extract the matching APK slice yourself and pass
--adi-library-dir.
xcross auth --issuer-id <uuid> --key-id <id> --private-key /path/to/AuthKey.p8# 1. One-time machine setup (see Installation above)
xcross setup
xcross sdk install ~/Downloads/Xcode.xip
xcross auth --apple-id you@example.com
# 2. Once per device reconnect: mount DDI + start the RSD tunnel
# (Administrator PowerShell on Windows; root on Linux)
xcross tunnel
# 3. Build, sign, install, launch, hot-reload
cd my_flutter_app
xcross flutter run
# 4. Optional: wire up your IDE
xcross ide vscode # or: xcross ide ideaWhile the app is running:
| Key | Action |
|---|---|
r |
Hot reload |
R |
Hot restart |
q / Ctrl-C / Ctrl-D |
Quit |
With multiple iPhones connected, an interactive terminal shows a numbered device picker; pass -u <UDID> for CI or piped runs.
| Command | Description |
|---|---|
xcross setup |
Install host dependencies (apt packages, pymobiledevice3) |
xcross sdk install <Xcode.xip> |
Extract a private Darwin Swift SDK from an Xcode archive |
xcross auth |
Save Apple ID or App Store Connect credentials |
xcross tunnel |
Mount the Developer Disk Image + start the iOS 17+ RSD tunnel |
xcross flutter run |
Build → sign → install → launch → hot reload |
xcross flutter dap |
Run the Debug Adapter Protocol server (used by IDEs) |
xcross ide vscode |
Upsert .vscode/* for Run & Debug / Hot Reload |
xcross ide idea |
Write a JetBrains DAP run configuration (needs LSP4IJ) |
xcross completion |
Print a shell-completion script |
xcross flutter run options
-t, --target <path> entrypoint (default: lib/main.dart)
-D, --dart-define k=v repeatable
--dart-define-from-file .json / .env, repeatable
--[no-]pub run flutter pub get first (default: on)
--build-name CFBundleShortVersionString
--build-number CFBundleVersion
--flavor flavor entrypoint selection
-i, --ipa package an .ipa instead of an .app
-d, --device-id device id or name
-u, --udid device UDID; wins if both selectors are set
--usb / --wifi
--device-connection attached | wireless | both
--route
-a, --dart-entrypoint-args repeatable
-v, --verbose
Swift Package Manager iOS plugins work on both Windows and Linux. Their native code is compiled against the extracted Darwin SDK into Frameworks/libFlutterPluginsGenerated.dylib and registered by the generated runner.
Plugins that only ship a CocoaPods podspec are currently skipped with a warning - prefer plugin releases that include ios/<package_name>/Package.swift.
The bundle id is read from the Flutter iOS project, using the same sources as Flutter's own tooling on non-macOS hosts:
- A literal
CFBundleIdentifierinios/Runner/Info.plist(no$(…)variables), otherwise - the first
PRODUCT_BUNDLE_IDENTIFIERinios/Runner.xcodeproj/project.pbxproj.
The packed .app's Info.plist is always derived from ios/Runner/Info.plist when present.
xcross ide vscodeWrites the DAP shim and upserts an xcross launch entry + DAP settings into .vscode/. Press F5 to build, sign, install, and launch. Hot Reload / Restart buttons drive the same r/R commands as the CLI, and DevTools attaches to the same VM Service connection.
- Works in VS Code forks with the Dart-Code extension installed.
- The xcross launch config sets
"env": { "XCROSS": "true" }; other Flutter launch configs in the workspace keep working - sessions without that marker are handed to Flutter's own debug adapter. - For multiple iPhones, set
"args": ["--udid", "<UDID>"]on the xcross entry; re-running the command preserves those args. - Existing
launch.json/settings.jsonare merged in place (xcross keys upserted, everything else kept). A second run is a no-op when already current. - Run the installed
xcross; the generated DAP shim records that binary's path.
xcross ide ideaWrites .run/xcross_ios_device.run.xml - a shared LSP4IJ Debug Adapter Protocol run configuration that starts xcross flutter dap over stdio.
- Install the LSP4IJ plugin, then Debug xcross: iOS device (don't use Flutter's Run button - it still calls
flutter run). - Breakpoints and stepping use the DAP/VM Service path; Restart maps to hot restart. Console
r/Rstill work. - An existing
.run/xcross_ios_device.run.xmlis never overwritten.
Why can't it build release/AOT?
Flutter's gen_snapshot for iOS AOT only runs on macOS hosts - Dart does not cross-compile an iOS AOT executable from Windows/Linux. Debug (JIT) builds don't need it, which is exactly what xcross produces. Release builds still need Flutter's macOS toolchain.
What does xcross tunnel do, and why does it need elevation?
It mounts the Developer Disk Image and starts the pymobiledevice3 RSD tunnel - the encrypted QUIC/TUN tunnel iOS 17+ requires for developer services. Creating the TUN interface needs an Administrator PowerShell on Windows or root on Linux. Run it once per device reconnect.
Does it support Compose Multiplatform?
Not yet. xcross currently builds Flutter applications.
Is my Apple password stored anywhere?
No. xcross auth performs the login handshake locally and persists only the resulting Developer Services session token.
xcross does not wrap or patch flutter build ios - that command simply refuses to run off-macOS. Instead, it re-implements the parts of Flutter's toolchain that matter for a debug device build, using the same engine artifacts, the same compilers, and the same device protocols the official tooling uses.
xcross flutter run
├─ FlutterPacker
│ ├─ IosEngineCache download engine artifacts pinned to the SDK's engine hash
│ ├─ FlutterDebugBundler frontend_server → app.dill → App.framework (JIT)
│ ├─ SwiftPM plugins swift build (Darwin SDK) → libFlutterPluginsGenerated.dylib
│ ├─ RunnerShim clang / ld64.lld → Runner Mach-O
│ └─ assemble Flutter.framework + Info.plist + flutter_assets → .app
├─ in-process codesign → install
└─ CoreDeviceLauncher RSD tunnel → launch suspended → gdb-remote attach
└─ HotReloadController DevFS + VM Service ⇄ frontend_server
IosEngineCache reads the engine revision from your Flutter SDK (the same hash flutter itself pins) and downloads exactly what Flutter's tool would cache: the prebuilt Flutter.xcframework, the debug vm_snapshot_data / isolate_snapshot_data JIT snapshots, the host frontend_server, and the Flutter patched Dart SDK. Your app therefore runs on the identical engine binary an Xcode build would embed - xcross never rebuilds or modifies the engine.
In debug mode Flutter apps are not compiled to machine code - the Dart VM runs kernel bytecode (JIT). FlutterDebugBundler drives the same frontend_server the Flutter tool uses (against the patched SDK, with your --dart-defines and flavor entrypoint) to produce app.dill, then lays out App.framework exactly like Flutter does:
flutter_assets/kernel_blob.bin- the kernel programflutter_assets/vm_snapshot_data,isolate_snapshot_data- VM heap seedsAssetManifest.bin/json,FontManifest.json, fonts and assets - generated in Dart from yourpubspec.yaml, replicating Flutter's asset bundling- The
App.frameworkbinary in a debug build is only a stub - xcross compiles that stub withclangtargetingarm64-apple-iosand writes the framework'sInfo.plistitself.
Because the app is pure JIT, no gen_snapshot is needed - which is precisely what makes macOS unnecessary (and why release/AOT is out of scope).
- Darwin SDK -
darwin_sdk_kitunpacksXcode.xipwith pure-Dart xar, pbzx, and cpio readers and assembles a Swift SDK bundle (iOS sysroot + frameworks) usable by upstream Swift/LLVM on Windows and Linux. - Runner - the
Runnerexecutable (Flutter'sAppDelegate/mainshim) is compiled withclangand linked withld64.lld, LLVM's Mach-O linker, againstFlutter.xcframeworkfrom the SDK above. - Plugins - SwiftPM iOS plugins are built with
swift buildagainst the same SDK into a singlelibFlutterPluginsGenerated.dylib, with a generated registrant mirroring Flutter'sGeneratedPluginRegistrant. A Mach-O rewriter fixes install names and rpaths so the dylibs resolve inside the.appbundle.
apple_developer_kit implements Apple's GrandSlam login (with ADI machine attestation via the Android libraries), Developer Services provisioning (certificates, device registration, provisioning profiles), and in-process Mach-O code signing - no codesign, no ldid. Installation goes over the standard device protocols via pymobiledevice3.
iOS 17 replaced the old debug-launch path with CoreDevice over an encrypted RSD tunnel. xcross tunnel mounts the Developer Disk Image and brings the tunnel up; CoreDeviceLauncher then launches the app suspended, attaches a minimal gdb-remote client (the same protocol debugserver speaks) to resume and supervise the process, and port-forwards the Dart VM Service from the phone to localhost.
Hot reload is pure Flutter-internals territory, reimplemented protocol-for-protocol:
- A long-lived
frontend_serversession (fromfrontend_server_kit) holds incremental compile state; a file watcher tracks yourlib/. - On
r, changed files are recompiled to an incremental dill, which is gzip-uploaded to the device via the VM Service's HTTP DevFS endpoint (_createDevFS+PUT- the sameorg-dartlang-devfs://filesystem Flutter's tool uses). - xcross calls
reloadSourceson the root isolate, then triggersext.flutter.reassembleso the widget tree rebuilds. R(hot restart) resets the compiler, uploads a full dill, and re-runs the app in eachFlutterViewvia_flutter.listViews/ run-in-view - matching Flutter's hot restart semantics.- The
frontend_serveris also registered as the VM Service's expression compiler, so debugger watch/evaluate works on-device.
xcross_dap implements a Debug Adapter Protocol server that routes launch/attach, breakpoints, stepping, and hot-reload requests to the same VM Service connection. VS Code reaches it through a shim that intercepts launch configs marked with "env": { "XCROSS": "true" } (everything else falls through to Flutter's own adapter); JetBrains IDEs reach it through an LSP4IJ DAP run configuration.
Thanks for maintainers for these repositories for their work, i took and adapt a lot from them:
- xtool - cross-platform Xcode replacement that pioneered building and deploying iOS apps with SwiftPM from Linux and Windows.
- Provision - reverse-engineered Apple ADI machine attestation and anisette provisioning; the foundation of xcross's Apple ID authentication approach.
- anisette-v3-server - anisette data server powering Apple ID logins without Apple hardware.
xcross is free and open source. If it saves you a Mac, consider giving back:
-
Contribute - pull requests are welcome, from typo fixes to new features.
-
Report - found a bug or missing a feature? Open an issue.
-
Donate - support ongoing development: