diff --git a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/core/utils.kt b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/core/utils.kt index 58fc4ee8512..f47994e4c6d 100644 --- a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/core/utils.kt +++ b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/core/utils.kt @@ -10,6 +10,18 @@ import kotlin.reflect.KProperty inline infix fun ((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) -> 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 idFunction(): (T) -> T = { it } diff --git a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/ir/IR.kt b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/ir/IR.kt index 0e6ec724d11..e08a0c52625 100644 --- a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/ir/IR.kt +++ b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/ir/IR.kt @@ -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 -} \ No newline at end of file diff --git a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/ir/buildsystem/BuildFileIR.kt b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/ir/buildsystem/BuildFileIR.kt index efb22b8a909..657b5f0005b 100644 --- a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/ir/buildsystem/BuildFileIR.kt +++ b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/ir/buildsystem/BuildFileIR.kt @@ -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()?.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 diff --git a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/ir/buildsystem/gradle/GradleIR.kt b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/ir/buildsystem/gradle/GradleIR.kt index 610f2925930..f12e6d82552 100644 --- a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/ir/buildsystem/gradle/GradleIR.kt +++ b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/ir/buildsystem/gradle/GradleIR.kt @@ -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 = 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 diff --git a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/ir/buildsystem/gradle/GradleTaskIR.kt b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/ir/buildsystem/gradle/GradleTaskIR.kt index 223723c9877..656f3c3d03f 100644 --- a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/ir/buildsystem/gradle/GradleTaskIR.kt +++ b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/ir/buildsystem/gradle/GradleTaskIR.kt @@ -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) } -} \ No newline at end of file +} + +data class GradleConfigureTaskIR( + val taskAccess: GradleTaskAccessIR, + val dependsOn: List = emptyList(), + val irs: List = 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() + } + } +} diff --git a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/ir/buildsystem/tasks.kt b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/ir/buildsystem/tasks.kt index 1d0f9715f51..05f3f664bd0 100644 --- a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/ir/buildsystem/tasks.kt +++ b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/ir/buildsystem/tasks.kt @@ -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)) ) } } diff --git a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/plugins/templates/TemplatesPlugin.kt b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/plugins/templates/TemplatesPlugin.kt index ff45cab29d3..2108f2b875d 100644 --- a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/plugins/templates/TemplatesPlugin.kt +++ b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/plugins/templates/TemplatesPlugin.kt @@ -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>( @@ -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 + }.flatten().fold() + interceptors.applyUntilConverge(buildFile).asSuccess() + } + } + } } \ No newline at end of file diff --git a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/templates/SimpleJsClientTemplate.kt b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/templates/SimpleJsClientTemplate.kt index 37c4d28a7bc..4398ffb6a0c 100644 --- a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/templates/SimpleJsClientTemplate.kt +++ b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/templates/SimpleJsClientTemplate.kt @@ -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 = buildList { + +interceptTemplate(KtorServerTemplate()) { + applicableIf { buildFileIR -> + val tasks = buildFileIR.irsOfTypeOrNull() ?: return@applicableIf false + tasks.none { it.taskAccess.name.endsWith("Jar") } + } + + transformBuildFile { buildFileIR -> + val jsSourcesetName = sourceset.safeAs()?.targetName ?: return@transformBuildFile null + val jvmTarget = buildFileIR.targets.firstOrNull { target -> + target.safeAs()?.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 = 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" } } \ No newline at end of file diff --git a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/templates/Template.kt b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/templates/Template.kt index e3aa25f8a8e..623f55e735b 100644 --- a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/templates/Template.kt +++ b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/templates/Template.kt @@ -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 = emptyList() + open fun createInterceptors(sourceset: SourcesetIR): List = emptyList() + fun TaskRunningContext.applyToSourceset( templateEngine: TemplateEngine, sourceset: SourcesetIR diff --git a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/transformers/interceptors/Interceptor.kt b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/transformers/interceptors/Interceptor.kt new file mode 100644 index 00000000000..e1f2f1cad3d --- /dev/null +++ b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/transformers/interceptors/Interceptor.kt @@ -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>, + val buildFileTransformers: List> +) : 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(private val template: T) { + private val buildFileTransformers = mutableListOf>() + fun transformBuildFile(transform: TransformerFunction) { + buildFileTransformers += transform + } + + private val applicabilityCheckers = mutableListOf>() + fun applicableIf(checker: Predicate) { + applicabilityCheckers += checker + } + + init { + applicableIf { buildFile -> + buildFile.sourcesets.any { it.template?.id == template.id } + } + } + + fun build() = TemplateInterceptor(applicabilityCheckers, buildFileTransformers) +} + +fun List.fold() = + TemplateInterceptor( + flatMap { it.applicabilityCheckers }, + flatMap { it.buildFileTransformers } + ) + + +fun interceptTemplate(template: T, builder: TemplateInterceptorBuilder.() -> Unit) = + TemplateInterceptorBuilder(template).apply(builder).build() \ No newline at end of file diff --git a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/transformers/transformer.kt b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/transformers/transformer.kt new file mode 100644 index 00000000000..68f65bd16ab --- /dev/null +++ b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/transformers/transformer.kt @@ -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? +typealias Predicate = (T) -> Boolean + +fun Iterable>.foldTransformers() = + fold, TransformerFunction>({ null }) { acc, transformer -> + acc andThenNullable transformer + } + +fun Iterable>.foldPredicates() = { value: T -> + all { it.invoke(value) } +} \ No newline at end of file