Wizard: introduce interceptors for module templates

This commit is contained in:
Ilya Kirillov
2019-12-16 15:07:49 +03:00
parent 8bf9c31880
commit 32a151f561
11 changed files with 256 additions and 56 deletions
@@ -10,6 +10,18 @@ import kotlin.reflect.KProperty
inline infix fun <A, B, C> ((A) -> B).andThen(crossinline then: (B) -> C): (A) -> C =
{ then(this(it)) }
/**
* Composes two function together like normal composition if first function returns non-null value;
* Otherwise, applies second function
*/
inline infix fun <A : Any> ((A) -> A?).andThenNullable(crossinline then: (A) -> A?): (A) -> A? =
{ initial ->
val thisResult = this(initial)
if (thisResult != null) then(thisResult) else then(initial)
}
@Suppress("NOTHING_TO_INLINE")
inline fun <T> idFunction(): (T) -> T = { it }
@@ -1,10 +1,4 @@
package org.jetbrains.kotlin.tools.projectWizard.ir
import org.jetbrains.kotlin.tools.projectWizard.core.safeAs
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.FreeIR
interface IR
interface TaskIR : FreeIR {
val name: String
}
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.BuildFilePrinter
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.GradlePrinter
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.MavenPrinter
import java.nio.file.Path
import javax.xml.transform.Source
data class BuildFileIR(
val name: String,
@@ -81,6 +82,17 @@ data class BuildFileIR(
}
}
val BuildFileIR.targets
get() = modules.safeAs<MultiplatformModulesStructureIR>()?.targets.orEmpty()
val BuildFileIR.sourcesets
get() = modules.modules.flatMap { module ->
when (module) {
is SingleplatformModuleIR -> module.sourcesets
is SourcesetModuleIR -> listOf(module as SourcesetIR)
}
}
sealed class ModulesStructureIR : BuildSystemIR, IrsOwner {
abstract val modules: List<ModuleIR>
@@ -40,13 +40,17 @@ data class CreateGradleValueIR(val name: String, val body: GradleIR) : GradleIR
}
data class GradleCallIr(
val name: String
val name: String,
val parameters: List<BuildSystemIR> = emptyList()
) : GradleIR {
override fun GradlePrinter.renderGradle() {
call(name, forceBrackets = true) {}
call(name, forceBrackets = true) {
parameters.list { it.render(this) }
}
}
}
data class GradleSectionIR(
val name: String,
val body: BodyIR
@@ -1,48 +1,49 @@
package org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle
import org.jetbrains.kotlin.tools.projectWizard.ir.TaskIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.BuildSystemIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.FreeIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.render
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.GradlePrinter
interface GradleTaskIR : GradleIR, TaskIR {
val taskClass: String
val body: BodyIR
interface GradleTaskAccessIR : GradleIR {
val name: String
}
data class CreateGradleTaskIR(
data class GradleByNameTaskAccessIR(
override val name: String,
override val taskClass: String,
override val body: BodyIR
) : GradleTaskIR {
override fun GradlePrinter.renderGradle() {
when (dsl) {
GradlePrinter.GradleDsl.GROOVY -> {
+"tasks.create('$name', $taskClass)"
}
GradlePrinter.GradleDsl.KOTLIN -> {
+"val $name by tasks.registering($taskClass::class)"
}
}
+" "
body.renderIfNotEmpty(this)
}
}
data class GetGradleTaskIR(
override val name: String,
override val taskClass: String,
override val body: BodyIR
) : GradleTaskIR {
val taskClass: String? = null
) : GradleTaskAccessIR {
override fun GradlePrinter.renderGradle() {
when (dsl) {
GradlePrinter.GradleDsl.GROOVY -> {
+"tasks.getByName('$name')"
}
GradlePrinter.GradleDsl.KOTLIN -> {
+"tasks.getByName<$taskClass>(${name.quotified})"
+"tasks.getByName"
taskClass?.let { +"<$it>" }
+"(${name.quotified})"
}
}
+" "
body.renderIfNotEmpty(this)
}
}
}
data class GradleConfigureTaskIR(
val taskAccess: GradleTaskAccessIR,
val dependsOn: List<BuildSystemIR> = emptyList(),
val irs: List<BuildSystemIR> = emptyList()
) : GradleIR, FreeIR {
override fun GradlePrinter.renderGradle() {
taskAccess.render(this)
+" "
inBrackets {
if (dependsOn.isNotEmpty()) {
indent()
call("dependsOn", forceBrackets = true) {
dependsOn.list { it.render(this) }
}
nl()
}
irs.listNl()
}
}
}
@@ -16,12 +16,12 @@ fun runTaskIrs(mainClass: String, classPath: BuildSystemIR? = null) = buildList<
})
if (classPath != null) {
+GetGradleTaskIR(
name = "run",
taskClass = "JavaExec",
body = buildBody {
classPath.let { +GradleAssignmentIR("classpath", it) }
}
+GradleConfigureTaskIR(
GradleByNameTaskAccessIR(
name = "run",
taskClass = "JavaExec"
),
irs = listOf(GradleAssignmentIR("classpath", classPath))
)
}
}
@@ -7,6 +7,8 @@ import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.BuildSystemP
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.updateBuildFiles
import org.jetbrains.kotlin.tools.projectWizard.templates.*
import org.jetbrains.kotlin.tools.projectWizard.transformers.interceptors.TemplateInterceptor
import org.jetbrains.kotlin.tools.projectWizard.transformers.interceptors.fold
class TemplatesPlugin(context: Context) : Plugin(context) {
val templates by property<Map<String, Template>>(
@@ -91,4 +93,20 @@ class TemplatesPlugin(context: Context) : Plugin(context) {
}
}
}
val postApplyTemplatesToSourcesets by pipelineTask(GenerationPhase.PROJECT_GENERATION) {
runBefore(BuildSystemPlugin::createModules)
runAfter(KotlinPlugin::createModules)
runAfter(TemplatesPlugin::addTemplatesToSourcesets)
withAction {
updateBuildFiles { buildFile ->
@Suppress("UNCHECKED_CAST")
val interceptors = buildFile.sourcesets.mapNotNull { sourceset ->
sourceset.template?.createInterceptors(sourceset) as? List<TemplateInterceptor>
}.flatten().fold()
interceptors.applyUntilConverge(buildFile).asSuccess()
}
}
}
}
@@ -7,17 +7,18 @@ package org.jetbrains.kotlin.tools.projectWizard.templates
import org.jetbrains.kotlin.tools.projectWizard.core.TaskRunningContext
import org.jetbrains.kotlin.tools.projectWizard.core.buildList
import org.jetbrains.kotlin.tools.projectWizard.core.entity.TemplateSetting
import org.jetbrains.kotlin.tools.projectWizard.core.safeAs
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.*
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.*
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.multiplatform.DefaultTargetConfigurationIR
import org.jetbrains.kotlin.tools.projectWizard.library.MavenArtifact
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleSubType
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleType
import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.DefaultRepository
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Repositories
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.SourcesetType
import org.jetbrains.kotlin.tools.projectWizard.settings.version.Version
import org.jetbrains.kotlin.tools.projectWizard.transformers.interceptors.Interceptor
import org.jetbrains.kotlin.tools.projectWizard.transformers.interceptors.interceptTemplate
class SimpleJsClientTemplate : Template() {
override val title: String = "Simple JS client"
@@ -39,23 +40,87 @@ class SimpleJsClientTemplate : Template() {
FileTemplateDescriptor("$id/client.kt.vm", sourcesPath("client.kt"))
)
override fun createInterceptors(sourceset: SourcesetIR): List<Interceptor> = buildList {
+interceptTemplate(KtorServerTemplate()) {
applicableIf { buildFileIR ->
val tasks = buildFileIR.irsOfTypeOrNull<GradleConfigureTaskIR>() ?: return@applicableIf false
tasks.none { it.taskAccess.name.endsWith("Jar") }
}
transformBuildFile { buildFileIR ->
val jsSourcesetName = sourceset.safeAs<SourcesetModuleIR>()?.targetName ?: return@transformBuildFile null
val jvmTarget = buildFileIR.targets.firstOrNull { target ->
target.safeAs<DefaultTargetConfigurationIR>()?.targetAccess?.type == ModuleSubType.jvm
} as? DefaultTargetConfigurationIR ?: return@transformBuildFile null
val jvmTargetName = jvmTarget.name
val webPackTaskName = "${jsSourcesetName}BrowserWebpack"
val jvmJarTaskAccess = GradleByNameTaskAccessIR("${jvmTargetName}Jar", "Jar")
val jvmJarTaskConfiguration = run {
val webPackTaskVariable = CreateGradleValueIR(
webPackTaskName,
GradleByNameTaskAccessIR(webPackTaskName, WEBPACK_TASK_CLASS)
)
val from = GradleCallIr(
"from",
listOf(
GradleCallIr(
"File",
listOf(
GradlePropertyAccessIR("$webPackTaskName.destinationDirectory"),
GradlePropertyAccessIR("$webPackTaskName.outputFileName")
)
)
)
)
GradleConfigureTaskIR(
jvmJarTaskAccess,
dependsOn = listOf(GradleByNameTaskAccessIR(webPackTaskName)),
irs = listOf(
webPackTaskVariable,
from
)
)
}
val runTaskConfiguration = run {
val taskAccess = GradleByNameTaskAccessIR("run", "JavaExec")
val classpath = GradleCallIr("classpath", listOf(jvmJarTaskAccess))
GradleConfigureTaskIR(
taskAccess,
dependsOn = listOf(jvmJarTaskAccess),
irs = listOf(classpath)
)
}
buildFileIR.withIrs(jvmJarTaskConfiguration, runTaskConfiguration)
}
}
}
override fun TaskRunningContext.getIrsToAddToBuildFile(sourceset: SourcesetIR): List<BuildSystemIR> = buildList {
+RepositoryIR(DefaultRepository.JCENTER)
if (sourceset is SourcesetModuleIR) {
+GradleImportIR("org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpack")
val taskAccessIR = GetGradleTaskIR(
val taskAccessIR = GradleByNameTaskAccessIR(
"${sourceset.targetName}BrowserWebpack",
"KotlinWebpack",
buildBody {
+GradleAssignmentIR("outputFileName", GradleStringConstIR(JS_OUTPUT_FILE_NAME))
}
WEBPACK_TASK_CLASS
)
+GradleConfigureTaskIR(
taskAccessIR,
irs = listOf(
GradleAssignmentIR(
"outputFileName", GradleStringConstIR(JS_OUTPUT_FILE_NAME)
)
)
)
+CreateGradleValueIR(JS_BROWSER_WEBPACK_TASK_ALIAS, taskAccessIR)
}
}
companion object {
const val JS_OUTPUT_FILE_NAME = "output.js"
const val JS_BROWSER_WEBPACK_TASK_ALIAS = "jsBrowserWebpack"
const val WEBPACK_TASK_CLASS = "KotlinWebpack"
}
}
@@ -5,6 +5,7 @@ import org.jetbrains.kotlin.tools.projectWizard.SettingsOwner
import org.jetbrains.kotlin.tools.projectWizard.core.*
import org.jetbrains.kotlin.tools.projectWizard.core.entity.*
import org.jetbrains.kotlin.tools.projectWizard.core.service.FileSystemWizardService
import org.jetbrains.kotlin.tools.projectWizard.transformers.interceptors.Interceptor
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.*
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.multiplatform.TargetConfigurationIR
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
@@ -78,6 +79,8 @@ abstract class Template : SettingsOwner {
open fun TaskRunningContext.getFileTemplates(sourceset: SourcesetIR): List<FileTemplateDescriptor> = emptyList()
open fun createInterceptors(sourceset: SourcesetIR): List<Interceptor> = emptyList()
fun TaskRunningContext.applyToSourceset(
templateEngine: TemplateEngine,
sourceset: SourcesetIR
@@ -0,0 +1,71 @@
/*
* Copyright 2010-2019 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.tools.projectWizard.transformers.interceptors
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.BuildFileIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.sourcesets
import org.jetbrains.kotlin.tools.projectWizard.templates.Template
import org.jetbrains.kotlin.tools.projectWizard.transformers.Predicate
import org.jetbrains.kotlin.tools.projectWizard.transformers.TransformerFunction
interface Interceptor
data class TemplateInterceptor(
val applicabilityCheckers: List<Predicate<BuildFileIR>>,
val buildFileTransformers: List<TransformerFunction<BuildFileIR>>
) : Interceptor {
fun applyTo(buildFileIR: BuildFileIR): BuildFileIR? {
if (applicabilityCheckers.any { checker -> !checker(buildFileIR) }) return null
var isApplied = false
var result: BuildFileIR = buildFileIR
for (transformer in buildFileTransformers) {
val newResult = transformer(result)
if (newResult != null) {
result = newResult
isApplied = true
}
}
return result.takeIf { isApplied }
}
fun applyUntilConverge(buildFileIR: BuildFileIR) =
generateSequence(buildFileIR, { buildFile ->
applyTo(buildFile)
}).last()
}
class TemplateInterceptorBuilder<T : Template>(private val template: T) {
private val buildFileTransformers = mutableListOf<TransformerFunction<BuildFileIR>>()
fun transformBuildFile(transform: TransformerFunction<BuildFileIR>) {
buildFileTransformers += transform
}
private val applicabilityCheckers = mutableListOf<Predicate<BuildFileIR>>()
fun applicableIf(checker: Predicate<BuildFileIR>) {
applicabilityCheckers += checker
}
init {
applicableIf { buildFile ->
buildFile.sourcesets.any { it.template?.id == template.id }
}
}
fun build() = TemplateInterceptor(applicabilityCheckers, buildFileTransformers)
}
fun List<TemplateInterceptor>.fold() =
TemplateInterceptor(
flatMap { it.applicabilityCheckers },
flatMap { it.buildFileTransformers }
)
fun <T : Template> interceptTemplate(template: T, builder: TemplateInterceptorBuilder<T>.() -> Unit) =
TemplateInterceptorBuilder(template).apply(builder).build()
@@ -0,0 +1,20 @@
/*
* Copyright 2010-2019 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.tools.projectWizard.transformers
import org.jetbrains.kotlin.tools.projectWizard.core.andThenNullable
typealias TransformerFunction<I> = (I) -> I?
typealias Predicate<T> = (T) -> Boolean
fun <T : Any> Iterable<TransformerFunction<T>>.foldTransformers() =
fold<TransformerFunction<T>, TransformerFunction<T>>({ null }) { acc, transformer ->
acc andThenNullable transformer
}
fun <T> Iterable<Predicate<T>>.foldPredicates() = { value: T ->
all { it.invoke(value) }
}