diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt index bac3628d2c2..de4f1ad8b26 100644 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt @@ -143,7 +143,7 @@ internal fun List.toNativeStringArray(placement: NativePlacement): CArra } } -internal val NativeLibrary.preambleLines: List +val NativeLibrary.preambleLines: List get() = this.includes.map { "#include <$it>" } internal fun Appendable.appendPreamble(library: NativeLibrary) = this.apply { diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt index 83b967e0489..3a3b526b91c 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt @@ -24,6 +24,8 @@ enum class KotlinPlatform { NATIVE } +private class StubsFragment(val kotlinStubLines: List, val nativeStubLines: List = emptyList()) + // TODO: mostly rename 'jni' to 'c'. class StubGenerator( @@ -43,6 +45,12 @@ class StubGenerator( val pkgName: String get() = configuration.pkgName + private val jvmFileClassName = if (pkgName.isEmpty()) { + libName + } else { + pkgName.substringAfterLast('.') + } + val excludedFunctions: Set get() = configuration.excludedFunctions @@ -173,12 +181,19 @@ class StubGenerator( } } - private fun transaction(action: () -> R): R { - val lines = mutableListOf() - val res = withOutput({ lines.add(it) }, action) - // if action is completed successfully: - lines.forEach(out) - return res + fun generateLinesBy(action: () -> Unit): List { + val result = mutableListOf() + withOutput({ result.add(it) }, action) + return result + } + + fun withOutput(appendable: Appendable, action: () -> R): R { + return withOutput({ appendable.appendln(it) }, action) + } + + private fun generateKotlinFragmentBy(block: () -> Unit): StubsFragment { + val lines = generateLinesBy(block) + return StubsFragment(kotlinStubLines = lines) } private fun indent(action: () -> R): R { @@ -737,6 +752,22 @@ class StubGenerator( } } + private fun generateStubsForFunction(func: FunctionDecl): StubsFragment { + val kotlinStubLines = generateLinesBy { + generateKotlinBindingMethod(func) + if (func.requiresKotlinAdapter()) { + out("") + generateKotlinExternalMethod(func) + } + } + + val nativeStubLines = generateLinesBy { + generateCJniFunction(func) + } + + return StubsFragment(kotlinStubLines, nativeStubLines) + } + private fun FunctionDecl.generateAsFfiVarargs(): Boolean = (platform == KotlinPlatform.NATIVE && this.isVararg && // Neither takes nor returns structs by value: !this.returnsRecord() && this.parameters.all { it.type.unwrapTypedefs() !is RecordType }) @@ -862,10 +893,7 @@ class StubGenerator( arguments.add("alloc<${retValMirror.pointedTypeName}>().rawPtr") } - val callee = when (platform) { - KotlinPlatform.JVM -> "externals.${func.name.asSimpleName()}" - KotlinPlatform.NATIVE -> func.kotlinExternalName - } + val callee = func.kotlinExternalName out("val res = $callee(" + arguments.joinToString(", ") + ")") val result = retValBinding.conv("res") @@ -1144,7 +1172,6 @@ class StubGenerator( private val FunctionDecl.kotlinExternalName: String get() { - require(platform == KotlinPlatform.NATIVE) require(this.requiresKotlinAdapter()) return "kni_$name" } @@ -1188,22 +1215,79 @@ class StubGenerator( "${name.asSimpleName()}: " + paramBindings[i].kotlinJniBridgeType }.joinToString(", ") - when (platform) { - KotlinPlatform.JVM -> { - out("external fun ${func.name.asSimpleName()}($args): ${retValBinding.kotlinJniBridgeType}") - } - KotlinPlatform.NATIVE -> { - out(func.symbolNameAnnotation) - out("private external fun ${func.kotlinExternalName}($args): ${retValBinding.kotlinJniBridgeType}") - } - else -> TODO(platform.toString()) + if (platform == KotlinPlatform.NATIVE) { + out(func.symbolNameAnnotation) } + out("private external fun ${func.kotlinExternalName}($args): ${retValBinding.kotlinJniBridgeType}") + } + + private fun generateStubs(): List { + val stubs = mutableListOf() + + functionsToBind.forEach { + try { + stubs.add(generateStubsForFunction(it)) + } catch (e: Throwable) { + log("Warning: cannot generate stubs for function ${it.name}") + } + } + + nativeIndex.macroConstants.forEach { + try { + stubs.add( + generateKotlinFragmentBy { generateConstant(it) } + ) + } catch (e: Throwable) { + log("Warning: cannot generate stubs for constant ${it.name}") + } + } + + nativeIndex.structs.forEach { s -> + try { + stubs.add( + generateKotlinFragmentBy { generateStruct(s) } + ) + } catch (e: Throwable) { + log("Warning: cannot generate definition for struct ${s.kotlinName}") + } + } + + nativeIndex.enums.forEach { + try { + stubs.add( + generateKotlinFragmentBy { generateEnum(it) } + ) + } catch (e: Throwable) { + log("Warning: cannot generate definition for enum ${it.spelling}") + } + } + + nativeIndex.typedefs.forEach { t -> + try { + stubs.add( + generateKotlinFragmentBy { generateTypedef(t) } + ) + } catch (e: Throwable) { + log("Warning: cannot generate typedef ${t.name}") + } + } + + usedFunctionTypes.entries.forEach { + stubs.add( + generateKotlinFragmentBy { generateFunctionType(it.key, it.value) } + ) + } + + return stubs } /** * Produces to [out] the contents of file with Kotlin bindings. */ - fun generateKotlinFile() { + private fun generateKotlinFile(stubs: List) { + if (platform == KotlinPlatform.JVM) { + out("@file:JvmName(${jvmFileClassName.quoteAsKotlinLiteral()})") + } out("@file:Suppress(\"UNUSED_EXPRESSION\", \"UNUSED_VARIABLE\")") if (pkgName != "") { out("package $pkgName") @@ -1215,83 +1299,13 @@ class StubGenerator( out("import kotlinx.cinterop.*") out("") - functionsToBind.forEach { - try { - transaction { - generateKotlinBindingMethod(it) - out("") - } - } catch (e: Throwable) { - log("Warning: cannot generate binding definition for function ${it.name}") - } - } - - nativeIndex.macroConstants.forEach { - generateConstant(it) - } - out("") - - nativeIndex.structs.forEach { s -> - try { - transaction { - generateStruct(s) - out("") - } - } catch (e: Throwable) { - log("Warning: cannot generate definition for struct ${s.kotlinName}") - } - } - - nativeIndex.enums.forEach { e -> - generateEnum(e) + stubs.forEach { + it.kotlinStubLines.forEach { out(it) } out("") } - nativeIndex.typedefs.forEach { t -> - try { - transaction { - generateTypedef(t) - out("") - } - } catch (e: Throwable) { - log("Warning: cannot generate typedef ${t.name}") - } - } - - usedFunctionTypes.entries.forEach { - generateFunctionType(it.key, it.value) - out("") - } - - generateKotlinExternals() - } - - private fun generateKotlinExternals() = when (platform) { - KotlinPlatform.JVM -> block("object externals") { - out("init { System.loadLibrary(\"$libName\") }") - functionsToBind.forEach { - try { - transaction { - generateKotlinExternalMethod(it) - out("") - } - } catch (e: Throwable) { - log("Warning: cannot generate external definition for function ${it.name}") - } - } - } - - KotlinPlatform.NATIVE -> functionsToBind.forEach { - try { - transaction { - if (it.requiresKotlinAdapter()) { - generateKotlinExternalMethod(it) - out("") - } - } - } catch (e: Throwable) { - log("Warning: cannot generate external definition for function ${it.name}") - } + if (platform == KotlinPlatform.JVM) { + out("private val loadLibrary = System.loadLibrary(\"$libName\")") } } @@ -1331,27 +1345,30 @@ class StubGenerator( else -> throw NotImplementedError(kotlinJniBridgeType) } + private val libraryForCStubs = configuration.library.copy( + includes = mutableListOf().apply { + add("stdint.h") + if (platform == KotlinPlatform.JVM) { + add("jni.h") + } + addAll(configuration.library.includes) + } + ) + /** * Produces to [out] the contents of C source file to be compiled into JNI lib used for Kotlin bindings impl. */ - fun generateCFile(headerFiles: List, entryPoint: String?) { - out("#include ") - if (platform == KotlinPlatform.JVM) { - out("#include ") - } - headerFiles.forEach { - out("#include <$it>") + private fun generateCFile(stubs: List, entryPoint: String?) { + libraryForCStubs.preambleLines.forEach { + out(it) } out("") - functionsToBind.forEach { func -> - try { - if (func.requiresCAdapter()) { - generateCJniFunction(func) - } - } catch (e: Throwable) { - log("Warning: cannot generate C JNI function definition ${func.name}") + stubs.forEach { + it.nativeStubLines.forEach { + out(it) } + out("") } if (entryPoint != null) { @@ -1399,13 +1416,18 @@ class StubGenerator( "" } - val funcFullName = if (pkgName.isEmpty()) { - "externals.${func.name}" - } else { - "$pkgName.externals.${func.name}" + val funcFullName = buildString { + if (pkgName.isNotEmpty()) { + append(pkgName) + append('.') + } + append(jvmFileClassName) + append('.') + append(func.kotlinExternalName) } + val functionName = "Java_" + funcFullName.replace("_", "_1").replace('.', '_').replace("$", "_00024") - "JNIEXPORT $cReturnType JNICALL $functionName (JNIEnv *jniEnv, jobject externalsObj$joinedParameters)" + "JNIEXPORT $cReturnType JNICALL $functionName (JNIEnv *jniEnv, jclass jclss$joinedParameters)" } KotlinPlatform.NATIVE -> { val joinedParameters = parameters.joinToString(", ") @@ -1442,4 +1464,16 @@ class StubGenerator( } } } + + fun generateFiles(ktFile: Appendable, cFile: Appendable, entryPoint: String?) { + val stubs = generateStubs() + + withOutput(cFile) { + generateCFile(stubs, entryPoint) + } + + withOutput(ktFile) { + generateKotlinFile(stubs) + } + } } diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt index ee9de7941f4..f7a4acab17a 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt @@ -344,19 +344,13 @@ private fun processLib(konanHome: String, val gen = StubGenerator(nativeIndex, configuration, libName, generateShims, verbose, flavor) outKtFile.parentFile.mkdirs() - outKtFile.bufferedWriter().use { out -> - gen.withOutput({ out.appendln(it) }) { - gen.generateKotlinFile() - } - } File(nativeLibsDir).mkdirs() - val outCFile = File("$nativeLibsDir/$libName.c") // TODO: select the better location. - outCFile.bufferedWriter().use { out -> - gen.withOutput({ out.appendln(it) }) { - gen.generateCFile(headerFiles, entryPoint) + outKtFile.bufferedWriter().use { ktFile -> + outCFile.bufferedWriter().use { cFile -> + gen.generateFiles(ktFile = ktFile, cFile = cFile, entryPoint = entryPoint) } }