Relying on platform stepping goes better only from 2019.3

This commit is contained in:
Anton Yalyshev
2019-09-24 17:29:24 +03:00
parent a66078f2cd
commit 721aa81ca5
6 changed files with 647 additions and 4 deletions
@@ -5,11 +5,15 @@
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.ExternalSystemModulePropertyManager
import com.intellij.openapi.externalSystem.model.ProjectSystemId
import com.intellij.openapi.externalSystem.model.project.ModuleData
import com.intellij.openapi.externalSystem.service.project.wizard.ExternalModuleSettingsStep
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.openapi.module.Module
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
@@ -19,6 +23,7 @@ import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.target.TargetSupportException
import org.jetbrains.plugins.gradle.frameworkSupport.BuildScriptDataBuilder
import org.jetbrains.plugins.gradle.service.project.wizard.GradleModuleBuilder
import org.jetbrains.plugins.gradle.service.settings.GradleProjectSettingsControl
import org.jetbrains.plugins.gradle.settings.DistributionType
import com.intellij.openapi.externalSystem.model.project.ProjectData
import org.jetbrains.kotlin.idea.statistics.FUSEventGroups
@@ -33,6 +38,14 @@ abstract class KotlinGradleAbstractMultiplatformModuleBuilder(
override fun getNodeIcon(): Icon = KotlinIcons.MPP
override fun createWizardSteps(wizardContext: WizardContext, modulesProvider: ModulesProvider): Array<ModuleWizardStep> {
super.createWizardSteps(wizardContext, modulesProvider) // initializes GradleModuleBuilder.myWizardContext
return arrayOf(
// Let us have to edit project name only
ExternalModuleSettingsStep(wizardContext, this, GradleProjectSettingsControl(externalProjectSettings))
)
}
private fun setupMppModule(module: Module, parentDir: VirtualFile): VirtualFile? {
val moduleDir = parentDir.createChildDirectory(this, mppDirName)
val buildGradle = moduleDir.createChildData(null, "build.gradle")
@@ -0,0 +1,188 @@
/*
* 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.idea.configuration
import com.intellij.openapi.externalSystem.ExternalSystemModulePropertyManager
import com.intellij.openapi.externalSystem.model.ProjectSystemId
import com.intellij.openapi.externalSystem.model.project.ModuleData
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.openapi.module.Module
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.KotlinIcons
import org.jetbrains.kotlin.idea.util.rootManager
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.target.TargetSupportException
import org.jetbrains.plugins.gradle.frameworkSupport.BuildScriptDataBuilder
import org.jetbrains.plugins.gradle.service.project.wizard.GradleModuleBuilder
import org.jetbrains.plugins.gradle.settings.DistributionType
import com.intellij.openapi.externalSystem.model.project.ProjectData
import org.jetbrains.kotlin.idea.statistics.FUSEventGroups
import org.jetbrains.kotlin.idea.statistics.KotlinFUSLogger
import javax.swing.Icon
abstract class KotlinGradleAbstractMultiplatformModuleBuilder(
val mppInApplication: Boolean = false
) : GradleModuleBuilder() {
var explicitPluginVersion: String? = null
val mppDirName = "app"
override fun getNodeIcon(): Icon = KotlinIcons.MPP
private fun setupMppModule(module: Module, parentDir: VirtualFile): VirtualFile? {
val moduleDir = parentDir.createChildDirectory(this, mppDirName)
val buildGradle = moduleDir.createChildData(null, "build.gradle")
val builder = BuildScriptDataBuilder(buildGradle)
builder.setupAdditionalDependenciesForApplication()
GradleKotlinMPPFrameworkSupportProvider().addSupport(
builder,
module,
sdk = null,
specifyPluginVersionIfNeeded = true,
explicitPluginVersion = explicitPluginVersion
)
VfsUtil.saveText(buildGradle, builder.buildConfigurationPart() + builder.buildMainPart() + buildMultiPlatformPart())
return moduleDir
}
private fun enableGradleMetadataPreview(rootDir: VirtualFile) {
val settingsGradle = rootDir.findOrCreateChildData(null, "settings.gradle")
val previousText = settingsGradle.inputStream.bufferedReader().use { it.readText() }
settingsGradle.bufferedWriter().use {
it.write(previousText)
if (previousText.isNotEmpty()) {
it.newLine()
}
it.write("enableFeaturePreview('GRADLE_METADATA')")
it.newLine()
}
}
override fun setupModule(module: Module) {
try {
KotlinFUSLogger.log(FUSEventGroups.NPWizards, this.javaClass.simpleName)
module.gradleModuleBuilder = this
super.setupModule(module)
val rootDir = module.rootManager.contentRoots.firstOrNull() ?: return
val buildGradle = rootDir.findOrCreateChildData(null, "build.gradle")
if (mppInApplication) {
setupMppModule(module, rootDir)
}
val builder = BuildScriptDataBuilder(buildGradle)
builder.setupAdditionalDependencies()
if (shouldEnableGradleMetadataPreview) {
enableGradleMetadataPreview(rootDir)
}
val buildGradleText = if (!mppInApplication) {
GradleKotlinMPPFrameworkSupportProvider().addSupport(
builder,
module,
sdk = null,
specifyPluginVersionIfNeeded = true,
explicitPluginVersion = explicitPluginVersion
)
builder.buildConfigurationPart() + builder.buildMainPart() + buildMultiPlatformPart()
} else {
builder.buildConfigurationPart() + builder.buildMainPart()
}
VfsUtil.saveText(buildGradle, buildGradleText)
if (mppInApplication) {
updateSettingsScript(module) {
it.addIncludedModules(listOf(":$mppDirName"))
}
}
createProjectSkeleton(rootDir)
if (externalProjectSettings.distributionType == DistributionType.DEFAULT_WRAPPED) {
setGradleWrapperToUseVersion(rootDir, "4.10")
}
if (notImportedCommonSourceSets) GradlePropertiesFileFacade.forProject(module.project).addNotImportedCommonSourceSetsProperty()
// Ensure project root path is set
val propertyManager = ExternalSystemModulePropertyManager.getInstance(module)
val path = externalProjectSettings.externalProjectPath
val externalSystemId = propertyManager.getExternalSystemId()
if (ExternalSystemApiUtil.getExternalRootProjectPath(module) == null && externalSystemId != null) {
val projectSystemId = ProjectSystemId(externalSystemId)
val projectData = ProjectData(projectSystemId, module.name, path, path)
val moduleData = ModuleData(
propertyManager.getLinkedProjectId() ?: "",
projectSystemId,
propertyManager.getExternalModuleType() ?: "",
module.name,
path,
path
)
propertyManager.setExternalOptions(projectSystemId, moduleData, projectData)
}
} finally {
flushSettingsGradleCopy(module)
}
}
private fun setGradleWrapperToUseVersion(rootDir: VirtualFile, version: String) {
val wrapperDir = rootDir.createChildDirectory(null, "gradle").createChildDirectory(null, "wrapper")
val wrapperProperties = wrapperDir.createChildData(null, "gradle-wrapper.properties").bufferedWriter()
wrapperProperties.use {
it.write(
"""
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-$version-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
""".trimIndent()
)
}
}
protected abstract fun buildMultiPlatformPart(): String
protected open fun BuildScriptDataBuilder.setupAdditionalDependencies() {}
protected open fun BuildScriptDataBuilder.setupAdditionalDependenciesForApplication() {}
protected fun VirtualFile.bufferedWriter() = getOutputStream(this).bufferedWriter()
protected fun VirtualFile.createKotlinSampleFileWriter(
sourceRootName: String,
platformName: String = "",
languageName: String = "kotlin",
fileName: String = "Sample${platformName.capitalize()}.kt"
) = createChildDirectory(this, sourceRootName)
.createChildDirectory(this, languageName)
.createChildDirectory(this, "sample")
.createChildData(this, fileName)
.bufferedWriter()
protected open fun createProjectSkeleton(rootDir: VirtualFile) {}
protected open val notImportedCommonSourceSets = false
protected open val shouldEnableGradleMetadataPreview = false
protected val defaultNativeTarget by lazy {
try {
HostManager.host
} catch (e: TargetSupportException) {
KonanTarget.IOS_X64
}
}
// Examples: ios_x64 -> ios, macos_x64 -> macos, wasm32 -> wasm.
protected val KonanTarget.userTargetName: String
get() {
val index = name.indexOfAny("_0123456789".toCharArray())
return if (index > 0) name.substring(0, index) else name
}
companion object {
const val productionSuffix = "Main"
const val testSuffix = "Test"
}
}
@@ -139,8 +139,8 @@ class KotlinGradleMobileSharedMultiplatformModuleBuilder : KotlinGradleAbstractM
override fun buildMultiPlatformPart(): String {
return """
group '${projectId.groupId ?: "com.example"}'
version '${projectId.version ?: "0.0.1"}'
group 'com.example'
version '0.0.1'
apply plugin: 'maven-publish'
@@ -0,0 +1,205 @@
/*
* Copyright 2010-2018 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.idea.configuration
import com.intellij.openapi.vfs.VirtualFile
import java.io.BufferedWriter
class KotlinGradleMobileSharedMultiplatformModuleBuilder : KotlinGradleAbstractMultiplatformModuleBuilder() {
private val commonName: String = "common"
private var jvmTargetName: String = "jvm"
private var nativeTargetName: String = "ios"
private val commonSourceName get() = "$commonName$productionSuffix"
private val commonTestName get() = "$commonName$testSuffix"
private val jvmSourceName get() = "$jvmTargetName$productionSuffix"
private val jvmTestName get() = "$jvmTargetName$testSuffix"
private val nativeSourceName get() = "$nativeTargetName$productionSuffix"
val nativeTestName get() = "$nativeTargetName$testSuffix"
override val shouldEnableGradleMetadataPreview: Boolean = true
override fun getBuilderId() = "kotlin.gradle.multiplatform.mobileshared"
override fun getPresentableName() = "Mobile Shared Library | Gradle"
override fun getDescription() =
"Multiplatform Gradle project allowing reuse of the same Kotlin code between two mobile platforms (JVM/Android and Native)"
override fun createProjectSkeleton(rootDir: VirtualFile) {
val src = rootDir.createChildDirectory(this, "src")
val commonMain = src.createKotlinSampleFileWriter(commonSourceName)
val commonTest = src.createKotlinSampleFileWriter(commonTestName, fileName = "SampleTests.kt")
val jvmMain = src.createKotlinSampleFileWriter(jvmSourceName, jvmTargetName)
val jvmTest = src.createKotlinSampleFileWriter(jvmTestName, fileName = "SampleTestsJVM.kt")
val nativeMain = src.createKotlinSampleFileWriter(nativeSourceName, nativeTargetName)
val nativeTest = src.createKotlinSampleFileWriter(nativeTestName, fileName = "SampleTestsNative.kt")
try {
commonMain.write(
"""
package sample
expect class Sample() {
fun checkMe(): Int
}
expect object Platform {
fun name(): String
}
fun hello(): String = "Hello from ${"$"}{Platform.name()}"
""".trimIndent()
)
jvmMain.write(
"""
package sample
actual class Sample {
actual fun checkMe() = 42
}
actual object Platform {
actual fun name(): String = "JVM"
}
""".trimIndent()
)
nativeMain.write(
"""
package sample
actual class Sample {
actual fun checkMe() = 7
}
actual object Platform {
actual fun name(): String = "iOS"
}
""".trimIndent()
)
commonTest.write(
"""
package sample
import kotlin.test.Test
import kotlin.test.assertTrue
class SampleTests {
@Test
fun testMe() {
assertTrue(Sample().checkMe() > 0)
}
}
""".trimIndent()
)
jvmTest.write(
"""
package sample
import kotlin.test.Test
import kotlin.test.assertTrue
class SampleTestsJVM {
@Test
fun testHello() {
assertTrue("JVM" in hello())
}
}
""".trimIndent()
)
nativeTest.write(
"""
package sample
import kotlin.test.Test
import kotlin.test.assertTrue
class SampleTestsNative {
@Test
fun testHello() {
assertTrue("iOS" in hello())
}
}
""".trimIndent()
)
} finally {
listOf(commonMain, commonTest, jvmMain, jvmTest, nativeMain, nativeTest).forEach(BufferedWriter::close)
}
}
override fun buildMultiPlatformPart(): String {
return """
group '${projectId.groupId ?: "com.example"}'
version '${projectId.version ?: "0.0.1"}'
apply plugin: 'maven-publish'
kotlin {
jvm()
// This is for iPhone emulator
// Switch here to iosArm64 (or iosArm32) to build library for iPhone device
iosX64("$nativeTargetName") {
binaries {
framework()
}
}
sourceSets {
$commonSourceName {
dependencies {
implementation kotlin('stdlib-common')
}
}
$commonTestName {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
}
}
$jvmSourceName {
dependencies {
implementation kotlin('stdlib')
}
}
$jvmTestName {
dependencies {
implementation kotlin('test')
implementation kotlin('test-junit')
}
}
$nativeSourceName {
}
$nativeTestName {
}
}
}
task $nativeTestName {
def device = project.findProperty("${nativeTargetName}Device")?.toString() ?: "iPhone 8"
dependsOn kotlin.targets.$nativeTargetName.binaries.getTest('DEBUG').linkTaskName
group = JavaBasePlugin.VERIFICATION_GROUP
description = "Runs tests for target '$nativeTargetName' on an iOS simulator"
doLast {
def binary = kotlin.targets.$nativeTargetName.binaries.getTest('DEBUG').outputFile
exec {
commandLine 'xcrun', 'simctl', 'spawn', device, binary.absolutePath
}
}
}
configurations {
compileClasspath
}
""".trimIndent()
}
}
@@ -175,8 +175,8 @@ class KotlinGradleSharedMultiplatformModuleBuilder : KotlinGradleAbstractMultipl
override fun buildMultiPlatformPart(): String {
return """
group '${projectId.groupId ?: "com.example"}'
version '${projectId.version ?: "0.0.1"}'
group 'com.example'
version '0.0.1'
apply plugin: 'maven-publish'
@@ -0,0 +1,237 @@
/*
* Copyright 2010-2018 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.idea.configuration
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.konan.target.presetName
import java.io.BufferedWriter
class KotlinGradleSharedMultiplatformModuleBuilder : KotlinGradleAbstractMultiplatformModuleBuilder() {
private val commonName: String = "common"
private var jvmTargetName: String = "jvm"
private var jsTargetName: String = "js"
private var nativeTargetName: String = defaultNativeTarget.userTargetName
private val commonSourceName get() = "$commonName$productionSuffix"
private val commonTestName get() = "$commonName$testSuffix"
private val jvmSourceName get() = "$jvmTargetName$productionSuffix"
private val jvmTestName get() = "$jvmTargetName$testSuffix"
private val jsSourceName get() = "$jsTargetName$productionSuffix"
private val jsTestName get() = "$jsTargetName$testSuffix"
private val nativeSourceName get() = "$nativeTargetName$productionSuffix"
val nativeTestName get() = "$nativeTargetName$testSuffix"
override val shouldEnableGradleMetadataPreview: Boolean = true
override fun getBuilderId() = "kotlin.gradle.multiplatform.shared"
override fun getPresentableName() = "Multiplatform Library | Gradle"
override fun getDescription() =
"Multiplatform Gradle project allowing reuse of the same Kotlin code between all three main platforms (JVM, JS, and Native)"
override fun createProjectSkeleton(rootDir: VirtualFile) {
val src = rootDir.createChildDirectory(this, "src")
val commonMain = src.createKotlinSampleFileWriter(commonSourceName)
val commonTest = src.createKotlinSampleFileWriter(commonTestName, fileName = "SampleTests.kt")
val jvmMain = src.createKotlinSampleFileWriter(jvmSourceName, jvmTargetName)
val jvmTest = src.createKotlinSampleFileWriter(jvmTestName, fileName = "SampleTestsJVM.kt")
val jsMain = src.createKotlinSampleFileWriter(jsSourceName, jsTargetName)
val jsTest = src.createKotlinSampleFileWriter(jsTestName, fileName = "SampleTestsJS.kt")
val nativeMain = src.createKotlinSampleFileWriter(nativeSourceName, nativeTargetName)
val nativeTest = src.createKotlinSampleFileWriter(nativeTestName, fileName = "SampleTestsNative.kt")
try {
commonMain.write(
"""
package sample
expect class Sample() {
fun checkMe(): Int
}
expect object Platform {
val name: String
}
fun hello(): String = "Hello from ${"$"}{Platform.name}"
""".trimIndent()
)
jvmMain.write(
"""
package sample
actual class Sample {
actual fun checkMe() = 42
}
actual object Platform {
actual val name: String = "JVM"
}
""".trimIndent()
)
jsMain.write(
"""
package sample
actual class Sample {
actual fun checkMe() = 12
}
actual object Platform {
actual val name: String = "JS"
}
""".trimIndent()
)
nativeMain.write(
"""
package sample
actual class Sample {
actual fun checkMe() = 7
}
actual object Platform {
actual val name: String = "Native"
}
""".trimIndent()
)
commonTest.write(
"""
package sample
import kotlin.test.Test
import kotlin.test.assertTrue
class SampleTests {
@Test
fun testMe() {
assertTrue(Sample().checkMe() > 0)
}
}
""".trimIndent()
)
jvmTest.write(
"""
package sample
import kotlin.test.Test
import kotlin.test.assertTrue
class SampleTestsJVM {
@Test
fun testHello() {
assertTrue("JVM" in hello())
}
}
""".trimIndent()
)
jsTest.write(
"""
package sample
import kotlin.test.Test
import kotlin.test.assertTrue
class SampleTestsJS {
@Test
fun testHello() {
assertTrue("JS" in hello())
}
}
""".trimIndent()
)
nativeTest.write(
"""
package sample
import kotlin.test.Test
import kotlin.test.assertTrue
class SampleTestsNative {
@Test
fun testHello() {
assertTrue("Native" in hello())
}
}
""".trimIndent()
)
} finally {
listOf(commonMain, commonTest, jvmMain, jvmTest, jsMain, jsTest, nativeMain, nativeTest).forEach(BufferedWriter::close)
}
}
override fun buildMultiPlatformPart(): String {
return """
group '${projectId.groupId ?: "com.example"}'
version '${projectId.version ?: "0.0.1"}'
apply plugin: 'maven-publish'
kotlin {
jvm()
js {
browser {
}
nodejs {
}
}
// For ARM, should be changed to iosArm32 or iosArm64
// For Linux, should be changed to e.g. linuxX64
// For MacOS, should be changed to e.g. macosX64
// For Windows, should be changed to e.g. mingwX64
${defaultNativeTarget.presetName}("${defaultNativeTarget.userTargetName}")
sourceSets {
$commonSourceName {
dependencies {
implementation kotlin('stdlib-common')
}
}
$commonTestName {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
}
}
$jvmSourceName {
dependencies {
implementation kotlin('stdlib-jdk8')
}
}
$jvmTestName {
dependencies {
implementation kotlin('test')
implementation kotlin('test-junit')
}
}
$jsSourceName {
dependencies {
implementation kotlin('stdlib-js')
}
}
$jsTestName {
dependencies {
implementation kotlin('test-js')
}
}
$nativeSourceName {
}
$nativeTestName {
}
}
}
""".trimIndent()
}
}