One of the most jarring friction points when adopting Kotlin Multiplatform (KMP) for iOS development is hitting the Run button in Xcode, only to be stopped by a cryptic build error: Task 'embedAndSignAppleFrameworkForXcode' not found in project.
This error halts the development loop immediately. It indicates a disconnection between Xcode's build phases and the Gradle tasks responsible for compiling your Kotlin code into an iOS-consumable framework.
This guide provides a root cause analysis of why this disconnection occurs and a rigorous, technical solution to fix it permanently.
The Root Cause: The Gradle-Xcode Disconnect
To understand the fix, you must understand the architecture of a KMP build. Gradle and Xcode are unaware of each other's existence by default.
When you configure a KMP module (commonly named shared), the Kotlin Gradle Plugin (KGP) dynamically registers tasks based on the targets you define (e.g., iosX64, iosArm64). Specifically, for the standard framework integration (non-CocoaPods), KGP generates a task named embedAndSignAppleFrameworkForXcode.
The error "Task not found" occurs due to one of three specific failures:
- Target Mismatch: Your
build.gradle.ktsdoes not strictly define the target architecture matching the iOS Simulator or Device you are currently selecting in Xcode (e.g., missingiosSimulatorArm64for Apple Silicon simulators). - Missing Binary Definition: The Kotlin framework binary has not been explicitly declared in the Gradle configuration.
- Path Resolution Failure: The shell script in Xcode's "Run Script" phase is executing Gradle from the wrong directory or targeting the wrong module path.
The Solution: Step-by-Step
We will resolve this by verifying the Gradle configuration and then correcting the Xcode Build Phase script.
1. Correcting the Kotlin Gradle Configuration
Open your shared module's build.gradle.kts. You must ensure that you are generating the framework for all necessary architectures and that the binaries are explicitly configured.
Modern KMP development (especially on Apple Silicon) requires three specific targets: iosArm64 (Devices), iosX64 (Intel Simulators), and iosSimulatorArm64 (Apple Silicon Simulators).
Update your build.gradle.kts to look like this:
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidLibrary)
}
kotlin {
// 1. Define all necessary iOS targets
val iosTarget = { target: org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget ->
target.binaries.framework {
// 2. Define the framework base name (this matches what you import in Swift)
baseName = "Shared"
// 3. (Optional) Static vs Dynamic.
// Static is recommended for simpler linking unless you have specific dynamic needs.
isStatic = true
}
}
// Apply the configuration to all targets
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget(it) }
androidTarget {
compilations.all {
kotlinOptions {
jvmTarget = "17"
}
}
}
sourceSets {
commonMain.dependencies {
// Your common dependencies
}
}
}
Why this matters: If you are running an iPhone 15 Pro Simulator on a generic M3 MacBook, Xcode demands the iosSimulatorArm64 target. If that target is missing from the listOf(...), KGP never generates the associated build tasks, resulting in the "Task not found" error.
2. Validating the Xcode Build Phase
The bridge between Xcode and Gradle is a bash script located in Build Phases > Run Script.
If this script is malformed or uses relative paths incorrectly, Gradle cannot locate the task.
- Open your iOS project in Xcode.
- Navigate to the project settings -> Build Phases.
- Locate the Run Script phase (often renamed to "Compile Kotlin" or similar).
- Replace the script with the following standard, robust implementation:
# 1. Move to the root of the repo relative to the Xcode project
cd "$SRCROOT/.."
# 2. Execute the Gradle wrapper
# NOTE: Replace ':shared' with your actual module name if different.
./gradlew :shared:embedAndSignAppleFrameworkForXcode \
-Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
-Pkotlin.native.cocoapods.archs="$ARCHS" \
-Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
Technical Nuance: Even if you are not using the CocoaPods plugin, the -P flags pass environment variables (Platform, Architecture, Configuration) from Xcode to Gradle. Gradle uses these to determine which specific link task to delegate to inside the embedAndSign wrapper.
3. Cleaning the Build Cache
After modifying build.gradle.kts, task registration might be stale. You must force a clean state.
- In Android Studio/IntelliJ: Run
./gradlew cleanin the terminal. - In Xcode: Use
Product > Clean Build Folder(Cmd+Shift+K). - Sync: Re-sync your Gradle project in Android Studio.
Deep Dive: How embedAndSign Actually Works
Why is this specific task so fragile?
The embedAndSignAppleFrameworkForXcode is a "lifecycle" task. It doesn't do the heavy lifting itself. Instead, it inspects the environment variables passed by the bash script (specifically ARCHS and CONFIGURATION) to determine the underlying compilation task.
If Xcode sends ARCHS=arm64 and CONFIGURATION=Debug, Gradle looks for a task named linkDebugFrameworkIosSimulatorArm64 (if running on a simulator).
If you configured your KMP targets incorrectly (as shown in Step 1), the link... task doesn't exist. Consequently, the parent embedAndSign task fails to register properly or fails during execution because it cannot find its dependency.
Common Pitfalls and Edge Cases
The CocoaPods Confusion
If you are using the native.cocoapods plugin in your build.gradle.kts, you should not use the embedAndSignAppleFrameworkForXcode task.
The CocoaPods plugin uses a different integration mechanism (syncFramework). If you use CocoaPods, delete the "Run Script" phase entirely from Xcode build phases, as the CocoaPods installation hook handles the build script injection automatically.
"Shared" vs. Project Name
The script command ./gradlew :shared:embedAndSign... assumes your KMP module is named shared. If your module is named core, common, or kmp, you must update the script path to match: ./gradlew :core:embedAndSign....
Space in File Paths
If your project path contains spaces (e.g., /Users/Dev/My App/), the shell script in Xcode might fail. Always ensure your $SRCROOT and paths are quoted in the bash script, as demonstrated in the code snippet above.
Conclusion
The "Task embedAndSignAppleFrameworkForXcode not found" error is rarely a bug in Kotlin itself; it is almost always a configuration drift between Gradle's target definitions and Xcode's build environment.
By strictly defining your iOS targets to cover all architectures (especially iosSimulatorArm64) and ensuring the Xcode Run Script points to the correct Gradle module path, you create a resilient build pipeline that works across all device and simulator combinations.