Kotlin/Native adding c library ncurses via Cinterop - kotlin

I have been trying to add ncurses to Kotlin/Native using cinterop, but this error shows up:
Exception in thread "main" java.lang.Error: /usr/include/stdint.h:26:10: fatal error: 'bits/libc-header-start.h' file not found
I checked and I have this file installed in this path.
this is my code:
src/nativeInterop/cinterop:
headers = ncurses.h
headerFilter = ncurses.h
compilerOpts.linux = -I/usr/include -I/usr/include
linkerOpts.linux = -L/usr/lib64 -L/usr/lib/x86_64-linux-gnu -lncurses
build.gradle.kts
plugins {
kotlin("multiplatform") version "1.6.10"
}
group = "org.example"
version = "1.0-SNAPSHOT"
kotlin {
val hostOs = System.getProperty("os.name")
val isMingwX64 = hostOs.startsWith("Windows")
val nativeTarget = when {
hostOs == "Mac OS X" -> macosX64("native")
hostOs == "Linux" -> linuxX64("native")
isMingwX64 -> mingwX64("native")
else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
}
nativeTarget.apply {
compilations["main"].cinterops {
val ncurses by creating {
when(preset) {
presets["linuxX64"] -> includeDirs.headerFilterOnly("/usr/include", "/usr/include/x86_64-linux-gnu")
}
}
}
binaries {
executable {
entryPoint = "main"
}
}
}
sourceSets {
val nativeMain by getting
val nativeTest by getting
}
}

It looks like your include filter might be too strict. Try adding the parent directory of that header file to your cinterop file. On my Ubuntu install, it's in /usr/include/x86_64-linux-gnu, but that's not listed in your compilerOpts.
Another note -- is there a reason to have -I/usr/include listed twice in your file?

Related

How to add a dependency to a native library from a kotlin native

For example, I know that there is CoreCrypto for kotlin native. How to modify kotlin gradle script so the following code compiles:
import kotlinx.cinterop.*
import platform.CoreCrypto.CC_SHA256 // compile error, obviously, as there's no depenedncy
import platform.CoreCrypto.CC_SHA256_DIGEST_LENGTH
fun main() {
// ...
}
My gradle script:
plugins {
kotlin("multiplatform") version "1.6.10"
}
group = "com.jaga"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
kotlin {
val hostOs = System.getProperty("os.name")
val isMingwX64 = hostOs.startsWith("Windows")
val nativeTarget = when {
hostOs == "Mac OS X" -> macosX64("native")
hostOs == "Linux" -> linuxX64("native")
isMingwX64 -> mingwX64("native")
else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
}
nativeTarget.apply {
binaries {
executable {
entryPoint = "main"
}
}
}
sourceSets {
val nativeMain by getting
val nativeTest by getting
}
}
I've been searching for a solution literally for hours now.
Docs: https://kotlinlang.org/docs/native-platform-libs.html
There are examples here: https://github.com/JetBrains/kotlin/tree/master/kotlin-native/samples
But they are not self-contained and handle dependencies in some complicated generic way which I cannot reproduce in a single gradle script.
EDIT: Actually, even those samples fail to build so they aren't useful as a reference...
I understand the frustration; it took me hours to get curl working with a custom build. I agree that the official documentation isn't helpful and is outdated, so use a reference project like Ktor instead.
Ktor GitHub for curl interop (cinterop reference, build.gradle.kts, and real usages in code)

Kotlin Multiplatform. Cannot access class SqlDriver.Schema. Check your module classpath for missing or conflicting dependencies

