Wizard: introduce mobile mpp module template & allow module templates for mpp modules

This commit is contained in:
Ilya Kirillov
2020-10-02 20:13:04 +03:00
parent ac6b9e4aa0
commit d547cb9ee6
29 changed files with 594 additions and 392 deletions
@@ -165,13 +165,14 @@ private class ModuleTemplateComponent(
private object NoneTemplate : Template() {
override val title = KotlinNewProjectWizardUIBundle.message("module.settings.template.none")
override val description: String = ""
override val moduleTypes: Set<ModuleType> = ModuleType.ALL
override fun isSupportedByModuleType(module: Module): Boolean = true
override val id: String = "none"
}
fun Reader.availableTemplatesFor(module: Module) =
TemplatesPlugin.templates.propertyValue.values.filter { template ->
module.configurator.moduleType in template.moduleTypes
template.isSupportedByModuleType(module)
}
@@ -17,6 +17,7 @@ version = '1.0-SNAPSHOT'
allprojects {
repositories {
google()
jcenter()
mavenCentral()
maven { url 'KOTLIN_REPO' }
}
@@ -17,6 +17,7 @@ version = "1.0-SNAPSHOT"
allprojects {
repositories {
google()
jcenter()
mavenCentral()
maven { url = uri("KOTLIN_REPO") }
}
@@ -17,6 +17,7 @@ version = '1.0-SNAPSHOT'
allprojects {
repositories {
google()
jcenter()
mavenCentral()
maven { url 'KOTLIN_REPO' }
}
@@ -17,6 +17,7 @@ version = "1.0-SNAPSHOT"
allprojects {
repositories {
google()
jcenter()
mavenCentral()
maven { url = uri("KOTLIN_REPO") }
}
@@ -150,6 +150,10 @@ project.template.mpp.mobile.lib.description=Library that supports sharing code b
module.template.console.jvm.title=Console Application
module.template.console.jvm.description=Simple "Hello World!" Kotlin/JVM application that works in the console
module.template.mpp.mobile.title=Mobile Application
module.template.mpp.mobile.description=Mobile applications for iOS and Android with Kotlin Multiplatform Mobile, which supports sharing common code between platforms.
module.template.ktor.server.title=Ktor server
module.template.ktor.server.description=Configurable Ktor web server for building web applications
module.template.ktor.server.setting.engine=Ktor server engine
@@ -10,6 +10,10 @@ import org.jetbrains.kotlin.tools.projectWizard.core.service.ServicesManager
import org.jetbrains.kotlin.tools.projectWizard.core.service.SettingSavingWizardService
import org.jetbrains.kotlin.tools.projectWizard.core.service.WizardService
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.allIRModules
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.ModuleReference
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.path
import kotlin.reflect.KClass
@@ -239,6 +243,11 @@ fun Reader.getUnspecifiedSettings(phases: Set<GenerationPhase>): List<AnySetting
val provided = pluginSettings.map(PluginSetting<*, *>::path).toSet()
return required.filterNot { it.path in provided }
}
fun Reader.moduleByReference(reference: ModuleReference): Module = when (reference) {
is ModuleReference.ByModule -> reference.module
is ModuleReference.ByPath -> allIRModules.first { it.originalModule.path == reference.path }.originalModule
}
typealias Reader = Context.Reader
@@ -12,6 +12,9 @@ import kotlin.reflect.KProperty
inline infix fun <A, B, C> ((A) -> B).andThen(crossinline then: (B) -> C): (A) -> C =
{ then(this(it)) }
inline fun <A, B, C> compose(crossinline function: (A) -> B, crossinline then: (B) -> C): (A) -> C =
{ then(function(it)) }
/**
* Composes two function together like normal composition if first function returns non-null value;
@@ -138,6 +138,7 @@ sealed class ModulesStructureIR : BuildSystemIR, IrsOwner {
data class MultiplatformModulesStructureIR(
val targets: List<BuildSystemIR>,
val multiplatformModule: FakeMultiplatformModuleIR,
override val modules: List<MultiplatformModuleIR>,
override val irs: PersistentList<BuildSystemIR>
) : GradleIR, ModulesStructureIR() {
@@ -173,6 +174,15 @@ fun MultiplatformModulesStructureIR.updateTargets(
return copy(targets = newTargets)
}
fun MultiplatformModulesStructureIR.updateSourceSets(
updater: (ModuleIR) -> ModuleIR
): MultiplatformModulesStructureIR {
val newModules = modules.map { updater(it) as MultiplatformModuleIR }
return copy(modules = newModules)
}
data class SingleplatformModulesStructureWithSingleModuleIR(
val module: SingleplatformModuleIR,
override val irs: PersistentList<BuildSystemIR>
@@ -16,6 +16,11 @@ fun <I : IrsOwner> I.withIrs(irs: List<BuildSystemIR>) = withReplacedIrs(irs = t
@Suppress("UNCHECKED_CAST")
fun <I : IrsOwner> I.withIrs(vararg irs: BuildSystemIR) = withReplacedIrs(irs = this.irs + irs) as I
@Suppress("UNCHECKED_CAST")
inline fun <I : IrsOwner> I.withoutIrs(filterNot: (BuildSystemIR) -> Boolean): I =
withReplacedIrs(irs = this.irs.filterNot(filterNot).toPersistentList()) as I
inline fun <reified I : BuildSystemIR> IrsOwner.irsOfType(): List<I> =
irs.filterIsInstance<I>().let { irs ->
@@ -1,6 +1,7 @@
package org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.GradleIR
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.BuildFilePrinter
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.GradlePrinter
@@ -16,8 +17,26 @@ sealed class ModuleIR : IrsOwner, BuildSystemIR {
abstract val template: Template?
abstract val originalModule: Module
abstract val sourcesets: List<SourcesetIR>
abstract override fun withReplacedIrs(irs: PersistentList<BuildSystemIR>): ModuleIR
}
data class FakeMultiplatformModuleIR(
override val name: String,
override val path: Path,
override val template: Template?,
val targets: List<MultiplatformModuleIR>,
override val originalModule: Module
) : ModuleIR() {
override val sourcesets: List<SourcesetIR> = emptyList()
override val irs: PersistentList<BuildSystemIR> = persistentListOf()
override fun withReplacedIrs(irs: PersistentList<BuildSystemIR>): FakeMultiplatformModuleIR = this
override fun BuildFilePrinter.render() {
error("Should not be called")
}
}
data class SingleplatformModuleIR(
override val name: String,
@@ -66,6 +66,7 @@ object AndroidSinglePlatformModuleConfigurator :
override fun createBuildFileIRs(reader: Reader, configurationData: ModulesToIrConversionData, module: Module) = irsList {
+super<AndroidModuleConfigurator>.createBuildFileIRs(reader, configurationData, module)
+RepositoryIR((DefaultRepository.JCENTER))
+AndroidConfigIR(
javaPackage = when (reader.createAndroidPlugin(module)) {
AndroidGradlePlugin.APPLICATION -> module.javaPackage(configurationData.pomIr)
@@ -7,31 +7,20 @@ package org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators
import org.jetbrains.annotations.NonNls
import org.jetbrains.kotlin.tools.projectWizard.KotlinNewProjectWizardBundle
import org.jetbrains.kotlin.tools.projectWizard.core.*
import org.jetbrains.kotlin.tools.projectWizard.core.entity.properties.ModuleConfiguratorProperty
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.ModuleConfiguratorSetting
import org.jetbrains.kotlin.tools.projectWizard.core.TaskResult
import org.jetbrains.kotlin.tools.projectWizard.core.Writer
import org.jetbrains.kotlin.tools.projectWizard.core.compute
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.KotlinBuildSystemPluginIR
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.GradlePlugin
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleSubType
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModulesToIrConversionData
import org.jetbrains.kotlin.tools.projectWizard.plugins.projectPath
import org.jetbrains.kotlin.tools.projectWizard.plugins.templates.TemplatesPlugin
import org.jetbrains.kotlin.tools.projectWizard.settings.JavaPackage
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.ModuleKind
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.SourcesetType
import org.jetbrains.kotlin.tools.projectWizard.templates.FileDescriptor
import org.jetbrains.kotlin.tools.projectWizard.templates.FileTemplate
import org.jetbrains.kotlin.tools.projectWizard.templates.FileTextDescriptor
import java.nio.file.Path
object MppModuleConfigurator : ModuleConfigurator,
ModuleConfiguratorWithSettings,
ModuleConfiguratorSettings(),
ModuleConfiguratorProperties,
ModuleConfiguratorWithProperties {
ModuleConfiguratorSettings() {
override val moduleKind = ModuleKind.multiplatform
@@ -49,11 +38,6 @@ object MppModuleConfigurator : ModuleConfigurator,
version = configurationData.kotlinVersion
)
val mppSources by listProperty<MppSources>()
override fun getConfiguratorProperties(): List<ModuleConfiguratorProperty<*>> =
listOf(mppSources)
// TODO remove when be removed in KMM wizard
val generateTests by booleanSetting("Generate Tests", GenerationPhase.PROJECT_GENERATION) {
defaultValue = value(false)
@@ -64,295 +48,6 @@ object MppModuleConfigurator : ModuleConfigurator,
module: Module,
modulePath: Path,
): TaskResult<Unit> = compute {
createMppFiles(module, modulePath).ensure()
createSimpleFiles(module, modulePath).ensure()
GradlePlugin.gradleProperties.addValues("kotlin.mpp.enableGranularSourceSetsMetadata" to true)
}
private fun Writer.createMppFiles(
module: Module,
modulePath: Path
): TaskResult<Unit> = inContextOfModuleConfigurator(module) {
val mppFiles = mppSources.reference.propertyValue.flatMap { it.mppFiles }
.distinctBy { it.fileNameWithPackage } // todo merge files with the same name here
mppFiles.mapSequenceIgnore { file ->
module.subModules.mapSequenceIgnore mapTargets@{ target ->
val moduleSubType = //TODO handle for non-simple target configurator
target.configurator.safeAs<SimpleTargetConfigurator>()?.moduleSubType ?: return@mapTargets UNIT_SUCCESS
val path = pathForFileInTarget(modulePath, module, file.javaPackage, file.filename, target, SourcesetType.main)
val fileTemplate = FileTemplate(
FileTextDescriptor(file.printForModuleSubType(moduleSubType), path),
projectPath,
)
TemplatesPlugin.fileTemplatesToRender.addValues(fileTemplate)
}
}
}
private fun Writer.createSimpleFiles(
module: Module,
modulePath: Path
): TaskResult<Unit> = inContextOfModuleConfigurator(module) {
val mppFiles = mppSources.reference.propertyValue
val filesWithPaths = module.subModules.flatMap { target ->
val moduleSubType = //TODO handle for non-simple target configurator
target.configurator.safeAs<SimpleTargetConfigurator>()?.moduleSubType ?: return@flatMap emptyList()
mppFiles
.flatMap { it.getFilesFor(moduleSubType) }
.distinctBy { it.uniqueIdentifier }
.map { file ->
val path = projectPath / pathForFileInTarget(modulePath, module, file.javaPackage, file.filename, target, file.type)
file to path
}
}.distinctBy { (_, path) -> path }
filesWithPaths.mapSequenceIgnore { (file, path) ->
val fileTemplate = FileTemplate(file.fileDescriptor, path, mapOf("package" to file.javaPackage?.asCodePackage()))
TemplatesPlugin.fileTemplatesToRender.addValues(fileTemplate)
}
}
private fun pathForFileInTarget(
mppModulePath: Path,
mppModule: Module,
javaPackage: JavaPackage?,
filename: String,
target: Module,
sourcesetType: SourcesetType,
) = mppModulePath /
Defaults.SRC_DIR /
"${target.name}${sourcesetType.name.capitalize()}" /
mppModule.configurator.kotlinDirectoryName /
javaPackage?.asPath() /
filename
}
@DslMarker
annotation class ExpectFileDSL
data class MppFile(
@NonNls val filename: String,
@NonNls val javaPackage: JavaPackage? = null,
val declarations: List<MppDeclaration>
) {
val fileNameWithPackage
get() = javaPackage?.let { "${it.asCodePackage()}." }.orEmpty() + filename
fun printForModuleSubType(moduleSubType: ModuleSubType): String = buildString {
javaPackage?.let { appendLine("package ${it.asCodePackage()}"); appendLine() }
if (moduleSubType == ModuleSubType.common) {
declarations.joinTo(this, separator = "\n\n") { it.printExpect() }
} else {
printImports(declarations.flatMap { it.importsFor(moduleSubType) })
declarations.joinTo(this, separator = "\n\n") { it.printActualFor(moduleSubType) }
}
}.trim()
private fun StringBuilder.printImports(imports: List<String>) {
if (imports.isEmpty()) return
imports.distinct().joinTo(this, separator = "\n", prefix = "\n", postfix = "\n\n") { import ->
"import $import"
}
}
@ExpectFileDSL
class Builder(private val filename: String, val javaPackage: JavaPackage? = null) {
private val declarations = mutableListOf<MppDeclaration>()
fun function(signature: String, init: MppFunction.Builder.() -> Unit = {}) {
declarations += MppFunction.Builder(signature).apply(init).build()
}
fun `class`(name: String, init: MppClass.Builder.() -> Unit = {}) {
declarations += MppClass.Builder(name).apply(init).build()
}
fun build() = MppFile(filename, javaPackage, declarations)
}
}
@ExpectFileDSL
class MppSources(val mppFiles: List<MppFile>, val simpleFiles: List<SimpleFiles>) {
fun getFilesFor(moduleSubType: ModuleSubType): List<SimpleFile> =
simpleFiles.filter { moduleSubType in it.moduleSubTypes }.flatMap { it.files }
class Builder(val javaPackage: JavaPackage?) {
private val mppFiles = mutableListOf<MppFile>()
private val simpleFiles = mutableListOf<SimpleFiles>()
fun mppFile(filename: String, init: MppFile.Builder.() -> Unit) {
mppFiles += MppFile.Builder(filename, javaPackage).apply(init).build()
}
fun filesFor(vararg moduleSubTypes: ModuleSubType, init: SimpleFiles.Builder.() -> Unit) {
simpleFiles += SimpleFiles.Builder(moduleSubTypes.toList(), javaPackage).apply(init).build()
}
fun build() = MppSources(mppFiles, simpleFiles)
}
}
data class SimpleFiles(val moduleSubTypes: List<ModuleSubType>, val files: List<SimpleFile>) {
class Builder(private val moduleSubTypes: List<ModuleSubType>, val filesPackage: JavaPackage?) {
private val files = mutableListOf<SimpleFile>()
fun file(fileDescriptor: FileDescriptor, filename: String, type: SourcesetType, init: SimpleFile.Builder.() -> Unit = {}) {
files += SimpleFile.Builder(fileDescriptor, filename, type).apply(init).apply {
javaPackage = filesPackage
}.build()
}
fun build() = SimpleFiles(moduleSubTypes, files)
}
}
data class SimpleFile(val fileDescriptor: FileDescriptor, val javaPackage: JavaPackage?, val filename: String, val type: SourcesetType) {
val uniqueIdentifier get() = "${type}/${javaPackage}/${filename}"
class Builder(private val fileDescriptor: FileDescriptor, private val filename: String, private val type: SourcesetType) {
var javaPackage: JavaPackage? = null
fun build() = SimpleFile(fileDescriptor, javaPackage, filename, type)
}
}
fun mppSources(javaPackage: JavaPackage? = null, init: MppSources.Builder.() -> Unit): MppSources =
MppSources.Builder(javaPackage).apply(init).build()
sealed class MppDeclaration {
@Suppress("SpellCheckingInspection")
abstract val actuals: Actuals
abstract fun printExpect(): String
abstract fun printActualFor(moduleSubType: ModuleSubType): String
fun importsFor(moduleSubType: ModuleSubType): List<String> = actuals.getBodyFor(moduleSubType).imports
@ExpectFileDSL
abstract class Builder {
private var defaultActualDeclarationBody = DefaultActualDeclarationBody(text = "", imports = emptyList())
@Suppress("SpellCheckingInspection")
private val actuals = mutableMapOf<ModuleSubType, ActualDeclarationBody>()
fun default(defaultText: String, init: DefaultActualDeclarationBody.Builder.() -> Unit = {}) {
defaultActualDeclarationBody = DefaultActualDeclarationBody.Builder(defaultText).also(init).build()
}
fun actualFor(vararg moduleSubTypes: ModuleSubType, actualBody: String, init: ActualDeclarationBody.Builder.() -> Unit = {}) {
moduleSubTypes.forEach { moduleSubType ->
actuals[moduleSubType] = ActualDeclarationBody.Builder(actualBody).apply(init).build()
}
}
protected fun buildActuals() = Actuals(defaultActualDeclarationBody, actuals)
}
}
data class MppFunction(
val signature: String,
@Suppress("SpellCheckingInspection")
override val actuals: Actuals,
) : MppDeclaration() {
override fun printExpect(): String =
"expect fun $signature"
override fun printActualFor(moduleSubType: ModuleSubType): String {
val body = actuals.getBodyFor(moduleSubType)
return buildString {
appendLine("actual fun $signature {")
appendLine(body.text.indented(4))
append("}")
}
}
@ExpectFileDSL
class Builder(private val signature: String) : MppDeclaration.Builder() {
fun build(): MppFunction = MppFunction(
signature,
buildActuals(),
)
}
}
data class MppClass(
val name: String,
val expectBody: String?,
@Suppress("SpellCheckingInspection")
override val actuals: Actuals,
) : MppDeclaration() {
override fun printExpect(): String = buildString {
append("expect class $name()")
expectBody?.let { body ->
appendLine(" {")
appendLine(body.indented(4))
append("}")
}
}
override fun printActualFor(moduleSubType: ModuleSubType): String {
val body = actuals.getBodyFor(moduleSubType)
return buildString {
appendLine("actual class $name actual constructor() {")
appendLine(body.text.indented(4))
append("}")
}
}
@ExpectFileDSL
class Builder(private val name: String) : MppDeclaration.Builder() {
var expectBody: String? = null
fun build(): MppClass = MppClass(
name,
expectBody,
buildActuals(),
)
}
}
private fun String.indented(indentValue: Int): String {
val indent = " ".repeat(indentValue)
return split('\n').joinToString(separator = "\n") { line -> indent + line.trim() }
}
@Suppress("SpellCheckingInspection")
data class Actuals(
val defaultActualDeclarationBody: DefaultActualDeclarationBody,
val actuals: Map<ModuleSubType, ActualDeclarationBody>,
) {
fun getBodyFor(moduleSubType: ModuleSubType): DeclarationBody =
actuals[moduleSubType] ?: defaultActualDeclarationBody
}
sealed class DeclarationBody {
abstract val text: String
abstract val imports: List<String>
abstract class Builder {
protected val imports = mutableListOf<String>()
fun import(import: String) {
imports += import
}
}
}
data class ActualDeclarationBody(override val text: String, override val imports: List<String>) : DeclarationBody() {
class Builder(private val text: String) : DeclarationBody.Builder() {
fun build() = ActualDeclarationBody(text, imports)
}
}
data class DefaultActualDeclarationBody(override val text: String, override val imports: List<String>) : DeclarationBody() {
class Builder(private val text: String) : DeclarationBody.Builder() {
fun build() = DefaultActualDeclarationBody(text, imports)
}
}
@@ -0,0 +1,327 @@
/*
* Copyright 2010-2020 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.mpp
import org.jetbrains.annotations.NonNls
import org.jetbrains.kotlin.tools.projectWizard.core.*
import org.jetbrains.kotlin.tools.projectWizard.core.safeAs
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.SimpleTargetConfigurator
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.inContextOfModuleConfigurator
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.GradlePlugin
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleSubType
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModulesToIrConversionData
import org.jetbrains.kotlin.tools.projectWizard.plugins.projectPath
import org.jetbrains.kotlin.tools.projectWizard.plugins.templates.TemplatesPlugin
import org.jetbrains.kotlin.tools.projectWizard.settings.JavaPackage
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.SourcesetType
import org.jetbrains.kotlin.tools.projectWizard.templates.FileDescriptor
import org.jetbrains.kotlin.tools.projectWizard.templates.FileTemplate
import org.jetbrains.kotlin.tools.projectWizard.templates.FileTextDescriptor
import java.nio.file.Path
@DslMarker
annotation class ExpectFileDSL
data class MppFile(
@NonNls val filename: String,
@NonNls val javaPackage: JavaPackage? = null,
val declarations: List<MppDeclaration>
) {
val fileNameWithPackage
get() = javaPackage?.let { "${it.asCodePackage()}." }.orEmpty() + filename
fun printForModuleSubType(moduleSubType: ModuleSubType): String = buildString {
javaPackage?.let { appendLine("package ${it.asCodePackage()}"); appendLine() }
if (moduleSubType == ModuleSubType.common) {
declarations.joinTo(this, separator = "\n\n") { it.printExpect() }
} else {
printImports(declarations.flatMap { it.importsFor(moduleSubType) })
declarations.joinTo(this, separator = "\n\n") { it.printActualFor(moduleSubType) }
}
}.trim()
private fun StringBuilder.printImports(imports: List<String>) {
if (imports.isEmpty()) return
imports.distinct().joinTo(this, separator = "\n", prefix = "\n", postfix = "\n\n") { import ->
"import $import"
}
}
@ExpectFileDSL
class Builder(private val filename: String, val javaPackage: JavaPackage? = null) {
private val declarations = mutableListOf<MppDeclaration>()
fun function(signature: String, init: MppFunction.Builder.() -> Unit = {}) {
declarations += MppFunction.Builder(signature).apply(init).build()
}
fun `class`(name: String, init: MppClass.Builder.() -> Unit = {}) {
declarations += MppClass.Builder(name).apply(init).build()
}
fun build() = MppFile(filename, javaPackage, declarations)
}
}
@ExpectFileDSL
class MppSources(val mppFiles: List<MppFile>, val simpleFiles: List<SimpleFiles>) {
fun getFilesFor(moduleSubType: ModuleSubType): List<SimpleFile> =
simpleFiles.filter { moduleSubType in it.moduleSubTypes }.flatMap { it.files }
class Builder(val javaPackage: JavaPackage?) {
private val mppFiles = mutableListOf<MppFile>()
private val simpleFiles = mutableListOf<SimpleFiles>()
fun mppFile(filename: String, init: MppFile.Builder.() -> Unit) {
mppFiles += MppFile.Builder(filename, javaPackage).apply(init).build()
}
fun filesFor(vararg moduleSubTypes: ModuleSubType, init: SimpleFiles.Builder.() -> Unit) {
simpleFiles += SimpleFiles.Builder(moduleSubTypes.toList(), javaPackage).apply(init).build()
}
fun build() = MppSources(mppFiles, simpleFiles)
}
}
data class SimpleFiles(val moduleSubTypes: List<ModuleSubType>, val files: List<SimpleFile>) {
class Builder(private val moduleSubTypes: List<ModuleSubType>, val filesPackage: JavaPackage?) {
private val files = mutableListOf<SimpleFile>()
fun file(fileDescriptor: FileDescriptor, filename: String, type: SourcesetType, init: SimpleFile.Builder.() -> Unit = {}) {
files += SimpleFile.Builder(fileDescriptor, filename, type).apply(init).apply {
javaPackage = filesPackage
}.build()
}
fun build() = SimpleFiles(moduleSubTypes, files)
}
}
data class SimpleFile(val fileDescriptor: FileDescriptor, val javaPackage: JavaPackage?, val filename: String, val type: SourcesetType) {
val uniqueIdentifier get() = "${type}/${javaPackage}/${filename}"
class Builder(private val fileDescriptor: FileDescriptor, private val filename: String, private val type: SourcesetType) {
var javaPackage: JavaPackage? = null
fun build() = SimpleFile(fileDescriptor, javaPackage, filename, type)
}
}
fun mppSources(javaPackage: JavaPackage? = null, init: MppSources.Builder.() -> Unit): MppSources =
MppSources.Builder(javaPackage).apply(init).build()
sealed class MppDeclaration {
@Suppress("SpellCheckingInspection")
abstract val actuals: Actuals
abstract fun printExpect(): String
abstract fun printActualFor(moduleSubType: ModuleSubType): String
fun importsFor(moduleSubType: ModuleSubType): List<String> = actuals.getBodyFor(moduleSubType).imports
@ExpectFileDSL
abstract class Builder {
private var defaultActualDeclarationBody = DefaultActualDeclarationBody(text = "", imports = emptyList())
@Suppress("SpellCheckingInspection")
private val actuals = mutableMapOf<ModuleSubType, ActualDeclarationBody>()
fun default(defaultText: String, init: DefaultActualDeclarationBody.Builder.() -> Unit = {}) {
defaultActualDeclarationBody = DefaultActualDeclarationBody.Builder(defaultText).also(init).build()
}
fun actualFor(vararg moduleSubTypes: ModuleSubType, actualBody: String, init: ActualDeclarationBody.Builder.() -> Unit = {}) {
moduleSubTypes.forEach { moduleSubType ->
actuals[moduleSubType] = ActualDeclarationBody.Builder(actualBody).apply(init).build()
}
}
protected fun buildActuals() = Actuals(defaultActualDeclarationBody, actuals)
}
}
data class MppFunction(
val signature: String,
@Suppress("SpellCheckingInspection")
override val actuals: Actuals,
) : MppDeclaration() {
override fun printExpect(): String =
"expect fun $signature"
override fun printActualFor(moduleSubType: ModuleSubType): String {
val body = actuals.getBodyFor(moduleSubType)
return buildString {
appendLine("actual fun $signature {")
appendLine(body.text.indented(4))
append("}")
}
}
@ExpectFileDSL
class Builder(private val signature: String) : MppDeclaration.Builder() {
fun build(): MppFunction = MppFunction(
signature,
buildActuals(),
)
}
}
data class MppClass(
val name: String,
val expectBody: String?,
@Suppress("SpellCheckingInspection")
override val actuals: Actuals,
) : MppDeclaration() {
override fun printExpect(): String = buildString {
append("expect class $name()")
expectBody?.let { body ->
appendLine(" {")
appendLine(body.indented(4))
append("}")
}
}
override fun printActualFor(moduleSubType: ModuleSubType): String {
val body = actuals.getBodyFor(moduleSubType)
return buildString {
appendLine("actual class $name actual constructor() {")
appendLine(body.text.indented(4))
append("}")
}
}
@ExpectFileDSL
class Builder(private val name: String) : MppDeclaration.Builder() {
var expectBody: String? = null
fun build(): MppClass = MppClass(
name,
expectBody,
buildActuals(),
)
}
}
private fun String.indented(indentValue: Int): String {
val indent = " ".repeat(indentValue)
return split('\n').joinToString(separator = "\n") { line -> indent + line.trim() }
}
@Suppress("SpellCheckingInspection")
data class Actuals(
val defaultActualDeclarationBody: DefaultActualDeclarationBody,
val actuals: Map<ModuleSubType, ActualDeclarationBody>,
) {
fun getBodyFor(moduleSubType: ModuleSubType): DeclarationBody =
actuals[moduleSubType] ?: defaultActualDeclarationBody
}
sealed class DeclarationBody {
abstract val text: String
abstract val imports: List<String>
abstract class Builder {
protected val imports = mutableListOf<String>()
fun import(import: String) {
imports += import
}
}
}
data class ActualDeclarationBody(override val text: String, override val imports: List<String>) : DeclarationBody() {
class Builder(private val text: String) : DeclarationBody.Builder() {
fun build() = ActualDeclarationBody(text, imports)
}
}
data class DefaultActualDeclarationBody(override val text: String, override val imports: List<String>) : DeclarationBody() {
class Builder(private val text: String) : DeclarationBody.Builder() {
fun build() = DefaultActualDeclarationBody(text, imports)
}
}
fun Writer.applyMppStructure(
mppSources: List<MppSources>,
module: Module,
modulePath: Path,
): TaskResult<Unit> = compute {
createMppFiles(mppSources, module, modulePath).ensure()
createSimpleFiles(mppSources, module, modulePath).ensure()
}
fun Writer.applyMppStructure(
mppSources: MppSources,
module: Module,
modulePath: Path,
): TaskResult<Unit> = applyMppStructure(listOf(mppSources), module, modulePath)
private fun Writer.createMppFiles(
mppSources: List<MppSources>,
module: Module,
modulePath: Path
): TaskResult<Unit> = inContextOfModuleConfigurator(module) {
val mppFiles = mppSources.flatMap { it.mppFiles }
.distinctBy { it.fileNameWithPackage } // todo merge files with the same name here
mppFiles.mapSequenceIgnore { file ->
module.subModules.mapSequenceIgnore mapTargets@{ target ->
val moduleSubType = //TODO handle for non-simple target configurator
target.configurator.safeAs<SimpleTargetConfigurator>()?.moduleSubType ?: return@mapTargets UNIT_SUCCESS
val path = pathForFileInTarget(modulePath, module, file.javaPackage, file.filename, target, SourcesetType.main)
val fileTemplate = FileTemplate(
FileTextDescriptor(file.printForModuleSubType(moduleSubType), path),
projectPath,
)
TemplatesPlugin.fileTemplatesToRender.addValues(fileTemplate)
}
}
}
private fun Writer.createSimpleFiles(
mppSources: List<MppSources>,
module: Module,
modulePath: Path
): TaskResult<Unit> = inContextOfModuleConfigurator(module) {
val mppFiles = mppSources
val filesWithPaths = module.subModules.flatMap { target ->
val moduleSubType = //TODO handle for non-simple target configurator
target.configurator.safeAs<SimpleTargetConfigurator>()?.moduleSubType ?: return@flatMap emptyList()
mppFiles
.flatMap { it.getFilesFor(moduleSubType) }
.distinctBy { it.uniqueIdentifier }
.map { file ->
val path = projectPath / pathForFileInTarget(modulePath, module, file.javaPackage, file.filename, target, file.type)
file to path
}
}.distinctBy { (_, path) -> path }
filesWithPaths.mapSequenceIgnore { (file, path) ->
val fileTemplate = FileTemplate(file.fileDescriptor, path, mapOf("package" to file.javaPackage?.asCodePackage()))
TemplatesPlugin.fileTemplatesToRender.addValues(fileTemplate)
}
}
private fun pathForFileInTarget(
mppModulePath: Path,
mppModule: Module,
javaPackage: JavaPackage?,
filename: String,
target: Module,
sourcesetType: SourcesetType,
) = mppModulePath /
Defaults.SRC_DIR /
"${target.name}${sourcesetType.name.capitalize()}" /
mppModule.configurator.kotlinDirectoryName /
javaPackage?.asPath() /
filename
@@ -32,7 +32,8 @@ object Plugins {
JsTemplatesPlugin(context),
ReactJsTemplatesPlugin(context),
SimpleNodeJsTemplatesPlugin(context),
NativeConsoleApplicationTemplatePlugin(context)
NativeConsoleApplicationTemplatePlugin(context),
MobileMppTemplatePlugin(context),
)
}
}
@@ -230,6 +230,13 @@ class ModulesToIRsConverter(
modulePath,
MultiplatformModulesStructureIR(
targetIrs,
FakeMultiplatformModuleIR(
module.name,
modulePath,
module.template,
targetModuleIrs,
module,
),
targetModuleIrs,
persistentListOf()
),
@@ -0,0 +1,26 @@
package org.jetbrains.kotlin.tools.projectWizard.plugins.templates
import org.jetbrains.kotlin.tools.projectWizard.core.Context
import org.jetbrains.kotlin.tools.projectWizard.core.PluginSettingsOwner
import org.jetbrains.kotlin.tools.projectWizard.core.entity.PipelineTask
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
import org.jetbrains.kotlin.tools.projectWizard.templates.mpp.MobileMppTemplate
class MobileMppTemplatePlugin(context: Context) : TemplatePlugin(context) {
override val path = pluginPath
override val pipelineTasks: List<PipelineTask> = super.pipelineTasks +
listOf(
addTemplate,
)
companion object : PluginSettingsOwner() {
override val pluginPath = "template.mobile.mpp"
val addTemplate by pipelineTask(GenerationPhase.PREPARE) {
withAction {
TemplatesPlugin.addTemplate.execute(MobileMppTemplate())
}
}
}
}
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.PluginSetti
import org.jetbrains.kotlin.tools.projectWizard.core.service.TemplateEngineService
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.*
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.ModuleConfiguratorWithTests
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.MppModuleConfigurator.runArbitraryTask
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.isPresent
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.BuildSystemPlugin
@@ -67,20 +68,31 @@ class TemplatesPlugin(context: Context) : Plugin(context) {
withAction {
updateBuildFiles { buildFile ->
buildFile.modules.modules.mapSequence { module ->
val moduleStructure = buildFile.modules
val moduleIRs = buildList<ModuleIR> {
if (moduleStructure is MultiplatformModulesStructureIR) {
+moduleStructure.multiplatformModule
}
+moduleStructure.modules
}
moduleIRs.mapSequence { module ->
applyTemplateToModule(
module.template,
module
).map { result -> module.withIrs(result.librariesToAdd) to result }
).map { result -> result.updateModuleIR(module.withIrs(result.librariesToAdd)) to result }
}.map {
val (moduleIrs, results) = it.unzip()
val foldedResults = results.fold()
buildFile.copy(
modules = buildFile.modules.withModules(moduleIrs)
modules = buildFile.modules.withModules(moduleIrs.filterNot { it is FakeMultiplatformModuleIR })
).withIrs(foldedResults.irsToAddToBuildFile).let { buildFile ->
when (val structure = buildFile.modules) {
is MultiplatformModulesStructureIR ->
buildFile.copy(modules = structure.updateTargets(foldedResults.updateTarget))
buildFile.copy(
modules = structure
.updateTargets(foldedResults.updateTarget)
.updateSourceSets(foldedResults.updateModuleIR)
)
else -> buildFile
}
}
@@ -164,6 +176,7 @@ class TemplatesPlugin(context: Context) : Plugin(context) {
}
SRC_DIR / "${module.name}${filePath.sourcesetType.name.capitalize()}" / directory
}
is FakeMultiplatformModuleIR -> error("Not supported for FakeMultiplatformModuleIR")
}
}
}
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ProjectKind
import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.*
import org.jetbrains.kotlin.tools.projectWizard.templates.*
import org.jetbrains.kotlin.tools.projectWizard.templates.mpp.MobileMppTemplate
sealed class ProjectTemplate : DisplayableSettingItem {
abstract val title: String
@@ -165,7 +166,7 @@ object MultiplatformApplicationProjectTemplate : ProjectTemplate() {
override val setsPluginSettings: List<SettingWithValue<*, *>>
get() = listOf(
KotlinPlugin.modules.reference withValue listOf(
MultiplatformModule("mainModule", listOf(ModuleType.common.createDefaultTarget()))
MultiplatformModule("mainModule", targets = listOf(ModuleType.common.createDefaultTarget()))
)
)
}
@@ -206,7 +207,7 @@ object MultiplatformLibraryProjectTemplate : ProjectTemplate() {
KotlinPlugin.modules.reference withValue listOf(
MultiplatformModule(
"library",
listOf(
targets = listOf(
ModuleType.common.createDefaultTarget(),
ModuleType.jvm.createDefaultTarget(),
ModuleType.js.createDefaultTarget().withConfiguratorSettings(JsBrowserTargetConfigurator) {
@@ -231,7 +232,7 @@ object FullStackWebApplicationProjectTemplate : ProjectTemplate() {
KotlinPlugin.modules.reference withValue listOf(
MultiplatformModule(
"application",
listOf(
targets = listOf(
ModuleType.common.createDefaultTarget(),
ModuleType.jvm.createDefaultTarget().apply {
withTemplate(KtorServerTemplate()) {
@@ -336,7 +337,8 @@ object MultiplatformMobileApplicationProjectTemplate : ProjectTemplate() {
override val setsModules: List<Module> = buildList {
val shared = MultiplatformModule(
"shared",
listOf(
template = MobileMppTemplate(),
targets = listOf(
ModuleType.common.createDefaultTarget(),
Module(
"android",
@@ -390,7 +392,7 @@ object MultiplatformMobileLibraryProjectTemplate : ProjectTemplate() {
KotlinPlugin.modules.reference withValue listOf(
MultiplatformModule(
"library",
listOf(
targets = listOf(
ModuleType.common.createDefaultTarget(),
Module(
"android",
@@ -185,11 +185,11 @@ fun MultiplatformTargetModule(@NonNls name: String, configurator: ModuleConfigur
)
@Suppress("FunctionName")
fun MultiplatformModule(@NonNls name: String, targets: List<Module> = emptyList()) =
fun MultiplatformModule(@NonNls name: String, template: Template? = null, targets: List<Module> = emptyList()) =
Module(
name,
MppModuleConfigurator,
null,
template,
emptyList(),
targets
)
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.GradleRootProject
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.ModuleDependencyIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.*
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.*
import org.jetbrains.kotlin.tools.projectWizard.mpp.mppSources
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.isGradle
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleSubType
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModulesToIrConversionData
@@ -78,14 +79,12 @@ sealed class ModuleDependencyType(
object AndroidSinglePlatformToMPP : ModuleDependencyType(
from = AndroidSinglePlatformModuleConfigurator::class,
to = MppModuleConfigurator::class
) {
override fun Writer.runArbitraryTask(
from: Module,
to: Module,
toModulePath: Path,
data: ModulesToIrConversionData
): TaskResult<Unit> = addExpectFilesForMppModuleForAndroidAndIos(to, data)
}
)
object JVMSinglePlatformToMPP : ModuleDependencyType(
from = JvmSinglePlatformModuleConfigurator::class,
to = MppModuleConfigurator::class
)
object IOSToMppSinglePlatformToMPP : ModuleDependencyType(
from = IOSSinglePlatformModuleConfigurator::class,
@@ -100,7 +99,6 @@ sealed class ModuleDependencyType(
toModulePath: Path,
data: ModulesToIrConversionData
): TaskResult<Unit> = compute {
addExpectFilesForMppModuleForAndroidAndIos(to, data)
inContextOfModuleConfigurator(from) {
IOSSinglePlatformModuleConfigurator.dependentModule.reference.update {
IOSSinglePlatformModuleConfigurator.DependentModuleReference(to).asSuccess()
@@ -113,7 +111,7 @@ sealed class ModuleDependencyType(
to: Module,
toModulePath: Path,
): TaskResult<Unit> {
val needDummyFile = inContextOfModuleConfigurator(to) { MppModuleConfigurator.mppSources.reference.propertyValue.isEmpty() }
val needDummyFile = false/*TODO*/
return if (needDummyFile) {
val dummyFilePath =
Defaults.SRC_DIR / "${to.iosTargetSafe()!!.name}Main" / to.configurator.kotlinDirectoryName / "dummyFile.kt"
@@ -184,6 +182,7 @@ sealed class ModuleDependencyType(
companion object {
private val ALL = listOf(
JVMSinglePlatformToJVMSinglePlatform,
JVMSinglePlatformToMPP,
AndroidSinglePlatformToMPP,
IOSToMppSinglePlatformToMPP
)
@@ -194,42 +193,4 @@ sealed class ModuleDependencyType(
fun isDependencyPossible(from: Module, to: Module): Boolean =
getPossibleDependencyType(from, to) != null
}
}
private fun Writer.addExpectFilesForMppModuleForAndroidAndIos(mppModule: Module, data: ModulesToIrConversionData): TaskResult<Unit> {
val javaPackage = mppModule.javaPackage(data.pomIr)
val expectFiles = mppSources(javaPackage) {
mppFile("Platform.kt") {
`class`("Platform") {
expectBody = "val platform: String"
actualFor(
ModuleSubType.android,
actualBody = """actual val platform: String = "Android ${'$'}{android.os.Build.VERSION.SDK_INT}""""
)
actualFor(
ModuleSubType.iosArm64, ModuleSubType.iosX64, ModuleSubType.ios,
actualBody =
"""actual val platform: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion"""
) {
import("platform.UIKit.UIDevice")
}
}
}
filesFor(ModuleSubType.common) {
file(FileTemplateDescriptor("mppCommon/Greeting.kt.vm", relativePath = null), "Greeting.kt", SourcesetType.main)
}
filesFor(ModuleSubType.android) {
file(FileTemplateDescriptor("android/androidTest.kt.vm", relativePath = null), "androidTest.kt", SourcesetType.test)
}
filesFor(ModuleSubType.iosArm64, ModuleSubType.iosX64, ModuleSubType.ios) {
file(FileTemplateDescriptor("ios/iosTest.kt.vm", relativePath = null), "iosTest.kt", SourcesetType.test)
}
}
return inContextOfModuleConfigurator(mppModule) { MppModuleConfigurator.mppSources.reference.addValues(listOf(expectFiles)) }
}
@@ -13,6 +13,8 @@ import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleType
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.SourcesetType
import org.jetbrains.kotlin.tools.projectWizard.KotlinNewProjectWizardBundle
import org.jetbrains.kotlin.tools.projectWizard.core.Reader
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.moduleType
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
class ConsoleJvmApplicationTemplate : Template() {
@NonNls
@@ -21,7 +23,9 @@ class ConsoleJvmApplicationTemplate : Template() {
override val title: String = KotlinNewProjectWizardBundle.message("module.template.console.jvm.title")
override val description: String = KotlinNewProjectWizardBundle.message("module.template.console.jvm.description")
override val moduleTypes: Set<ModuleType> = setOf(ModuleType.jvm)
override fun isSupportedByModuleType(module: Module): Boolean =
module.configurator.moduleType == ModuleType.jvm
override fun Writer.getIrsToAddToBuildFile(
module: ModuleIR
@@ -20,7 +20,8 @@ import org.jetbrains.kotlin.tools.projectWizard.transformers.interceptors.Templa
import org.jetbrains.kotlin.tools.projectWizard.transformers.interceptors.interceptTemplate
abstract class JsClientTemplate : Template() {
override val moduleTypes: Set<ModuleType> = setOf(ModuleType.js)
override fun isSupportedByModuleType(module: Module): Boolean =
module.configurator.moduleType == ModuleType.js
override fun isApplicableTo(
reader: Reader,
@@ -18,23 +18,23 @@ import org.jetbrains.kotlin.tools.projectWizard.library.MavenArtifact
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
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.transformers.interceptors.InterceptionPoint
import org.jetbrains.kotlin.tools.projectWizard.KotlinNewProjectWizardBundle
import org.jetbrains.kotlin.tools.projectWizard.Versions
import org.jetbrains.kotlin.tools.projectWizard.WizardGradleRunConfiguration
import org.jetbrains.kotlin.tools.projectWizard.WizardRunConfiguration
import org.jetbrains.kotlin.tools.projectWizard.core.Reader
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.moduleType
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.ModuleKind
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.*
import org.jetbrains.kotlin.tools.projectWizard.settings.version.Version
class KtorServerTemplate : Template() {
override val title: String = KotlinNewProjectWizardBundle.message("module.template.ktor.server.title")
override val description: String = KotlinNewProjectWizardBundle.message("module.template.ktor.server.description")
override val moduleTypes: Set<ModuleType> = setOf(ModuleType.jvm)
override fun isSupportedByModuleType(module: Module): Boolean =
module.configurator.moduleType == ModuleType.jvm
@NonNls
override val id: String = "ktorServer"
@@ -16,13 +16,16 @@ import org.jetbrains.kotlin.tools.projectWizard.KotlinNewProjectWizardBundle
import org.jetbrains.kotlin.tools.projectWizard.core.*
import org.jetbrains.kotlin.tools.projectWizard.core.safeAs
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.NativeTargetConfigurator
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.moduleType
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
class NativeConsoleApplicationTemplate : Template() {
override val title: String = KotlinNewProjectWizardBundle.message("module.template.native.console.title")
override val description: String = KotlinNewProjectWizardBundle.message("module.template.native.console.description")
override val moduleTypes: Set<ModuleType> = setOf(ModuleType.native)
override fun isSupportedByModuleType(module: Module): Boolean =
module.configurator.moduleType == ModuleType.native
override val id: String = "nativeConsoleApp"
override fun isApplicableTo(
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.*
import org.jetbrains.kotlin.tools.projectWizard.library.MavenArtifact
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.JsNodeTargetConfigurator
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.NodeJsSinglePlatformModuleConfigurator
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.moduleType
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleType
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.DefaultRepository.Companion.JCENTER
@@ -26,7 +27,9 @@ class SimpleNodeJsTemplate : Template() {
override val description: String = KotlinNewProjectWizardBundle.message("module.template.simple.nodejs.description")
override val moduleTypes: Set<ModuleType> = setOf(ModuleType.js)
override fun isSupportedByModuleType(module: Module): Boolean =
module.configurator.moduleType == ModuleType.js
@NonNls
override val id: String = "simpleNodeJs"
@@ -9,15 +9,13 @@ import org.jetbrains.kotlin.tools.projectWizard.core.*
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.*
import org.jetbrains.kotlin.tools.projectWizard.enumSettingImpl
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.BuildSystemIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.DependencyIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.ModuleIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.MultiplatformModuleIR
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
import org.jetbrains.kotlin.tools.projectWizard.plugins.RunConfigurationsPlugin
import org.jetbrains.kotlin.tools.projectWizard.plugins.StructurePlugin
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleType
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModulesToIrConversionData
import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
import org.jetbrains.kotlin.tools.projectWizard.settings.version.Version
@@ -73,7 +71,8 @@ abstract class Template : SettingsOwner, EntitiesOwnerDescriptor, DisplayableSet
abstract val title: String
abstract val description: String
abstract val moduleTypes: Set<ModuleType>
abstract fun isSupportedByModuleType(module: Module): Boolean
override val text: String get() = title
@@ -100,6 +99,10 @@ abstract class Template : SettingsOwner, EntitiesOwnerDescriptor, DisplayableSet
module: ModuleIR
): List<BuildSystemIR> = emptyList()
open fun Writer.runArbitratyTask(
module: ModuleIR
): TaskResult<Unit> = UNIT_SUCCESS
open fun updateTargetIr(
module: ModuleIR,
targetConfigurationIR: TargetConfigurationIR
@@ -112,24 +115,39 @@ abstract class Template : SettingsOwner, EntitiesOwnerDescriptor, DisplayableSet
open fun Reader.createRunConfigurations(module: ModuleIR): List<WizardRunConfiguration> = emptyList()
open fun Reader.updateModuleIR(module: ModuleIR): ModuleIR = module
open fun Reader.updateBuildFileIRs(irs: List<BuildSystemIR>): List<BuildSystemIR> = irs
open fun createRootBuildFileIrs(): List<BuildSystemIR> = emptyList()
fun Writer.applyToSourceset(
module: ModuleIR
): TaskResult<TemplateApplicationResult> {
): TaskResult<TemplateApplicationResult> = compute {
val librariesToAdd = getRequiredLibraries(module)
val irsToAddToBuildFile = getIrsToAddToBuildFile(module)
runArbitratyTask(module).ensure()
val targetsUpdater = when (module) {
is MultiplatformModuleIR -> { target: TargetConfigurationIR ->
if (target.targetName == module.name) updateTargetIr(module, target)
else target
}
is FakeMultiplatformModuleIR -> {
module.targets.map { expectedTarget ->
{ configurationIR: TargetConfigurationIR ->
if (configurationIR.targetName == expectedTarget.name) updateTargetIr(expectedTarget, configurationIR)
else configurationIR
}
}.reduce(::compose)
}
else -> idFunction()
}
RunConfigurationsPlugin.configurations.addValues(createRunConfigurations(module))
val result = TemplateApplicationResult(librariesToAdd, irsToAddToBuildFile, targetsUpdater)
return result.asSuccess()
TemplateApplicationResult(librariesToAdd, irsToAddToBuildFile, targetsUpdater) { updateModuleIR(it) }
}
fun Reader.settingsAsMap(module: Module): Map<String, Any> = mutableMapOf<String, Any>().apply {
@@ -290,13 +308,15 @@ fun Writer.applyTemplateToModule(
data class TemplateApplicationResult(
val librariesToAdd: List<DependencyIR>,
val irsToAddToBuildFile: List<BuildSystemIR>,
val updateTarget: (TargetConfigurationIR) -> TargetConfigurationIR
val updateTarget: (TargetConfigurationIR) -> TargetConfigurationIR,
val updateModuleIR: (ModuleIR) -> ModuleIR
) {
companion object {
val EMPTY = TemplateApplicationResult(
librariesToAdd = emptyList(),
irsToAddToBuildFile = emptyList(),
updateTarget = { it }
updateTarget = { it },
updateModuleIR = { it },
)
}
}
@@ -308,5 +328,6 @@ operator fun TemplateApplicationResult.plus(other: TemplateApplicationResult) =
TemplateApplicationResult(
librariesToAdd + other.librariesToAdd,
irsToAddToBuildFile + other.irsToAddToBuildFile,
updateTarget andThen other.updateTarget
updateTarget andThen other.updateTarget,
updateModuleIR andThen other.updateModuleIR,
)
@@ -0,0 +1,82 @@
/*
* Copyright 2010-2020 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.templates.mpp
import org.jetbrains.annotations.NonNls
import org.jetbrains.kotlin.tools.projectWizard.KotlinNewProjectWizardBundle
import org.jetbrains.kotlin.tools.projectWizard.core.Reader
import org.jetbrains.kotlin.tools.projectWizard.core.TaskResult
import org.jetbrains.kotlin.tools.projectWizard.core.Writer
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.BuildSystemIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.ModuleIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.RepositoryIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.irsList
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.MppModuleConfigurator
import org.jetbrains.kotlin.tools.projectWizard.mpp.applyMppStructure
import org.jetbrains.kotlin.tools.projectWizard.mpp.mppSources
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleSubType
import org.jetbrains.kotlin.tools.projectWizard.plugins.pomIR
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.DefaultRepository
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.SourcesetType
import org.jetbrains.kotlin.tools.projectWizard.settings.javaPackage
import org.jetbrains.kotlin.tools.projectWizard.templates.FileTemplateDescriptor
import org.jetbrains.kotlin.tools.projectWizard.templates.Template
class MobileMppTemplate : Template() {
@NonNls
override val id: String = "mobileMppModule"
override val title: String = KotlinNewProjectWizardBundle.message("module.template.mpp.mobile.title")
override val description: String = KotlinNewProjectWizardBundle.message("module.template.mpp.mobile.description")
override fun isSupportedByModuleType(module: Module): Boolean =
module.configurator == MppModuleConfigurator
override fun Writer.getIrsToAddToBuildFile(module: ModuleIR): List<BuildSystemIR> = irsList {
+RepositoryIR(DefaultRepository.JCENTER)
}
override fun Writer.runArbitratyTask(module: ModuleIR): TaskResult<Unit> {
val javaPackage = module.originalModule.javaPackage(pomIR())
val mpp = mppSources(javaPackage) {
mppFile("Platform.kt") {
`class`("Platform") {
expectBody = "val platform: String"
actualFor(
ModuleSubType.android,
actualBody = """actual val platform: String = "Android ${'$'}{android.os.Build.VERSION.SDK_INT}""""
)
actualFor(
ModuleSubType.iosArm64, ModuleSubType.iosX64, ModuleSubType.ios,
actualBody =
"""actual val platform: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion"""
) {
import("platform.UIKit.UIDevice")
}
}
}
filesFor(ModuleSubType.common) {
file(FileTemplateDescriptor("mppCommon/Greeting.kt.vm", relativePath = null), "Greeting.kt", SourcesetType.main)
}
filesFor(ModuleSubType.android) {
file(FileTemplateDescriptor("android/androidTest.kt.vm", relativePath = null), "androidTest.kt", SourcesetType.test)
}
filesFor(ModuleSubType.iosArm64, ModuleSubType.iosX64, ModuleSubType.ios) {
file(FileTemplateDescriptor("ios/iosTest.kt.vm", relativePath = null), "iosTest.kt", SourcesetType.test)
}
}
return applyMppStructure(mpp, module.originalModule, module.path)
}
}
@@ -5,7 +5,7 @@
package org.jetbrains.kotlin.tools.projectWizard.core
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.mppSources
import org.jetbrains.kotlin.tools.projectWizard.mpp.mppSources
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleSubType
import org.junit.Test
import kotlin.test.assertEquals