Generate gradle options from compiler arguments

#KT-13633 fixed
This commit is contained in:
Alexey Tsvetkov
2016-09-23 19:57:11 +03:00
parent 2c34088859
commit 892fc63fd1
36 changed files with 972 additions and 410 deletions
@@ -0,0 +1,211 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.generators.arguments
import com.sampullara.cli.Argument
import org.jetbrains.kotlin.cli.common.arguments.GradleOption
import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.utils.Printer
import java.io.File
import java.io.PrintStream
import kotlin.reflect.*
// Additional properties that should be included in interface
interface AdditionalGradleProperties {
@GradleOption(defaultValue = "emptyList()")
@Argument(description = "A list of additional compiler arguments")
var freeCompilerArgs: List<String>
}
fun main(args: Array<String>) {
val dslSrcDir = File("libraries/tools/kotlin-gradle-plugin-dsl/src/main/kotlin")
val dslImplDir = File("libraries/tools/kotlin-gradle-plugin-core/src/main/kotlin")
val additionalGradleOptions = gradleOptions<AdditionalGradleProperties>()
// generate jvm interface
val jvmInterfaceFqName = FqName("org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions")
val optionsFromK2JVMCompilerArguments = gradleOptions<K2JVMCompilerArguments>()
File(dslSrcDir, jvmInterfaceFqName).usePrinter {
generateInterface(jvmInterfaceFqName,
optionsFromK2JVMCompilerArguments + additionalGradleOptions)
}
// generate jvm impl
val k2JvmCompilerArgumentsFqName = FqName(K2JVMCompilerArguments::class.qualifiedName!!)
val jvmImplFqName = FqName("org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptionsBase")
File(dslImplDir, jvmImplFqName).usePrinter {
generateImpl(jvmImplFqName,
jvmInterfaceFqName,
k2JvmCompilerArgumentsFqName,
optionsFromK2JVMCompilerArguments)
}
// generate js interface
val jsInterfaceFqName = FqName("org.jetbrains.kotlin.gradle.dsl.KotlinJsOptions")
val optionsFromK2JSCompilerArguments = gradleOptions<K2JSCompilerArguments>()
File(dslSrcDir, jsInterfaceFqName).usePrinter {
generateInterface(jsInterfaceFqName,
optionsFromK2JSCompilerArguments +
additionalGradleOptions)
}
val k2JsCompilerArgumentsFqName = FqName(K2JSCompilerArguments::class.qualifiedName!!)
val jsImplFqName = FqName("org.jetbrains.kotlin.gradle.dsl.KotlinJsOptionsBase")
File(dslImplDir, jsImplFqName).usePrinter {
generateImpl(jsImplFqName,
jsInterfaceFqName,
k2JsCompilerArgumentsFqName,
optionsFromK2JSCompilerArguments)
}
}
private inline fun <reified T : Any> gradleOptions(): List<KProperty1<T, *>> =
T::class.memberProperties.filter { it.findAnnotation<GradleOption>() != null }.sortedBy { it.name }
private fun File(baseDir: File, fqName: FqName): File {
val fileRelativePath = fqName.asString().replace(".", "/") + ".kt"
return File(baseDir, fileRelativePath)
}
private inline fun File.usePrinter(fn: Printer.()->Unit) {
if (!exists()) {
parentFile.mkdirs()
createNewFile()
}
PrintStream(outputStream()).use {
val printer = Printer(it)
printer.fn()
}
}
private fun Printer.generateInterface(type: FqName, properties: List<KProperty1<*, *>>) {
generateDeclaration("interface", type) {
for (property in properties) {
println()
generateDoc(property)
generatePropertyDeclaration(property)
}
}
}
private fun Printer.generateImpl(
type: FqName,
parentType: FqName,
argsType: FqName,
properties: List<KProperty1<*, *>>
) {
generateDeclaration("abstract class", type, afterType = ": $parentType") {
fun KProperty1<*, *>.backingField(): String = "${this.name}Field"
for (property in properties) {
println()
val backingField = property.backingField()
val backingFieldType = property.gradleReturnType + "?"
println("private var $backingField: $backingFieldType = null")
generatePropertyDeclaration(property, modifiers = "override")
withIndent {
println("get() = $backingField ?: ${property.gradleDefaultValue}")
println("set(value) { $backingField = value }")
}
}
println()
println("open fun updateArguments(args: $argsType) {")
withIndent {
for (property in properties) {
val backingField = property.backingField()
println("$backingField?.let { args.${property.name} = it }")
}
}
println("}")
}
println()
println("fun $argsType.fillDefaultValues() {")
withIndent {
for (property in properties) {
println("${property.name} = ${property.gradleDefaultValue}")
}
}
println("}")
}
private fun Printer.generateDeclaration(
modifiers: String,
type: FqName,
afterType: String? = null,
generateBody: Printer.()->Unit
) {
println("// DO NOT EDIT MANUALLY!")
println("// Generated by org/jetbrains/kotlin/generators/arguments/GenerateGradleOptions.kt")
if (!type.parent().isRoot) {
println("package ${type.parent()}")
println()
}
print("$modifiers ${type.shortName()} ")
afterType?.let { print("$afterType ") }
println("{")
withIndent {
generateBody()
}
println("}")
}
private fun Printer.generatePropertyDeclaration(property: KProperty1<*, *>, modifiers: String = "") {
val returnType = property.gradleReturnType
println("$modifiers var ${property.name}: $returnType")
}
private fun Printer.generateDoc(property: KProperty1<*, *>) {
val description = property.findAnnotation<Argument>()!!.description
val possibleValues = property.gradlePossibleValues
val defaultValue = property.gradleDefaultValue
println("/**")
println(" * $description")
if (possibleValues.isNotEmpty()) {
println(" * Possible values: ${possibleValues.joinToString()}")
}
println(" * Default value: $defaultValue")
println(" */")
}
private inline fun Printer.withIndent(fn: Printer.()->Unit) {
pushIndent()
fn()
popIndent()
}
private val KProperty1<*, *>.gradlePossibleValues: Array<String>
get() = findAnnotation<GradleOption>()!!.possibleValues
private val KProperty1<*, *>.gradleDefaultValue: String
get() = findAnnotation<GradleOption>()!!.defaultValue
private val KProperty1<*, *>.gradleReturnType: String
get() {
var type = returnType.toString().substringBeforeLast("!")
if (gradleDefaultValue == "null") {
type += "?"
}
return type
}
private inline fun <reified T> KAnnotatedElement.findAnnotation(): T? =
annotations.filterIsInstance<T>().firstOrNull()