diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/GradleKotlinFrameworkSupportProvider.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/GradleKotlinFrameworkSupportProvider.kt index d601b1c0d2f..3d6b9327f5c 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/GradleKotlinFrameworkSupportProvider.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/GradleKotlinFrameworkSupportProvider.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.idea.configuration import com.intellij.framework.FrameworkTypeEx import com.intellij.framework.addSupport.FrameworkSupportInModuleProvider import com.intellij.openapi.module.Module +import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.roots.ModifiableModelsProvider import com.intellij.openapi.roots.ModifiableRootModel import org.jetbrains.kotlin.idea.KotlinIcons @@ -42,6 +43,10 @@ abstract class GradleKotlinFrameworkSupportProvider(val frameworkTypeId: String, rootModel: ModifiableRootModel, modifiableModelsProvider: ModifiableModelsProvider, buildScriptData: BuildScriptDataBuilder) { + addSupport(buildScriptData, rootModel.sdk) + } + + fun addSupport(buildScriptData: BuildScriptDataBuilder, sdk: Sdk?) { var kotlinVersion = bundledRuntimeVersion() val additionalRepository = getRepositoryForVersion(kotlinVersion) if (isSnapshot(bundledRuntimeVersion())) { @@ -57,17 +62,17 @@ abstract class GradleKotlinFrameworkSupportProvider(val frameworkTypeId: String, } buildScriptData - .addPluginDefinition(KotlinWithGradleConfigurator.getGroovyApplyPluginDirective(getPluginId())) + .addPluginDefinition(KotlinWithGradleConfigurator.getGroovyApplyPluginDirective(getPluginId())) - .addBuildscriptRepositoriesDefinition("mavenCentral()") - .addRepositoriesDefinition("mavenCentral()") + .addBuildscriptRepositoriesDefinition("mavenCentral()") + .addRepositoriesDefinition("mavenCentral()") - .addBuildscriptPropertyDefinition("ext.kotlin_version = '$kotlinVersion'") - .addDependencyNotation(getRuntimeLibrary(rootModel)) - .addBuildscriptDependencyNotation(KotlinWithGradleConfigurator.CLASSPATH) + .addBuildscriptPropertyDefinition("ext.kotlin_version = '$kotlinVersion'") + .addDependencyNotation(getRuntimeLibrary(sdk)) + .addBuildscriptDependencyNotation(KotlinWithGradleConfigurator.CLASSPATH) } - protected abstract fun getRuntimeLibrary(rootModel: ModifiableRootModel): String + protected abstract fun getRuntimeLibrary(sdk: Sdk?): String protected abstract fun getPluginId(): String } @@ -78,8 +83,8 @@ open class GradleKotlinJavaFrameworkSupportProvider(frameworkTypeId: String = "K override fun getPluginId() = KotlinGradleModuleConfigurator.KOTLIN - override fun getRuntimeLibrary(rootModel: ModifiableRootModel) = - KotlinWithGradleConfigurator.getGroovyDependencySnippet(getStdlibArtifactId(rootModel.sdk, bundledRuntimeVersion())) + override fun getRuntimeLibrary(sdk: Sdk?) = + KotlinWithGradleConfigurator.getGroovyDependencySnippet(getStdlibArtifactId(sdk, bundledRuntimeVersion())) override fun addSupport(module: Module, rootModel: ModifiableRootModel, modifiableModelsProvider: ModifiableModelsProvider, buildScriptData: BuildScriptDataBuilder) { super.addSupport(module, rootModel, modifiableModelsProvider, buildScriptData) @@ -97,7 +102,7 @@ open class GradleKotlinJSFrameworkSupportProvider(frameworkTypeId: String = "KOT override fun getPluginId() = KotlinJsGradleModuleConfigurator.KOTLIN_JS - override fun getRuntimeLibrary(rootModel: ModifiableRootModel) = + override fun getRuntimeLibrary(sdk: Sdk?) = KotlinWithGradleConfigurator.getGroovyDependencySnippet(MAVEN_JS_STDLIB_ID) } @@ -105,7 +110,7 @@ open class GradleKotlinMPPCommonFrameworkSupportProvider : GradleKotlinFrameworkSupportProvider("KOTLIN_MPP_COMMON", "Kotlin (Multiplatform - Common)", KotlinIcons.SMALL_LOGO) { override fun getPluginId() = "kotlin-platform-common" - override fun getRuntimeLibrary(rootModel: ModifiableRootModel) = + override fun getRuntimeLibrary(sdk: Sdk?) = KotlinWithGradleConfigurator.getGroovyDependencySnippet(MAVEN_COMMON_STDLIB_ID) } diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleMultiplatformModuleBuilder.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleMultiplatformModuleBuilder.kt new file mode 100644 index 00000000000..3d44012c2ed --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleMultiplatformModuleBuilder.kt @@ -0,0 +1,95 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.configuration + +import com.intellij.ide.util.projectWizard.ModuleWizardStep +import com.intellij.ide.util.projectWizard.WizardContext +import com.intellij.openapi.externalSystem.service.project.wizard.ExternalModuleSettingsStep +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.module.Module +import com.intellij.openapi.roots.ModifiableRootModel +import com.intellij.openapi.roots.ui.configuration.ModulesProvider +import com.intellij.openapi.vfs.VfsUtil +import com.intellij.openapi.vfs.VirtualFile +import org.jetbrains.kotlin.idea.KotlinIcons +import org.jetbrains.kotlin.idea.util.application.executeCommand +import org.jetbrains.kotlin.idea.util.rootManager +import org.jetbrains.plugins.gradle.frameworkSupport.BuildScriptDataBuilder +import org.jetbrains.plugins.gradle.service.project.wizard.GradleModuleBuilder +import org.jetbrains.plugins.gradle.service.settings.GradleProjectSettingsControl + +class KotlinGradleMultiplatformModuleBuilder : GradleModuleBuilder() { + var jvmModuleName: String? = null + var jsModuleName: String? = null + + override fun getBuilderId() = "kotlin.gradle.multiplatform" + + override fun getNodeIcon() = KotlinIcons.SMALL_LOGO + + override fun getPresentableName() = "Kotlin (Multiplatform)" + + override fun getDescription() = + "Multiplatform projects allow reusing the same code between multiple platforms supported by Kotlin. Such projects are built with Gradle." + + override fun createWizardSteps(wizardContext: WizardContext, modulesProvider: ModulesProvider): Array { + super.createWizardSteps(wizardContext, modulesProvider) // initializes GradleModuleBuilder.myWizardContext + return arrayOf(KotlinGradleMultiplatformWizardStep(this, wizardContext), + ExternalModuleSettingsStep( + wizardContext, this, GradleProjectSettingsControl(externalProjectSettings)) + ) + } + + override fun setupRootModel(modifiableRootModel: ModifiableRootModel) { + super.setupRootModel(modifiableRootModel) + val module = modifiableRootModel.module + val buildScriptData = getBuildScriptData(module) ?: return + val sdk = modifiableRootModel.sdk + GradleKotlinMPPCommonFrameworkSupportProvider().addSupport(buildScriptData, sdk) + } + + override fun setupModule(module: Module) { + super.setupModule(module) + + val contentRoot = module.rootManager.contentRoots.firstOrNull() ?: return + setupPlatformModule(module, contentRoot, jvmModuleName, GradleKotlinMPPJavaFrameworkSupportProvider()) + setupPlatformModule(module, contentRoot, jsModuleName, GradleKotlinMPPJSFrameworkSupportProvider()) + + val settingsGradle = contentRoot.findChild("settings.gradle") + settingsGradle?.let { + module.project.executeCommand("Update settings.gradle") { + val doc = FileDocumentManager.getInstance().getDocument(it) ?: return@executeCommand + doc.insertString(doc.textLength, "include '$jvmModuleName', '$jsModuleName'") + FileDocumentManager.getInstance().saveDocument(doc) + } + } + } + + private fun setupPlatformModule(module: Module, + contentRoot: VirtualFile, + platformModuleName: String?, + supportProvider: GradleKotlinFrameworkSupportProvider) { + if (platformModuleName.isNullOrEmpty()) return + + val sdk = module.rootManager.sdk + val moduleDir = contentRoot.createChildDirectory(this, platformModuleName!!) + val buildGradle = moduleDir.createChildData(null, "build.gradle") + val buildScriptData = BuildScriptDataBuilder(buildGradle) + supportProvider.addSupport(buildScriptData, sdk) + buildScriptData.addDependencyNotation("implement project(\":\")") + VfsUtil.saveText(buildGradle, buildScriptData.build()) + } +} diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleMultiplatformWizardStep.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleMultiplatformWizardStep.kt new file mode 100644 index 00000000000..d735914d3c6 --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleMultiplatformWizardStep.kt @@ -0,0 +1,142 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.configuration + +import com.intellij.ide.util.projectWizard.ModuleWizardStep +import com.intellij.ide.util.projectWizard.ProjectWizardUtil +import com.intellij.ide.util.projectWizard.WizardContext +import com.intellij.openapi.externalSystem.model.project.ProjectId +import com.intellij.openapi.options.ConfigurationException +import com.intellij.openapi.ui.LabeledComponent +import com.intellij.ui.DocumentAdapter +import com.intellij.ui.PanelWithAnchor +import com.intellij.util.ui.JBUI +import com.intellij.util.ui.UIUtil +import java.awt.BorderLayout +import java.awt.GridBagConstraints +import java.awt.GridBagLayout +import javax.swing.* +import javax.swing.event.DocumentEvent + +class KotlinGradleMultiplatformWizardStep( + private val builder: KotlinGradleMultiplatformModuleBuilder, + private val wizardContext: WizardContext +) : ModuleWizardStep() { + + private val mainModuleNameComponent: LabeledComponent + private val jvmModuleNameComponent: LabeledComponent + private val jsModuleNameComponent: LabeledComponent + + private val panel: JPanel + private var syncEditing: Boolean = true + private var inSyncUpdate: Boolean = false + + init { + mainModuleNameComponent = LabeledComponent.create(JTextField(), "Main module name:", BorderLayout.WEST) + jvmModuleNameComponent = LabeledComponent.create(JTextField(), "JVM module name:", BorderLayout.WEST) + jsModuleNameComponent = LabeledComponent.create(JTextField(), "JS module name:", BorderLayout.WEST) + + panel = object : JPanel(GridBagLayout()), PanelWithAnchor { + private var anchor: JComponent? = mainModuleNameComponent.anchor + + override fun getAnchor(): JComponent? = anchor + + override fun setAnchor(anchor: JComponent?) { + this.anchor = anchor + mainModuleNameComponent.anchor = anchor + jvmModuleNameComponent.anchor = anchor + jsModuleNameComponent.anchor = anchor + } + } + + val baseDir = wizardContext.projectFileDirectory + val projectName = wizardContext.projectName + val initialProjectName = projectName ?: ProjectWizardUtil.findNonExistingFileName(baseDir, "untitled", "") + mainModuleNameComponent.component.text = initialProjectName + mainModuleNameComponent.component.select(0, initialProjectName.length) + + updateDerivedModuleNames() + + mainModuleNameComponent.component.document.addDocumentListener(object : DocumentAdapter() { + override fun textChanged(e: DocumentEvent?) { + if (syncEditing) { + updateDerivedModuleNames() + } + } + }) + + val stopSyncEditingListener = object : DocumentAdapter() { + override fun textChanged(e: DocumentEvent?) { + if (!inSyncUpdate) { + syncEditing = false + } + } + } + jvmModuleNameComponent.component.document.addDocumentListener(stopSyncEditingListener) + jsModuleNameComponent.component.document.addDocumentListener(stopSyncEditingListener) + + panel.border = BorderFactory.createEmptyBorder(10, 10, 10, 10) + panel.add(mainModuleNameComponent, GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, JBUI.insets(10, 0, 0, 0), 0, 0)) + panel.add(jvmModuleNameComponent, GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, JBUI.emptyInsets(), 0, 0)) + panel.add(jsModuleNameComponent, GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, JBUI.emptyInsets(), 0, 0)) + panel.add(JLabel(""), GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1.0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, JBUI.emptyInsets(), 0, 0)) + UIUtil.mergeComponentsWithAnchor(panel) + } + + private fun updateDerivedModuleNames() { + inSyncUpdate = true + try { + jvmModuleNameComponent.component.text = mainModuleName + "-jvm" + jsModuleNameComponent.component.text = mainModuleName + "-js" + + } + finally { + inSyncUpdate = false + } + } + + override fun updateDataModel() { + wizardContext.projectBuilder = builder + wizardContext.projectName = mainModuleName + + builder.projectId = ProjectId("", mainModuleName, "") + builder.jvmModuleName = jvmModuleName + builder.jsModuleName = jsModuleName + } + + override fun getComponent() = panel + + private val mainModuleName: String + get() = mainModuleNameComponent.component.text + private val jvmModuleName: String + get() = jvmModuleNameComponent.component.text + private val jsModuleName: String + get() = jsModuleNameComponent.component.text + + override fun validate(): Boolean { + if (mainModuleName.isEmpty()) { + throw ConfigurationException("Please specify the main module name") + } + if (jvmModuleName.isNotEmpty() && (jvmModuleName == mainModuleName || jvmModuleName == jsModuleName)) { + throw ConfigurationException("The JVM module name should be distinct") + } + if (jsModuleName.isNotEmpty() && jsModuleName == mainModuleName) { + throw ConfigurationException("The IS module name should be distinct") + } + return true + } +} diff --git a/idea/src/META-INF/extensions/common.xml b/idea/src/META-INF/extensions/common.xml index 8345e6907e9..9e08f4484e8 100644 --- a/idea/src/META-INF/extensions/common.xml +++ b/idea/src/META-INF/extensions/common.xml @@ -50,5 +50,7 @@ interface="org.jetbrains.kotlin.script.ScriptHelper"/> + diff --git a/idea/src/META-INF/gradle.xml b/idea/src/META-INF/gradle.xml index a3f2d1a2da1..ef723454437 100644 --- a/idea/src/META-INF/gradle.xml +++ b/idea/src/META-INF/gradle.xml @@ -55,5 +55,7 @@ + + diff --git a/idea/src/org/jetbrains/kotlin/idea/framework/KotlinTemplatesFactory.kt b/idea/src/org/jetbrains/kotlin/idea/framework/KotlinTemplatesFactory.kt index 5ebd098642e..412a00570f2 100644 --- a/idea/src/org/jetbrains/kotlin/idea/framework/KotlinTemplatesFactory.kt +++ b/idea/src/org/jetbrains/kotlin/idea/framework/KotlinTemplatesFactory.kt @@ -16,7 +16,11 @@ package org.jetbrains.kotlin.idea.framework +import com.intellij.ide.util.projectWizard.ModuleBuilder import com.intellij.ide.util.projectWizard.WizardContext +import com.intellij.openapi.extensions.ExtensionPointName +import com.intellij.openapi.extensions.Extensions +import com.intellij.platform.ProjectTemplate import com.intellij.platform.ProjectTemplatesFactory import com.intellij.platform.templates.BuilderBasedTemplate import org.jetbrains.kotlin.idea.KotlinIcons @@ -25,21 +29,27 @@ import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform class KotlinTemplatesFactory : ProjectTemplatesFactory() { companion object { + val EP_NAME = ExtensionPointName.create("org.jetbrains.kotlin.moduleBuilder") + val KOTLIN_GROUP_NAME: String = "Kotlin" } override fun getGroups() = arrayOf(KOTLIN_GROUP_NAME) override fun getGroupIcon(group: String) = KotlinIcons.SMALL_LOGO - override fun createTemplates(group: String?, context: WizardContext?) = - arrayOf( - BuilderBasedTemplate(KotlinModuleBuilder(JvmPlatform, - "Kotlin/JVM", - "Kotlin module for JVM target", - KotlinIcons.SMALL_LOGO)), + override fun createTemplates(group: String?, context: WizardContext?): Array { + val result = mutableListOf( + BuilderBasedTemplate(KotlinModuleBuilder(JvmPlatform, + "Kotlin/JVM", + "Kotlin module for JVM target", + KotlinIcons.SMALL_LOGO)), - BuilderBasedTemplate(KotlinModuleBuilder(JsPlatform, "Kotlin/JS", - "Kotlin module for JavaScript target", - KotlinIcons.JS)) - ) + BuilderBasedTemplate(KotlinModuleBuilder(JsPlatform, "Kotlin/JS", + "Kotlin module for JavaScript target", + KotlinIcons.JS) + ) + ) + result.addAll(Extensions.getExtensions(EP_NAME).map { BuilderBasedTemplate(it) }) + return result.toTypedArray() + } }