Kotlin/native cinterop build problem with gradle - ios

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.

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 multiplatform project depending on another one with cocoapods

I have a kotlin multipltform library which has several cocoapods
cocoapods {
....
pod("gRPC/GRPCCore", grpcVersion)
pod("gRPC-ProtoRPC", grpcVersion)
pod("Protobuf", "3.15.8")
// etc
}
I published this library to artifactory (maven) and the ios target contains all the klib's for the cinterop'ed pods.
I have a second Kotlin Multiplatform Library where I wish to consume the previous "core" library.
iOSTarget("ios") {
binaries {
framework {
baseName = "SecondSharedLib"
export("first.shared.lib:1.0.0")
xcf.add(this)
}
}
}
sourceSets {
val commonMain by getting {
dependencies {
api("first.shared.lib:1.0.0")
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
}
val androidMain by getting {
dependencies {
}
}
val androidTest by getting {
dependencies {
implementation("junit:junit:4.13.2")
}
}
val iosMain by getting {
dependencies {
}
}
val iosTest by getting
}
When linking ios however the pod modules are not found.
ex: ld: framework not found gRPC_ProtoRPC
I also tried redeclaring the pods to no avail.
Is what I am trying to do even possible? does anyone have any suggestions?
Note: I am not an iOS developer so please be harsh if my understanding is off
For anyone dealing with a nested multiplatform library where the base library relies on the cocoapods plugin, I had to link all the frameworks via the linker opts as well as redefine all the cocoapods so that the frameworks would be available for linking.
have you tried below code instead when referencing another KMM project? Below code snippet is from Kotlin's documentation
kotlin {
sourceSets["commonMain"].dependencies {
implementation(project(":some-other-multiplatform-module"))
}
sourceSets["androidMain"].dependencies {
//platform part of :some-other-multiplatform-module will be added automatically
}
}

How to make dependency project visible for Ios app?

I have a kotlin multiplatform project MusicFeature with targets for ios, android, common with the following build.gradle
sourceSets {
commonMain {
dependencies {
implementation(project(":ProjectUtils"))
}
}
androidMain {
}
iosMain {
}
}
ProjectUtils is also a multiplatform project.
There's no troubles in using ProjectUtils code from MusicFeature. But when I export MusicFeature as a framework for iOS, I don't have access to classes from ProjectUtils.
I guess you are looking for transitiveExport = true for your framework.
binaries {
framework {
export project(':dependency')
// Export transitively.
transitiveExport = true
}
}
You can find more reference here in export dependencies to binaries.

Kotlin Multiplatform Library: Unable to generate .framework for 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.

Resources