Native: introduce binary options machinery

This commit is contained in:
Svyatoslav Scherbina
2021-08-10 11:28:48 +03:00
committed by Space
parent 18cc498763
commit 7cc1ea8801
3 changed files with 140 additions and 13 deletions
@@ -367,6 +367,10 @@ class K2Native : CLICompiler<K2NativeCompilerArguments>() {
}
}
})
parseBinaryOptions(arguments, configuration).forEach { optionWithValue ->
configuration.put(optionWithValue)
}
}
}
}
@@ -570,22 +574,64 @@ private fun parseDebugPrefixMap(
}
}.toMap()
private class BinaryOptionWithValue<T : Any>(val option: BinaryOption<T>, val value: T)
private fun <T : Any> CompilerConfiguration.put(binaryOptionWithValue: BinaryOptionWithValue<T>) {
this.put(binaryOptionWithValue.option.compilerConfigurationKey, binaryOptionWithValue.value)
}
private fun parseBinaryOptions(
arguments: K2NativeCompilerArguments,
configuration: CompilerConfiguration
): List<BinaryOptionWithValue<*>> {
val keyValuePairs = parseKeyValuePairs(arguments.binaryOptions, configuration) ?: return emptyList()
return keyValuePairs.mapNotNull { (key, value) ->
val option = BinaryOptions.getByName(key)
if (option == null) {
configuration.report(STRONG_WARNING, "Unknown binary option '$key'")
null
} else {
parseBinaryOption(option, value, configuration)
}
}
}
private fun <T : Any> parseBinaryOption(
option: BinaryOption<T>,
valueName: String,
configuration: CompilerConfiguration
): BinaryOptionWithValue<T>? {
val value = option.valueParser.parse(valueName)
return if (value == null) {
configuration.report(STRONG_WARNING, "Unknown value '$valueName' of binary option '${option.name}'. " +
"Possible values are: ${option.valueParser.validValuesHint}")
null
} else {
BinaryOptionWithValue(option, value)
}
}
private fun parseOverrideKonanProperties(
arguments: K2NativeCompilerArguments,
configuration: CompilerConfiguration
): Map<String, String>? =
arguments.overrideKonanProperties?.mapNotNull {
val keyValueSeparatorIndex = it.indexOf('=')
if (keyValueSeparatorIndex > 0) {
it.substringBefore('=') to it.substringAfter('=')
} else {
configuration.report(
ERROR,
"incorrect property format: expected '<key>=<value>', got '$it'"
)
null
}
}?.toMap()
): Map<String, String>? = parseKeyValuePairs(arguments.overrideKonanProperties, configuration)
private fun parseKeyValuePairs(
argumentValue: Array<String>?,
configuration: CompilerConfiguration
): Map<String, String>? = argumentValue?.mapNotNull {
val keyValueSeparatorIndex = it.indexOf('=')
if (keyValueSeparatorIndex > 0) {
it.substringBefore('=') to it.substringAfter('=')
} else {
configuration.report(
ERROR,
"incorrect property format: expected '<key>=<value>', got '$it'"
)
null
}
}?.toMap()
@@ -344,6 +344,13 @@ class K2NativeCompilerArguments : CommonCompilerArguments() {
)
var llvmVariant: String? = null
@Argument(
value = "-Xbinary",
valueDescription = "<option=value>",
description = "Specify binary option"
)
var binaryOptions: Array<String>? = null
override fun configureAnalysisFlags(collector: MessageCollector, languageVersion: LanguageVersion): MutableMap<AnalysisFlag<*>, Any> =
super.configureAnalysisFlags(collector, languageVersion).also {
val useExperimental = it[AnalysisFlags.useExperimental] as List<*>
@@ -0,0 +1,74 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.backend.konan
import org.jetbrains.kotlin.config.CompilerConfigurationKey
import kotlin.properties.PropertyDelegateProvider
import kotlin.properties.ReadOnlyProperty
// Note: options defined in this class are a part of user interface, including the names:
// users can pass these options using a -Xbinary=name=value compiler argument or corresponding Gradle DSL.
object BinaryOptions : BinaryOptionRegistry() {
}
open class BinaryOption<T : Any>(
val name: String,
val valueParser: ValueParser<T>,
val compilerConfigurationKey: CompilerConfigurationKey<T> = CompilerConfigurationKey.create(name)
) {
interface ValueParser<T : Any> {
fun parse(value: String): T?
val validValuesHint: String?
}
}
open class BinaryOptionRegistry {
private val registeredOptionsByName = mutableMapOf<String, BinaryOption<*>>()
protected fun register(option: BinaryOption<*>) {
val previousOption = registeredOptionsByName[option.name]
if (previousOption != null) {
error("option '${option.name}' is registered twice")
}
registeredOptionsByName[option.name] = option
}
fun getByName(name: String): BinaryOption<*>? = registeredOptionsByName[name]
protected fun booleanOption(): PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, CompilerConfigurationKey<Boolean>>> =
PropertyDelegateProvider { _, property ->
val option = BinaryOption(property.name, BooleanValueParser)
register(option)
ReadOnlyProperty { _, _ ->
option.compilerConfigurationKey
}
}
protected inline fun <reified T : Enum<T>> option(): PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, CompilerConfigurationKey<T>>> =
PropertyDelegateProvider { _, property ->
val option = BinaryOption(property.name, EnumValueParser(enumValues<T>().toList()))
register(option)
ReadOnlyProperty { _, _ ->
option.compilerConfigurationKey
}
}
}
private object BooleanValueParser : BinaryOption.ValueParser<Boolean> {
override fun parse(value: String): Boolean? = value.toBooleanStrictOrNull()
override val validValuesHint: String?
get() = "true|false"
}
@PublishedApi
internal class EnumValueParser<T : Enum<T>>(val values: List<T>) : BinaryOption.ValueParser<T> {
// TODO: should we really ignore case here?
override fun parse(value: String): T? = values.firstOrNull { it.name.equals(value, ignoreCase = true) }
override val validValuesHint: String?
get() = values.joinToString("|")
}