Kotlin Multiplatform Library: Unable to generate .framework for iOS - ios

I am new to Android/KotlinMultiplatform , I am trying to create a library for iOS/Android using Kotlin Multiplatform.
When I run the command on terminal
./gradlew :shared:packForXcode
It succeeds but could not find a /build/xcode-frameworks folder inside the root folder.
Could anyone help me to find where it is going wrong...?
IntelliJ CE Version : 2020.2.3
My Gradle file Content:
plugins {
id("org.jetbrains.kotlin.multiplatform") version "1.4.10"
id("com.android.library")
id("kotlin-android-extensions")
"maven-publish"
}
repositories {
mavenCentral()
}
group "me.myname"
version "0.0.1"
kotlin {
targets {
android()
ios {
binaries {
framework {
baseName = "MyLib"
}
}
}
}
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9")
}
}
val androidMain by getting {
dependencies { }
}
val iosMain by getting {
dependencies { }
}
}
}
android {
compileSdkVersion(29)
defaultConfig {
minSdkVersion(24)
targetSdkVersion(29)
versionCode = 1
versionName = "1.0"
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
}
}
}
val packForXcode by tasks.creating(Sync::class) {
val targetDir = File(buildDir, "xcode-frameworks")
/// selecting the right configuration for the iOS
/// framework depending on the environment
/// variables set by Xcode build
val mode = System.getenv("CONFIGURATION") ?: "DEBUG"
val sdkName: String? = System.getenv("SDK_NAME")
val isiOSDevice = sdkName.orEmpty().startsWith("iphoneos")
val framework = kotlin.targets
.getByName<KotlinNativeTarget>(
if(isiOSDevice) {
"iosArm64"
} else {
"iosX64"
}
)
.binaries.getFramework(mode)
inputs.property("mode", mode)
dependsOn(framework.linkTask)
from({ framework.outputDirectory })
into(targetDir)
println("Build Folder => $targetDir")
/// generate a helpful ./gradlew wrapper with embedded Java path
doLast {
val gradlew = File(targetDir, "gradlew")
gradlew.writeText("#!/bin/bash\n"
+ "export 'JAVA_HOME=${System.getProperty("java.home")}'\n"
+ "cd '${rootProject.rootDir}'\n"
+ "./gradlew \$#\n")
gradlew.setExecutable(true)
}
}
tasks.build.dependsOn("packForXCode")
UPDATE
Project Created using IntelliJ IDEA, as below screenshot:
My project structure looks like below:

I've only been able to see the template of your screenshot by using
IntelliJ 2020.2.3 Ultimate
This template doesn't have the packForXcode task set by default, so you would have put it by hands I suppose.
Anyway, with a cleaned project, if you run it, you could have the debug framework in the build folder where you want to have it.
You should have, of course, at least one source (Greeting.kt) file like the one I've shown you in my pic.
I suggest you to look deep at the documentation starting from here and here.

If I remember correctly, this task is not designed to be executed manually. It should be triggered as a part of the Xcode project build, see in the documentation. Please try to follow the steps from the documentation, and see if the framework connects and works fine from Xcode.

Related

Can't generate arm64 simulator with KMM

