How it works
A conceptual overview of what happens when you run xcross flutter run, from engine artifacts to hot reload.
Flutter’s own iOS tooling refuses to run on Windows or Linux. xcross does not wrap or patch it - it re-implements the parts that matter for running and debugging on a device, using the same engine artifacts, the same compilers, and the same device protocols the official tooling uses. Running xcross flutter run walks through five stages.
This page stays at the conceptual level - it explains what each stage does and why, without the package-level implementation detail.
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
1. Engine artifacts
xcross reads the engine revision pinned by your Flutter SDK and downloads exactly what Flutter’s own tool would cache: the prebuilt Flutter.xcframework, the debug JIT snapshots, the host frontend_server, and the patched Dart SDK. Your app runs on the identical engine binary the official macOS tooling would embed. Nothing about the engine itself is touched. This step only runs again when your Flutter SDK’s engine hash changes.
2. Kernel compile
In debug mode a Flutter app runs kernel bytecode rather than machine code, so xcross drives frontend_server against the patched SDK to compile your Dart sources into app.dill. It then lays out App.framework the way Flutter does, including the kernel blob, VM snapshot data, and generated asset manifests.
Because the app is pure JIT, no gen_snapshot step is needed - which is exactly what makes macOS unnecessary for this stage. Assets are generated straight from your pubspec.yaml, mirroring Flutter’s own asset bundling.
3. Native code without Xcode
The Darwin SDK extracted from your Xcode.xip supplies the iOS sysroot and frameworks that upstream Swift and LLVM need. xcross compiles the Runner executable with clang and links it with ld64.lld, and compiles any SwiftPM plugins against the same SDK. Xcode’s own toolchain is never involved at any point. Plugin binaries are linked into a single libFlutterPluginsGenerated.dylib and registered by a generated registrant, mirroring Flutter’s own plugin registration.
4. Onto the device
xcross establishes the app’s device identity in-process using your Apple ID or App Store Connect API key credentials - there is no codesign or ldid call. That covers certificates, device registration, and provisioning profiles through Apple’s Developer Services. The result is then installed on the device over the standard device protocols, via pymobiledevice3.
5. CoreDevice launch and hot reload
xcross tunnel mounts the Developer Disk Image and brings up the iOS 17+ RSD tunnel, after which xcross launches the app suspended and attaches a minimal debug client to resume it. From there, the Dart VM Service is port-forwarded to your machine, and hot reload/restart upload incremental or full compiles to the device through the same DevFS mechanism Flutter’s own tooling uses. Pressing r recompiles only the changed files and reassembles the widget tree; pressing R resets the compiler and re-runs the app from scratch.
For the full technical breakdown of each stage, see Architecture.
The packages behind each stage
xcross is split into focused Dart packages rather than one monolith. darwin_sdk_kit owns Xcode archive extraction and Darwin SDK assembly. apple_developer_kit handles Apple ID/API key authentication, provisioning, and device identity. frontend_server_kit drives kernel compilation and hot reload. xcross_flutter assembles the iOS app bundle and drives hot reload over the device tunnel. dart_mobile_device is the device transport layer: pymobiledevice3 wrappers, RSD tunnels, port forwarding, and the gdb-remote debug proxy. xcross_dap implements the Debug Adapter Protocol server that IDEs connect to.
A shared cli_kit package provides the logging, spinners, and process-running utilities that every command above uses. All of these live as separate packages in a single pub workspace inside the xcross repository.
Why this approach works
Every stage reuses an existing, well-tested piece: Flutter’s engine artifacts, the official Swift and LLVM compilers, and the device protocols that pymobiledevice3 already speaks. xcross’s own code is the glue between them - the SDK extraction, the compile driving, the device identity, and the launch orchestration. That is also why the scope is deliberately bounded to debug JIT runs: it is the one mode that never needs gen_snapshot, and therefore never needs a macOS host.
See also
- Architecture - the detailed, package-by-package deep dive.
- Requirements - what your host machine needs.
- CLI reference - every command and flag, including
xcross flutter run.