Refactor interop stub generation

Explicitly group stub lines related to the single declaration
This commit is contained in:
Svyatoslav Scherbina
2017-04-10 17:30:28 +03:00
committed by SvyatoslavScherbina
parent eadcd98740
commit 05dae83185
3 changed files with 152 additions and 124 deletions
@@ -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 {
@@ -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)
}
}
} }
@@ -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)
} }
} }