TL;DR Can't run Xcode project with KMM package on simulator on M1 (Max) MacBook.
I have tried to create a simple KMM project from the official guide where I want to import the module into my project in Xcode as a swiftpackage. This is where I found Multiplatform-swiftpackage.
I have managed to get the package over to my project, and I am able to run it on my phone, but I can't run it on the simulator as it seems to not build arm64 simulator which are very much present in the build.gradle.kts. Which leads to this error in Xcode:
ld: warning: ignoring file /Users/x/Library/Developer/Xcode/DerivedData/Urge-enlioljftsmplubupdnskjmixphi/Build/Products/Debug-iphonesimulator/UrgeNetwork.framework/UrgeNetwork, building for iOS Simulator-arm64 but attempting to link with file built for iOS Simulator-x86_64. I can't just exclude arm64 from my project as I have other packages not building for x86_64
I had friend try the same with his project and it build a combined x86_64 and arm simulator package. Then he tried 2 days later and it stopped generating it.
The simulator works when I run it from Android Studio, so it's probably the package. I can't see any other solutions to get the package to run via SPM in Xcode, so open for alternative solutions.
Any hints is greatly appreciated.
build.gradle.kts
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework
plugins {
kotlin("multiplatform")
id("com.android.library")
kotlin("plugin.serialization") version "1.6.21"
id("com.chromaticnoise.multiplatform-swiftpackage") version "2.0.3"
}
val ktorVersion = "2.0.2"
kotlin {
android()
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach {
it.binaries.framework {
baseName = "UrgeNetwork"
}
}
multiplatformSwiftPackage {
swiftToolsVersion("5.5")
targetPlatforms {
iOS { v("15") }
}
outputDirectory(File(rootDir, "/"))
}
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.2")
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
}
val androidMain by getting {
dependencies {
implementation("io.ktor:ktor-client-android:$ktorVersion")
}
}
val androidTest by getting
val iosX64Main by getting
val iosArm64Main by getting
val iosSimulatorArm64Main by getting
val iosMain by creating {
dependencies {
implementation("io.ktor:ktor-client-darwin:$ktorVersion")
}
dependsOn(commonMain)
iosX64Main.dependsOn(this)
iosArm64Main.dependsOn(this)
iosSimulatorArm64Main.dependsOn(this)
}
val iosX64Test by getting
val iosArm64Test by getting
val iosSimulatorArm64Test by getting
val iosTest by creating {
dependsOn(commonTest)
iosX64Test.dependsOn(this)
iosArm64Test.dependsOn(this)
iosSimulatorArm64Test.dependsOn(this)
}
}
}
android {
compileSdk = 32
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdk = 29
targetSdk = 32
}
}
kotlin.targets.withType(org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget::class.java) {
binaries.all {
binaryOptions["memoryModel"] = "experimental"
}
}

Failing to generate libraryname.xcframework in kotlin multiplatform mobile (KMM)

