Troubleshooting

Configuration errors

Please set 'kotlin.mpp.enableCInteropCommonization=true' in gradle.properties: you have a Kotlin/Native target. See Getting started.

Please call either 'generateFromLibrary' or 'generateFromUdl'.: the uniffi { } block needs one of the two. See Proc-macros and UDL.

Kotlin Multiplatform Plugin is required: apply kotlin("multiplatform") in the same module.

Unhandled target: ...: the module declares a Kotlin target the plugin can't build Rust for. See Targets for the supported list.

Unsupported Android ABI(s): ...: androidDebugAbis or -PandroidAbis contains something other than arm64-v8a, armeabi-v7a or x86_64.

Build errors

NDK not found: install an NDK through the SDK manager, or set ANDROID_NDK_ROOT, or pin an installed version with cargo { ndkVersion = "..." }. See Targets.

Missing Rust target: the plugin runs rustup target add automatically. If that fails, for example because rustup is not available, install the target yourself.

Kotlin/Native link errors in Rust code (unknown relocations, unsupported object file features): Kotlin/Native's bundled linker is older than your Rust toolchain. Use useRustUpLinker() for the affected compilation.

Unresolved references to cinterop.* in the generated nativeMain code: cinterop commonization is off, or the IDE hasn't run a sync since it was turned on.

Runtime errors

UniFFI contract version mismatch or UniFFI API checksum mismatch (JVM and Android): the bindings were generated for a different library than the one that was loaded. Usually stale build outputs, or a generator that doesn't match the runtime. Run a clean build. If you changed the generator source, make sure it matches your plugin version.

IllegalStateException: ... object has already been destroyed: a method was called after destroy(), close(), or after the end of a use { } block.

InternalException with a Rust panic message: the Rust code panicked. See Errors.

UnsatisfiedLinkError / Unable to load library on JVM: the library for the current platform is not on the classpath. Debug JVM builds only include the host platform. Build with -PreleaseBuild=true to include all of them.

Crashes or memory corruption when passing strings, records or lists to Rust: check whether one of your crates, or a dependency, sets a #[global_allocator]. That isn't supported, see Requirements.

The process aborts (exit code 134) when Rust calls a Kotlin callback: most likely a Kotlin implementation of a trait from another module's crate was passed across modules. See Multi-module projects and #35.

Swift interop with spmForKmp

On Apple targets this plugin works together with spmForKmp, which lets Kotlin call Swift code through cinterop. Use spmForKmp 1.9.5 or newer. Older versions configured their cinterop in a way that conflicted with this plugin's cinterop.

plugins {
    kotlin("multiplatform")
    id("io.github.frankois944.spmForKmp") version "1.9.5"
    id("ch.ubique.uniffi.plugin")
}

kotlin {
    listOf(iosArm64(), iosSimulatorArm64()).forEach { target ->
        target.swiftPackageConfig(cinteropName = "swiftGreeter") {
            minIos = "16.4"
        }
    }
}

If you're stuck on spmForKmp older than 1.9.5, add this workaround to the build script (spm4Kmp#326). It points this plugin's cinterop back at its own def file after spmForKmp has reconfigured it:

import ch.ubique.uniffi.plugin.tasks.GenerateDefFileTask
import ch.ubique.uniffi.plugin.tasks.GenerateDummyDefFileTask
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget

afterEvaluate {
    kotlin.targets.withType<KotlinNativeTarget>().configureEach {
        val defFile = uniffiDefFileFor(targetName)

        compilations.getByName("main").cinterops.named("uniffi-cinterop") {
            definitionFile.set(defFile)

            tasks.named(interopProcessingTaskName) {
                dependsOn(defFile)
            }
        }
    }
}

/**
 * The def file the plugin feeds to its cinterop for [targetName]. During an IDE sync the plugin
 * registers a single GenerateDummyDefFileTask shared by every target, otherwise one
 * GenerateDefFileTask per target.
 */
fun uniffiDefFileFor(targetName: String): Provider<RegularFile> {
    // idea.sync.active is set by IntelliJ-based IDEs during a sync
    val isSync = providers.systemProperty("idea.sync.active").map(String::toBoolean).getOrElse(false)

    return if (isSync) {
        tasks.named<GenerateDummyDefFileTask>("generateDummyDefFile")
            .flatMap { it.outputFile }
    } else {
        tasks.named<GenerateDefFileTask>(
            "generateDefFileFor${targetName.replaceFirstChar(Char::uppercaseChar)}"
        ).flatMap { it.outputFile }
    }
}

See examples/swift-interop for a module that calls both Rust and Swift.

Migrating

From 1.0.x: Android moved to the Android Kotlin Multiplatform library plugin and AGP 9, see Targets. The macosX64 Kotlin/Native target was removed. kotlinx-datetime is no longer added, and the bindings use kotlin.time.Instant and kotlin.time.Duration.

From 1.1.x (UniFFI 0.28): version 1.2.0 moved to UniFFI 0.32. Update your crate's uniffi dependency to 0.32.0. The marker for fake objects is now NoHandle (was NoPointer). See UniFFI's own changelog for the Rust-side changes, for example use_remote_type! replacing use_udl_record! and friends.

The full list of changes is in the changelog.