I am trying to build a KMP library targeting iOS, Android, JS(Browser), Mac, Windows and Linux. For now I am only using Ktor and SQLDelight as a dependency. But getting the following issue in nativeMain's actual implementation while creating driver for SQLDelight
While the same code doesn't give any issue for iOS main which is also using the same NativeSqliteDriver (I need them separately since Ktor client for iOS and desktop platforms are separate).
Following is my build.gradle.kts
plugins {
kotlin("multiplatform") version "1.5.31"
id("maven-publish")
id("com.android.library")
kotlin("plugin.serialization") version "1.5.31"
id("com.squareup.sqldelight") version "1.5.3"
}
group = "me.group"
version = "1.0-SNAPSHOT"
val xcFrameworkName = "AddressLib"
repositories {
google()
mavenCentral()
}
kotlin {
jvm {
compilations.all {
kotlinOptions.jvmTarget = "1.8"
}
testRuns["test"].executionTask.configure {
useJUnit()
}
}
js(LEGACY) {
browser {
commonWebpackConfig {
cssSupport.enabled = true
}
}
}
val xcFramework = XCFramework(xcFrameworkName)
val hostOs = System.getProperty("os.name")
val isMingwX64 = hostOs.startsWith("Windows")
when {
hostOs == "Mac OS X" -> macosX64("native") {
binaries.framework(xcFrameworkName) {
xcFramework.add(this)
}
}
hostOs == "Linux" -> linuxX64("native")
isMingwX64 -> mingwX64("native")
else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
}
android()
ios {
binaries.framework(xcFrameworkName) {
xcFramework.add(this)
}
}
val coroutinesVersion = "1.5.2-native-mt"
val serializationVersion = "1.3.1"
val ktorVersion = "1.6.5"
val sqlDelightVersion = "1.5.3"
val napierVersion = "2.2.0"
val koinVersion = "3.1.4"
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:$serializationVersion")
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-serialization:$ktorVersion")
implementation("io.ktor:ktor-client-logging:$ktorVersion")
implementation("com.squareup.sqldelight:runtime:$sqlDelightVersion")
implementation("io.insert-koin:koin-core:$koinVersion")
implementation("io.github.aakira:napier:$napierVersion")
}
}
val commonTest by getting
val jvmMain by getting {
dependencies {
implementation("io.ktor:ktor-client-java:$ktorVersion")
implementation("com.squareup.sqldelight:sqlite-driver:$sqlDelightVersion")
}
}
val jvmTest by getting
val jsMain by getting {
dependencies {
implementation("io.ktor:ktor-client-js:$ktorVersion")
implementation("com.squareup.sqldelight:sqljs-driver:$sqlDelightVersion")
}
}
val jsTest by getting
val nativeMain by getting {
dependencies {
implementation("io.ktor:ktor-client-curl:$ktorVersion")
implementation("com.squareup.sqldelight:native-driver:$sqlDelightVersion")
}
}
val nativeTest by getting
val androidMain by getting {
dependencies {
implementation("io.ktor:ktor-client-android:$ktorVersion")
implementation("com.squareup.sqldelight:android-driver:$sqlDelightVersion")
}
}
val androidTest by getting {
dependencies {
implementation(kotlin("test-junit"))
implementation("junit:junit:4.13.2")
}
}
val iosMain by getting {
dependencies {
implementation("io.ktor:ktor-client-ios:$ktorVersion")
implementation("com.squareup.sqldelight:native-driver:$sqlDelightVersion")
}
}
val iosTest by getting
}
sqldelight {
database("AddressDatabase") {
packageName = "com.library.address.database"
}
}
}
android {
compileSdkVersion(31)
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdkVersion(24)
targetSdkVersion(31)
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}
publishing {
repositories {
maven {
credentials {
username = "<username>"
password = "<pwd>"
}
url = URI("https://mymavenrepo.com")
}
}
}
So it seems the issue was somewhat due to same dependency being added to the build gradle twice and it's corresponding code being added twice as well. To solve the same I had to make a separate source set like the following
val sqlDriverNativeMain by creating {
dependsOn(commonMain)
dependencies {
implementation("com.squareup.sqldelight:native-driver:$sqlDelightVersion")
}
}
val iosMain by getting {
dependsOn(sqlDriverNativeMain)
dependencies {
implementation("io.ktor:ktor-client-ios:$ktorVersion")
}
}
val nativeMain by getting {
dependsOn(sqlDriverNativeMain)
dependencies {
implementation("io.ktor:ktor-client-curl:$ktorVersion")
}
}
and after that move the driver creation code inside the sourceSet directory named sqlDriverNativeMain. This resolved the issue.

Kotlin/Native can't import io.ktor.network.selector.ActorSelectorManager

