Skip to content
xcross
Documentation menu

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

ESC