Wizard: introduce interceptors for module templates
This commit is contained in:
+12
@@ -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 }
|
||||
|
||||
|
||||
-6
@@ -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
|
||||
}
|
||||
+12
@@ -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>
|
||||
|
||||
|
||||
+6
-2
@@ -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
|
||||
|
||||
+32
-31
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-6
@@ -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))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+18
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+76
-11
@@ -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"
|
||||
}
|
||||
}
|
||||
+3
@@ -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
|
||||
|
||||
+71
@@ -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()
|
||||
+20
@@ -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) }
|
||||
}
|
||||
Reference in New Issue
Block a user