I want to use TCP Sockets in Kotlin/Native using KTOR and I followed the according tutorial. But somehow I can't import ActorSelectorManager from io.ktor.network.selector and get Unresolved reference: ActorSelectorManager
My build.gradle.kts looks like this:
plugins {
kotlin("multiplatform") version "1.4.32"
kotlin("plugin.serialization") version "1.4.32"
}
repositories {
mavenCentral()
jcenter()
maven { url = uri("https://plugins.gradle.org/m2/") }
maven { url = uri("http://dl.bintray.com/kotlin/kotlin-dev") }
maven { url = uri("https://kotlin.bintray.com/kotlinx") }
}
kotlin {
val hostOs = System.getProperty("os.name")
val isMingwX64 = hostOs.startsWith("Windows")
val target = when {
hostOs == "Mac OS X" -> macosX64("SocketStuff")
hostOs == "Linux" -> linuxX64("SocketStuff")
isMingwX64 -> mingwX64("SocketStuff")
else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
}
target.apply {
binaries {
executable {
entryPoint = "main"
}
}
}
sourceSets {
val Main by getting
val Test by getting
commonMain {
sourceSets["commonMain"].dependencies {
implementation("io.ktor:ktor-network:1.5.3")
}
}
}
}
Does anyone know if I forgot to add a dependency or something?
There are the ActorSelectorManager on the JVM and the WorkerSelectorManager on the native target. You can use constructor function SelectorManager() to instantiate appropriate selector manager instance:
import io.ktor.network.selector.*
fun main() {
val manager = SelectorManager()
}

mock common tests in kotlin using multiplatform

I can't use the common mock library ( mockk.io ), with kotlin multiplatform. In their website it says that to use mockk in kotlin multiplatform you just need to add this line to your gradle. testImplementation "io.mockk:mockk-common:{version}"
I added it and it builds normally, only when I want to use it is when it fails. Giving
Unresolved reference: io
Unresolved reference: mockk
my gradle file
kotlin {
val hostOs = System.getProperty("os.name")
val isMingwX64 = hostOs.startsWith("Windows")
val nativeTarget = when {
hostOs == "Mac OS X" -> macosX64("native")
hostOs == "Linux" -> linuxX64("native")
isMingwX64 -> mingwX64("native")
else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
}
nativeTarget.apply {
binaries {
executable {
entryPoint = "main"
}
}
}
sourceSets {
val nativeMain by getting
val nativeTest by getting
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
implementation("io.mockk:mockk-common:1.10.4")
}
}
}
}
Unless something has changed, mockk does not work on Kotlin Native.
You can use Mockative to mock interfaces in Kotlin/Native and Kotlin Multiplatform, not unlike how you'd mock dependencies using MockK or Mockito.
Full disclosure: I am one of the authors of Mockative
Here's an example:
class GitHubServiceTests {
#Mock val api = mock(classOf<GitHubAPI>())
val service = GitHubService(api)
#Test
fun test() {
// Given
val id = "mockative/mockative"
val mockative = Repository(id = id, name = "Mockative")
given(api).invocation { fetchRepository(id) }
.thenReturn(mockative)
// When
val repository = service.getRepository(id)
// Then
assertEquals(mockative, repository)
// You can also verify function calls on mocks
verify(api).invocation { fetchRepository(id) }
.wasInvoked(exactly = once)
}
}

Kotlin cross-compilation for multiple targets in single build run

Default Kotlin native project gradle configuration looks like:
plugins {
kotlin("multiplatform") version "1.4.10"
}
group = "org.example"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
kotlin {
val hostOs = System.getProperty("os.name")
val isMingwX64 = hostOs.startsWith("Windows")
val nativeTarget = when {
hostOs == "Mac OS X" -> macosX64("native")
hostOs == "Linux" -> linuxX64("native")
isMingwX64 -> mingwX64("native")
else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
}
nativeTarget.apply {
binaries {
executable {
entryPoint = "main"
}
}
}
sourceSets {
val nativeMain by getting
val nativeTest by getting
}
}
Such configuration can produce binaries only for single target. How can be the configuration adjusted so that single build will produce 3 binary for all mentioned targets: Windows, Linux and MacOS?
You can just set a number of targets, and then running the assemble task will produce binaries for all available on your host machine. It is important because one cannot create a binary for macOS anywhere but on macOS host, Windows target also has some complex preparations(see this issue). Also, there could be some problem with source sets - if their contents are identical, maybe it worth connecting them as in the example below:
kotlin {
macosX64("nativeMac")
linuxX64("nativeLin")
mingwX64("nativeWin")
targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget> {
binaries {
executable {
entryPoint = "main"
}
}
}
sourceSets {
val nativeMacMain by getting //let's assume that you work on Mac and so put all the code here
val nativeLinMain by getting {
dependsOn(nativeMacMain)
}
}
}