I was trying to generate the XCFramework with Kotlin 1.5.31 containing the targets for iOSArm64 and iOSX64 .
With the below build.gradle.kt , it generates a FatFrameworks . I’m failing to generate XCFrameworks .
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework
kotlin {
val xcFramework = XCFramework(libName)
android()
ios {
binaries.framework(libName) {
xcFramework.add(this)
}
}
sourceSets {
val commonMain by getting
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
val androidMain by getting {
dependencies {
implementation("com.google.android.material:material:1.2.1")
}
}
val androidTest by getting {
dependencies {
implementation(kotlin("test-junit"))
implementation("junit:junit:4.13")
}
}
val iosMain by getting
val iosTest by getting
}
}
And also I have included the tasks in build.gradle.kts :
tasks {
register(“buildDebugXCFramework”)
register(“buildReleaseXCFramework”)
register(“publishDevFramework”)
register(“publishFramework”)
}
This is the output I got : fatframeowrks generated but not the libraryname.xcframeworks
If Any suggestions to generate XCFrameworks with targets iOSArm64 and iOSX64 ? , it would be helpful , Thank you .
I think that following the documentation might help here.
Please set library name by the baseName option and build the final XCFramework by running assembleXCFramework Gradle task.
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework
kotlin {
val xcFramework = XCFramework()
android()
ios {
binaries.framework() {
baseName = "libName"
xcFramework.add(this)
}
}
...

Kotlin/native cinterop build problem with gradle

I'm experimenting with Kotlin native and iOS. I tried to use the example at raywenderlich as a starting point. This example is a bit old , so I have updated the code to fit the Kotlin multiplatform 1.3.61. I'm using AppCode to build the code.
I'm struggling with the Kotlin DSL gradle file ( build.gradle.kts) , the example is using build.gradle :
plugins {
id "org.jetbrains.kotlin.platform.native" version "1.3.0"
}
components.main {
def productsDir = new File("").absolutePath
targets = ['ios_arm64', 'ios_x64']
outputKinds = [EXECUTABLE]
allTargets {
linkerOpts '-rpath', '#executable_path/Frameworks'
linkerOpts "-F${productsDir}"
}
dependencies {
cinterop('AFNetworking'){
packageName 'com.afnetworking'
compilerOpts "-F${productsDir}"
linkerOpts "-F${productsDir}"
includeDirs{
allHeaders "${productsDir}/AFNetworking.framework/Headers"
}
}
}
}
task copyExecutable() {
doLast {
def srcFile = tasks['compileDebugIos_x64KotlinNative'].outputFile
def targetDir = getProperty("konan.configuration.build.dir")
copy {
from srcFile.parent
into targetDir
}
}
}
I have tried to translate this into the build.gradle.kts file of my own :
plugins {
kotlin("multiplatform") version "1.3.61"
kotlin("xcode-compat") version "0.2.5"
}
repositories {
mavenCentral()
}
kotlin {
val productsDir = "/Users/trond/Desktop/native_meteor/native_meteor/"
iosX64("ios")
{
compilations.getByName("main")
{
val myInterop by cinterops.creating {
defFile(project.file("src/nativeInterop/cinterop/afnetworking.def"))
packageName ("com.afnetworking")
compilerOpts ("-F${productsDir}")
// linkerOpts ("-F${productsDir}")
// 3
includeDirs{
allHeaders ("${productsDir}/AFNetworking.framework/Headers")
}
}
}
}
xcode {
setupApplication("native_meteor")
}
}
From what I can see the cinterops tool is doing it's job and is creating a klib file and a afnetworing.kt ( build/classes/kotlin/ios/main/..... )
But the I'm not able to use the library AFNetworking! I try to add the import directive in the ViewController.kt file :
import com.afnetworking.*
But this is not recognized. This results in that the project is not building :
> Task :native_meteor:compileKotlinNative_meteor FAILED
e: /Users/trond/Desktop/native_meteor/native_meteor/src/native_meteorMain/kotlin/ViewController.kt: (15, 8): Unresolved reference: com
e: /Users/trond/Desktop/native_meteor/native_meteor/src/native_meteorMain/kotlin/ViewController.kt: (37, 23): Unresolved reference: AFHTTPSessionManager
e: /Users/trond/Desktop/native_meteor/native_meteor/src/native_meteorMain/kotlin/ViewController.kt: (40, 38): Unresolved reference: AFJSONResponseSerializer
Anyone that can shed some light on this ?
Maybe it will make sense to check the contents of your result .klib to make sure you use the correct package name. It can be done using ~/.konan/kotlin-native-macos-1.3.61/bin/klib CLI tool. Also, I would recommend you to take a look at this sample from the Kotlin/Native Github, it also utilizes AFNetworking and uses the latest compiler version.
Thank you for pointing me in that direction. I investigated the .klib file, and I discovered that it contained the package that I expected. I then took a look at the Gradle sourcsets that I had in the Gradle pane in Appcode. Here I discovered that the .klib library was defined under "iosMain" and not "native_metorMain". I then found out that the line iosX64("ios") should be iosX64("meteor_main").
After that I got an error : "ld: framework not found AFNetworking" when I tried to compile in XCode. I then added -F{full path to framework} for the linkerOpts ( linkerOpts= -F{full path to framwork} -framework AFNetworking ) in the afnetworking.def file.
Now the project builds successfully.

Ktor and kotlinx dependencies not found when building iOS framework

I am trying to include support for ktor and kotlinx (serialization and coroutines to be exact) to my Kotlin/Native project. All libraries work on the Android version of the project, but when I try to compile the Xcode project, I receive errors stating that the kotlinx.serialization and kotlinx.coroutines libraries could not be found:
src/commonMain/kotlin/.../.kt:4:16: error: unresolved reference: coroutines
import kotlinx.coroutines.CoroutineDispatcher
^
src/commonMain/kotlin/.../.kt:5:16: error: unresolved reference: serialization
import kotlinx.serialization.json.JSON
Here is my gradle file for my project:
plugins {
id 'kotlin-multiplatform' version '1.3.11'
id 'kotlinx-serialization' version '1.3.0'
}
ext {
ktor_version = '1.0.1'
kotlinx_version = '1.1.0'
}
repositories {
maven { url "https://kotlin.bintray.com/ktor" }
maven { url "https://kotlin.bintray.com/kotlinx" }
google()
jcenter()
mavenCentral()
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android-extensions'
configurations {
compilerPlugin
}
android {
compileSdkVersion 27
defaultConfig {
applicationId "org.jetbrains.kotlin.mpp_app_android"
minSdkVersion 15
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
}
kotlin {
targets {
fromPreset(presets.android, 'android')
// This preset is for iPhone emulator
// Switch here to presets.iosArm64 (or iosArm32) to build library for iPhone device
fromPreset(presets.iosX64, 'ios') {
compilations.main.outputKinds('FRAMEWORK')
}
}
sourceSets {
commonMain {
dependencies {
implementation 'commons-codec:commons-codec:1.10'
implementation 'org.jetbrains.kotlin:kotlin-stdlib-common'
implementation("io.ktor:ktor-client:$ktor_version")
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$kotlinx_version"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:0.9.1"
}
}
commonTest {
dependencies {
implementation 'org.jetbrains.kotlin:kotlin-test-common'
implementation 'org.jetbrains.kotlin:kotlin-test-annotations-common'
}
}
androidMain {
dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib'
implementation("io.ktor:ktor-client-android:$ktor_version")
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinx_version"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.9.1"
}
}
androidTest {
dependencies {
implementation 'org.jetbrains.kotlin:kotlin-test'
implementation 'org.jetbrains.kotlin:kotlin-test-junit'
}
}
iosMain {
dependencies {
implementation("io.ktor:ktor-client-ios:$ktor_version")
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-native:$kotlinx_version"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:0.9.1"
}
}
iosTest {
}
}
}
// This task attaches native framework built from ios module to Xcode project
// (see iosApp directory). Don't run this task directly,
// Xcode runs this task itself during its build process.
// Before opening the project from iosApp directory in Xcode,
// make sure all Gradle infrastructure exists (gradle.wrapper, gradlew).
task copyFramework {
def buildType = project.findProperty("kotlin.build.type") ?: "DEBUG"
def target = project.findProperty("kotlin.target") ?: "ios"
dependsOn "link${buildType.toLowerCase().capitalize()}Framework${target.capitalize()}"
doLast {
def srcFile = kotlin.targets."$target".compilations.main.getBinary("FRAMEWORK", buildType)
def targetDir = getProperty("configuration.build.dir")
copy {
from srcFile.parent
into targetDir
include 'app.framework/**'
include 'app.framework.dSYM'
}
}
}
Any help would be greatly appreciated, thanks!
Make sure enableFeaturePreview("GRADLE_METADATA") is in the settings.gradle file. Once I added this line, I was able to compile from Xcode.
I'm having this problem as well. And it suddenly appeared on a project that I've been working on for months. I do have enableFeaturePreview('GRADLE_METADATA') set in settings.gradle. What could be going on? This is really frustrating; my project is suddenly at a standstill for no apparent reason.

How can I move the AndroidManifest.xml file with the experimental gradle plugin 0.7.x?

I'm trying to change the location of the AndroidManifest.xml file when using the experimental gradle plugin version 0.7.x. The reason for doing this is that I generate the file (as there is no manifest merger/property replacer in the experimental plugin) so I don't want an output file together with the sources.
My app build.gradle:
apply plugin: "com.android.model.application"
def buildDir = project.buildDir
model {
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
applicationId "com.myapp.android"
minSdkVersion.apiLevel 9
targetSdkVersion.apiLevel 23
versionCode 1
versionName "1.0"
}
sources {
main {
manifest {
source {
srcDirs = ["$buildDir"]
}
}
}
}
}
}
task createManifest {
doLast {
buildDir.mkdirs()
new FileOutputStream(new File(buildDir, "AndroidManifest.xml"))
}
}
tasks.all { task ->
if (task.name.startsWith('check') && task.name.endsWith('Manifest')) {
task.dependsOn createManifest
}
}
The above configures fine but when I try to build I get:
A problem was found with the configuration of task ':app:checkDebugManifest'.
> File '/home/the_jk/source/test/app/src/main/AndroidManifest.xml' specified for property 'manifest' does not exist.`
I cannot seem to change the default manifest "property" at all, anyone had any luck?
Try
manifest { source { srcDirs = $buildDir } }
This seems to do ok with just 1 manifest, but the experimental plugin barfs if you give it 2 directories that you want to merge.
(I'm also guessing you have some other task to generate the manifest in the $buildDir since that gets blown away by clean tasks....)
Update:
Secondary issue, the check[Deubg|Release]Manifest task wants the file to exist when it runs. The above works ok for me for a static file. For something generated from a task in the build directory I had to add a dependency that looks like
task createManifest {
// code to create $buildDir if it did not exist
// code to generate AndrdroidManfest.xml in $buildDir
}
tasks.all {
task ->
if (task.name.startsWith('check') && task.name.endsWith('Manifest')) {
task.dependsOn createManifest
}
}
The tasks.all loop lets me only add it if checkDebugManifest and/or checkReleaseManifest tasks are going to happen (I had trouble with ./gradlew clean not finding the checkDebugManifest task without it.)
I had a similar issue with com.android.tools.build:gradle-experimental:0.7.0-alpha4. The way I solve it was with the following code:
sources {
main {
manifest {
source {
srcDirs = [ "$buildDir" ]
}
}
}
}

Resources