Refactor interop stub generation
Explicitly group stub lines related to the single declaration
This commit is contained in:
committed by
SvyatoslavScherbina
parent
eadcd98740
commit
05dae83185
+1
-1
@@ -143,7 +143,7 @@ internal fun List<String>.toNativeStringArray(placement: NativePlacement): CArra
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val NativeLibrary.preambleLines: List<String>
|
val NativeLibrary.preambleLines: List<String>
|
||||||
get() = this.includes.map { "#include <$it>" }
|
get() = this.includes.map { "#include <$it>" }
|
||||||
|
|
||||||
internal fun Appendable.appendPreamble(library: NativeLibrary) = this.apply {
|
internal fun Appendable.appendPreamble(library: NativeLibrary) = this.apply {
|
||||||
|
|||||||
+148
-114
@@ -24,6 +24,8 @@ enum class KotlinPlatform {
|
|||||||
NATIVE
|
NATIVE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class StubsFragment(val kotlinStubLines: List<String>, val nativeStubLines: List<String> = emptyList())
|
||||||
|
|
||||||
// TODO: mostly rename 'jni' to 'c'.
|
// TODO: mostly rename 'jni' to 'c'.
|
||||||
|
|
||||||
class StubGenerator(
|
class StubGenerator(
|
||||||
@@ -43,6 +45,12 @@ class StubGenerator(
|
|||||||
val pkgName: String
|
val pkgName: String
|
||||||
get() = configuration.pkgName
|
get() = configuration.pkgName
|
||||||
|
|
||||||
|
private val jvmFileClassName = if (pkgName.isEmpty()) {
|
||||||
|
libName
|
||||||
|
} else {
|
||||||
|
pkgName.substringAfterLast('.')
|
||||||
|
}
|
||||||
|
|
||||||
val excludedFunctions: Set<String>
|
val excludedFunctions: Set<String>
|
||||||
get() = configuration.excludedFunctions
|
get() = configuration.excludedFunctions
|
||||||
|
|
||||||
@@ -173,12 +181,19 @@ class StubGenerator(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R> transaction(action: () -> R): R {
|
fun generateLinesBy(action: () -> Unit): List<String> {
|
||||||
val lines = mutableListOf<String>()
|
val result = mutableListOf<String>()
|
||||||
val res = withOutput({ lines.add(it) }, action)
|
withOutput({ result.add(it) }, action)
|
||||||
// if action is completed successfully:
|
return result
|
||||||
lines.forEach(out)
|
}
|
||||||
return res
|
|
||||||
|
fun <R> 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 <R> indent(action: () -> R): R {
|
private fun <R> 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 &&
|
private fun FunctionDecl.generateAsFfiVarargs(): Boolean = (platform == KotlinPlatform.NATIVE && this.isVararg &&
|
||||||
// Neither takes nor returns structs by value:
|
// Neither takes nor returns structs by value:
|
||||||
!this.returnsRecord() && this.parameters.all { it.type.unwrapTypedefs() !is RecordType })
|
!this.returnsRecord() && this.parameters.all { it.type.unwrapTypedefs() !is RecordType })
|
||||||
@@ -862,10 +893,7 @@ class StubGenerator(
|
|||||||
arguments.add("alloc<${retValMirror.pointedTypeName}>().rawPtr")
|
arguments.add("alloc<${retValMirror.pointedTypeName}>().rawPtr")
|
||||||
}
|
}
|
||||||
|
|
||||||
val callee = when (platform) {
|
val callee = func.kotlinExternalName
|
||||||
KotlinPlatform.JVM -> "externals.${func.name.asSimpleName()}"
|
|
||||||
KotlinPlatform.NATIVE -> func.kotlinExternalName
|
|
||||||
}
|
|
||||||
out("val res = $callee(" + arguments.joinToString(", ") + ")")
|
out("val res = $callee(" + arguments.joinToString(", ") + ")")
|
||||||
|
|
||||||
val result = retValBinding.conv("res")
|
val result = retValBinding.conv("res")
|
||||||
@@ -1144,7 +1172,6 @@ class StubGenerator(
|
|||||||
|
|
||||||
private val FunctionDecl.kotlinExternalName: String
|
private val FunctionDecl.kotlinExternalName: String
|
||||||
get() {
|
get() {
|
||||||
require(platform == KotlinPlatform.NATIVE)
|
|
||||||
require(this.requiresKotlinAdapter())
|
require(this.requiresKotlinAdapter())
|
||||||
return "kni_$name"
|
return "kni_$name"
|
||||||
}
|
}
|
||||||
@@ -1188,22 +1215,79 @@ class StubGenerator(
|
|||||||
"${name.asSimpleName()}: " + paramBindings[i].kotlinJniBridgeType
|
"${name.asSimpleName()}: " + paramBindings[i].kotlinJniBridgeType
|
||||||
}.joinToString(", ")
|
}.joinToString(", ")
|
||||||
|
|
||||||
when (platform) {
|
if (platform == KotlinPlatform.NATIVE) {
|
||||||
KotlinPlatform.JVM -> {
|
out(func.symbolNameAnnotation)
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
|
out("private external fun ${func.kotlinExternalName}($args): ${retValBinding.kotlinJniBridgeType}")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateStubs(): List<StubsFragment> {
|
||||||
|
val stubs = mutableListOf<StubsFragment>()
|
||||||
|
|
||||||
|
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.
|
* Produces to [out] the contents of file with Kotlin bindings.
|
||||||
*/
|
*/
|
||||||
fun generateKotlinFile() {
|
private fun generateKotlinFile(stubs: List<StubsFragment>) {
|
||||||
|
if (platform == KotlinPlatform.JVM) {
|
||||||
|
out("@file:JvmName(${jvmFileClassName.quoteAsKotlinLiteral()})")
|
||||||
|
}
|
||||||
out("@file:Suppress(\"UNUSED_EXPRESSION\", \"UNUSED_VARIABLE\")")
|
out("@file:Suppress(\"UNUSED_EXPRESSION\", \"UNUSED_VARIABLE\")")
|
||||||
if (pkgName != "") {
|
if (pkgName != "") {
|
||||||
out("package $pkgName")
|
out("package $pkgName")
|
||||||
@@ -1215,83 +1299,13 @@ class StubGenerator(
|
|||||||
out("import kotlinx.cinterop.*")
|
out("import kotlinx.cinterop.*")
|
||||||
out("")
|
out("")
|
||||||
|
|
||||||
functionsToBind.forEach {
|
stubs.forEach {
|
||||||
try {
|
it.kotlinStubLines.forEach { out(it) }
|
||||||
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)
|
|
||||||
out("")
|
out("")
|
||||||
}
|
}
|
||||||
|
|
||||||
nativeIndex.typedefs.forEach { t ->
|
if (platform == KotlinPlatform.JVM) {
|
||||||
try {
|
out("private val loadLibrary = System.loadLibrary(\"$libName\")")
|
||||||
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}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1331,27 +1345,30 @@ class StubGenerator(
|
|||||||
else -> throw NotImplementedError(kotlinJniBridgeType)
|
else -> throw NotImplementedError(kotlinJniBridgeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val libraryForCStubs = configuration.library.copy(
|
||||||
|
includes = mutableListOf<String>().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.
|
* Produces to [out] the contents of C source file to be compiled into JNI lib used for Kotlin bindings impl.
|
||||||
*/
|
*/
|
||||||
fun generateCFile(headerFiles: List<String>, entryPoint: String?) {
|
private fun generateCFile(stubs: List<StubsFragment>, entryPoint: String?) {
|
||||||
out("#include <stdint.h>")
|
libraryForCStubs.preambleLines.forEach {
|
||||||
if (platform == KotlinPlatform.JVM) {
|
out(it)
|
||||||
out("#include <jni.h>")
|
|
||||||
}
|
|
||||||
headerFiles.forEach {
|
|
||||||
out("#include <$it>")
|
|
||||||
}
|
}
|
||||||
out("")
|
out("")
|
||||||
|
|
||||||
functionsToBind.forEach { func ->
|
stubs.forEach {
|
||||||
try {
|
it.nativeStubLines.forEach {
|
||||||
if (func.requiresCAdapter()) {
|
out(it)
|
||||||
generateCJniFunction(func)
|
|
||||||
}
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
log("Warning: cannot generate C JNI function definition ${func.name}")
|
|
||||||
}
|
}
|
||||||
|
out("")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entryPoint != null) {
|
if (entryPoint != null) {
|
||||||
@@ -1399,13 +1416,18 @@ class StubGenerator(
|
|||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
|
||||||
val funcFullName = if (pkgName.isEmpty()) {
|
val funcFullName = buildString {
|
||||||
"externals.${func.name}"
|
if (pkgName.isNotEmpty()) {
|
||||||
} else {
|
append(pkgName)
|
||||||
"$pkgName.externals.${func.name}"
|
append('.')
|
||||||
|
}
|
||||||
|
append(jvmFileClassName)
|
||||||
|
append('.')
|
||||||
|
append(func.kotlinExternalName)
|
||||||
}
|
}
|
||||||
|
|
||||||
val functionName = "Java_" + funcFullName.replace("_", "_1").replace('.', '_').replace("$", "_00024")
|
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 -> {
|
KotlinPlatform.NATIVE -> {
|
||||||
val joinedParameters = parameters.joinToString(", ")
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-9
@@ -344,19 +344,13 @@ private fun processLib(konanHome: String,
|
|||||||
val gen = StubGenerator(nativeIndex, configuration, libName, generateShims, verbose, flavor)
|
val gen = StubGenerator(nativeIndex, configuration, libName, generateShims, verbose, flavor)
|
||||||
|
|
||||||
outKtFile.parentFile.mkdirs()
|
outKtFile.parentFile.mkdirs()
|
||||||
outKtFile.bufferedWriter().use { out ->
|
|
||||||
gen.withOutput({ out.appendln(it) }) {
|
|
||||||
gen.generateKotlinFile()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
File(nativeLibsDir).mkdirs()
|
File(nativeLibsDir).mkdirs()
|
||||||
|
|
||||||
val outCFile = File("$nativeLibsDir/$libName.c") // TODO: select the better location.
|
val outCFile = File("$nativeLibsDir/$libName.c") // TODO: select the better location.
|
||||||
|
|
||||||
outCFile.bufferedWriter().use { out ->
|
outKtFile.bufferedWriter().use { ktFile ->
|
||||||
gen.withOutput({ out.appendln(it) }) {
|
outCFile.bufferedWriter().use { cFile ->
|
||||||
gen.generateCFile(headerFiles, entryPoint)
|
gen.generateFiles(ktFile = ktFile, cFile = cFile, entryPoint = entryPoint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user