targetIos() in 6.0.0 produces non-runnable .app on iOS 26 simulator (missing UIKit, missing rpath)
- Dominant language
- Kotlin
- Stars
- 3k
- Forks
- 148
- Avg merge
- 13h 44m
- Merged PRs (30d)
- 1
Description
```markdown
## Environment
- Korge plugin: 6.0.0
- Kotlin: 2.0.20
- Xcode: 26.5 (iOS 26.5 simulator runtime)
- Host: macOS (Apple Silicon)
## Summary
Building a Korge app via `./gradlew packageIosSimulatorDebug` succeeds and produces
a `.app` bundle, but launching it on the iOS simulator immediately crashes with
`Library not loaded: @rpath/GameMain.framework/GameMain`. The generated
Xcode project is missing two things: the UIKit framework link, and the
LD_RUNPATH_SEARCH_PATHS build setting.
## Bug 1: Missing UIKit framework in generated project.yml
The generated `build/platforms/ios/project.yml` only links `GameMain.framework`:
```yaml
dependencies:
- framework: ../../bin/iosSimulatorArm64/debugFramework/GameMain.framework
```
But app/main.m imports UIKit/UIKit.h and calls UIApplicationMain. Linker fails:
```
Undefined symbols for architecture arm64:
"_OBJC_CLASS_$_UIResponder", referenced from: _OBJC_CLASS_$_AppDelegate in main.o
"_UIApplicationMain", referenced from: _main in main.o
ld: symbol(s) not found for architecture arm64
```
Expected: UIKit added to framework dependencies.
Bug 2: Missing LD_RUNPATH_SEARCH_PATHS
After fixing Bug 1 (linking works), the app crashes at launch:
```
dyld: Library not loaded: @rpath/GameMain.framework/GameMain
Referenced from: .../Nikias and the Fall of Olympus.app/Nikias and the Fall of Olympus
Reason: tried: '.../iOS 26.5.simruntime/.../GameMain.framework/GameMain' (no such file)
```
otool -l on the main binary shows zero LC_RPATH load commands. The
GameMain framework is correctly embedded in .app/Frameworks/GameMain.framework/,
but @rpath cannot resolve without an rpath that includes
@executable_path/Frameworks.
Expected: The generated Xcode project should set
LD_RUNPATH_SEARCH_PATHS = @executable_path/Frameworks (at minimum the simulator
target, ideally the device target as well).
Reproduction
Minimal build.gradle.kts:
```kotlin
plugins { alias(libs.plugins.korge) }
korge {
id = "com.example.test"
name = "Test"
targetJvm()
targetJs()
targetIos()
targetAndroid()
}
```
Then:
```bash
./gradlew packageIosSimulatorDebug -x installXcodeGen
cd build/platforms/ios/app.xcodeproj/build/Build/Products/Debug-iphonesimulator
xcrun simctl install booted "Test.app" # works
xcrun simctl launch booted com.example.test.app-SimulatorArm64-Debug
# → crash with "Library not loaded: @rpath/GameMain.framework/GameMain"
```
Side note: preferredIphoneSimulatorVersion = 8 is hardcoded
In KorgeExtension.kt (and consumed in Ios.kt's iosCreateIphone task),
the default simulator device type is iPhone 8. iOS 26.5 simulator runtime
no longer ships iPhone 8 device types, so the auto-create step fails with
Incompatible device and aborts the build.
Workaround: preferredIphoneSimulatorVersion = 16 in the korge block, but
this is fragile since the integer corresponds directly to a Xcode device type
name (e.g. iPhone-16, iPhone-15). A device type list or a string-based
override (e.g. iosSimulatorDevice = "iPhone 16") would be more flexible.
Workarounds currently used
In build.gradle.kts:
```kotlin
korge {
// ...
preferredIphoneSimulatorVersion = 16
}
// Patch project.yml to add UIKit and regenerate the Xcode project
val patchIosProjectYml by tasks.registering {
doLast {
val ymlFile = file("${layout.buildDirectory.get().asFile}/platforms/ios/project.yml")
if (!ymlFile.exists()) return@doLast
val text = ymlFile.readText()
val patched = if (text.contains("sdk: UIKit.framework")) text else text
.replace("- framework: ../../bin/iosSimulatorArm64/releaseFramework/GameMain.framework",
"- framework: ../../bin/iosSimulatorArm64/releaseFramework/GameMain.framework\n - sdk: UIKit.framework")
.replace("- framework: ../../bin/iosSimulatorArm64/debugFramework/GameMain.framework",
"- framework: ../../bin/iosSimulatorArm64/debugFramework/GameMain.framework\n - sdk: UIKit.framework")
if (patched != text) {
ymlFile.writeText(patched)
file("${layout.buildDirectory.get().asFile}/platforms/ios/app.xcodeproj").deleteRecursively()
exec {
workingDir = file("${layout.buildDirectory.get().asFile}/platforms/ios")
commandLine("${System.getProperty("user.home")}/.korge/XcodeGen-2.42.0/.build/release/xcodegen")
}
}
}
}
// Rewrite framework install_name + re-sign so @rpath resolves at runtime
val patchIosFrameworkPath by tasks.registering {
doLast {
val appRoot = file("${layout.buildDirectory.get().asFile}/platforms/ios/app.xcodeproj/build/Build/Products/Debug-iphonesimulator/Nikias and the Fall of Olympus.app")
val appBin = File(appRoot, "Nikias and the Fall of Olympus")
val frameworkBin = File(appRoot, "Frameworks/GameMain.framework/GameMain")
if (!appBin.exists() || !frameworkBin.exists()) return@doLast
val absPath = "@executable_path/Frameworks/GameMain.framework/GameMain"
exec { commandLine("/usr/bin/install_name_tool", "-id", absPath, frameworkBin.absolutePath) }
exec { commandLine("/usr/bin/install_name_tool", "-change", "@rpath/GameMain.framework/GameMain", absPath, appBin.absolutePath) }
exec { workingDir = frameworkBin.parentFile; commandLine("/usr/bin/codesign", "--force", "--sign", "-", frameworkBin.name) }
exec { workingDir = appRoot; commandLine("/usr/bin/codesign", "--force", "--sign", "-", appBin.name) }
}
}
afterEvaluate {
tasks.findByName("prepareKotlinNativeIosProject")?.finalizedBy(patchIosProjectYml)
tasks.findByName("iosBuildSimulatorDebug")?.dependsOn(patchIosProjectYml)
tasks.findByName("iosBuildSimulatorDebug")?.finalizedBy(patchIosFrameworkPath)
}
```
Proposed fix
In IosProjectTools.prepareKotlinNativeIosProjectYml, add:
1. - sdk: UIKit.framework to every target's dependencies.
2. LD_RUNPATH_SEARCH_PATHS: ["@executable_path/Frameworks"] to base settings
(or per-config in each target's settings).
3. Allow overriding preferredIphoneSimulatorVersion with a string device name
instead of a version integer.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with IosProjectTools.prepareKotlinNativeIosProjectYml, then inspect KorgeExtension.kt and the iosCreateIphone task in Ios.kt. Run packageIosSimulatorDebug using the reported minimal build.gradle.kts and inspect the generated project.yml and Xcode build settings. Done means the generated simulator app links UIKit, resolves GameMain through its embedded framework, and supports the requested simulator device override.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ios, kotlin
- Domain
- build-system, mobile
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100