Wizard: add MPP mobile application project skeleton

This commit is contained in:
Ilya Kirillov
2020-03-22 21:32:26 +03:00
parent 732154cef5
commit b169d260d3
10 changed files with 209 additions and 53 deletions
@@ -10,7 +10,7 @@ import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.*
import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.ModuleKind
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.htmlText
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.fullTextHtml
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.icon
import javax.swing.Icon
@@ -46,7 +46,7 @@ class CreateModuleOrTargetPopup private constructor(
}
) {
override fun getIconFor(value: ModuleConfigurator): Icon = value.icon
override fun getTextFor(value: ModuleConfigurator): String = value.htmlText
override fun getTextFor(value: ModuleConfigurator): String = value.fullTextHtml
override fun onChosen(selectedValue: ModuleConfigurator?, finalChoice: Boolean): PopupStep<*>? =
when (selectedValue) {
@@ -71,7 +71,7 @@ class CreateModuleOrTargetPopup private constructor(
else -> null
}
override fun getTextFor(value: DisplayableSettingItem): String = value.htmlText
override fun getTextFor(value: DisplayableSettingItem): String = value.fullTextHtml
override fun onChosen(selectedValue: DisplayableSettingItem?, finalChoice: Boolean): PopupStep<*>? {
when {
@@ -77,20 +77,6 @@ fun ValidationResult.ValidationError.asHtml() = when (messages.size) {
}
}
val DisplayableSettingItem.htmlText
get() = (text + greyText?.let { " <i>($greyText)</i>" }.orEmpty()).asHtml()
fun splitterFor(
vararg components: JComponent,
vertical: Boolean = false
) = components.reduce { left, right ->
JBSplitter(vertical, 1f / components.size).apply {
firstComponent = left
secondComponent = right
dividerWidth = 1
}
}
val ModuleType.icon: Icon
get() = when (this) {
ModuleType.jvm -> KotlinIcons.SMALL_LOGO
@@ -196,6 +196,10 @@ class Context private constructor(
}
open inner class Writer : Reader() {
@Deprecated("Allows to get SettingsWriter where it is not supposed to be")
val unsafeSettingWriter: SettingsWriter
get() = settingWritingContext
val eventManager: EventManager
get() = settingContext.eventManager
@@ -235,7 +239,7 @@ class Context private constructor(
setValue(defaultValue)
}
}
@JvmName("writeSettings")
inline operator fun <T> invoke(writer: SettingsWriter.() -> T): T = writer()
}
@@ -25,6 +25,14 @@ abstract class Parser<out T : Any> {
abstract fun ParsingContext.parse(value: Any?, path: String): TaskResult<T>
}
fun <T : Any> alwaysFailingParser(errorMessage: String) = object : Parser<T>() {
override fun ParsingContext.parse(value: Any?, path: String): TaskResult<T> =
Failure(object : Error() {
override val message: String get() = errorMessage
})
}
fun <T : Any> Parser<T>.parse(context: ParsingContext, value: Any?, path: String) =
with(context) { parse(value, path) }
@@ -126,7 +126,7 @@ object AndroidTargetConfigurator : TargetConfigurator,
SingleCoexistenceTargetConfigurator,
ModuleConfiguratorSettings() {
override val moduleSubType = ModuleSubType.android
override val moduleType = ModuleType.jvm
override val moduleType = ModuleType.android
override fun Reader.createAndroidPlugin(module: Module): AndroidGradlePlugin =
withSettingsOf(module) { androidPlugin.reference.settingValue }
@@ -6,17 +6,16 @@
package org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators
import org.jetbrains.kotlin.tools.projectWizard.core.*
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModulesToIrConversionData
import org.jetbrains.kotlin.tools.projectWizard.plugins.templates.TemplatesPlugin
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.*
import org.jetbrains.kotlin.tools.projectWizard.templates.FileTemplate
import org.jetbrains.kotlin.tools.projectWizard.templates.FileTemplateDescriptor
import java.io.File
import java.nio.file.Path
object IOSSinglePlatformModuleConfigurator :
SinglePlatformModuleConfigurator {
object IOSSinglePlatformModuleConfigurator : SinglePlatformModuleConfigurator, ModuleConfiguratorSettings() {
override val id = "IOS Module"
override val suggestedModuleName = "ios"
override val moduleKind: ModuleKind = ModuleKind.singleplatformJvm
@@ -25,20 +24,16 @@ object IOSSinglePlatformModuleConfigurator :
override val needCreateBuildFile: Boolean = false
override val requiresRootBuildFile: Boolean = true
override fun Writer.runArbitraryTask(
override fun Reader.createTemplates(
configurationData: ModulesToIrConversionData,
module: Module,
modulePath: Path
): TaskResult<Unit> {
val settings = mapOf<String, Any?>(
"moduleName" to module.name,
"sharedModuleName" to "SHARED",
"pathToSharedModule" to "../SHARED"
)
): List<FileTemplate> {
val settings = createTemplatesSettingValues(module, configurationData)
fun fileTemplate(path: Path) = FileTemplate(descriptor(path, module.name), modulePath, settings)
return TemplatesPlugin::addFileTemplates.execute(buildList {
return buildList {
+fileTemplate("$DEFAULT_APP_NAME.xcodeproj" / "project.pbxproj")
+fileTemplate(DEFAULT_APP_NAME / "AppDelegate.swift")
@@ -56,7 +51,27 @@ object IOSSinglePlatformModuleConfigurator :
+fileTemplate("${DEFAULT_APP_NAME}UITests" / "Info.plist")
+fileTemplate("${DEFAULT_APP_NAME}UITests" / "${DEFAULT_APP_NAME}UITests.swift")
})
}
}
private fun Reader.createTemplatesSettingValues(
module: Module,
configurationData: ModulesToIrConversionData
): Map<String, Any?> {
val dependentModule = withSettingsOf(module) {
dependentModule.reference.notRequiredSettingValue?.module
}
val dependentModulePath = dependentModule
?.path
?.considerSingleRootModuleMode(configurationData.isSingleRootModuleMode)
?.asString(separator = File.separator)
?.asPath()
return mapOf(
"moduleName" to module.name,
"sharedModuleName" to dependentModule?.name,
"pathToSharedModule" to dependentModulePath
)
}
private fun descriptor(path: Path, moduleName: String) =
@@ -66,4 +81,18 @@ object IOSSinglePlatformModuleConfigurator :
)
private const val DEFAULT_APP_NAME = "moduleName"
val dependentModule by valueSetting<DependentModuleReference>(
"",
GenerationPhase.PROJECT_GENERATION,
alwaysFailingParser("Dependent module setting should not be parsed")
) {
defaultValue = value(DependentModuleReference.EMPTY)
}
data class DependentModuleReference(val module: Module?) {
companion object {
val EMPTY = DependentModuleReference(module = null)
}
}
}
@@ -15,19 +15,21 @@ import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.correspondingStdl
import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem
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.ModulePath
import org.jetbrains.kotlin.tools.projectWizard.settings.version.Version
import org.jetbrains.kotlin.tools.projectWizard.templates.FileTemplate
import java.nio.file.Path
import kotlin.properties.ReadOnlyProperty
sealed class ModuleCondifuratorSettingsEnvironment {
abstract val <V : Any, T : SettingType<V>> ModuleConfiguratorSetting<V, T>.reference: ModuleConfiguratorSettingReference<V, T>
interface ModuleConfiguratorSettingsEnvironment {
val <V : Any, T : SettingType<V>> ModuleConfiguratorSetting<V, T>.reference: ModuleConfiguratorSettingReference<V, T>
}
class ModuleBasedConfiguratorSettingsEnvironment(
private val configurator: ModuleConfigurator,
private val module: Module
) : ModuleCondifuratorSettingsEnvironment() {
) : ModuleConfiguratorSettingsEnvironment {
override val <V : Any, T : SettingType<V>> ModuleConfiguratorSetting<V, T>.reference: ModuleConfiguratorSettingReference<V, T>
get() = ModuleBasedConfiguratorSettingReference(configurator, module, this)
}
@@ -35,7 +37,7 @@ class ModuleBasedConfiguratorSettingsEnvironment(
class IdBasedConfiguratorSettingsEnvironment(
private val configurator: ModuleConfigurator,
private val moduleId: Identificator
) : ModuleCondifuratorSettingsEnvironment() {
) : ModuleConfiguratorSettingsEnvironment {
override val <V : Any, T : SettingType<V>> ModuleConfiguratorSetting<V, T>.reference: ModuleConfiguratorSettingReference<V, T>
get() = IdBasedConfiguratorSettingReference(configurator, moduleId, this)
}
@@ -43,13 +45,13 @@ class IdBasedConfiguratorSettingsEnvironment(
fun <T> withSettingsOf(
moduleId: Identificator,
configurator: ModuleConfigurator,
function: ModuleCondifuratorSettingsEnvironment.() -> T
function: ModuleConfiguratorSettingsEnvironment.() -> T
): T = function(IdBasedConfiguratorSettingsEnvironment(configurator, moduleId))
fun <T> withSettingsOf(
module: Module,
configurator: ModuleConfigurator = module.configurator,
function: ModuleCondifuratorSettingsEnvironment.() -> T
function: ModuleConfiguratorSettingsEnvironment.() -> T
): T = function(ModuleBasedConfiguratorSettingsEnvironment(configurator, module))
@@ -231,6 +233,12 @@ interface ModuleConfigurator : DisplayableSettingItem, EntitiesOwnerDescriptor {
modulePath: Path
): TaskResult<Unit> = UNIT_SUCCESS
fun Reader.createTemplates(
configurationData: ModulesToIrConversionData,
module: Module,
modulePath: Path
): List<FileTemplate> = emptyList()
companion object {
val ALL = buildList<ModuleConfigurator> {
+RealNativeTargetConfigurator.configurators
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.*
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.BuildSystemType
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.GradlePlugin
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.isGradle
import org.jetbrains.kotlin.tools.projectWizard.plugins.templates.TemplatesPlugin
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.*
import org.jetbrains.kotlin.tools.projectWizard.settings.version.Version
import java.nio.file.Path
@@ -106,8 +107,13 @@ class ModulesToIRsConverter(
val to = data.moduleByPath.getValue(dependency.path)
val (dependencyType) = ModuleDependencyType.getPossibleDependencyType(module, to)
.toResult { InvalidModuleDependencyError(module, to) }
dependencyType.createDependencyIrs(module, to, data)
}.sequence()
with(dependencyType) {
@Suppress("DEPRECATION")
with(unsafeSettingWriter) { runArbitraryTask(module, to, data).ensure() }
createDependencyIrs(module, to, data)
}
}.sequence().map { it.flatten() }
mutateProjectStructureByModuleConfigurator(module, modulePath)
val buildFileIR = run {
if (!configurator.needCreateBuildFile) return@run null
@@ -237,6 +243,7 @@ class ModulesToIRsConverter(
compute {
rootBuildFileIrs += createRootBuildFileIrs(data)
runArbitraryTask(data, module, modulePath).ensure()
TemplatesPlugin::addFileTemplates.execute(createTemplates(data, module, modulePath)).ensure()
if (this@with is GradleModuleConfigurator) {
GradlePlugin::settingsGradleFileIRs.addValues(createSettingsGradleIRs(module)).ensure()
}
@@ -23,18 +23,22 @@ sealed class ProjectTemplate : DisplayableSettingItem {
private val setsDefaultValues: List<SettingWithValue<*, *>>
get() = listOf(KotlinPlugin::projectKind.reference withValue projectKind)
protected abstract val setsPluginSettings: List<SettingWithValue<*, *>>
protected open val setsPluginSettings: List<SettingWithValue<*, *>> = emptyList()
protected open val setsModules: List<Module> = emptyList()
private val setsAdditionalSettingValues = mutableListOf<SettingWithValue<*, *>>()
val setsValues: List<SettingWithValue<*, *>>
get() = buildList {
setsModules.takeIf { it.isNotEmpty() }?.let { modules ->
+(KotlinPlugin::modules withValue modules)
}
+setsDefaultValues
+setsPluginSettings
+setsAdditionalSettingValues
}
fun <T : Template> Module.withTemplate(
protected fun <T : Template> Module.withTemplate(
template: T,
createSettings: TemplateSettingsBuilder<T>.() -> Unit = {}
) = apply {
@@ -45,6 +49,17 @@ sealed class ProjectTemplate : DisplayableSettingItem {
}
}
protected fun <C : ModuleConfigurator> Module.withConfiguratorSettings(
configurator: C,
createSettings: ConfiguratorSettingsBuilder<C>.() -> Unit = {}
) = apply {
assert(this.configurator === configurator)
with(ConfiguratorSettingsBuilder(this, configurator)) {
createSettings()
setsAdditionalSettingValues += setsSettings
}
}
companion object {
val ALL = listOf(
@@ -55,6 +70,7 @@ sealed class ProjectTemplate : DisplayableSettingItem {
MultiplatformLibrary,
AndroidApplication,
IOSApplication,
MPPMobileApplication,
MultiplatformMobileLibrary,
NativeConsoleApplication,
JsBrowserApplication
@@ -77,6 +93,23 @@ class TemplateSettingsBuilder<Q : Template>(
infix fun <V : Any, T : SettingType<V>> TemplateSetting<V, T>.withValue(value: V) {
settings += SettingWithValue(reference, value)
}
}
class ConfiguratorSettingsBuilder<C : ModuleConfigurator>(
val module: Module,
val configurator: C
) : ModuleConfiguratorSettingsEnvironment by ModuleBasedConfiguratorSettingsEnvironment(configurator, module) {
init {
assert(module.configurator === configurator)
}
private val settings = mutableListOf<SettingWithValue<*, *>>()
val setsSettings: List<SettingWithValue<*, *>>
get() = settings
infix fun <V : Any, T : SettingType<V>> ModuleConfiguratorSetting<V, T>.withValue(value: V) {
settings += SettingWithValue(reference, value)
}
}
@@ -89,7 +122,7 @@ inline infix fun <V : Any, reified T : SettingType<V>> PluginSettingPropertyRefe
value: V
): SettingWithValue<V, T> = reference.withValue(value)
private fun ModuleType.createDefaultSourcesets() =
private fun createDefaultSourcesets() =
SourcesetType.values().map { sourcesetType ->
Sourceset(
sourcesetType,
@@ -110,7 +143,7 @@ object EmptySingleplatformProjectTemplate : ProjectTemplate() {
override val setsPluginSettings: List<SettingWithValue<*, *>>
get() = listOf(
KotlinPlugin::modules withValue listOf(
SingleplatformModule("mainModule", ModuleType.jvm.createDefaultSourcesets())
SingleplatformModule("mainModule", createDefaultSourcesets())
)
)
}
@@ -140,7 +173,7 @@ object JvmConsoleApplication : ProjectTemplate() {
KotlinPlugin::modules withValue listOf(
SingleplatformModule(
"consoleApp",
ModuleType.jvm.createDefaultSourcesets()
createDefaultSourcesets()
).apply {
withTemplate(ConsoleJvmApplicationTemplate())
}
@@ -173,7 +206,7 @@ object MultiplatformLibrary : ProjectTemplate() {
object JvmServerJsClient : ProjectTemplate() {
override val title: String = "Full-Stack Web Application"
override val description: String = "" +
"Create a fully-functional web application using Kotlin/JS for the frontend and Kotlin/JVM for the backend"
"Create a fully-functional web application using Kotlin/JS for the frontend and Kotlin/JVM for the backend."
override val suggestedProjectName: String = "myFullStackApplication"
override val projectKind: ProjectKind = ProjectKind.Multiplatform
override val setsPluginSettings: List<SettingWithValue<*, *>> = listOf(
@@ -288,6 +321,57 @@ object IOSApplication : ProjectTemplate() {
)
}
object MPPMobileApplication : ProjectTemplate() {
override val title = "Multiplatform Mobile Application"
override val description =
"Create mobile applications for iOS and Android with Kotlin Multiplatform Mobile, which supports sharing common code between platforms."
override val suggestedProjectName = "myIOSApplication"
override val projectKind = ProjectKind.Multiplatform
override val setsModules: List<Module> = buildList {
val shared = MultiplatformModule(
"shared",
listOf(
ModuleType.common.createDefaultTarget(),
Module(
"android",
AndroidTargetConfigurator,
null,
sourcesets = createDefaultSourcesets(),
subModules = emptyList()
).withConfiguratorSettings(AndroidTargetConfigurator) {
configurator.androidPlugin withValue AndroidGradlePlugin.APPLICATION
},
Module(
"ios",
RealNativeTargetConfigurator.configuratorsByModuleType.getValue(ModuleSubType.iosX64),
null,
sourcesets = createDefaultSourcesets(),
subModules = emptyList()
)
)
)
+shared
+Module(
"iosApp",
IOSSinglePlatformModuleConfigurator,
template = null,
sourcesets = createDefaultSourcesets(),
subModules = emptyList(),
dependencies = mutableListOf(ModuleReference.ByModule(shared))
)
+Module(
"androidApp",
AndroidSinglePlatformModuleConfigurator,
template = null,
sourcesets = createDefaultSourcesets(),
subModules = emptyList(),
dependencies = mutableListOf(ModuleReference.ByModule(shared))
)
}
}
object MultiplatformMobileLibrary : ProjectTemplate() {
override val title = "Multiplatform Mobile Library"
override val description = "Create a library that supports sharing code between iOS and Android."
@@ -309,7 +393,9 @@ object MultiplatformMobileLibrary : ProjectTemplate() {
Sourceset(type, dependencies = emptyList())
},
emptyList()
),
).withConfiguratorSettings(AndroidTargetConfigurator) {
configurator.androidPlugin withValue AndroidGradlePlugin.LIBRARY
},
Module(
"ios",
RealNativeTargetConfigurator.configuratorsByModuleType.getValue(ModuleSubType.iosX64),
@@ -5,12 +5,13 @@
package org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem
import org.jetbrains.kotlin.tools.projectWizard.core.*
import org.jetbrains.kotlin.tools.projectWizard.core.asSingletonList
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.BuildSystemIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.DependencyType
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.GradleRootProjectDependencyIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.ModuleDependencyIR
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.JvmSinglePlatformModuleConfigurator
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.ModuleConfigurator
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.*
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.isGradle
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModulesToIrConversionData
import kotlin.reflect.KClass
@@ -22,7 +23,7 @@ sealed class ModuleDependencyType(
fun accepts(from: Module, to: Module) =
this.from.isInstance(from.configurator) && this.to.isInstance(to.configurator)
open fun createDependencyIrs(from: Module, to: Module, data: ModulesToIrConversionData): BuildSystemIR {
open fun createDependencyIrs(from: Module, to: Module, data: ModulesToIrConversionData): List<BuildSystemIR> {
val path = to.path
val modulePomIr = data.pomIr.copy(artifactId = to.name)
return when {
@@ -34,18 +35,45 @@ sealed class ModuleDependencyType(
modulePomIr,
DependencyType.MAIN
)
}
}.asSingletonList()
}
open fun SettingsWriter.runArbitraryTask(from: Module, to: Module, data: ModulesToIrConversionData): TaskResult<Unit> =
UNIT_SUCCESS
object JVMSinglePlatformToJVMSinglePlatform : ModuleDependencyType(
from = JvmSinglePlatformModuleConfigurator::class,
to = JvmSinglePlatformModuleConfigurator::class
)
object AndroidSinglePlatformToMPP : ModuleDependencyType(
from = AndroidSinglePlatformModuleConfigurator::class,
to = MppModuleConfigurator::class
)
object IOSToMppSinglePlatformToMPP : ModuleDependencyType(
from = IOSSinglePlatformModuleConfigurator::class,
to = MppModuleConfigurator::class
) {
override fun createDependencyIrs(from: Module, to: Module, data: ModulesToIrConversionData): List<BuildSystemIR> =
emptyList()
override fun SettingsWriter.runArbitraryTask(
from: Module,
to: Module,
data: ModulesToIrConversionData
): TaskResult<Unit> = withSettingsOf(from) {
IOSSinglePlatformModuleConfigurator.dependentModule.reference
.setValue(IOSSinglePlatformModuleConfigurator.DependentModuleReference(to))
UNIT_SUCCESS
}
}
companion object {
private val ALL = listOf(
JVMSinglePlatformToJVMSinglePlatform
JVMSinglePlatformToJVMSinglePlatform,
AndroidSinglePlatformToMPP,
IOSToMppSinglePlatformToMPP
)
fun getPossibleDependencyType(from: Module, to: Module): ModuleDependencyType? =