[interop] Prepare interop driver for a metadata-only mode.

This commit is contained in:
Sergey Bogolepov
2019-11-01 17:46:12 +07:00
committed by Sergey Bogolepov
parent 8a8ba03f9e
commit e8ad4712d3
9 changed files with 194 additions and 67 deletions
@@ -4,6 +4,7 @@
*/
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.gen.jvm.GenerationMode
import org.jetbrains.kotlin.native.interop.gen.jvm.InteropConfiguration
import org.jetbrains.kotlin.native.interop.gen.jvm.KotlinPlatform
import org.jetbrains.kotlin.native.interop.indexer.*
@@ -86,18 +87,75 @@ class StubIrContext(
}
}
class StubIrDriver(private val context: StubIrContext) {
fun run(outKtFile: File, outCFile: File, entryPoint: String?) {
class StubIrDriver(
private val context: StubIrContext,
private val options: DriverOptions
) {
data class DriverOptions(
val mode: GenerationMode,
val entryPoint: String?,
val outCFile: File,
val outKtFileCreator: () -> File
)
sealed class Result {
object SourceCode : Result()
// Will contain Km* objects.
class Metadata(): Result()
}
fun run(): Result {
val (mode, entryPoint, outCFile, outKtFile) = options
val builderResult = StubIrBuilder(context).build()
val bridgeBuilderResult = StubIrBridgeBuilder(context, builderResult).build()
outKtFile.bufferedWriter().use { ktFile ->
File(outCFile.absolutePath).bufferedWriter().use { cFile ->
StubIrTextEmitter(
context,
builderResult,
bridgeBuilderResult
).emit(ktFile, cFile, entryPoint)
outCFile.bufferedWriter().use {
emitCFile(context, it, entryPoint, bridgeBuilderResult.nativeBridges)
}
return when (mode) {
GenerationMode.SOURCE_CODE -> {
emitSourceCode(outKtFile(), builderResult, bridgeBuilderResult)
}
GenerationMode.METADATA -> emitMetadata(builderResult)
}
}
private fun emitSourceCode(
outKtFile: File, builderResult: StubIrBuilderResult, bridgeBuilderResult: BridgeBuilderResult
): Result.SourceCode {
outKtFile.bufferedWriter().use { ktFile ->
StubIrTextEmitter(context, builderResult, bridgeBuilderResult).emit(ktFile)
}
return Result.SourceCode
}
private fun emitMetadata(builderResult: StubIrBuilderResult): Result.Metadata {
return Result.Metadata()
}
private fun emitCFile(context: StubIrContext, cFile: Appendable, entryPoint: String?, nativeBridges: NativeBridges) {
val out = { it: String -> cFile.appendln(it) }
context.libraryForCStubs.preambleLines.forEach {
out(it)
}
out("")
out("// NOTE THIS FILE IS AUTO-GENERATED")
out("")
nativeBridges.nativeLines.forEach { out(it) }
if (entryPoint != null) {
out("extern int Konan_main(int argc, char** argv);")
out("")
out("__attribute__((__used__))")
out("int $entryPoint(int argc, char** argv) {")
out(" return Konan_main(argc, argv);")
out("}")
}
}
}
@@ -148,28 +148,7 @@ class StubIrTextEmitter(
out("// NOTE THIS FILE IS AUTO-GENERATED")
}
fun emit(ktFile: Appendable, cFile: Appendable, entryPoint: String?) {
withOutput(cFile) {
context.libraryForCStubs.preambleLines.forEach {
out(it)
}
out("")
out("// NOTE THIS FILE IS AUTO-GENERATED")
out("")
nativeBridges.nativeLines.forEach(out)
if (entryPoint != null) {
out("extern int Konan_main(int argc, char** argv);")
out("")
out("__attribute__((__used__))")
out("int $entryPoint(int argc, char** argv) {")
out(" return Konan_main(argc, argv);")
out("}")
}
}
fun emit(ktFile: Appendable) {
// Stubs generation may affect imports list so do it before header generation.
val stubLines = generateKotlinFragmentBy {
@@ -48,6 +48,8 @@ open class CommonInteropArguments(val argParser: ArgParser) {
.multiple()
val repo by argParser.option(ArgType.String, shortName = "r", description = "repository to resolve dependencies")
.multiple()
val mode by argParser.option(ArgType.Choice(listOf(MODE_METADATA, MODE_SOURCECODE)), description = "the way interop library is generated")
.default(MODE_SOURCECODE)
val nodefaultlibs by argParser.option(ArgType.Boolean, NODEFAULTLIBS,
description = "don't link the libraries from dist/klib automatically").default(false)
val nodefaultlibsDeprecated by argParser.option(ArgType.Boolean, NODEFAULTLIBS_DEPRECATED,
@@ -59,6 +61,11 @@ open class CommonInteropArguments(val argParser: ArgParser) {
description = "don't link unused libraries even explicitly specified").default(false)
val tempDir by argParser.option(ArgType.String, TEMP_DIR,
description = "save temporary files to the given directory")
companion object {
const val MODE_SOURCECODE = "sourcecode"
const val MODE_METADATA = "metadata"
}
}
class CInteropArguments(argParser: ArgParser =
@@ -0,0 +1,13 @@
package org.jetbrains.kotlin.native.interop.gen.jvm
import org.jetbrains.kotlin.native.interop.tool.CommonInteropArguments
enum class GenerationMode {
SOURCE_CODE, METADATA
}
fun parseGenerationMode(mode: String): GenerationMode? = when(mode) {
CommonInteropArguments.MODE_METADATA -> GenerationMode.METADATA
CommonInteropArguments.MODE_SOURCECODE -> GenerationMode.SOURCE_CODE
else -> null
}
@@ -0,0 +1,37 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin.native.interop.gen.jvm
import org.jetbrains.kotlin.konan.CURRENT
import org.jetbrains.kotlin.konan.KonanVersion
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.konan.library.impl.KonanLibraryWriterImpl
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.library.KonanLibraryVersioning
import org.jetbrains.kotlin.library.KotlinAbiVersion
import java.util.*
data class LibraryCreationArguments(
val outputPath: String,
val moduleName: String,
val nativeBitcodePath: String,
val target: KonanTarget,
val manifest: Properties
)
fun createInteropLibrary(arguments: LibraryCreationArguments) {
val version = KonanLibraryVersioning(
libraryVersion = null,
abiVersion = KotlinAbiVersion.CURRENT,
compilerVersion = KonanVersion.CURRENT
)
KonanLibraryWriterImpl(
File(arguments.outputPath),arguments.moduleName, version, arguments.target
).apply {
addNativeBitcode(arguments.nativeBitcodePath)
addManifestAddend(arguments.manifest)
commit()
}
}
@@ -34,8 +34,10 @@ fun main(args: Array<String>) {
processCLib(args)
}
fun interop(flavor: String, args: Array<String>, additionalArgs: Map<String, Any> = mapOf()) =
when(flavor) {
fun interop(
flavor: String, args: Array<String>,
additionalArgs: Map<String, Any> = mapOf()
): Array<String>? = when(flavor) {
"jvm", "native" -> processCLib(args, additionalArgs)
"wasm" -> processIdlLib(args, additionalArgs)
else -> error("Unexpected flavor")
@@ -159,7 +161,7 @@ private fun findFilesByGlobs(roots: List<Path>, globs: List<String>): Map<Path,
}
private fun processCLib(args: Array<String>, additionalArgs: Map<String, Any> = mapOf()): Array<String> {
private fun processCLib(args: Array<String>, additionalArgs: Map<String, Any> = mapOf()): Array<String>? {
val cinteropArguments = CInteropArguments()
cinteropArguments.argParser.parse(args)
val ktGenRoot = cinteropArguments.generated
@@ -204,11 +206,7 @@ private fun processCLib(args: Array<String>, additionalArgs: Map<String, Any> =
val fqParts = (cinteropArguments.pkg ?: def.config.packageName)?.split('.')
?: defFile!!.name.split('.').reversed().drop(1)
val outKtFileName = fqParts.last() + ".kt"
val outKtPkg = fqParts.joinToString(".")
val outKtFileRelative = (fqParts + outKtFileName).joinToString("/")
val outKtFile = File(ktGenRoot, outKtFileRelative)
val libName = (additionalArgs["cstubsname"] as? String)?: fqParts.joinToString("") + "stubs"
@@ -240,7 +238,6 @@ private fun processCLib(args: Array<String>, additionalArgs: Map<String, Any> =
target = target
)
outKtFile.parentFile.mkdirs()
File(nativeLibsDir).mkdirs()
val outCFile = tempFiles.create(libName, ".${language.sourceFileExtension}")
@@ -251,9 +248,22 @@ private fun processCLib(args: Array<String>, additionalArgs: Map<String, Any> =
{}
}
val mode = parseGenerationMode(cinteropArguments.mode)
?: error ("Unexpected interop generation mode: ${cinteropArguments.mode}")
val stubIrContext = StubIrContext(logger, configuration, nativeIndex, imports, flavor, libName)
val stubIrDriver = StubIrDriver(stubIrContext)
stubIrDriver.run(outKtFile, File(outCFile.absolutePath), entryPoint)
val stubIrOutput = run {
val outKtFileCreator = {
val outKtFileName = fqParts.last() + ".kt"
val outKtFileRelative = (fqParts + outKtFileName).joinToString("/")
val file = File(ktGenRoot, outKtFileRelative)
file.parentFile.mkdirs()
file
}
val driverOptions = StubIrDriver.DriverOptions(mode, entryPoint, File(outCFile.absolutePath), outKtFileCreator)
val stubIrDriver = StubIrDriver(stubIrContext, driverOptions)
stubIrDriver.run()
}
// TODO: if a library has partially included headers, then it shouldn't be used as a dependency.
def.manifestAddendProperties["includedHeaders"] = nativeIndex.includedHeaders.joinToString(" ") { it.value }
@@ -270,31 +280,48 @@ private fun processCLib(args: Array<String>, additionalArgs: Map<String, Any> =
manifestAddend?.parentFile?.mkdirs()
manifestAddend?.let { def.manifestAddendProperties.storeProperties(it) }
if (flavor == KotlinPlatform.JVM) {
val compilerArgs = stubIrContext.libraryForCStubs.compilerArgs.toTypedArray()
val nativeOutputPath: String = when (flavor) {
KotlinPlatform.JVM -> {
val outOFile = tempFiles.create(libName,".o")
val compilerCmd = arrayOf(compiler, *compilerArgs,
"-c", outCFile.absolutePath, "-o", outOFile.absolutePath)
runCmd(compilerCmd, verbose)
val outOFile = tempFiles.create(libName,".o")
val outLib = File(nativeLibsDir, System.mapLibraryName(libName))
val linkerCmd = arrayOf(linker,
outOFile.absolutePath, "-shared", "-o", outLib.absolutePath,
*linkerOpts)
runCmd(linkerCmd, verbose)
outOFile.absolutePath
}
KotlinPlatform.NATIVE -> {
val outLib = File(nativeLibsDir, "$libName.bc")
val compilerCmd = arrayOf(compiler, *compilerArgs,
"-emit-llvm", "-c", outCFile.absolutePath, "-o", outLib.absolutePath)
val compilerCmd = arrayOf(compiler, *stubIrContext.libraryForCStubs.compilerArgs.toTypedArray(),
"-c", outCFile.absolutePath, "-o", outOFile.absolutePath)
runCmd(compilerCmd, verbose)
val outLib = File(nativeLibsDir, System.mapLibraryName(libName))
val linkerCmd = arrayOf(linker,
outOFile.absolutePath, "-shared", "-o", outLib.absolutePath,
*linkerOpts)
runCmd(linkerCmd, verbose)
} else if (flavor == KotlinPlatform.NATIVE) {
val outBcName = libName + ".bc"
val outLib = File(nativeLibsDir, outBcName)
val compilerCmd = arrayOf(compiler, *stubIrContext.libraryForCStubs.compilerArgs.toTypedArray(),
"-emit-llvm", "-c", outCFile.absolutePath, "-o", outLib.absolutePath)
runCmd(compilerCmd, verbose)
runCmd(compilerCmd, verbose)
outLib.absolutePath
}
}
return when (stubIrOutput) {
is StubIrDriver.Result.SourceCode -> {
argsToCompiler(staticLibraries, libraryPaths)
}
is StubIrDriver.Result.Metadata -> {
val moduleName = File(cinteropArguments.output).nameWithoutExtension
val args = LibraryCreationArguments(
nativeBitcodePath = nativeOutputPath,
target = tool.target,
moduleName = moduleName,
outputPath = cinteropArguments.output,
manifest = def.manifestAddendProperties
)
createInteropLibrary(args)
return null
}
}
return argsToCompiler(staticLibraries, libraryPaths)
}
internal fun prepareTool(target: String?, flavor: KotlinPlatform): ToolConfig {
@@ -83,7 +83,7 @@ private fun generatePlatformLibraries(target: String, inputDirectory: File, outp
"-no-default-libs", "-no-endorsed-libs", "-Xpurge-user-libs",
*def.depends.flatMap { listOf("-l", "$outputDirectory/${it.name}") }.toTypedArray())
println("Processing ${def.name}...")
K2Native.mainNoExit(invokeInterop("native", args))
invokeInterop("native", args)?.let { K2Native.mainNoExit(it) }
org.jetbrains.kotlin.cli.klib.main(arrayOf("install", outKlib,
"-target", target,
"-repository", "${outputDirectory.absolutePath}"
@@ -19,7 +19,11 @@ import org.jetbrains.kotlin.native.interop.tool.*
// TODO: this function should eventually be eliminated from 'utilities'.
// The interaction of interop and the compiler should be streamlined.
fun invokeInterop(flavor: String, args: Array<String>): Array<String> {
/**
* @return null if there is no need in compiler invocation.
* Otherwise returns array of compiler args.
*/
fun invokeInterop(flavor: String, args: Array<String>): Array<String>? {
val arguments = if (flavor == "native") CInteropArguments() else JSInteropArguments()
arguments.argParser.parse(args)
val outputFileName = arguments.output
@@ -68,7 +72,9 @@ fun invokeInterop(flavor: String, args: Array<String>): Array<String> {
additionalProperties.putAll(mapOf("cstubsname" to cstubsName, "import" to imports))
}
val cinteropArgsToCompiler = interop(flavor, args + additionalArgs, additionalProperties)
?: return null // There is no need in compiler invocation if we're generating only metadata.
val nativeStubs =
if (flavor == "wasm")
@@ -17,11 +17,11 @@ private fun mainImpl(args: Array<String>, konancMain: (Array<String>) -> Unit) {
konancMain(utilityArgs)
"cinterop" -> {
val konancArgs = invokeInterop("native", utilityArgs)
konancMain(konancArgs)
konancArgs?.let { konancMain(it) }
}
"jsinterop" -> {
val konancArgs = invokeInterop("wasm", utilityArgs)
konancMain(konancArgs)
konancArgs?.let { konancMain(it) }
}
"klib" ->
klibMain(utilityArgs)