Build the iOS app of a Kotlin Multiplatform project without a Mac
With Kotlin Multiplatform, most of your app lives in shared Kotlin code. You can write it on Windows or Linux. The iOS framework and the Xcode app still need macOS. We tested this on October 19, 2026, with JetBrains' official KMP template. Below is what passed, and one trap.
What works on Windows and Linux
The Kotlin docs say you can work on shared code and on every platform except iOS from any system. We checked that with KMP-App-Template, the official template from the Kotlin team on GitHub. It is a Compose Multiplatform app for Android, iOS, desktop and web. On Windows and Ubuntu runners, this passed:
./gradlew :shared:compileKotlinJvm :androidApp:assembleDebug
The shared code compiled for the JVM, and the Android app built. You can run Android, desktop and JVM tests the same way. Write your business logic and your Compose UI there, and check it on Android.
The trap: a green build with no iOS framework
Next we asked the Windows and Ubuntu runners for the iOS framework. Gradle compiled the Kotlin for iOS, then skipped the link step. It still reported success:
> Task :shared:compileKotlinIosSimulatorArm64 > Task :shared:linkDebugFrameworkIosSimulatorArm64 SKIPPED BUILD SUCCESSFUL in 27s
No framework came out. Kotlin's target support page explains why. Linux and Windows can make Kotlin libraries for Apple targets. Final binaries for Apple targets cannot be built there. The configure step also warned that iOS simulator tests require macOS.
What needs macOS
- Linking the framework. The iOS app imports your shared code as a framework, and only a Mac can link it.
- The iosApp project. It is a normal Xcode project. Its build phase calls Gradle to embed the framework.
- iOS tests and the Simulator. Both need Xcode.
- CocoaPods and other cinterop. The Kotlin docs say these need a macOS host even for libraries.
The commands on the Mac
A MacRun Mac comes with Xcode 27.1 and Xcode 26.6. It has no JDK, so install one first. We used Zulu JDK 21. The Kotlin quickstart says to open Xcode once before your first build, so it can finish setup. Then clone your repo and link the simulator framework:
./gradlew :shared:linkDebugFrameworkIosSimulatorArm64
Then build the iosApp project for the Simulator. These flags match the template's own CI file. CODE_SIGNING_ALLOWED=NO skips signing, which a simulator build does not need.
cd iosApp xcodebuild -project iosApp.xcodeproj -scheme iosApp \ -configuration Debug -sdk iphonesimulator -arch arm64 \ -derivedDataPath build CODE_SIGNING_ALLOWED=NO build
Keep -arch arm64. Our first try used a generic simulator destination instead. Xcode then asked Gradle for an Intel simulator slice too, and the build failed:
Xcode requested target architectures that are not configured in your Gradle build: ios_x64.
The template only defines iosArm64 and iosSimulatorArm64. Adding both -destination and -arch failed too. Xcode said the destination already implies the architecture.
To open the app, boot a simulator and install the build. Use your own bundle ID. On MacRun, the iOS 27.1 runtime brings the iPhone Duo device.
xcrun simctl list devices available xcrun simctl boot "iPhone Duo" xcrun simctl install booted "build/Build/Products/Debug-iphonesimulator/KMP App.app" xcrun simctl launch booted com.jetbrains.kmpapp.KMP-App-Template
You can also open iosApp.xcodeproj in Xcode over screen sharing. Set your team, then press Run. From Windows, see Xcode from Windows for the remote screen.
The workflow we ran on GitHub Actions
This is the macOS job from our test. It pins the template to one commit, so you can run it as is. In your own repo, check out your code instead.
name: kmp-ios
on: workflow_dispatch
env:
TEMPLATE_SHA: 6b6d09e4f3f5e845116422d59b658791ed6149c6
jobs:
macos:
runs-on: macos-26
steps:
- uses: actions/checkout@v4
with:
repository: Kotlin/KMP-App-Template
ref: ${{ env.TEMPLATE_SHA }}
- uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 21
- run: xcodebuild -version && sw_vers && java -version
- name: Link the iOS simulator framework
run: time ./gradlew :shared:linkDebugFrameworkIosSimulatorArm64
- name: Build the iosApp for the Simulator
run: |
time xcodebuild -project iosApp.xcodeproj -scheme iosApp \
-configuration Debug -sdk iphonesimulator -arch arm64 \
-derivedDataPath build CODE_SIGNING_ALLOWED=NO build
working-directory: iosApp
- name: Install and launch in the Simulator
run: |
APP=$(ls -d build/Build/Products/Debug-iphonesimulator/*.app | head -1)
UDID=$(xcrun simctl list devices available -j | python3 -c "import json,sys; d=json.load(sys.stdin)['devices']; print(next(x['udid'] for k,v in d.items() if 'iOS' in k for x in v if x['name'].startswith('iPhone')))")
xcrun simctl boot "$UDID"
xcrun simctl bootstatus "$UDID"
xcrun simctl install booted "$APP"
xcrun simctl launch booted com.jetbrains.kmpapp.KMP-App-Template
sleep 10
xcrun simctl io booted screenshot shot.png && ls -la shot.png
working-directory: iosAppThe job took 6 minutes 35 seconds. The Gradle link took 4 minutes 7 seconds on a cold runner, mostly downloading the Kotlin/Native toolchain. The xcodebuild step took 26 seconds. These lines come from the log:
Xcode 26.6 Build version 17F113 openjdk version "21.0.12" 2026-07-21 LTS BUILD SUCCESSFUL in 4m 4s 263M shared/build/bin/iosSimulatorArm64/debugFramework/Shared.framework ** BUILD SUCCEEDED ** 59M build/Build/Products/Debug-iphonesimulator/KMP App.app com.jetbrains.kmpapp.KMP-App-Template: 26325
The last line is the app running in the Simulator. Our run used Xcode 26.6, because GitHub's image has no Xcode 27. On a MacRun Mac you can match it with sudo xcode-select -s /Applications/Xcode.app, which is Xcode 26.6 there.
Shipping to TestFlight
The release build happens in the iosApp project, like any iOS app. Set your team in Xcode, archive, and upload to App Store Connect. Our publishing guide covers signing, API keys and review. We did not test those steps, since our runner had no Apple account. For a real iPhone, install from TestFlight. A remote Mac cannot see a phone plugged into your PC.
Who should not rent a Mac for this
If you only need a build to prove iOS still compiles, use CI. The workflow above runs on a GitHub Actions macOS runner, billed by the minute. Codemagic personal accounts include 500 free macOS build minutes a month. If you work on iOS every day, a Mac of your own may cost less over a year.
Rent a Mac when you need to see the iOS app. That means Compose UI on a real iOS screen, a crash in Xcode, or a signing problem. A MacRun day pass is $15 for a dedicated M6 Mac mini, and a week is $59. You pay once and nothing renews. The Mac is wiped at the end, so push your work to git first.
Frequently asked questions
Can I build the iOS app of a Kotlin Multiplatform project on Windows?
+
No. Kotlin's docs say final binaries for Apple targets cannot be built on Linux or Windows. Shared code, Android and JVM targets build fine there. The iOS framework and the Xcode app need a Mac.
Why does Gradle say BUILD SUCCESSFUL for iOS on Windows?
+
Gradle skips Apple link tasks on Linux and Windows instead of failing. In our test, linkDebugFrameworkIosSimulatorArm64 printed SKIPPED, and Gradle still reported success. No framework was produced.
Can I write Compose Multiplatform UI on Windows?
+
Yes. In our test, the template's shared code compiled for the JVM and its Android app built on Windows. You only need a Mac to build and run the UI on iOS.
What fixes "Xcode Requested Architecture Not Configured in Gradle"?
+
Build for one architecture. We passed -sdk iphonesimulator -arch arm64 to xcodebuild, as the KMP template's own CI does. A generic simulator destination also asked for x86_64, which the template does not define.
How long does a Kotlin Multiplatform iOS build take?
+
On GitHub's macos-26 runner, linking the template's simulator framework took about 4 minutes on a cold runner. The Xcode build of iosApp then took 26 seconds.
Is a JDK installed on a MacRun Mac?
+
No. Xcode 27.1, Xcode 26.6 and the iOS 27.1 simulator are installed. You have admin rights and Homebrew, so you install the JDK and anything else you need.