Resolving third party cocoapod dependencies in Kotlin MPP - kotlin

I am trying to setup a tracking library written in Kotlin Multiplatform to support all our mobile clients.
Tests for Android went well (integrating snowplow via gradle).
I also managed to integrate Snowplow via cocoapods into the MPP.
kotlin {
...
cocoapods {
...
pod("SnowplowTracker") {
version = "~> 1.3.0"
}
}
And wrote following class in the X64 sourceset:
import cocoapods.SnowplowTracker.*
import com.tracking.domain.model.*
class X64Tracker {
private val tracker: SPTracker
init {
val emitter = SPEmitter.build {
it?.setUrlEndpoint("our.staging.endpoint")
it?.setProtocol(SPProtocol.SPProtocolHttps)
}
tracker = SPTracker.build {
emitter?.let { spEmitter -> it?.setEmitter(spEmitter) }
it?.setBase64Encoded(false)
it?.setSubject(SPSubject(platformContext = true, andGeoContext = true))
it?.setAppId("MPP.test")
it?.setApplicationContext(true)
}
}
fun trackSomething() {
track(
eventData = getEventData(
name = "MPP_test_event",
appArea = EventArea.Lifecycle,
action = EventAction.Check,
objectType = EventObjectType.Device,
source = EventSource.Client,
screenName = EventScreenName.Congratulations,
), contexts = emptyList()
)
}
private fun track(eventData: SPSelfDescribingJson, contexts: List<SPSelfDescribingJson?>) {
try {
val yo = SPSelfDescribing.build {
it?.setEventData(eventData)
it?.setContexts(contexts.toMutableList())
}
tracker.track(yo)
} catch (e: IllegalStateException) {
print("snowplow was not yet initialized when the following event occurred: $eventData")
}
}
private fun getEventData(
name: String,
appArea: EventArea,
action: EventAction,
objectType: EventObjectType,
source: EventSource,
screenName: EventScreenName,
) = SPSelfDescribingJson(
SCHEMA_EVENT, mapOf(
PROPERTY_EVENT_NAME to name,
PROPERTY_APP_AREA to appArea.serializedName,
PROPERTY_ACTION to action.serializedName,
PROPERTY_OBJECT_TYPE to objectType.serializedName,
PROPERTY_SOURCE to source.serializedName,
PROPERTY_SCREEN_NAME to screenName.serializedName,
)
)
}
Which is compiling and building our .framework files fine. Here is how we do that:
tasks.register<org.jetbrains.kotlin.gradle.tasks.FatFrameworkTask>("debugFatFramework") {
baseName = frameworkName + "_sim"
group = "Universal framework"
description = "Builds a universal (fat) debug framework"
from(iosX64.binaries.getFramework("DEBUG"))
}
tasks.register<org.jetbrains.kotlin.gradle.tasks.FatFrameworkTask>("releaseFatFramework") {
baseName = frameworkName + "_arm"
group = "Universal framework"
description = "Builds a universal (release) debug framework"
from(iosArm64.binaries.getFramework("RELEASE"))
}
Afterwards we combine this into an .xcframework file using following command:
xcrun xcodebuild -create-xcframework \
-framework tracking_arm.framework \
-framework tracking_sim.framework \
-output tracking.xcframework
We use Carthage to integrate it into our main app, but as soon I try to build the iOS project following error pops up:
Undefined symbols for architecture x86_64:
"_OBJC_CLASS_$_SPSelfDescribing", referenced from:
objc-class-ref in tracking_sim(result.o)
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
The weird thing: No matter which version of Snowplow I define in the cocoapods block - the syntax in my class does not need to change. Even updating to Snowplow 2.x doesn't require me to get rid of the SP prefixes.
I am not sure how to continue at all and appreciate any help.

The following:
Undefined symbols for architecture x86_64:
"_OBJC_CLASS_$_SPSelfDescribing", referenced from:
objc-class-ref in kaiaTracking_sim(result.o)
ld: symbol(s) not found for architecture x86_64
That says the linker can't find SPSelfDescribing. I assume SPSelfDescribing is part of SnowplowTracker. You'll need to add SnowplowTracker to the iOS app.
If you were using Cocoapods to integrate Kotlin into your iOS project, the podspec generated would include SnowplowTracker as a dependency.
Since you are not using Cocoapods, you need to include it yourself. We recently had to figure out Carthage for a client. I would highly recommend moving to SPM or Cocoapods for a number of reasons, but that's a different discussion. To add SnowplowTracker with Carthage, add this to your Cartfile:
github "snowplow/snowplow-objc-tracker" ~> 1.3
Then when you update Carthage, add that to your iOS project. The linker will be able to find it.
To be clear, the Undefined symbols for architecture x86_64 error isn't complaining about Kotlin. It's saying it can't find SnowplowTracker, which you need to add to the iOS project.

Related

React Native: Could not find method isNewArchitectureEnabled() for arguments on project :app

Trying to run React native project on my new device Macbook Pro got following error.
My Device specification:
Note: The project was running properly in my previous intel chip device.
Got stuck with an error and could not found the proper solution, any help and suggestion will be very helpful. Thanks in advance.
If the project use React native version < 0.68.0, you consider first migrate the whole project to the newer like >= 0.68.
Run the following command to start the process of upgrading to the latest version:
npx react-native upgrade
You may specify a React Native version by passing an argument, e.g. to upgrade to 0.XX.X
documentation here link
if you have a react native version >= 0.68.0 go to the
app/build.gradle
and you need to change many things like:
old version
...
/*** Architectures to build native code for in debug.
*/
def nativeArchitectures = project.getProperties().get("reactNativeDebugArchitectures")
new version
/*** Architectures to build native code for in debug.
*/
def reactNativeArchitectures() {
def value = project.getProperties().get("reactNativeArchitectures")
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}
...
defaultConfig {
...
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
if (isNewArchitectureEnabled()) {
// We configure the CMake build only if you decide to opt-in for the New Architecture.
externalNativeBuild {
cmake {
arguments "-DPROJECT_BUILD_DIR=$buildDir",
"-DREACT_ANDROID_DIR=$rootDir/../node_modules/react-native/ReactAndroid",
"-DREACT_ANDROID_BUILD_DIR=$rootDir/../node_modules/react-native/ReactAndroid/build",
"-DNODE_MODULES_DIR=$rootDir/../node_modules",
"-DANDROID_STL=c++_shared"
}
}
if (!enableSeparateBuildPerCPUArchitecture) {
ndk {
abiFilters (*reactNativeArchitectures())
}
}
}
if (isNewArchitectureEnabled()) {
// We configure the NDK build only if you decide to opt-in for the New Architecture.
externalNativeBuild {
cmake {
path "$projectDir/src/main/jni/CMakeLists.txt"
}
}
def reactAndroidProjectDir = project(':ReactAndroid').projectDir
def packageReactNdkDebugLibs = tasks.register("packageReactNdkDebugLibs", Copy) {
dependsOn(":ReactAndroid:packageReactNdkDebugLibsForBuck")
from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib")
into("$buildDir/react-ndk/exported")
}
def packageReactNdkReleaseLibs = tasks.register("packageReactNdkReleaseLibs", Copy) {
dependsOn(":ReactAndroid:packageReactNdkReleaseLibsForBuck")
from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib")
into("$buildDir/react-ndk/exported")
}
afterEvaluate {
// If you wish to add a custom TurboModule or component locally,
// you should uncomment this line.
// preBuild.dependsOn("generateCodegenArtifactsFromSchema")
preDebugBuild.dependsOn(packageReactNdkDebugLibs)
preReleaseBuild.dependsOn(packageReactNdkReleaseLibs)
// Due to a bug inside AGP, we have to explicitly set a dependency
// between configureCMakeDebug* tasks and the preBuild tasks.
// This can be removed once this is solved: https://issuetracker.google.com/issues/207403732
configureCMakeRelWithDebInfo.dependsOn(preReleaseBuild)
configureCMakeDebug.dependsOn(preDebugBuild)
reactNativeArchitectures().each { architecture ->
tasks.findByName("configureCMakeDebug[${architecture}]")?.configure {
dependsOn("preDebugBuild")
}
tasks.findByName("configureCMakeRelWithDebInfo[${architecture}]")?.configure {
dependsOn("preReleaseBuild")
}
}
}
}
splits {
abi {
...
include (*reactNativeArchitectures())
}
}
dependencies {
...
}
if (isNewArchitectureEnabled()) {
// If new architecture is enabled, we let you build RN from source
// Otherwise we fallback to a prebuilt .aar bundled in the NPM package.
// This will be applied to all the imported transtitive dependency.
configurations.all {
resolutionStrategy.dependencySubstitution {
substitute(module("com.facebook.react:react-native"))
.using(project(":ReactAndroid"))
.because("On New Architecture we're building React Native from source")
substitute(module("com.facebook.react:hermes-engine"))
.using(project(":ReactAndroid:hermes-engine"))
.because("On New Architecture we're building Hermes from source")
}
}
}
...
def isNewArchitectureEnabled() {
// To opt-in for the New Architecture, you can either:
// - Set `newArchEnabled` to true inside the `gradle.properties` file
// - Invoke gradle with `-newArchEnabled=true`
// - Set an environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true`
return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" }
I hope this help you, for more information you can check the upgrade helper tool link
cheers ヽ( •_)ᕗ

Unable to resolve cinterop IOS import in Kotlin Multiplatform

I have followed the Kotlin documentation for adding iOS dependencies. In my case the dependency is a pre-compiled framework provided through a third party. So I have followed the case for framework without cocoapod.
I placed my MyFramework.def file in /src
language = Objective-C
modules = MyFramework
package = MyFramework
Then I added the following to the build.gradle.kts in the Kotlin object
```
ios {
binaries {
framework {
baseName = "shared"
}
}
}
iosArm64() {
compilations.getByName("main") {
val JWBLe by cinterops.creating {
// Path to .def file
defFile("src/nativeInterop/cinterop/MyFramework.def")
compilerOpts("-framework", "MyFramework", "-F/Users/user/Projects/MyFramework/ios/SDK")
}
}
binaries.all {
// Tell the linker where the framework is located.
linkerOpts("-framework", "MyFramework", "-F/Users/user/Projects/MyFramework/ios/SDK")
}
}
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
}
Then I build the project. The library does indeed get seen and I see that in External Libraries, there is a shared-cinterop-MyFramework.klib
However, when I try to import this package into my code under src/iosMain/kotlin/com.example.testapp.shared/platform.kt
I get unresolved error for the library. It seems like I should also need to add something to sourceSets? But I am unsure.
First of all, I got to notice that the Gradle script is incorrect. In this case, the iosArm64 target was declared twice - by the target shortcut and once again where you configure the cinterop. To avoid this duplication, it would be better to configure cinterop like that:
ios()
val iosArm = targets.getByName("iosArm64") as org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
// A bit dirty cast, but as I'm sure iosArm64 is the Native target, it should be fine. Needed to make highlighting below work as expected.
iosArm.apply {
compilations.getByName("main") {
val JWBLe by cinterops.creating {
// Path to .def file
defFile("src/nativeInterop/cinterop/MyFramework.def")
compilerOpts("-framework", "MyFramework", "-F/Users/user/Projects/MyFramework/ios/SDK")
}
}
binaries.all {
// Tell the linker where the framework is located.
linkerOpts("-framework", "MyFramework", "-F/Users/user/Projects/MyFramework/ios/SDK")
}
}
However, this adjustment won't help with accessing cinterop bindings from the iosMain. In the current state of commonizer, it can share only platform libraries. So anyway, moving all code utilizing those bindings into the src/iosArm64Main folder is the best option available at the moment. Here go an issue from the official tracker to upvote and subscribe - Support commonization of user-defined libraries.
So after some playing around I found the answer.
The dependency was set for a module of iosArm64 which is not available to the iosMain.
I created another folder src/iosArm64Main and placed the source file there. At that point it was able to resolve the library.

Kotlin/Native compileKotlinIosX64 task fails when building iOS app

I have a Kotlin Multiplatform project. I recently updated to Kotlin 1.4-M2 (I need it to solve some issues with Ktor).
After updating all the required libraries, resolving all gradle issues and having my Android project compile successfully, I now encounter the following error when building the iOS app:
Task :shared:compileKotlinIosX64
e: Compilation failed: Could not find declaration for unbound symbol org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionPublicSymbolImpl#56f11f08
* Source files: [all shared folder kt files]
* Compiler version info: Konan: 1.4-M2 / Kotlin: 1.4.0
* Output kind: LIBRARY
e: java.lang.IllegalStateException: Could not find declaration for unbound symbol org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionPublicSymbolImpl#56f11f08
at org.jetbrains.kotlin.ir.util.ExternalDependenciesGeneratorKt.getDeclaration(ExternalDependenciesGenerator.kt:76)
The curious thing is that in Source files it shows all the files in the shared code folder. I checked and absolutely all kt files appear in there. So my guess is that it is some issue when building the shared code, but does not seem specific of any library.
This is a slightly reduced version of how my build.gradle.kts looks like:
plugins {
kotlin("multiplatform")
kotlin("native.cocoapods")
id("kotlinx-serialization")
id("com.android.library")
id("io.fabric")
}
// CocoaPods requires the podspec to have a version.
version = "1.0"
tasks {
withType<KotlinCompile> {
kotlinOptions {
jvmTarget = "1.8"
}
}
}
kotlin {
ios()
android()
cocoapods {
// Configure fields required by CocoaPods.
summary = "Some description for a Kotlin/Native module"
homepage = "Link to a Kotlin/Native module homepage"
}
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-common")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime:$serializationVersion")
api("org.kodein.di:kodein-di:7.1.0-kotlin-1.4-M3-84")
implementation("io.mockk:mockk:1.9.2")
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
api("com.russhwolf:multiplatform-settings:$multiplatformSettingsVersion")
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-json:$ktorVersion")
implementation("io.ktor:ktor-client-logging:$ktorVersion")
implementation("io.ktor:ktor-client-serialization:$ktorVersion")
}
}
}
}
And the library versions are as follows:
val ktorVersion = "1.3.2-1.4-M2"
val kotlinVersion = "1.4-M2"
val coroutinesVersion = "1.3.7-native-mt-1.4-M2"
val serializationVersion = "0.20.0-1.4-M2"
val multiplatformSettingsVersion = "0.6-1.4-M2"
It's worth mentioning this was building correctly in iOS when using 1.3.72.
As #KevinGalligan suggested, I updated Kotlin and all related libs to 1.4.0-rc and the problem was solved.
The root issue with 1.4-M2 remains unknown.

Kotlin native interop linker could not find framework

I'm trying to use cocoapods framework in Kotlin Multiplatform project.
So I
added framework to Pods file.
ran pod install.
created .def file
added cinterop config in build.gradle
./gradlew cinteropFirebaseIos runs successfully. It generates .klib so I can see classes in kotlin code.
But when I'm trying to run iOS app build fails with message:
Showing Recent Messages
> Task :app:linkDebugFrameworkIos
ld: framework not found FirebaseDatabase
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld invocation reported errors
Here is my config in build.gradle
fromPreset(presets.iosX64, 'ios') {
compilations.main {
outputKinds('FRAMEWORK')
cinterops {
firebase {
def proj = "${System.getProperty("user.home")}/Projects/kmpp"
def pods = "${proj}/iosApp/Pods"
defFile "${proj}/app/src/iosMain/c_interop/libfirebase.def"
includeDirs "${pods}/Firebase",
"${pods}/Firebase/CoreOnly/Sources",
"${pods}/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/Headers"
}
}
}
}
here is my .def file:
language = Objective-C
headers = /Users/oleg/Projects/klug/crckalculator/iosApp/Pods/FirebaseCore/Firebase/Core/Public/FIRApp.h /Users/oleg/Projects/klug/crckalculator/iosApp/Pods/FirebaseDatabase/Firebase/Database/Public/FIRDatabase.h /Users/oleg/Projects/klug/crckalculator/iosApp/Pods/FirebaseCore/Firebase/Core/Public/FirebaseCore.h
compilerOpts = -framework FirebaseDatabase
linkerOpts = -framework FirebaseDatabase
How can I figure out what is wrong ? Did I miss something in .def file ? In build.gradle ?
There are two problematic moments here:
full paths to C headers in .def file are usually not desirable, instead passing includeDirs to Firebase installation, like in https://github.com/JetBrains/kotlin-native/blob/c7c566ce0f12221088a8908b6dc8e116c56a931b/samples/gtk/build.gradle#L22 would be helpful
linking problem comes from the similar issue - linker just got no idea where to look for framework libraries, so passing to compilations.main.linkerOpts smth like -F /Users/oleg/Projects/klug/crckalculator/iosApp/Pods/FirebaseCore/ shall help, see for example https://github.com/JetBrains/kotlin-native/blob/c7c566ce0f12221088a8908b6dc8e116c56a931b/samples/videoplayer/build.gradle#L15

Trying to use Swift with Obj C header for Coda2 plugin. Getting error: Undefined symbols for architecture x86_64

Trying to build a PlugIn for Coda 2.5 with swift.I'm getting this error:
Undefined symbols for architecture x86_64:
"_OBJC_CLASS_$_CodaPlugInsController", referenced from:
_get_field_types_PowPlugInViewController in PowPlugInViewController.o
_get_field_types_PowPlugIn in PowPlugin.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
I have cleaned my build folder.
I have created and added import statements to the project name-bridging-swift.h.
Here is a look at my project.
CodaPlugInsController.h
You can find this file here.
https://github.com/panicinc/CodaPluginKit/tree/master/Cocoa%20Plug-ins
Discription:
This header provides protocols and facilities to implement Coda
text-based, syntax validator and sidebar plug-in.
CodaPow-Bridging-Header.h
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "CodaPlugInsController.h"
PowPlugin.swift
import Foundation
class PowPlugIn: NSObject, CodaPlugIn, CodaSidebarPlugIn {
let PowBundle: CodaPlugInBundle
let PowController: CodaPlugInsController
required init(plugInController: CodaPlugInsController, plugInBundle: CodaPlugInBundle) {
self.PowBundle = plugInBundle
self.PowController = plugInController
super.init()
}
func name() -> String {
return "Coda Pow"
}
func didLoadSiteNamed(name: String!) {
}
func viewController() -> NSViewController {
return PowPlugInViewController(nibName: "PowPlugInView", plugInBundle: PowBundle, plugInController: PowController)!
}
}
PowPlugInViewController.swift
import Foundation
class PowPlugInViewController: NSViewController, CodaSidebarViewController {
let PowController: CodaPlugInsController
init?(nibName: String, plugInBundle: AnyObject, plugInController: CodaPlugInsController) {
self.PowController = plugInController
super.init(nibName: nibName, bundle: plugInBundle as? NSBundle)
}
// Xcode Says I need this.
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I have two more source files.
ServerAndHosts.swift and
Shell.swift They don't use any of the classes in CodaPlugInsController.h. The Shell.swift file is just a set of Class functions.
Edit:
I can use swift as long as I don't subclass from the CodaPlugInsController.h or pass in an object that has been.
Swift and Obj-C versions of Project: https://www.dropbox.com/sh/bsw8cinn7kp9kfe/AAAgG_B44dbHNP5-JwJ3Amf8a?dl=0
The project compiles, so the bridging headers may not be the issue.
You fail at the linking stage, the compiler can't find a set of symbols for your current architecture.
This could either mean
1) The Coda library is not compiled for your architecture
or
2) the linker couldn't find the library at all.
I looked at your Obj-C project, and it appears the Coda library is not in the project, so I'm guessing its #2. You'll either want to move the lib into the project somehow, or add a path to the lib elsewhere on your system in your project's "library search paths"