Wizard: Add initial version of the new project wizard

This commit is contained in:
Ilya Kirillov
2019-12-12 15:29:27 +03:00
parent 4916f166fe
commit aca193ddd2
191 changed files with 11132 additions and 3 deletions
@@ -0,0 +1,24 @@
plugins {
kotlin("jvm")
id("jps-compatible")
}
dependencies {
api("org.apache.velocity:velocity:1.7") // we have to use the old version as it is the same as bundled into IntelliJ
compileOnly(project(":kotlin-reflect-api"))
testImplementation(project(":kotlin-test:kotlin-test-junit"))
testImplementation(commonDep("junit:junit"))
}
sourceSets {
"main" { projectDefault() }
"test" { projectDefault() }
}
projectTest {
dependsOn(":dist")
workingDir = rootDir
}
testsJar()
@@ -0,0 +1,29 @@
plugins {
kotlin("jvm")
id("jps-compatible")
}
dependencies {
implementation(kotlinStdlib())
compileOnly(project(":kotlin-reflect-api"))
implementation(project(":libraries:tools:new-project-wizard"))
compileOnly(intellijDep()) { includeJars("snakeyaml-1.24") }
testImplementation(projectTests(":compiler:tests-common"))
testImplementation(project(":kotlin-test:kotlin-test-junit"))
testImplementation(commonDep("junit:junit"))
testImplementation(intellijDep())
}
sourceSets {
"main" { projectDefault() }
"test" { projectDefault() }
}
projectTest {
dependsOn(":dist")
workingDir = rootDir
}
testsJar()
@@ -0,0 +1,64 @@
package org.jetbrains.kotlin.tools.projectWizard
import YamlParsingError
import org.jetbrains.kotlin.tools.projectWizard.core.*
import org.jetbrains.kotlin.tools.projectWizard.core.entity.*
import org.yaml.snakeyaml.Yaml
import org.yaml.snakeyaml.parser.ParserException
import java.nio.file.Path
class YamlSettingsParser(settings: List<PluginSetting<Any, *>>, private val parsingState: ParsingState) {
private val settingByName = settings.associateBy { it.path }
fun parseYamlText(yaml: String): TaskResult<Map<SettingReference<*, *>, Any>> {
val yamlObject = try {
Success(Yaml().load<Any>(yaml) ?: emptyMap<String, Any>())
} catch (e: ParserException) {
Failure(YamlParsingError(e))
} catch (e: Exception) {
Failure(ExceptionErrorImpl(e))
}
return yamlObject.flatMap { map ->
if (map is Map<*, *>) {
val result = ComputeContext.runInComputeContextWithState(parsingState) {
parseSettingValues(map, "")
}
result.map { (pluginSettings, newState) ->
pluginSettings + newState.settingValues
}
} else Failure(
BadSettingValueError("Settings file should be a map of settings")
)
}
}
fun parseYamlFile(file: Path) = computeM {
val (yaml) = safe { file.toFile().readText() }
parseYamlText(yaml)
}
private fun ParsingContext.parseSettingValues(
data: Any,
settingPath: String
): TaskResult<Map<PluginSettingReference<*, *>, Any>> =
if (settingPath in settingByName) compute {
val setting = settingByName.getValue(settingPath)
val (parsed) = setting.type.parse(this, data, settingPath)
listOf(PluginSettingReference(setting) to parsed)
} else {
when (data) {
is Map<*, *> -> {
data.entries.mapCompute { (name, value) ->
if (value == null) fail(BadSettingValueError("No value was found for a key `$settingPath`"))
val prefix = if (settingPath.isEmpty()) "" else "$settingPath."
val (children) = parseSettingValues(value, "$prefix$name")
children.entries.map { (key, value) -> key to value }
}.sequence().map { it.flatten() }
}
else -> Failure(
BadSettingValueError("No value was found for a key `$settingPath`")
)
}
}.map { it.toMap() }
}
@@ -0,0 +1,42 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard
import org.jetbrains.kotlin.tools.projectWizard.YamlSettingsParser
import org.jetbrains.kotlin.tools.projectWizard.core.*
import org.jetbrains.kotlin.tools.projectWizard.core.entity.PipelineTask
import org.jetbrains.kotlin.tools.projectWizard.core.entity.PluginSettingReference
import org.jetbrains.kotlin.tools.projectWizard.core.entity.reference
import org.jetbrains.kotlin.tools.projectWizard.core.service.AndroidServiceImpl
import org.jetbrains.kotlin.tools.projectWizard.core.service.Service
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
import org.jetbrains.kotlin.tools.projectWizard.plugins.StructurePlugin
import org.jetbrains.kotlin.tools.projectWizard.plugins.templates.TemplatesPlugin
import java.nio.file.Paths
class YamlWizard(
private val yaml: String,
private val path: String,
createPlugins: (Context) -> List<Plugin>
) : Wizard(createPlugins, listOf(AndroidServiceImpl())) {
override fun apply(
services: List<Service>,
phases: Set<GenerationPhase>,
onTaskExecuting: (PipelineTask) -> Unit
): TaskResult<Unit> = computeM {
super.apply(services, setOf(GenerationPhase.PREPARE), onTaskExecuting)
val parsingData = with(valuesReadingContext) {
ParsingState(TemplatesPlugin::templates.propertyValue, emptyMap())
}
val yamlParser = YamlSettingsParser(pluginSettings, parsingData)
val (settingsValuesFromYaml) = yamlParser.parseYamlText(yaml)
val settingValues = defaultPluginSettingValues + settingsValuesFromYaml
settingValues.forEach { (path, value) -> context.settingContext[path] = value }
context.settingContext[StructurePlugin::projectPath.reference] = Paths.get(path)
super.apply(services, phases, onTaskExecuting)
}
private val defaultPluginSettingValues
get() = pluginSettings.mapNotNull { setting ->
val defaultValue = setting.defaultValue ?: return@mapNotNull null
PluginSettingReference(setting) to defaultValue
} .toMap()
}
@@ -0,0 +1,8 @@
import org.jetbrains.kotlin.tools.projectWizard.core.Error
import org.jetbrains.kotlin.tools.projectWizard.core.asString
import org.yaml.snakeyaml.parser.ParserException
data class YamlParsingError(val exception: ParserException) : Error() {
override val message: String
get() = exception.asString()
}
@@ -0,0 +1,36 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-android-extensions'
}
group = 'testGroupId'
version = '1.0-SNAPSHOT'
repositories {
gradlePluginPortal()
google()
jcenter()
mavenCentral()
}
android {
compileSdkVersion 29
defaultConfig {
applicationId 'testArtifactId'
minSdkVersion 24
targetSdkVersion 29
versionCode 1
versionName '1.0'
}
buildTypes {
'release' {
isMinifyEnabled false
}
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.1.0'
implementation kotlin('stdlib-jdk7')
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation kotlin('stdlib-jdk8')
}
@@ -0,0 +1,36 @@
plugins {
id("com.android.application")
id("kotlin-android")
id("kotlin-android-extensions")
}
group = "testGroupId"
version = "1.0-SNAPSHOT"
repositories {
gradlePluginPortal()
google()
jcenter()
mavenCentral()
}
android {
compileSdkVersion(29)
defaultConfig {
applicationId = "testArtifactId"
minSdkVersion(24)
targetSdkVersion(29)
versionCode = 1
versionName = "1.0"
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
}
}
}
dependencies {
implementation("androidx.appcompat:appcompat:1.1.0")
implementation("androidx.core:core-ktx:1.1.0")
implementation(kotlin("stdlib-jdk7"))
implementation("androidx.constraintlayout:constraintlayout:1.1.3")
implementation(kotlin("stdlib-jdk8"))
}
@@ -0,0 +1,17 @@
buildscript {
repositories {
gradlePluginPortal()
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
classpath kotlin('gradle-plugin', '1.3.61')
}
}
group = 'testGroupId'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
@@ -0,0 +1,17 @@
buildscript {
repositories {
gradlePluginPortal()
google()
jcenter()
}
dependencies {
classpath("com.android.tools.build:gradle:3.2.1")
classpath(kotlin("gradle-plugin", "1.3.61"))
}
}
group = "testGroupId"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
@@ -0,0 +1,3 @@
org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.GroovyDslPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.KotlinDslPlugin
@@ -0,0 +1,12 @@
kotlin:
version: 1.3.61
projectKind: Multiplatform
modules:
- type:
name: android
androidSdkPath: /home/ilya/Android/Sdk
kind: singleplatform
name: android
sourcesets:
- type: main
- type: test
@@ -0,0 +1,39 @@
plugins {
id 'org.jetbrains.kotlin.multiplatform' version '1.3.61'
}
group = 'testGroupId'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
kotlin {
js('nodeJs') {
nodejs {
}
}
js('browser') {
browser {
}
}
sourceSets {
nodeJsMain {
dependencies {
implementation kotlin('stdlib-js')
}
}
nodeJsTest {
}
browserMain {
dependencies {
implementation kotlin('stdlib-js')
}
}
browserTest {
}
}
}
@@ -0,0 +1,35 @@
plugins {
kotlin("multiplatform") version "1.3.61"
}
group = "testGroupId"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
kotlin {
js("nodeJs") {
nodejs {
}
}
js("browser") {
browser {
}
}
sourceSets {
val nodeJsMain by getting {
dependencies {
implementation(kotlin("stdlib-js"))
}
}
val nodeJsTest by getting
val browserMain by getting {
dependencies {
implementation(kotlin("stdlib-js"))
}
}
val browserTest by getting
}
}
@@ -0,0 +1,3 @@
org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.GroovyDslPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.KotlinDslPlugin
@@ -0,0 +1,20 @@
kotlin:
version: 1.3.61
projectKind: Multiplatform
modules:
- type: multiplatform
kind: multiplatform
name: a
subModules:
- type: jsNode
kind: target
name: nodeJs
sourcesets:
- type: main
- type: test
- type: jsBrowser
kind: target
name: browser
sourcesets:
- type: main
- type: test
@@ -0,0 +1,22 @@
plugins {
id 'org.jetbrains.kotlin.multiplatform' version '1.3.61'
}
group = 'testGroupId'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
kotlin {
jvm()
sourceSets {
jvmMain {
dependencies {
implementation kotlin('stdlib-jdk8')
}
}
jvmTest {
}
}
}
@@ -0,0 +1,20 @@
plugins {
kotlin("multiplatform") version "1.3.61"
}
group = "testGroupId"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
kotlin {
jvm()
sourceSets {
val jvmMain by getting {
dependencies {
implementation(kotlin("stdlib-jdk8"))
}
}
val jvmTest by getting
}
}
@@ -0,0 +1,3 @@
org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.GroovyDslPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.KotlinDslPlugin
@@ -0,0 +1,14 @@
kotlin:
version: 1.3.61
projectKind: Multiplatform
modules:
- type: multiplatform
kind: multiplatform
name: a
subModules:
- type: jvmTarget
kind: target
name: jvm
sourcesets:
- type: main
- type: test
@@ -0,0 +1,12 @@
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.3.61'
}
group = 'testGroupId'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
implementation kotlin('stdlib-jdk8')
}
@@ -0,0 +1,12 @@
plugins {
kotlin("jvm") version "1.3.61"
}
group = "testGroupId"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
dependencies {
implementation(kotlin("stdlib-jdk8"))
}
@@ -0,0 +1,5 @@
org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.MavenPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.GroovyDslPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.KotlinDslPlugin
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>nya</artifactId>
<groupId>testGroupId</groupId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>nya</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.code.style>official</kotlin.code.style>
</properties>
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>1.3.61</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>1.3.61</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,10 @@
kotlin:
version: 1.3.61
projectKind: Singleplatform
modules:
- type: JVM Module
name: nya
kind: singleplatform
sourcesets:
- type: main
@@ -0,0 +1,34 @@
plugins {
id 'org.jetbrains.kotlin.multiplatform' version '1.3.61'
}
group = 'testGroupId'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
kotlin {
def hostOs = System.getProperty("os.name")
def isMingwX64 = hostOs.startsWith("Windows")
org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetWithTests nativeTarget
if (hostOs == "Mac OS X") nativeTarget = macosX64('myNative')
else if (hostOs == "Linux") nativeTarget = linuxX64("myNative")
else if (isMingwX64) return nativeTarget = mingwX64("myNative")
else throw new GradleException("Host OS is not supported in Kotlin/Native.")
nativeTarget.with {
binaries {
executable {
entryPoint = 'MAIN CLASS'
}
}
}
sourceSets {
myNativeMain {
}
myNativeTest {
}
}
}
@@ -0,0 +1,31 @@
plugins {
kotlin("multiplatform") version "1.3.61"
}
group = "testGroupId"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
kotlin {
val hostOs = System.getProperty("os.name")
val isMingwX64 = hostOs.startsWith("Windows")
val nativeTarget = when {
hostOs == "Mac OS X" -> macosX64("myNative")
hostOs == "Linux" -> linuxX64("myNative")
isMingwX64 -> mingwX64("myNative")
else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
}
nativeTarget.apply {
binaries {
executable {
entryPoint = "MAIN CLASS"
}
}
}
sourceSets {
val myNativeMain by getting
val myNativeTest by getting
}
}
@@ -0,0 +1,3 @@
org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.GroovyDslPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.KotlinDslPlugin
@@ -0,0 +1,14 @@
kotlin:
version: 1.3.61
projectKind: Multiplatform
modules:
- type: multiplatform
kind: multiplatform
name: a
subModules:
- type: nativeForCurrentSystem
kind: target
name: myNative
sourcesets:
- type: main
- type: test
@@ -0,0 +1,35 @@
plugins {
id 'org.jetbrains.kotlin.multiplatform' version '1.3.61'
}
group = 'testGroupId'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
kotlin {
jvm()
js('a') {
browser {
}
}
sourceSets {
jvmMain {
dependencies {
implementation kotlin('stdlib-jdk8')
}
}
jvmTest {
}
aMain {
dependencies {
implementation kotlin('stdlib-js')
}
}
aTest {
}
}
}
@@ -0,0 +1,31 @@
plugins {
kotlin("multiplatform") version "1.3.61"
}
group = "testGroupId"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
kotlin {
jvm()
js("a") {
browser {
}
}
sourceSets {
val jvmMain by getting {
dependencies {
implementation(kotlin("stdlib-jdk8"))
}
}
val jvmTest by getting
val aMain by getting {
dependencies {
implementation(kotlin("stdlib-js"))
}
}
val aTest by getting
}
}
@@ -0,0 +1,3 @@
org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.GroovyDslPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.KotlinDslPlugin
@@ -0,0 +1,20 @@
kotlin:
version: 1.3.61
projectKind: Multiplatform
modules:
- type: multiplatform
kind: multiplatform
name: a
subModules:
- type: jvmTarget
kind: target
name: jvm
sourcesets:
- type: main
- type: test
- type: jsBrowser
kind: target
name: a
sourcesets:
- type: main
- type: test
@@ -0,0 +1,26 @@
plugins {
id 'org.jetbrains.kotlin.multiplatform' version '1.3.61'
}
group = 'testGroupId'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
kotlin {
linuxX64 {
binaries {
executable {
entryPoint = 'MAIN CLASS'
}
}
}
sourceSets {
linuxX64Main {
}
linuxX64Test {
}
}
}
@@ -0,0 +1,22 @@
plugins {
kotlin("multiplatform") version "1.3.61"
}
group = "testGroupId"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
kotlin {
linuxX64 {
binaries {
executable {
entryPoint = "MAIN CLASS"
}
}
}
sourceSets {
val linuxX64Main by getting
val linuxX64Test by getting
}
}
@@ -0,0 +1,3 @@
org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.GroovyDslPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.KotlinDslPlugin
@@ -0,0 +1,14 @@
kotlin:
version: 1.3.61
projectKind: Multiplatform
modules:
- type: multiplatform
kind: multiplatform
name: a
subModules:
- type: linuxX64Target
kind: target
name: linuxX64
sourcesets:
- type: main
- type: test
@@ -0,0 +1,99 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.tools.projectWizard.cli
import com.intellij.testFramework.UsefulTestCase
import org.jetbrains.kotlin.tools.projectWizard.core.ExceptionError
import org.jetbrains.kotlin.tools.projectWizard.core.div
import org.jetbrains.kotlin.tools.projectWizard.core.onFailure
import org.jetbrains.kotlin.tools.projectWizard.core.service.Services
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.MavenPlugin
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.GroovyDslPlugin
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.KotlinDslPlugin
import org.jetbrains.kotlin.tools.projectWizard.wizard.YamlWizard
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
abstract class AbstractBuildFileGenerationTest : AbstractPluginBasedTest() {
fun doTest(directoryPath: String) {
val directory = Paths.get(directoryPath)
val testData = init(directory)
if (KotlinDslPlugin::class in testData.pluginClasses) {
doTest(directory, testData, BuildSystem.GRADLE_KOTLIN_DSL)
}
if (GroovyDslPlugin::class in testData.pluginClasses) {
doTest(directory, testData, BuildSystem.GRADLE_GROOVY_DSL)
}
if (MavenPlugin::class in testData.pluginClasses) {
doTest(directory, testData, BuildSystem.MAVEN)
}
}
private fun doTest(directory: Path, testData: WizardTestData, buildSystem: BuildSystem) {
val yaml = directory.resolve("settings.yaml").toFile().readText() + "\n" +
defaultStructure + "\n" +
buildSystem.yaml
val tempDir = Files.createTempDirectory(null)
val wizard = YamlWizard(yaml, tempDir.toString(), testData.createPlugins)
val result = wizard.apply(Services.osServices, GenerationPhase.ALL)
result.onFailure { errors ->
errors.forEach { error ->
if (error is ExceptionError) {
throw error.exception
}
}
fail(errors.joinToString("\n"))
}
val expectedDirectory = (directory / EXPECTED_DIRECTORY_NAME).takeIf { Files.exists(it) } ?: directory
compareFiles(
expectedDirectory.allBuildFiles(buildSystem), expectedDirectory,
tempDir.allBuildFiles(buildSystem), tempDir
)
}
private fun Path.allBuildFiles(buildSystem: BuildSystem) =
listFiles { it.fileName.toString() == buildSystem.buildFileName }
private enum class BuildSystem(val buildFileName: String, val yaml: String) {
GRADLE_KOTLIN_DSL(
buildFileName = "build.gradle.kts",
yaml = """buildSystem:
type: GradleKotlinDsl
gradle:
createGradleWrapper: false
version: 5.4.1""".trimIndent()
),
GRADLE_GROOVY_DSL(
buildFileName = "build.gradle",
yaml = """buildSystem:
type: GradleGroovyDsl
gradle:
createGradleWrapper: false
version: 5.4.1""".trimIndent()
),
MAVEN(
buildFileName = "pom.xml",
yaml = """buildSystem:
type: Maven""".trimIndent()
)
}
companion object {
private const val EXPECTED_DIRECTORY_NAME = "expected"
private val defaultStructure =
"""structure:
name: generatedProject
groupId: testGroupId
artifactId: testArtifactId
""".trimIndent()
}
}
@@ -0,0 +1,45 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.tools.projectWizard.cli
import com.intellij.testFramework.UsefulTestCase
import org.jetbrains.kotlin.tools.projectWizard.core.Context
import org.jetbrains.kotlin.tools.projectWizard.core.Plugin
import org.jetbrains.kotlin.tools.projectWizard.core.PluginReference
import org.jetbrains.kotlin.tools.projectWizard.plugins.StructurePlugin
import org.jetbrains.kotlin.tools.projectWizard.plugins.templates.TemplatesPlugin
import java.nio.file.Path
import kotlin.reflect.full.primaryConstructor
abstract class AbstractPluginBasedTest : UsefulTestCase() {
open val defaultPlugins: List<PluginReference> = listOf(
StructurePlugin::class,
TemplatesPlugin::class
)
protected fun init(directory: Path): WizardTestData {
val pluginNames = directory.resolve("plugins.txt").toFile().readLines().mapNotNull { name ->
name.trim().takeIf { it.isNotBlank() }
}.distinct()
@Suppress("UNCHECKED_CAST")
val pluginClasses = defaultPlugins + pluginNames.map { pluginName ->
Class.forName(pluginName).kotlin as PluginReference
}
val createPlugins = { context: Context ->
pluginClasses.map { pluginClass ->
pluginClass.primaryConstructor!!.call(context)
}
}
return WizardTestData(pluginClasses, createPlugins)
}
}
data class WizardTestData(
val pluginClasses: List<PluginReference>,
val createPlugins: (Context) -> List<Plugin>
)
@@ -0,0 +1,65 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.tools.projectWizard.cli;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("libraries/tools/new-project-wizard/new-project-wizard-cli/testData/buildFileGeneration")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class BuildFileGenerationTestGenerated extends AbstractBuildFileGenerationTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInBuildFileGeneration() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("libraries/tools/new-project-wizard/new-project-wizard-cli/testData/buildFileGeneration"), Pattern.compile("^([^\\.]+)$"), false);
}
@TestMetadata("android")
public void testAndroid() throws Exception {
runTest("libraries/tools/new-project-wizard/new-project-wizard-cli/testData/buildFileGeneration/android/");
}
@TestMetadata("jsNodeAndBrowserTargets")
public void testJsNodeAndBrowserTargets() throws Exception {
runTest("libraries/tools/new-project-wizard/new-project-wizard-cli/testData/buildFileGeneration/jsNodeAndBrowserTargets/");
}
@TestMetadata("jvmTarget")
public void testJvmTarget() throws Exception {
runTest("libraries/tools/new-project-wizard/new-project-wizard-cli/testData/buildFileGeneration/jvmTarget/");
}
@TestMetadata("kotlinJvm")
public void testKotlinJvm() throws Exception {
runTest("libraries/tools/new-project-wizard/new-project-wizard-cli/testData/buildFileGeneration/kotlinJvm/");
}
@TestMetadata("nativeForCurrentSystem")
public void testNativeForCurrentSystem() throws Exception {
runTest("libraries/tools/new-project-wizard/new-project-wizard-cli/testData/buildFileGeneration/nativeForCurrentSystem/");
}
@TestMetadata("simpleMultiplatform")
public void testSimpleMultiplatform() throws Exception {
runTest("libraries/tools/new-project-wizard/new-project-wizard-cli/testData/buildFileGeneration/simpleMultiplatform/");
}
@TestMetadata("simpleNativeTarget")
public void testSimpleNativeTarget() throws Exception {
runTest("libraries/tools/new-project-wizard/new-project-wizard-cli/testData/buildFileGeneration/simpleNativeTarget/");
}
}
@@ -0,0 +1,37 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.tools.projectWizard.cli
import org.hamcrest.core.Is
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.junit.Assert.assertThat
import java.nio.file.Files
import java.nio.file.Path
import java.util.stream.Collectors
internal fun Path.readFile() = toFile().readText().trim()
internal fun Path.listFiles(filter: (Path) -> Boolean) =
Files.walk(this).filter { path ->
Files.isRegularFile(path) && filter(path)
}.collect(Collectors.toList()).sorted()
internal fun compareFiles(
expectedFiles: List<Path>, expectedDir: Path,
actualFiles: List<Path>, actualDir: Path
) {
val expectedFilesSorted = expectedFiles.sorted()
val actualFilesSorted = actualFiles.sorted()
assertThat(
actualFilesSorted.map { actualDir.relativize(it) },
Is.`is`(expectedFilesSorted.map { expectedDir.relativize(it) })
)
for ((actualFile, expectedFile) in actualFilesSorted zip expectedFilesSorted) {
KotlinTestUtils.assertEqualsToFile(expectedFile.toFile(), actualFile.readFile())
}
}
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="${package.asCodePackage()}">
<application
android:allowBackup="true"
android:supportsRtl="true">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
@@ -0,0 +1,11 @@
package ${package.asCodePackage()}
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,3 @@
fun main(args: Array<String>) {
println("Hello World!")
}
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-${version}-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
@@ -0,0 +1,3 @@
#foreach($property in $properties)
${property.getFirst()}=${property.getSecond()}
#end
@@ -0,0 +1,3 @@
#foreach($property in $properties)
${property.getFirst()}=${property.getSecond()}
#end
@@ -0,0 +1,5 @@
rootProject.name = "${projectName}"
#foreach($subProject in $subProjects)
include("${subProject}")
#end
@@ -0,0 +1,9 @@
import kotlin.test.Test
import kotlin.test.assertTrue
class Test {
@Test
fun testIt() {
assertTrue(true)
}
}
@@ -0,0 +1,25 @@
package org.jetbrains.kotlin.tools.projectWizard
import org.jetbrains.kotlin.tools.projectWizard.core.RandomIdGenerator
import org.jetbrains.kotlin.tools.projectWizard.core.safeAs
sealed class Identificator {
abstract val id: String
final override fun equals(other: Any?): Boolean = other.safeAs<Identificator>()?.id == id
final override fun hashCode(): Int = id.hashCode()
final override fun toString(): String = id
}
class GeneratedIdentificator(prefix: String?) : Identificator() {
override val id = """${prefix.orEmpty()}_${RandomIdGenerator.generate()}"""
}
interface IdentificatorOwner {
val identificator: Identificator
}
val IdentificatorOwner.id
get() = identificator.id
@@ -0,0 +1,79 @@
package org.jetbrains.kotlin.tools.projectWizard
import org.jetbrains.kotlin.tools.projectWizard.core.EntitiesOwnerDescriptor
import org.jetbrains.kotlin.tools.projectWizard.core.Parser
import org.jetbrains.kotlin.tools.projectWizard.core.entity.*
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem
import kotlin.properties.ReadOnlyProperty
interface SettingsOwner : EntitiesOwnerDescriptor {
fun <V : Any, T : SettingType<V>> settingDelegate(
create: (path: String) -> SettingBuilder<V, T>
): ReadOnlyProperty<Any, Setting<V, T>>
// the functions to create different kinds of settings
// for now should be overridden with specific settings types :(
// TODO think a way yo emulate higher-kind types
fun <V : DisplayableSettingItem> dropDownSetting(
title: String,
neededAtPhase: GenerationPhase,
parser: Parser<V>,
init: DropDownSettingType.Builder<V>.() -> Unit = {}
): ReadOnlyProperty<Any, Setting<V, DropDownSettingType<V>>> = settingDelegate { path ->
DropDownSettingType.Builder(path, title, neededAtPhase, parser).apply(init)
}
fun stringSetting(
title: String,
neededAtPhase: GenerationPhase,
init: StringSettingType.Builder.() -> Unit = {}
) = settingDelegate { path ->
StringSettingType.Builder(path, title, neededAtPhase).apply(init)
}
fun booleanSetting(
title: String,
neededAtPhase: GenerationPhase,
init: BooleanSettingType.Builder.() -> Unit = {}
) = settingDelegate { path ->
BooleanSettingType.Builder(path, title, neededAtPhase).apply(init)
}
fun <V : Any> valueSetting(
title: String,
neededAtPhase: GenerationPhase,
parser: Parser<V>,
init: ValueSettingType.Builder<V>.() -> Unit = {}
) = settingDelegate { path ->
ValueSettingType.Builder(path, title, neededAtPhase, parser).apply(init)
}
fun versionSetting(
title: String,
neededAtPhase: GenerationPhase,
init: VersionSettingType.Builder.() -> Unit = {}
) = settingDelegate { path ->
VersionSettingType.Builder(path, title, neededAtPhase).apply(init)
}
fun <V : Any> listSetting(
title: String,
neededAtPhase: GenerationPhase,
parser: Parser<V>,
init: ListSettingType.Builder<V>.() -> Unit = {}
) = settingDelegate { path ->
ListSettingType.Builder(path, title, neededAtPhase, parser).apply(init)
}
fun pathSetting(
title: String,
neededAtPhase: GenerationPhase,
init: PathSettingType.Builder.() -> Unit = {}
) = settingDelegate { path ->
PathSettingType.Builder(path, title, neededAtPhase).apply(init)
}
}
@@ -0,0 +1,68 @@
package org.jetbrains.kotlin.tools.projectWizard.core
import org.jetbrains.kotlin.tools.projectWizard.core.entity.SettingReference
import org.jetbrains.kotlin.tools.projectWizard.core.entity.SettingType
sealed class CheckerRule {
abstract fun check(context: ValuesReadingContext): Boolean
}
data class RuleBySettingValue(
val settingReference: SettingReference<Any, SettingType<Any>>,
val expectedValue: Any
) : CheckerRule() {
override fun check(context: ValuesReadingContext): Boolean = with(context) {
settingReference.notRequiredSettingValue() == expectedValue
}
}
data class OrRule(
val left: CheckerRule,
val right: CheckerRule
) : CheckerRule() {
override fun check(context: ValuesReadingContext): Boolean = left.check(context) || right.check(context)
}
data class Checker(val rules: List<CheckerRule>) {
fun check(context: ValuesReadingContext) =
rules.all { rule -> rule.check(context) }
class Builder {
private val rules = mutableListOf<CheckerRule>()
infix fun <V : Any, T: SettingType<V>> SettingReference<V, T>.shouldBeEqual(value: V) =
RuleBySettingValue(this, value)
infix fun CheckerRule.or(other: CheckerRule) =
OrRule(this, other)
fun extend(parent: Checker) {
this.rules += parent.rules
}
fun rule(rule: CheckerRule) {
rules += rule
}
fun build() = Checker(rules)
}
companion object {
val ALWAYS_AVAILABLE = Checker(emptyList())
}
}
fun checker(init: Checker.Builder.() -> Unit) =
Checker.Builder().apply(init).build()
interface ContextOwner {
val context: Context
}
interface ActivityCheckerOwner {
val activityChecker: Checker
fun isActive(valuesReadingContext: ValuesReadingContext) = activityChecker.check(valuesReadingContext)
}
@@ -0,0 +1,72 @@
package org.jetbrains.kotlin.tools.projectWizard.core
import org.jetbrains.kotlin.tools.projectWizard.core.entity.*
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
import kotlin.reflect.KClass
import kotlin.reflect.KProperty1
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.full.memberProperties
class Context(private val pluginsCreator: PluginsCreator, val eventManager: EventManager) {
private fun initPlugin(plugin: Plugin) {
for (entityReference in plugin::class.memberProperties) {
val type = entityReference.returnType.classifier.safeAs<KClass<*>>() ?: continue
if (type.isSubclassOf(Entity::class)) {
when (val entity = entityReference.getter.call(plugin)) {
is Property<*> -> {
@Suppress("UNCHECKED_CAST")
propertyContext[entityReference as PropertyReference<Any>] = entity.defaultValue
}
}
}
}
}
val settingContext = SettingContext(eventManager::fireListeners)
val propertyContext = PropertyContext()
val taskContext = TaskContext()
val plugins = pluginsCreator(this).onEach(::initPlugin)
@Suppress("UNUSED_PARAMETER")
fun getUnspecifiedSettings(phases: Set<GenerationPhase>): List<AnySetting> {
val required = plugins
.flatMap { plugin ->
plugin.declaredSettings.mapNotNull { setting ->
if (setting.neededAtPhase !in phases) return@mapNotNull null
if (setting.isRequired) setting else null
}
}.toSet()
val provided = settingContext.allPluginSettings.map { it.path }.toSet()
return required.filterNot { it.path in provided }
}
private val pipelineLineTasks: List<PipelineTask>
get() = plugins
.flatMap { it.declaredTasks.filterIsInstance<PipelineTask>() }
private fun task(reference: PipelineTaskReference) =
taskContext.getEntity(reference) as? PipelineTask
?: error(reference.path)
private val dependencyList: Map<PipelineTask, List<PipelineTask>>
get() {
val dependeeMap = pipelineLineTasks.flatMap { task ->
task.after.map { after -> task to task(after) }
}
val dependencyMap = pipelineLineTasks.flatMap { task ->
task.before.map { before -> task(before) to task }
}
return (dependeeMap + dependencyMap)
.groupBy { it.first }
.mapValues {
it.value.map { it.second }
}
}
fun copy() = Context(pluginsCreator, eventManager)
fun sortTasks(): TaskResult<List<PipelineTask>> =
TaskSorter().sort(pipelineLineTasks, dependencyList)
}
@@ -0,0 +1,9 @@
package org.jetbrains.kotlin.tools.projectWizard.core
import java.nio.file.Paths
object Defaults {
val SRC_DIR = Paths.get("src")
val KOTLIN_DIR = Paths.get("kotlin")
val RESOURCES_DIR = Paths.get("resources")
}
@@ -0,0 +1,11 @@
package org.jetbrains.kotlin.tools.projectWizard.core
import kotlin.reflect.KClass
interface EntitiesOwnerDescriptor {
val id: String
}
interface EntitiesOwner<D : EntitiesOwnerDescriptor> {
val descriptor: D
}
@@ -0,0 +1,15 @@
package org.jetbrains.kotlin.tools.projectWizard.core
import org.jetbrains.kotlin.tools.projectWizard.core.entity.SettingReference
class EventManager {
private val listeners = mutableListOf<(SettingReference<*, *>?) -> Unit>()
fun addSettingUpdaterEventListener(listener: (SettingReference<*, *>?) -> Unit) {
listeners += listener
}
fun fireListeners(reference: SettingReference<*, *>?) {
listeners.forEach { it(reference) }
}
}
@@ -0,0 +1,195 @@
package org.jetbrains.kotlin.tools.projectWizard.core
import org.jetbrains.kotlin.tools.projectWizard.core.entity.SettingReference
import org.jetbrains.kotlin.tools.projectWizard.templates.Template
import java.nio.file.Paths
import kotlin.reflect.KClass
import kotlin.reflect.full.isSubclassOf
data class ParsingState(
val idToTemplate: Map<String, Template>,
val settingValues: Map<SettingReference<*, *>, Any>
) : ComputeContextState
fun ParsingState.withSettings(newSettings: List<Pair<SettingReference<*, *>, Any>>) =
copy(settingValues = settingValues + newSettings)
typealias ParsingContext = ComputeContext<ParsingState>
abstract class Parser<out T : Any> {
abstract fun ParsingContext.parse(value: Any?, path: String): TaskResult<T>
}
fun <T : Any> alwaysFailingParser() = object : Parser<T>() {
override fun ParsingContext.parse(value: Any?, path: String): TaskResult<T> =
Failure(object : Error() {
override val message: String
get() = "Should not be called"
})
}
fun <T : Any> Parser<T>.parse(context: ParsingContext, value: Any?, path: String) =
with(context) { parse(value, path) }
inline fun <reified E : Enum<E>> enumParser(): Parser<E> = object : Parser<E>() {
override fun ParsingContext.parse(value: Any?, path: String): TaskResult<E> = computeM {
val (enumName) = value.parseAs<String>(path)
safe { enumValueOf<E>(enumName) }.mapFailure {
listOf(
ParseError(
"For setting `$path` one of [${enumValues<E>().joinToString { it.name }}] was expected but `$enumName` was found"
)
)
}
}
}
inline fun <reified R : Any> listParser(
elementParser: Parser<R>
) = object : Parser<List<R>>() {
override fun ParsingContext.parse(value: Any?, path: String): TaskResult<List<R>> = computeM {
val (list) = value.parseAs<List<*>>(path)
list.mapComputeM { elementParser.parse(this, it, path) }.sequence()
}
}
fun <T : Any> mapParser(parseMap: suspend ParsingContext.(map: Map<String, *>, path: String) -> T) =
object : Parser<T>() {
override fun ParsingContext.parse(value: Any?, path: String): TaskResult<T> = when (value) {
is String -> value.parseAs<String, T>(this, path) {
parseMap(emptyMap<String, Any?>(), path).asSuccess()
}
else -> value.parseAs<Map<String, *>, T>(this, path) {
parseMap(it, path).asSuccess()
}
}
}
fun <T : Any> valueParser(parser: suspend ParsingContext.(value: Any?, path: String) -> T) =
object : Parser<T>() {
override fun ParsingContext.parse(value: Any?, path: String): TaskResult<T> = compute {
parser(value, path)
}
}
fun <T : Any> valueParserM(parser: suspend ParsingContext.(value: Any?, path: String) -> TaskResult<T>) =
object : Parser<T>() {
override fun ParsingContext.parse(value: Any?, path: String): TaskResult<T> = computeM {
parser(value, path)
}
}
inline fun <reified T : Any> valueParser() = valueParser { value, path ->
value.parseAs(path, T::class).get()
}
class DisjunctionParser<T : Any>(
private val keyToParser: Map<String, Parser<T>>
) : Parser<T>() {
constructor(vararg keyToParser: Pair<String, Parser<T>>) : this(keyToParser.toMap())
override fun ParsingContext.parse(value: Any?, path: String): TaskResult<T> = when (value) {
is String -> // consider string value as an empty map
mapOf(value to emptyMap<Any?, Any?>()).asSuccess()
else -> value.parseAs<Map<*, *>>(path)
}.flatMap { map ->
computeM {
val (singleItem) = map.entries.singleOrNull()
?.takeIf { it.key is String }
.toResult { ParseError("Setting `$path` should contain a single-key value") }
val (parser) = keyToParser[singleItem.key as String]
.toResult { ParseError("`$path` should be one of [${keyToParser.keys.joinToString()}]") }
parser.parse(this@parse, singleItem.value, path)
}
}
}
class CollectingParser<T : Any>(
private val keyToParser: Map<String, Parser<T>>
) : Parser<List<T>>() {
override fun ParsingContext.parse(value: Any?, path: String): TaskResult<List<T>> =
value.parseAs<Map<*, *>, List<T>>(this, path) { map ->
map.mapNotNull { (key, value) ->
val parser = keyToParser[key] ?: return@mapNotNull null
parser.parse(this, value, "$path.$key")
}.sequence()
}
}
fun Any?.classMismatchError(path: String, expected: KClass<*>): ParseError {
val classpath = this?.let { it::class.simpleName } ?: "null"
return ParseError("Expected ${expected.simpleName!!} for `$path` but $classpath was found")
}
inline fun <reified V : Any> Any?.parseAs(path: String) =
safeAs<V>().toResult { classMismatchError(path, V::class) }
inline fun <reified T : Any> Any?.parseAs(path: String, klass: KClass<T>): TaskResult<T> =
this?.takeIf { it::class.isSubclassOf(klass) }?.safeAs<T>()
.toResult { classMismatchError(path, klass) }
inline fun <reified V : Any> Map<*, *>.parseValue(path: String, name: String) =
get(name).parseAs<V>("$path.$name")
inline fun <reified V : Any> Map<*, *>.parseValue(path: String, name: String, defaultValue: (() -> V)) =
get(name)?.parseAs<V>("$path.$name") ?: defaultValue().asSuccess()
inline fun <reified V : Any, R : Any> Map<*, *>.parseValue(
context: ParsingContext,
path: String,
name: String,
crossinline parser: suspend ParsingContext.(V) -> TaskResult<R>
) = with(context) {
computeM {
val (result) = get(path).parseAs<V>("$path.$name")
parser(result)
}
}
inline fun <reified T : Any> Map<*, *>.parseValue(
path: String,
name: String,
klass: KClass<T>
) = get(path).parseAs("$path.$name", klass)
fun <R : Any> Map<*, *>.parseValue(
context: ParsingContext,
path: String,
name: String,
parser: Parser<R>,
defaultValue: (() -> R)? = null
) = with(context) {
computeM {
when (val value = get(name)) {
null -> defaultValue?.invoke()?.asSuccess() ?: parser.parse(this, value = null, path = "$path.$name")
else -> parser.parse(this, value, "$path.$name")
}
}
}
inline fun <reified V : Any, R : Any> Any?.parseAs(
context: ParsingContext,
path: String,
crossinline parser: suspend ParsingContext.(V) -> TaskResult<R>
) = with(context) {
computeM {
val (result) = this@parseAs.parseAs<V>(path)
parser(result)
}
}
val pathParser = valueParser { value, path ->
value.parseAs<String>(path).map { Paths.get(it) }.get()
}
infix fun <V: Any> Parser<V>.or(alternative: Parser<V>): Parser<V> = object : Parser<V>() {
override fun ParsingContext.parse(value: Any?, path: String): TaskResult<V> =
this@or.parse(this, value, path).recover { alternative.parse(this, value, path) }
}
@@ -0,0 +1,190 @@
package org.jetbrains.kotlin.tools.projectWizard.core
import org.jetbrains.kotlin.tools.projectWizard.SettingsOwner
import org.jetbrains.kotlin.tools.projectWizard.core.entity.*
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem
import org.jetbrains.kotlin.tools.projectWizard.settings.version.Version
import java.nio.file.Path
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty1
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.full.memberProperties
typealias PluginReference = KClass<out Plugin>
typealias PluginsCreator = (Context) -> List<Plugin>
abstract class Plugin(override val context: Context) : EntityBase(),
SettingsOwner,
ContextOwner,
EntitiesOwner<Plugin> {
override val descriptor get() = this
override val id: String get() = path
override fun <V : Any, T : SettingType<V>> settingDelegate(
create: (path: String) -> SettingBuilder<V, T>
): ReadOnlyProperty<Any, PluginSetting<V, T>> = object : ReadOnlyProperty<Any, PluginSetting<V, T>> {
override fun getValue(thisRef: Any, property: KProperty<*>): PluginSetting<V, T> {
@Suppress("UNCHECKED_CAST")
val reference = property as PluginSettingPropertyReference<V, T>
context.settingContext.getPluginSetting(reference)?.let { return it }
val setting = PluginSetting(create(reference.path).buildInternal())
context.settingContext.setPluginSetting(reference, setting)
return setting
}
}
val reference = this::class
override val path = reference.path
open val title: String = reference.name
val declaredSettings get() = entitiesOfType<PluginSetting<*, *>>().toSet()
val declaredTasks get() = entitiesOfType<Task>()
private inline fun <reified E : Entity> entitiesOfType() =
reference.memberProperties.filter { kProperty ->
kProperty.returnType.classifier.safeAs<KClass<*>>()?.isSubclassOf(E::class) == true
}.mapNotNull { kProperty ->
kProperty.safeAs<EntityReference>()?.getter?.call(this) as? E
}
fun pipelineTask(phase: GenerationPhase, init: PipelineTask.Builder.() -> Unit) =
PipelineTask.delegate(context.taskContext, phase, init)
fun <A, B : Any> task1(init: Task1.Builder<A, B>.() -> Unit) =
Task1.delegate(context.taskContext, init)
fun <T : Any> property(defaultValue: T, init: Property.Builder<T>.() -> Unit = {}) =
propertyDelegate(context.propertyContext, init, defaultValue)
fun <T : Any> listProperty(vararg defaultValues: T, init: Property.Builder<List<T>>.() -> Unit = {}) =
property(defaultValues.toList(), init)
// setting types
@Suppress("UNCHECKED_CAST")
final override fun <V : DisplayableSettingItem> dropDownSetting(
title: String,
neededAtPhase: GenerationPhase,
parser: Parser<V>,
init: DropDownSettingType.Builder<V>.() -> Unit
): ReadOnlyProperty<Any, PluginSetting<V, DropDownSettingType<V>>> =
super.dropDownSetting(
title,
neededAtPhase,
parser,
init
) as ReadOnlyProperty<Any, PluginSetting<V, DropDownSettingType<V>>>
@Suppress("UNCHECKED_CAST")
final override fun stringSetting(
title: String,
neededAtPhase: GenerationPhase,
init: StringSettingType.Builder.() -> Unit
): ReadOnlyProperty<Any, PluginSetting<String, StringSettingType>> =
super.stringSetting(
title,
neededAtPhase,
init
) as ReadOnlyProperty<Any, PluginSetting<String, StringSettingType>>
@Suppress("UNCHECKED_CAST")
final override fun booleanSetting(
title: String,
neededAtPhase: GenerationPhase,
init: BooleanSettingType.Builder.() -> Unit
): ReadOnlyProperty<Any, PluginSetting<Boolean, BooleanSettingType>> =
super.booleanSetting(
title,
neededAtPhase,
init
) as ReadOnlyProperty<Any, PluginSetting<Boolean, BooleanSettingType>>
@Suppress("UNCHECKED_CAST")
final override fun <V : Any> valueSetting(
title: String,
neededAtPhase: GenerationPhase,
parser: Parser<V>,
init: ValueSettingType.Builder<V>.() -> Unit
): ReadOnlyProperty<Any, PluginSetting<V, ValueSettingType<V>>> =
super.valueSetting(
title,
neededAtPhase,
parser,
init
) as ReadOnlyProperty<Any, PluginSetting<V, ValueSettingType<V>>>
@Suppress("UNCHECKED_CAST")
final override fun versionSetting(
title: String,
neededAtPhase: GenerationPhase,
init: VersionSettingType.Builder.() -> Unit
): ReadOnlyProperty<Any, PluginSetting<Version, VersionSettingType>> =
super.versionSetting(
title,
neededAtPhase,
init
) as ReadOnlyProperty<Any, PluginSetting<Version, VersionSettingType>>
@Suppress("UNCHECKED_CAST")
final override fun <V : Any> listSetting(
title: String,
neededAtPhase: GenerationPhase,
parser: Parser<V>,
init: ListSettingType.Builder<V>.() -> Unit
): ReadOnlyProperty<Any, PluginSetting<List<V>, ListSettingType<V>>> =
super.listSetting(
title,
neededAtPhase,
parser,
init
) as ReadOnlyProperty<Any, PluginSetting<List<V>, ListSettingType<V>>>
@Suppress("UNCHECKED_CAST")
final override fun pathSetting(
title: String,
neededAtPhase: GenerationPhase,
init: PathSettingType.Builder.() -> Unit
): ReadOnlyProperty<Any, PluginSetting<Path, PathSettingType>> =
super.pathSetting(title, neededAtPhase, init) as ReadOnlyProperty<Any, PluginSetting<Path, PathSettingType>>
inline fun <reified E> enumSetting(
title: String,
neededAtPhase: GenerationPhase,
crossinline init: DropDownSettingType.Builder<E>.() -> Unit = {}
) where E : Enum<E>, E : DisplayableSettingItem = dropDownSetting<E>(title, neededAtPhase, enumParser()) {
values = enumValues<E>().asList()
init()
}
}
val PluginReference.withParentPlugins
get() = generateSequence(this) { klass ->
klass.supertypes.firstOrNull { supertype ->
supertype.classifier?.safeAs<KClass<Plugin>>()
?.isSubclassOf(Plugin::class) == true
}?.classifier
?.safeAs<KClass<Plugin>>()
?.takeIf { superClass ->
superClass.simpleName != null
}
}
val PluginReference.name
get() = simpleName
?.removeSuffix("Plugin")
?.decapitalize()
.orEmpty()
val PluginReference.path
get() = withParentPlugins.mapNotNull { klass ->
klass.name.takeIf { it.isNotEmpty() }
}.toList()
.reversed()
.joinToString(".")
@@ -0,0 +1,145 @@
package org.jetbrains.kotlin.tools.projectWizard.core
import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
import kotlin.coroutines.intrinsics.createCoroutineUnintercepted
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
import kotlin.coroutines.resume
sealed class TaskResult<out T : Any>
data class Success<T : Any>(val value: T) : TaskResult<T>()
data class Failure(val errors: List<Error>) : TaskResult<Nothing>() {
constructor(vararg errors: Error) : this(errors.toList())
}
val TaskResult<Any>.isSuccess
get() = this is Success<*>
fun <T : Any> success(value: T): TaskResult<T> =
Success(value)
fun <T : Any> failure(vararg errors: Error): TaskResult<T> =
Failure(*errors)
val <T : Any> TaskResult<T>.asNullable: T?
get() = safeAs<Success<T>>()?.value
inline fun <T : Any> TaskResult<T>.onSuccess(action: (T) -> Unit) = also {
if (this is Success<T>) {
action(value)
}
}
inline fun <T : Any> TaskResult<T>.onFailure(handler: (List<Error>) -> Unit) = apply {
if (this is Failure) {
handler(errors)
}
}
@Suppress("UNCHECKED_CAST")
inline fun <A : Any, B : Any, R : Any> TaskResult<A>.mappend(
other: TaskResult<B>,
op: (A, B) -> R
): TaskResult<R> = mappendM(other) { a, b -> success(op(a, b)) }
@Suppress("UNCHECKED_CAST")
inline fun <A : Any, B : Any, R : Any> TaskResult<A>.mappendM(
other: TaskResult<B>,
op: (A, B) -> TaskResult<R>
): TaskResult<R> = when {
this is Success<*> && other is Success<*> -> op(value as A, other.value as B)
this is Success<*> && other is Failure -> other
this is Failure && other is Success<*> -> this
this is Failure && other is Failure -> Failure(errors + other.errors)
else -> error("Can not happen")
}
infix fun <T : Any> TaskResult<*>.andThen(other: TaskResult<T>): TaskResult<T> =
mappendM(other) { _, _ -> other }
operator fun <T : Any> TaskResult<List<T>>.plus(other: TaskResult<List<T>>): TaskResult<Unit> =
mappend(other) { _, _ -> Unit }
fun <T : Any> T?.toResult(failure: () -> Error): TaskResult<T> =
this?.let { Success(it) } ?: Failure(
failure()
)
fun <T : Any> T.asSuccess(): Success<T> =
Success(this)
inline fun <T : Any> TaskResult<T>.raise(returnExpression: (Failure) -> (Unit)): T =
when (this) {
is Failure -> {
returnExpression(this)
error("fail should return out of the function")
}
is Success -> value
}
fun <T : Any> Iterable<TaskResult<T>>.sequence(): TaskResult<List<T>> =
fold(success(emptyList())) { acc, result ->
acc.mappend(result) { a, r -> a + r }
}
fun <T : Any> Iterable<TaskResult<T>>.sequenceIgnore(): TaskResult<Unit> =
fold<TaskResult<T>, TaskResult<Unit>>(UNIT_SUCCESS) { acc, result ->
acc.mappend(result) { _, _ -> Unit }
}
fun <T : Any> Iterable<T>.mapSequenceIgnore(f: (T) -> TaskResult<*>): TaskResult<Unit> =
fold<T, TaskResult<Unit>>(UNIT_SUCCESS) { acc, result ->
acc.mappend(f(result)) { _, _ -> Unit }
}
fun <T : Any, R : Any> Iterable<T>.mapSequence(f: (T) -> TaskResult<R>): TaskResult<List<R>> =
map(f).sequence()
fun <T : Any> Sequence<TaskResult<T>>.sequenceFailFirst(): TaskResult<List<T>> =
fold(success(emptyList())) { acc, result ->
if (acc.isSuccess) acc.mappend(result) { a, r -> a + r }
else acc
}
fun <T : Any, R : Any> TaskResult<T>.map(f: (T) -> R): TaskResult<R> = when (this) {
is Failure -> this
is Success<T> -> Success(f(value))
}
fun <T : Any> TaskResult<T>.mapFailure(f: (List<Error>) -> List<Error>): TaskResult<T> = when (this) {
is Failure -> Failure(f(errors))
is Success<T> -> this
}
fun <T : Any> TaskResult<T>.recover(f: (Failure) -> TaskResult<T>): TaskResult<T> = when (this) {
is Failure -> f(this)
is Success<T> -> this
}
fun <T : Any, R : Any> TaskResult<T>.flatMap(f: (T) -> TaskResult<R>): TaskResult<R> = when (this) {
is Failure -> this
is Success<T> -> f(value)
}
fun <T : Any> TaskResult<T>.withAssert(assertion: (T) -> Error?): TaskResult<T> = when (this) {
is Failure -> this
is Success<T> -> assertion(value)?.let { Failure(it) } ?: this
}
fun <T : Any> TaskResult<T>.getOrElse(default: () -> T): T = when (this) {
is Success<T> -> value
is Failure -> default()
}
fun <T : Any> TaskResult<T>.ignore(): TaskResult<Unit> = when (this) {
is Failure -> this
is Success<T> -> UNIT_SUCCESS
}
val UNIT_SUCCESS = Success(Unit)
@@ -0,0 +1,33 @@
package org.jetbrains.kotlin.tools.projectWizard.core
import org.jetbrains.kotlin.tools.projectWizard.core.entity.PropertyReference
import org.jetbrains.kotlin.tools.projectWizard.core.entity.Task1
import org.jetbrains.kotlin.tools.projectWizard.core.entity.Task1Reference
import org.jetbrains.kotlin.tools.projectWizard.core.service.Service
import kotlin.reflect.KClass
import kotlin.reflect.full.isSubclassOf
class TaskRunningContext(context: Context, services: List<Service>) : ValuesReadingContext(context, services) {
fun <A, B : Any> Task1Reference<A, B>.execute(value: A): TaskResult<B> {
@Suppress("UNCHECKED_CAST")
val task = context.taskContext.getEntity(this) as Task1<A, B>
return task.action(this@TaskRunningContext, value)
}
inline fun <reified T : Any> PropertyReference<T>.update(
crossinline updater: suspend ComputeContext<*>.(T) -> TaskResult<T>
): TaskResult<Unit> = compute {
val (newValue) = updater(propertyValue)
context.propertyContext[this@update] = newValue
Unit
}
fun <T : Any> PropertyReference<List<T>>.addValues(
vararg values: T
): TaskResult<Unit> = update { oldValues -> success(oldValues + values) }
fun <T : Any> PropertyReference<List<T>>.addValues(
values: List<T>
): TaskResult<Unit> = update { oldValues -> success(oldValues + values) }
}
@@ -0,0 +1,40 @@
package org.jetbrains.kotlin.tools.projectWizard.core
import org.jetbrains.kotlin.tools.projectWizard.core.entity.PipelineTask
class TaskSorter {
private enum class State {
VISITED, NOT_VISITED, ADDED
}
fun sort(
tasks: List<PipelineTask>,
taskToDependencies: Map<PipelineTask, List<PipelineTask>>
): TaskResult<List<PipelineTask>> {
val states = Array(tasks.size) { State.NOT_VISITED }
val result = mutableListOf<PipelineTask>()
val taskToIndex = tasks.indices.associateBy { tasks[it] }
fun dfs(index: Int): TaskResult<Unit> {
when (states[index]) {
State.ADDED -> return UNIT_SUCCESS
State.VISITED -> return Failure(
CircularTaskDependencyError(tasks[index].path)
)
State.NOT_VISITED -> {
states[index] = State.VISITED
}
}
taskToDependencies[tasks[index]].orEmpty().mapNotNull { dependent ->
dfs(taskToIndex[dependent] ?: return@mapNotNull null)
}.sequence().raise { return it }
result += tasks[index]
states[index] = State.ADDED
return UNIT_SUCCESS
}
return tasks.indices.map { index ->
dfs(index)
}.sequence().map { result }
}
}
@@ -0,0 +1,38 @@
package org.jetbrains.kotlin.tools.projectWizard.core
import org.jetbrains.kotlin.tools.projectWizard.core.entity.*
import org.jetbrains.kotlin.tools.projectWizard.core.service.Service
import kotlin.reflect.KClass
import kotlin.reflect.KProperty1
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.jvm.jvmName
@Suppress("UNCHECKED_CAST")
open class ValuesReadingContext(val context: Context, private val services: List<Service>) {
inline fun <reified S : Service> service() = serviceByClass(S::class)
@Suppress("UNCHECKED_CAST")
fun <S : Service> serviceByClass(klass: KClass<S>) =
services.firstOrNull { it::class.isSubclassOf(klass) } as? S ?: error("No service ${klass.jvmName}")
inline val <reified T : Any> PropertyReference<T>.propertyValue: T
get() = context.propertyContext[this] as T
inline val <reified V : Any, T : SettingType<V>> SettingReference<V, T>.settingValue: V
get() = context.settingContext[this] ?: error("No value is present for setting `$this`")
inline val <reified V : Any> KProperty1<out Plugin, PluginSetting<V, SettingType<V>>>.settingValue: V
get() = reference.settingValue
inline fun <reified V : Any> KProperty1<out Plugin, PluginSetting<V, SettingType<V>>>.settingValue(): V =
this.reference.settingValue
inline fun <reified V : Any, T : SettingType<V>> SettingReference<V, T>.settingValue(): V =
context.settingContext[this] ?: error("No value is present for setting `$this`")
val <V : Any, T : SettingType<V>> SettingReference<V, T>.notRequiredSettingValue: V?
get() = context.settingContext[this]
fun <V : Any, T : SettingType<V>> SettingReference<V, T>.notRequiredSettingValue(): V? =
context.settingContext[this]
}
@@ -0,0 +1,122 @@
package org.jetbrains.kotlin.tools.projectWizard.core
import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
import kotlin.coroutines.intrinsics.createCoroutineUnintercepted
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
import kotlin.coroutines.resume
interface ComputeContextState
object NoState : ComputeContextState
data class StateContainer<S : ComputeContextState>(var state: S)
open class ComputeContext<S : ComputeContextState> protected constructor(private val stateContainer: StateContainer<S>) {
private var result: TaskResult<Any>? = null
private var exception: Throwable? = null
val state
get() = stateContainer.state
suspend fun <T : Any> TaskResult<T>.get(): T = suspendCoroutineUninterceptedOrReturn { continuation ->
result = this
if (this is Success<T>) {
continuation.resume(value)
}
COROUTINE_SUSPENDED
}
suspend operator fun <T : Any> TaskResult<T>.component1(): T = get()
suspend fun <T : Any> TaskResult<T>.ensure() {
get()
}
suspend fun fail(error: Error): Nothing = suspendCoroutineUninterceptedOrReturn {
result = Failure(error)
COROUTINE_SUSPENDED
}
suspend fun fail(errors: List<Error>): Nothing = suspendCoroutineUninterceptedOrReturn {
result = Failure(errors)
COROUTINE_SUSPENDED
}
suspend inline fun <T : Any> TaskResult<T>?.nullableValue(): T? =
this?.onFailure { fail(it) }?.asNullable
fun <T : Any> compute(
block: suspend ComputeContext<S>.() -> T
): TaskResult<T> = computeM { success(block()) }
fun <T : Any> computeM(
block: suspend ComputeContext<S>.() -> TaskResult<T>
): TaskResult<T> {
val computeContext = ComputeContext(stateContainer)
val continuation = object : Continuation<TaskResult<T>> {
override val context: CoroutineContext
get() = EmptyCoroutineContext
override fun resumeWith(result: Result<TaskResult<T>>) {
result.onSuccess { computeContext.result = it }
result.onFailure { computeContext.exception = it }
}
}
block.createCoroutineUnintercepted(computeContext, continuation).resume(Unit)
if (computeContext.exception != null) {
throw computeContext.exception!!
}
@Suppress("UNCHECKED_CAST") return computeContext.result as TaskResult<T>
}
fun <A, B : Any> Iterable<A>.mapCompute(
f: suspend ComputeContext<S>.(A) -> B
): List<TaskResult<B>> = map { compute { f(it) } }
fun <A, B : Any> Iterable<A>.mapComputeM(
f: suspend ComputeContext<S>.(A) -> TaskResult<B>
): List<TaskResult<B>> = map { computeM { f(it) } }
fun updateState(updater: (S) -> S) {
stateContainer.state = updater(stateContainer.state)
}
companion object {
val PURE = ComputeContext(StateContainer(NoState))
fun <S : ComputeContextState, R : Any> runInComputeContextWithState(
state: S,
action: ComputeContext<S>.() -> TaskResult<R>
): TaskResult<Pair<R, S>> = with(ComputeContext(StateContainer(state))) {
val result = action()
result.map { it to this.state }
}
}
}
fun <T : Any> compute(
block: suspend ComputeContext<NoState>.() -> T
): TaskResult<T> = ComputeContext.PURE.compute(block)
fun <T : Any> computeM(
block: suspend ComputeContext<NoState>.() -> TaskResult<T>
): TaskResult<T> = ComputeContext.PURE.computeM(block)
fun <A, B : Any> Iterable<A>.mapCompute(
f: suspend ComputeContext<NoState>.(A) -> B
): List<TaskResult<B>> = with(ComputeContext.PURE) {
mapCompute(f)
}
fun <A, B : Any> Iterable<A>.mapComputeM(
f: suspend ComputeContext<NoState>.(A) -> TaskResult<B>
): List<TaskResult<B>> = with(ComputeContext.PURE) {
mapComputeM(f)
}
@@ -0,0 +1,35 @@
package org.jetbrains.kotlin.tools.projectWizard.core.entity
import org.jetbrains.kotlin.tools.projectWizard.SettingsOwner
import org.jetbrains.kotlin.tools.projectWizard.core.Plugin
import org.jetbrains.kotlin.tools.projectWizard.core.PluginReference
import org.jetbrains.kotlin.tools.projectWizard.core.path
import org.jetbrains.kotlin.tools.projectWizard.core.safeAs
import kotlin.reflect.KProperty1
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.jvm.javaGetter
interface Entity {
val path: String
}
abstract class EntityBase: Entity {
final override fun equals(other: Any?): Boolean = other.safeAs<Entity>()?.path == path
final override fun hashCode(): Int = path.hashCode()
final override fun toString(): String = path
}
abstract class EntityWithValue<out T : Any> : EntityBase()
typealias EntityReference = KProperty1<out SettingsOwner, Entity>
val EntityReference.path
get() = "${plugin.path}.$name"
@Suppress("UNCHECKED_CAST")
val <EP : EntityReference> EP.original
get() = plugin.declaredMemberProperties.first { it.name == name } as EP
@Suppress("UNCHECKED_CAST")
val EntityReference.plugin: PluginReference
get() = javaGetter!!.declaringClass.kotlin as PluginReference
@@ -0,0 +1,47 @@
package org.jetbrains.kotlin.tools.projectWizard.core.entity
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
abstract class EntityContext<E : Entity, ER : EntityReference> {
private val entities = mutableMapOf<ER, E>()
open fun addEntity(entityReference: ER, createEntityByName: (String) -> E): E =
createEntityByName(entityReference.original.path).also {
entities[entityReference.original] = it
}
fun getEntity(entityReference: ER): E? = entities[entityReference.original]
fun getAll(): List<E> = entities.values.toList()
}
abstract class ValuedEntityContext<E : EntityWithValue<Any>, ER : EntityReference> : EntityContext<E, ER>() {
private val values = mutableMapOf<String, Any>()
operator fun get(entityReference: ER) =
values[entityReference.original.path]
operator fun get(entityPath: String) =
values[entityPath]
open operator fun set(entityReference: ER, value: Any) {
values[entityReference.original.path] = value
}
open operator fun set(entityPath: String, value: Any) {
values[entityPath] = value
}
val allValues: Map<String, Any> = values
}
@Suppress("UNCHECKED_CAST")
fun <E : Entity, A : E, ER : EntityReference> entityDelegate(
entityContext: EntityContext<E, ER>,
createEntityByPath: (String) -> A
) = object : ReadOnlyProperty<Any, A> {
override fun getValue(thisRef: Any, property: KProperty<*>): A =
entityContext.getEntity(property as ER) as? A
?: entityContext.addEntity(property, createEntityByPath) as A
}
@@ -0,0 +1,31 @@
package org.jetbrains.kotlin.tools.projectWizard.core.entity
import org.jetbrains.kotlin.tools.projectWizard.core.Plugin
import org.jetbrains.kotlin.tools.projectWizard.core.PluginReference
import kotlin.reflect.KProperty1
typealias PropertyReference<T> = KProperty1<out Plugin, Property<T>>
class PropertyContext : ValuedEntityContext<Property<Any>, PropertyReference<Any>>()
data class Property<out T : Any>(
override val path: String,
val defaultValue: T
) : EntityWithValue<T>() {
class Builder<T : Any>(
private val name: String,
private val defaultValue: T
) {
fun build(): Property<T> = Property(name, defaultValue)
}
}
fun <T : Any> propertyDelegate(
context: PropertyContext,
init: Property.Builder<T>.() -> Unit,
defaultValue: T
) = entityDelegate(context) { name ->
Property.Builder(name, defaultValue).apply(init).build()
}
@@ -0,0 +1,385 @@
package org.jetbrains.kotlin.tools.projectWizard.core.entity
import org.jetbrains.kotlin.tools.projectWizard.Identificator
import org.jetbrains.kotlin.tools.projectWizard.core.*
import org.jetbrains.kotlin.tools.projectWizard.id
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.ModuleConfigurator
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
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.Sourceset
import org.jetbrains.kotlin.tools.projectWizard.settings.version.Version
import org.jetbrains.kotlin.tools.projectWizard.templates.Template
import java.nio.file.Files
import java.nio.file.Path
import kotlin.reflect.KClass
import kotlin.reflect.KProperty1
sealed class SettingReference<out V : Any, out T : SettingType<V>> {
abstract val path: String
abstract val type: KClass<out T>
abstract fun Context.getSetting(): Setting<V, T>
final override fun toString() = path
final override fun equals(other: Any?) = other.safeAs<SettingReference<*, *>>()?.path == path
final override fun hashCode() = path.hashCode()
}
data class PluginSettingReference<V : Any, T : SettingType<V>>(
override val path: String,
override val type: KClass<T>
) : SettingReference<V, T>() {
constructor(kProperty: KProperty1<out Plugin, PluginSetting<V, T>>, type: KClass<T>) :
this(kProperty.path, type)
@Suppress("UNCHECKED_CAST")
constructor(setting: PluginSetting<V, T>) :
this(setting.path, setting.type::class as KClass<T>)
override fun Context.getSetting(): Setting<V, T> =
settingContext.getPluginSetting(this@PluginSettingReference)
}
data class ModuleConfiguratorSettingReference<V : Any, T : SettingType<V>>(
val descriptor: ModuleConfigurator,
val moduleId: Identificator,
val setting: ModuleConfiguratorSetting<V, T>
) : SettingReference<V, T>() {
constructor(descriptor: ModuleConfigurator, module: Module, setting: ModuleConfiguratorSetting<V, T>) :
this(descriptor, module.identificator, setting)
override val path: String
get() = "${descriptor.id}/$moduleId/${setting.path}"
override val type: KClass<out T>
get() = setting.type::class
override fun Context.getSetting(): Setting<V, T> = setting
}
sealed class TemplateSettingReference<V : Any, T : SettingType<V>> : SettingReference<V, T>() {
abstract val descriptor: Template
abstract val setting: TemplateSetting<V, T>
abstract val sourcesetId: Identificator
override val path: String
get() = "${descriptor.id}/$sourcesetId/${setting.path}"
override val type: KClass<out T>
get() = setting.type::class
override fun Context.getSetting(): Setting<V, T> = setting
abstract val sourceset: Sourceset?
}
data class SourcesetBasedTemplateSettingReference<V : Any, T : SettingType<V>>(
override val descriptor: Template,
override val sourceset: Sourceset,
override val setting: TemplateSetting<V, T>
) : TemplateSettingReference<V, T>() {
override val sourcesetId: Identificator
get() = sourceset.identificator
}
data class IdBasedTemplateSettingReference<V : Any, T : SettingType<V>>(
override val descriptor: Template,
override val sourcesetId: Identificator,
override val setting: TemplateSetting<V, T>
) : TemplateSettingReference<V, T>() {
override val sourceset: Sourceset? = null
}
inline val <V : Any, reified T : SettingType<V>> PluginSettingPropertyReference<V, T>.reference: PluginSettingReference<V, T>
get() = PluginSettingReference(this, T::class)
typealias PluginSettingPropertyReference<V, T> = KProperty1<out Plugin, PluginSetting<V, T>>
typealias SettingPropertyReference<V, T> = KProperty1<out Plugin, Setting<V, T>>
class SettingContext(val onUpdated: (SettingReference<*, *>) -> Unit) {
private val values = mutableMapOf<String, Any>()
private val pluginSettings = mutableMapOf<String, PluginSetting<*, *>>()
@Suppress("UNCHECKED_CAST")
operator fun <V : Any, T : SettingType<V>> get(
reference: SettingReference<V, T>
): V? = values[reference.path] as? V
operator fun <V : Any, T : SettingType<V>> set(
reference: SettingReference<V, T>,
newValue: V
) {
values[reference.path] = newValue
onUpdated(reference)
}
fun initPluginSettings(settings: List<PluginSetting<*, *>>) {
for (setting in settings) {
setting.defaultValue?.let { values[setting.path] = it }
}
}
val allPluginSettings: Collection<PluginSetting<*, *>>
get() = pluginSettings.values
@Suppress("UNCHECKED_CAST")
fun <V : Any, T : SettingType<V>> getPluginSetting(pluginSettingReference: PluginSettingReference<V, T>) =
pluginSettings[pluginSettingReference.path] as PluginSetting<V, T>
@Suppress("UNCHECKED_CAST")
fun <V : Any, T : SettingType<V>> getPluginSetting(pluginSettingReference: PluginSettingPropertyReference<V, T>) =
pluginSettings[pluginSettingReference.path] as? PluginSetting<V, T>
@Suppress("UNCHECKED_CAST")
fun <V : Any, T : SettingType<V>> setPluginSetting(
pluginSettingReference: PluginSettingPropertyReference<V, T>,
setting: PluginSetting<V, T>
) {
pluginSettings[pluginSettingReference.path] = setting
}
@Suppress("UNCHECKED_CAST")
fun <V : Any, T : SettingType<V>> pluginSettingValue(setting: PluginSetting<V, T>): V? =
values[setting.path] as? V
}
typealias AnySetting = Setting<*, *>
interface Setting<out V : Any, out T : SettingType<V>> : Entity, ActivityCheckerOwner, Validatable<V> {
val title: String
val defaultValue: V?
val isRequired: Boolean
var neededAtPhase: GenerationPhase
val type: T
}
data class InternalSetting<out V : Any, out T : SettingType<V>>(
override val path: String,
override val title: String,
override val defaultValue: V?,
override val activityChecker: Checker,
override val isRequired: Boolean,
override var neededAtPhase: GenerationPhase,
override val validator: SettingValidator<@UnsafeVariance V>,
override val type: T
) : Setting<V, T>, EntityWithValue<V>()
sealed class SettingImpl<out V : Any, out T : SettingType<V>> : Setting<V, T>
class PluginSetting<out V : Any, out T : SettingType<V>>(
internal: InternalSetting<V, T>
) : SettingImpl<V, T>(), Setting<V, T> by internal
class ModuleConfiguratorSetting<out V : Any, out T : SettingType<V>>(
internal: InternalSetting<V, T>
) : SettingImpl<V, T>(), Setting<V, T> by internal
class TemplateSetting<out V : Any, out T : SettingType<V>>(
internal: InternalSetting<V, T>
) : SettingImpl<V, T>(), Setting<V, T> by internal
abstract class SettingBuilder<V : Any, T : SettingType<V>>(
private val path: String,
private val title: String,
private val neededAtPhase: GenerationPhase
) {
var checker: Checker = Checker.ALWAYS_AVAILABLE
var defaultValue: V? = null
protected var validator = SettingValidator<V> { ValidationResult.OK }
fun validate(validator: SettingValidator<V>) {
this.validator = this.validator and validator
}
fun validate(validator: ValuesReadingContext.(V) -> ValidationResult) {
this.validator = this.validator and settingValidator(validator)
}
abstract val type: T
fun buildInternal() = InternalSetting(
path = path,
title = title,
defaultValue = defaultValue,
activityChecker = checker,
isRequired = defaultValue == null,
neededAtPhase = neededAtPhase,
validator = validator,
type = type
)
}
sealed class SettingType<out V : Any> {
abstract fun parse(context: ParsingContext, value: Any, name: String): TaskResult<V>
}
object StringSettingType : SettingType<String>() {
override fun parse(context: ParsingContext, value: Any, name: String) =
value.parseAs<String>(name)
class Builder(
path: String,
private val title: String,
neededAtPhase: GenerationPhase
) : SettingBuilder<String, StringSettingType>(path, title, neededAtPhase) {
fun shouldNotBeBlank() = validate { value: String ->
if (value.isBlank()) ValidationResult.ValidationError("`${title.capitalize()}` should not be blank ")
else ValidationResult.OK
}
override val type = StringSettingType
}
}
object BooleanSettingType : SettingType<Boolean>() {
override fun parse(context: ParsingContext, value: Any, name: String) =
value.parseAs<Boolean>(name)
class Builder(
path: String,
title: String,
neededAtPhase: GenerationPhase
) : SettingBuilder<Boolean, BooleanSettingType>(path, title, neededAtPhase) {
override val type = BooleanSettingType
}
}
class DropDownSettingType<V : DisplayableSettingItem>(
val values: List<V>,
val filter: DropDownSettingTypeFilter<V>,
val parser: Parser<V>
) : SettingType<V>() {
override fun parse(context: ParsingContext, value: Any, name: String): TaskResult<V> = with(context) {
computeM {
parser.parse(this, value, name)
}
}
class Builder<V : DisplayableSettingItem>(
path: String,
title: String,
neededAtPhase: GenerationPhase,
private val parser: Parser<V>
) : SettingBuilder<V, DropDownSettingType<V>>(path, title, neededAtPhase) {
var values = emptyList<V>()
var filter: DropDownSettingTypeFilter<V> = { _, _ -> true }
override val type
get() = DropDownSettingType(values, filter, parser)
}
}
typealias DropDownSettingTypeFilter<V> = (SettingReference<V, DropDownSettingType<V>>, V) -> Boolean
class ValueSettingType<V : Any>(
private val parser: Parser<V>
) : SettingType<V>() {
override fun parse(context: ParsingContext, value: Any, name: String): TaskResult<V> = with(context) {
computeM {
parser.parse(this, value, name)
}
}
class Builder<V : Any>(
private val path: String,
title: String,
neededAtPhase: GenerationPhase,
private val parser: Parser<V>
) : SettingBuilder<V, ValueSettingType<V>>(path, title, neededAtPhase) {
init {
validate { value ->
if (value is Validatable<*>) value.validator.validate(this, value)
else ValidationResult.OK
}
}
override val type
get() = ValueSettingType(parser)
}
}
object VersionSettingType : SettingType<Version>() {
override fun parse(context: ParsingContext, value: Any, name: String): TaskResult<Version> = with(context) {
computeM {
Version.parser.parse(this, value, name)
}
}
class Builder(
path: String,
title: String,
neededAtPhase: GenerationPhase
) : SettingBuilder<Version, VersionSettingType>(path, title, neededAtPhase) {
override val type
get() = VersionSettingType
}
}
class ListSettingType<V : Any>(private val parser: Parser<V>) : SettingType<List<V>>() {
override fun parse(context: ParsingContext, value: Any, name: String): TaskResult<List<V>> = with(context) {
computeM {
val (list) = value.parseAs<List<*>>(name)
list.mapComputeM { parser.parse(this, it, name) }.sequence()
}
}
class Builder<V : Any>(
path: String,
title: String,
neededAtPhase: GenerationPhase,
parser: Parser<V>
) : SettingBuilder<List<V>, ListSettingType<V>>(path, title, neededAtPhase) {
init {
validate { values ->
values.fold(ValidationResult.OK as ValidationResult) { result, value ->
result and when (value) {
is Validatable<*> -> value.validator.validate(this, value)
else -> ValidationResult.OK
}
}
}
}
override val type = ListSettingType(parser)
}
}
object PathSettingType : SettingType<Path>() {
override fun parse(context: ParsingContext, value: Any, name: String): TaskResult<Path> = with(context) {
computeM {
pathParser.parse(this, value, name)
}
}
class Builder(
path: String,
private val title: String,
neededAtPhase: GenerationPhase
) : SettingBuilder<Path, PathSettingType>(path, title, neededAtPhase) {
init {
validate { pathValue ->
if (pathValue.toString().isBlank())
ValidationResult.ValidationError("${title.capitalize()} should not be blank")
else ValidationResult.OK
}
}
fun shouldExists() = validate { pathValue ->
if (!Files.exists(pathValue))
ValidationResult.ValidationError("File for ${title.capitalize()} should exists")
else ValidationResult.OK
}
override val type = PathSettingType
}
}
@@ -0,0 +1,86 @@
package org.jetbrains.kotlin.tools.projectWizard.core.entity
import org.jetbrains.kotlin.tools.projectWizard.core.Failure
import org.jetbrains.kotlin.tools.projectWizard.core.UNIT_SUCCESS
import org.jetbrains.kotlin.tools.projectWizard.core.ValidationError
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
inline class SettingValidator<V>(val validate: ValuesReadingContext.(V) -> ValidationResult) {
infix fun and(other: SettingValidator<V>) = SettingValidator<V> { value ->
validate(value) and other.validate(this, value)
}
operator fun ValuesReadingContext.invoke(value: V) = validate(value)
}
fun <V> settingValidator(validator: ValuesReadingContext.(V) -> ValidationResult) =
SettingValidator(validator)
fun <V> inValidatorContext(validator: ValuesReadingContext.(V) -> SettingValidator<V>) =
SettingValidator<V> { value ->
validator(value).validate(this, value)
}
typealias StringValidator = SettingValidator<String>
object StringValidators {
fun shouldNotBeBlank(name: String) = settingValidator { value: String ->
if (value.isBlank()) ValidationResult.ValidationError("${name.capitalize()} should not be blank ")
else ValidationResult.OK
}
fun shouldBeValidIdentifier(name: String) = settingValidator { value: String ->
if (value.any { !it.isLetterOrDigit() && it != '_' })
ValidationResult.ValidationError(
"${name.capitalize()} should consist only of letters, digits, and underscores"
)
else ValidationResult.OK
}
}
fun List<ValidationResult>.fold() = fold(ValidationResult.OK, ValidationResult::and)
sealed class ValidationResult {
abstract val isOk: Boolean
object OK : ValidationResult() {
override val isOk = true
}
class ValidationError(val messages: List<String>) : ValidationResult() {
constructor(message: String) : this(listOf(message))
override val isOk = false
}
infix fun and(other: ValidationResult) = when {
this is OK -> other
this is ValidationError && other is ValidationError -> ValidationError(messages + other.messages)
else -> this
}
companion object {
fun create(condition: Boolean, message: String) =
if (condition) OK else ValidationError(message)
}
}
fun ValidationResult.toResult() = when (this) {
ValidationResult.OK -> UNIT_SUCCESS
is ValidationResult.ValidationError -> Failure(messages.map { ValidationError(it) })
}
interface Validatable<out V> {
val validator: SettingValidator<@UnsafeVariance V>
}
fun <V, Q : Validatable<Q>> ValuesReadingContext.validateList(list: List<Q>) = settingValidator<V> {
list.fold(ValidationResult.OK as ValidationResult) { result, value ->
result and value.validator.validate(this, value)
}
}
@@ -0,0 +1,84 @@
package org.jetbrains.kotlin.tools.projectWizard.core.entity
import org.jetbrains.kotlin.tools.projectWizard.core.*
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
import kotlin.reflect.KProperty1
typealias TaskReference = KProperty1<out Plugin, Task>
typealias Task1Reference<A, B> = KProperty1<out Plugin, Task1<A, B>>
typealias PipelineTaskReference = KProperty1<out Plugin, PipelineTask>
class TaskContext : EntityContext<Task, TaskReference>()
sealed class Task : EntityBase()
data class Task1<A, B : Any>(
override val path: String,
val action: TaskRunningContext.(A) -> TaskResult<B>
) : Task() {
class Builder<A, B : Any>(private val name: String) {
private var action: TaskRunningContext.(A) -> TaskResult<B> = { Failure() }
fun withAction(action: TaskRunningContext.(A) -> TaskResult<B>) {
this.action = action
}
fun build(): Task1<A, B> = Task1(name, action)
}
companion object {
fun <A, B : Any> delegate(
context: EntityContext<Task, TaskReference>,
init: Builder<A, B>.() -> Unit
) = entityDelegate(context) { name ->
Builder<A, B>(name).apply(init).build()
}
}
}
data class PipelineTask(
override val path: String,
val action: TaskRunningContext.() -> TaskResult<Unit>,
val before: List<PipelineTaskReference>,
val after: List<PipelineTaskReference>,
val phase: GenerationPhase,
val checker: Checker,
val title: String?
) : Task() {
class Builder(
private val name: String,
private val phase: GenerationPhase
) {
private var action: TaskRunningContext.() -> TaskResult<Unit> = { UNIT_SUCCESS }
private val before = mutableListOf<PipelineTaskReference>()
private val after = mutableListOf<PipelineTaskReference>()
var activityChecker: Checker = Checker.ALWAYS_AVAILABLE
var title: String? = null
fun withAction(action: TaskRunningContext.() -> TaskResult<Unit>) {
this.action = action
}
fun runBefore(vararg before: PipelineTaskReference) {
this.before.addAll(before)
}
fun runAfter(vararg after: PipelineTaskReference) {
this.after.addAll(after)
}
fun build(): PipelineTask = PipelineTask(name, action, before, after, phase, activityChecker, title)
}
companion object {
fun delegate(
context: EntityContext<Task, TaskReference>,
phase: GenerationPhase,
init: Builder.() -> Unit
) = entityDelegate(context) { name ->
Builder(name, phase).apply(init).build()
}
}
}
@@ -0,0 +1,92 @@
package org.jetbrains.kotlin.tools.projectWizard.core
import org.jetbrains.kotlin.tools.projectWizard.settings.version.Version
import org.jetbrains.kotlin.tools.projectWizard.settings.version.VersionRange
import java.io.IOException
abstract class Error {
abstract val message: String
}
abstract class ExceptionError : Error() {
abstract val exception: Exception
override val message: String
get() = exception.asString()
}
data class IOError(override val exception: IOException) : ExceptionError()
data class ExceptionErrorImpl(override val exception: Exception) : ExceptionError()
data class ParseError(override val message: String) : Error()
data class TemplateNotFoundError(val id: String) : Error() {
override val message: String
get() = "Template with an id `$id` is not found"
}
data class SettingNotFoundError(val settingName: String) : Error() {
override val message: String
get() = "Setting with name `$settingName` was not found"
}
data class RequiredSettingsIsNotPresentError(val settingNames: List<String>) : Error() {
override val message: String
get() = buildString {
append("The following required settings is not present: \n")
settingNames.joinTo(this, "\n") { " $it" }
}
}
data class BadApplicationExitCodeError(val applicationName: String, val exitCode: Int) : Error() {
override val message: String
get() = "Application $applicationName exited with code $exitCode"
}
data class CircularTaskDependencyError(val taskName: String) : Error() {
override val message: String
get() = "$taskName task has circular dependencies"
}
data class BadSettingValueError(override val message: String) : Error()
data class LibraryNotFoundError(val name: String) : Error() {
override val message: String
get() = "Library with name `$name` was not found"
}
data class ConfiguratorNotFoundError(val id: String) : Error() {
override val message: String
get() = "Module type `$id` was not found"
}
data class VersionIsNotInRangeError(val subject: String, val version: Version, val range: VersionRange) : Error() {
override val message: String
get() = "${subject.capitalize()}'s version $version is not in expected range $range"
}
data class ValidationError(val validationMessage: String) : Error() {
override val message: String
get() = "Validation error: $validationMessage"
}
data class InvalidSourceSetName(val name: String) : Error() {
override val message: String
get() = "Source set name `$name` is invalid"
}
data class ModuleNotFoundError(val path: String) : Error() {
override val message: String
get() = "Sourceset with a path `$path` was not found invalid"
}
fun Throwable.asString() =
this::class.simpleName!!.removeSuffix("Exception").splitByWords() + message?.let { ": $it" }.orEmpty()
private val wordRegex = "[A-Z][a-z0-9]+".toRegex()
private fun String.splitByWords() =
wordRegex.findAll(this).joinToString(separator = " ") { it.value }
@@ -0,0 +1,12 @@
package org.jetbrains.kotlin.tools.projectWizard.core.service
import java.nio.file.Path
interface AndroidService : Service {
fun isValidAndroidSdk(path: Path): Boolean
}
class AndroidServiceImpl : AndroidService {
//TODO use some heuristics
override fun isValidAndroidSdk(path: Path): Boolean = true
}
@@ -0,0 +1,12 @@
package org.jetbrains.kotlin.tools.projectWizard.core.service
import org.jetbrains.kotlin.tools.projectWizard.core.TaskResult
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.ModuleIR
import java.nio.file.Path
interface BuildSystemService : Service {
fun importProject(
path: Path,
modulesIrs: List<ModuleIR>
): TaskResult<Unit>
}
@@ -0,0 +1,33 @@
package org.jetbrains.kotlin.tools.projectWizard.core.service
import org.jetbrains.kotlin.tools.projectWizard.core.TaskResult
import org.jetbrains.kotlin.tools.projectWizard.core.UNIT_SUCCESS
import org.jetbrains.kotlin.tools.projectWizard.core.computeM
import org.jetbrains.kotlin.tools.projectWizard.core.safe
import java.nio.file.Files
import java.nio.file.Path
interface FileSystemService : Service {
fun createFile(path: Path, text: String): TaskResult<Unit>
fun createDirectory(path: Path): TaskResult<Unit>
object DUMMY : FileSystemService {
override fun createFile(path: Path, text: String): TaskResult<Unit> = UNIT_SUCCESS
override fun createDirectory(path: Path): TaskResult<Unit> = UNIT_SUCCESS
}
}
class OsFileSystemService : FileSystemService {
override fun createFile(path: Path, text: String) = computeM {
createDirectory(path.parent).ensure()
safe { Files.createFile(path.normalize()).toFile().writeText(text) }
}
override fun createDirectory(path: Path) = safe {
@Suppress("NAME_SHADOWING") val path = path.normalize()
if (Files.notExists(path)) {
Files.createDirectories(path)
}
}
}
@@ -0,0 +1,14 @@
package org.jetbrains.kotlin.tools.projectWizard.core.service
import org.jetbrains.kotlin.tools.projectWizard.core.UNIT_SUCCESS
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.ModuleIR
import java.nio.file.Path
interface GradleService : BuildSystemService
class OsGradleIntegration : GradleService {
override fun importProject(
path: Path,
modulesIrs: List<ModuleIR>
) = UNIT_SUCCESS
}
@@ -0,0 +1,14 @@
package org.jetbrains.kotlin.tools.projectWizard.core.service
import org.jetbrains.kotlin.tools.projectWizard.core.UNIT_SUCCESS
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.ModuleIR
import java.nio.file.Path
interface JpsService : BuildSystemService
class OsJpsService : JpsService {
override fun importProject(
path: Path,
modulesIrs: List<ModuleIR>
) = UNIT_SUCCESS
}
@@ -0,0 +1,14 @@
package org.jetbrains.kotlin.tools.projectWizard.core.service
import org.jetbrains.kotlin.tools.projectWizard.core.UNIT_SUCCESS
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.ModuleIR
import java.nio.file.Path
interface MavenService : BuildSystemService
class OsMavenService : MavenService {
override fun importProject(
path: Path,
modulesIrs: List<ModuleIR>
) = UNIT_SUCCESS
}
@@ -0,0 +1,13 @@
package org.jetbrains.kotlin.tools.projectWizard.core.service
interface Service
object Services {
val osServices = listOf(
OsGradleIntegration(),
OsMavenService(),
OsFileSystemService(),
OsJpsService(),
AndroidServiceImpl()
)
}
@@ -0,0 +1,100 @@
package org.jetbrains.kotlin.tools.projectWizard.core
import org.jetbrains.kotlin.tools.projectWizard.core.entity.Setting
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.BuildSystemIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.BodyIR
import java.io.IOException
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.properties.ReadOnlyProperty
import kotlin.random.Random
import kotlin.reflect.KProperty
operator fun Path.div(other: String): Path =
resolve(other)
operator fun Path.div(other: Path): Path =
resolve(other)
operator fun String.div(other: Path): Path =
Paths.get(this).resolve(other)
operator fun String.div(other: String): Path =
Paths.get(this).resolve(other)
fun String.asPath(): Path = Paths.get(this)
fun <T : Any> safe(operation: () -> T): TaskResult<T> =
try {
Success(operation())
} catch (e: IOException) {
Failure(IOError(e))
} catch (e: Exception) {
Failure(ExceptionErrorImpl(e))
}
internal inline fun <T> cached(crossinline createValue: (name: String) -> T) = object : ReadOnlyProperty<Any?, T> {
var value: T? = null
override fun getValue(thisRef: Any?, property: KProperty<*>): T =
value ?: createValue(property.name).also { value = it }
}
@PublishedApi
internal inline fun <reified T> Any?.safeAs(): T? = this as? T
@Suppress("NOTHING_TO_INLINE")
inline fun Any?.ignore() = Unit
internal fun <T> T.asSingletonList() = listOf(this)
inline fun <reified R> Iterable<*>.filterIsInstanceWith(predicate: (R) -> Boolean): List<R> {
val result = mutableListOf<R>()
for (element in this) {
if (element is R && predicate(element)) {
result += element
}
}
return result
}
fun Path.asAbsolute(prefix: Path) =
if (isAbsolute) this else prefix / this
@DslMarker
annotation class Builder
@Builder
class ListBuilder<T> {
private val irs = mutableListOf<T>()
operator fun T.unaryPlus() {
irs += this
}
fun addIfNotNull(value: T?) {
if (value != null) irs += value
}
operator fun List<T>.unaryPlus() {
irs += this
}
// out here for working with vararg arguments
operator fun Array<out T>.unaryPlus() {
irs += this
}
fun build() = irs.toList()
}
fun <T> buildList(builder: ListBuilder<T>.() -> Unit) =
ListBuilder<T>().apply(builder).build()
object RandomIdGenerator {
private val chars = ('a'..'z') + ('A'..'Z') + ('0'..'9')
private const val idLength = 8
fun generate() = (0 until idLength).joinToString(separator = "") {
chars[Random.nextInt(chars.size)].toString()
}
}
@@ -0,0 +1,5 @@
package org.jetbrains.kotlin.tools.projectWizard.ir
import org.jetbrains.kotlin.tools.projectWizard.core.safeAs
interface IR
@@ -0,0 +1,148 @@
package org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem
import org.jetbrains.kotlin.tools.projectWizard.core.TaskResult
import org.jetbrains.kotlin.tools.projectWizard.core.map
import org.jetbrains.kotlin.tools.projectWizard.core.safeAs
import org.jetbrains.kotlin.tools.projectWizard.core.sequence
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.BuildScriptDependencyIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.BuildScriptIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.BuildScriptRepositoryIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.GradleIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.multiplatform.DefaultTargetConfigurationIR
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleSubType
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.BuildFilePrinter
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.GradlePrinter
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.MavenPrinter
import java.nio.file.Path
data class BuildFileIR(
val name: String,
val directoryPath: Path,
val modules: ModulesStructureIR,
val pom: PomIR,
override val irs: List<BuildSystemIR>
) : BuildSystemIR, IrsOwner {
override fun withReplacedIrs(irs: List<BuildSystemIR>): BuildFileIR = copy(irs = irs)
@Suppress("UNCHECKED_CAST")
fun withModulesUpdated(updater: (ModuleIR) -> TaskResult<ModuleIR>): TaskResult<BuildFileIR> =
modules.withModulesUpdated(updater).map { newModules -> copy(modules = newModules) }
override fun BuildFilePrinter.render() = when (this) {
is GradlePrinter -> {
irsOfTypeOrNull<BuildScriptIR>()?.let { buildScriptIrs ->
sectionCall("buildscript", needIndent = true) {
sectionCall("repositories", buildScriptIrs.filterIsInstance<BuildScriptRepositoryIR>())
nlIndented()
sectionCall("dependencies", buildScriptIrs.filterIsInstance<BuildScriptDependencyIR>())
}
}
sectionCall("plugins", irsOfType<BuildSystemPluginIR>()); nlIndented()
pom.render(this); nl()
sectionCall("repositories", irsOfType<RepositoryIR>())
nl()
irsOfTypeOrNull<FreeIR>()?.let { freeIrs ->
freeIrs.listNl()
nlIndented()
}
modules.render(this)
}
is MavenPrinter -> pom {
pom.render(this)
singleLineNode("packaging") { +"jar" }
nl()
singleLineNode("name") { +name }
nl()
node("properties") {
singleLineNode("project.build.sourceEncoding") { +"UTF-8" }
singleLineNode("kotlin.code.style") { +"official" }
}
val plugins = irsOfType<BuildSystemPluginIR>().takeIf { it.isNotEmpty() }
if (plugins != null) {
nl()
node("build") {
singleLineNode("sourceDirectory") { +"src/main/kotlin" }
singleLineNode("testSourceDirectory") { +"src/test/kotlin" }
node("plugins") {
plugins.listNl()
}
}
}
modules.render(this)
}
else -> Unit
}
}
sealed class ModulesStructureIR : BuildSystemIR, IrsOwner {
abstract val modules: List<ModuleIR>
abstract fun withModules(modules: List<ModuleIR>): ModulesStructureIR
fun withModulesUpdated(updater: (ModuleIR) -> TaskResult<ModuleIR>): TaskResult<ModulesStructureIR> =
modules.map { updater(it) }
.sequence()
.map {
withModules(it)
}
}
data class MultiplatformModulesStructureIR(
val targets: List<BuildSystemIR>,
override val modules: List<SourcesetModuleIR>,
override val irs: List<BuildSystemIR>
) : GradleIR, ModulesStructureIR() {
@Suppress("UNCHECKED_CAST")
override fun withModules(modules: List<ModuleIR>) = copy(modules = modules as List<SourcesetModuleIR>)
override fun withReplacedIrs(irs: List<BuildSystemIR>): MultiplatformModulesStructureIR = copy(irs = irs)
override fun GradlePrinter.renderGradle() {
sectionCall("kotlin") {
targets.filterNot {
it.safeAs<DefaultTargetConfigurationIR>()?.targetAccess?.type == ModuleSubType.common
}.listNl()
nlIndented()
sectionCall("sourceSets") {
modules.listNl()
}
}
}
}
data class SingleplatformModulesStructureWithSingleModuleIR(
val module: SingleplatformModuleIR,
override val irs: List<BuildSystemIR>
) : ModulesStructureIR() {
override val modules: List<SingleplatformModuleIR> = listOf(module)
override fun withReplacedIrs(irs: List<BuildSystemIR>): SingleplatformModulesStructureWithSingleModuleIR =
copy(irs = irs)
@Suppress("UNCHECKED_CAST")
override fun withModules(modules: List<ModuleIR>) =
copy(module = modules.single() as SingleplatformModuleIR)
override fun BuildFilePrinter.render() {
module.render(this)
}
}
data class RootFileModuleStructureIR(override val irs: List<BuildSystemIR>) : ModulesStructureIR() {
override val modules = emptyList<ModuleIR>()
override fun withReplacedIrs(irs: List<BuildSystemIR>): RootFileModuleStructureIR = copy(irs = irs)
override fun BuildFilePrinter.render() = when (this) {
is MavenPrinter -> {
irs.listNl()
}
else -> Unit
}
override fun withModules(modules: List<ModuleIR>): RootFileModuleStructureIR = this
}
@@ -0,0 +1,18 @@
package org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem
import org.jetbrains.kotlin.tools.projectWizard.ir.IR
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.BuildFilePrinter
interface BuildSystemIR : IR {
fun BuildFilePrinter.render()
}
// IR element, which is not hardcoded into the parent IR structure and can be printed
// In any place of the parent IR
interface FreeIR : BuildSystemIR
interface KotlinIR : BuildSystemIR
fun BuildSystemIR.render(printer: BuildFilePrinter) {
with(printer) { render() }
}
@@ -0,0 +1,86 @@
package org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem
import org.jetbrains.kotlin.tools.projectWizard.core.ignore
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
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.MavenPrinter
import org.jetbrains.kotlin.tools.projectWizard.settings.version.Version
interface BuildSystemPluginIR : BuildSystemIR
interface DefaultBuildSystemPluginIR : BuildSystemPluginIR
data class ApplicationPluginIR(val mainClass: String) : DefaultBuildSystemPluginIR {
override fun BuildFilePrinter.render() = when (this) {
is GradlePrinter -> when (dsl) {
GradlePrinter.GradleDsl.KOTLIN -> +"application"
GradlePrinter.GradleDsl.GROOVY -> +"id 'application'"
}
is MavenPrinter -> node("plugin") {
singleLineNode("groupId") { +"org.codehaus.mojo" }
singleLineNode("artifactId") { +"exec-maven-plugin" }
singleLineNode("version") { +"1.6.0" }
node("configuration") {
singleLineNode("mainClass") { +mainClass }
}
}
else -> Unit
}
}
data class GradleOnlyPluginByNameIR(val pluginId: String) : BuildSystemPluginIR, GradleIR {
override fun GradlePrinter.renderGradle() {
call("id") { +pluginId.quotified }
}
}
data class KotlinBuildSystemPluginIR(
val type: Type,
val version: Version?
) : BuildSystemPluginIR {
override fun BuildFilePrinter.render() = when (this) {
is GradlePrinter -> {
when {
type == Type.android -> call("id") { +"kotlin-android".quotified }
dsl == GradlePrinter.GradleDsl.KOTLIN -> call("kotlin") { +type.toString().quotified }
else -> call("id") { +"org.jetbrains.kotlin.$type".quotified }
}
version?.let {
+" version "
+it.toString().quotified
}.ignore()
}
is MavenPrinter -> node("plugin") {
singleLineNode("groupId") { +"org.jetbrains.kotlin" }
singleLineNode("artifactId") { +"kotlin-maven-plugin" }
singleLineNode("version") { +version.toString() }
node("executions") {
node("execution") {
singleLineNode("id") { +"compile" }
singleLineNode("phase") { +"compile" }
node("goals") {
singleLineNode("goal") { +"compile" }
}
}
node("execution") {
singleLineNode("id") { +"test-compile" }
singleLineNode("phase") { +"test-compile" }
node("goals") {
singleLineNode("goal") { +"test-compile" }
}
}
}
}
else -> Unit
}
@Suppress("EnumEntryName", "unused", "SpellCheckingInspection")
enum class Type {
jvm, multiplatform, android
}
}
@@ -0,0 +1,147 @@
package org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem
import org.jetbrains.kotlin.tools.projectWizard.core.safeAs
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.GradleIR
import org.jetbrains.kotlin.tools.projectWizard.library.LibraryArtifact
import org.jetbrains.kotlin.tools.projectWizard.library.MavenArtifact
import org.jetbrains.kotlin.tools.projectWizard.library.NpmArtifact
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.BuildFilePrinter
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.GradlePrinter
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.MavenPrinter
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.DefaultRepository.Companion.MAVEN_CENTRAL
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.ModulePath
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.SourcesetType
import org.jetbrains.kotlin.tools.projectWizard.settings.version.Version
interface DependencyIR : BuildSystemIR {
val dependencyType: DependencyType
fun withDependencyType(type: DependencyType): DependencyIR
}
data class ModuleDependencyIR(
val path: ModulePath,
val pomIR: PomIR,
override val dependencyType: DependencyType
) : DependencyIR {
override fun withDependencyType(type: DependencyType): DependencyIR = copy(dependencyType = type)
override fun BuildFilePrinter.render() = when (this) {
is GradlePrinter -> call(dependencyType.gradleName) {
call("project", forceBrackets = true) {
+path.parts.joinToString(separator = "") { ":$it" }.quotified
}
}
is MavenPrinter -> node("dependency") {
pomIR.render(this)
}
else -> Unit
}
}
data class GradleRootProjectDependencyIR(
override val dependencyType: DependencyType
) : DependencyIR, GradleIR {
override fun withDependencyType(type: DependencyType): GradleRootProjectDependencyIR =
copy(dependencyType = type)
override fun GradlePrinter.renderGradle() {
+"rootProject"
}
}
interface LibraryDependencyIR : DependencyIR {
val artifact: LibraryArtifact
val version: Version
}
enum class DependencyType {
MAIN, TEST
}
fun SourcesetType.toDependencyType() = when (this) {
SourcesetType.main -> DependencyType.MAIN
SourcesetType.test -> DependencyType.TEST
}
val DependencyType.gradleName
get() = when (this) {
DependencyType.MAIN -> "implementation"
DependencyType.TEST -> "testImplementation"
}
data class ArtifactBasedLibraryDependencyIR(
override val artifact: LibraryArtifact,
override val version: Version,
override val dependencyType: DependencyType
) : LibraryDependencyIR {
override fun withDependencyType(type: DependencyType): ArtifactBasedLibraryDependencyIR =
copy(dependencyType = type)
override fun BuildFilePrinter.render() = when (this) {
is GradlePrinter -> call(dependencyType.gradleName) {
with(artifact) {
when (this) {
is MavenArtifact -> +"$groupId:$artifactId:${version}".quotified
is NpmArtifact -> {
+"npm(${name.quotified}"
+","
+version.toString().quotified
+")"
}
}
}
}
is MavenPrinter -> {
node("dependency") {
with(artifact as MavenArtifact) {
singleLineNode("groupId") { +groupId }
singleLineNode("artifactId") { +artifactId }
}
singleLineNode("version") { +version.toString() }
if (dependencyType == DependencyType.TEST) {
singleLineNode("scope") { +"test" }
}
}
}
else -> Unit
}
}
data class KotlinLibraryDependencyIR(
val name: String,
override val version: Version,
override val dependencyType: DependencyType
) : LibraryDependencyIR {
override val artifact: LibraryArtifact
get() = MavenArtifact(
MAVEN_CENTRAL,
"org.jetbrains.kotlin",
"kotlin-$name"
)
override fun withDependencyType(type: DependencyType): KotlinLibraryDependencyIR =
copy(dependencyType = type)
override fun BuildFilePrinter.render() {
when (this) {
is GradlePrinter -> call(dependencyType.gradleName) {
+"kotlin("
+name.quotified
+")"
}
is MavenPrinter -> node("dependency") {
singleLineNode("groupId") { +"org.jetbrains.kotlin" }
singleLineNode("artifactId") { +"kotlin-stdlib" }
singleLineNode("version") { +version.toString() }
if (dependencyType == DependencyType.TEST) {
singleLineNode("scope") { +"test" }
}
}
}
}
}
val LibraryDependencyIR.isKotlinStdlib
get() = safeAs<KotlinLibraryDependencyIR>()?.name?.contains("stdlib") == true
@@ -0,0 +1,28 @@
package org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem
interface IrsOwner {
val irs: List<BuildSystemIR>
fun withReplacedIrs(irs: List<BuildSystemIR>): IrsOwner
}
@Suppress("UNCHECKED_CAST")
fun <I : IrsOwner> I.withIrs(irs: List<BuildSystemIR>) = withReplacedIrs(irs = this.irs + irs) as I
@Suppress("UNCHECKED_CAST")
fun <I : IrsOwner> I.withIrs(vararg irs: BuildSystemIR) = withReplacedIrs(irs = this.irs + irs) as I
inline fun <reified I : BuildSystemIR> IrsOwner.irsOfType(): List<I> =
irs.filterIsInstance<I>()
inline fun <reified I : BuildSystemIR> IrsOwner.irsOfTypeOrNull() =
irsOfType<I>().takeIf { it.isNotEmpty() }
inline fun <reified T : BuildSystemIR> IrsOwner.firstIrOfType() =
irs.firstOrNull { ir -> ir is T } as T?
fun IrsOwner.freeIrs(): List<FreeIR> =
irs.filterIsInstance<FreeIR>()
@@ -0,0 +1,86 @@
package org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.GradleIR
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleType
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.BuildFilePrinter
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.GradlePrinter
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.MavenPrinter
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Sourceset
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.SourcesetType
import org.jetbrains.kotlin.tools.projectWizard.templates.Template
import java.nio.file.Path
sealed class ModuleIR : IrsOwner, BuildSystemIR {
abstract val name: String
abstract val path: Path
abstract val type: ModuleType
}
interface SourcesetIR : BuildSystemIR {
val sourcesetType: SourcesetType
val path: Path
val template: Template?
val original: Sourceset
}
data class SingleplatformModuleIR(
override val name: String,
override val path: Path,
override val irs: List<BuildSystemIR>,
override val type: ModuleType,
val sourcesets: List<SingleplatformSourcesetIR>
) : ModuleIR() {
override fun withReplacedIrs(irs: List<BuildSystemIR>): SingleplatformModuleIR = copy(irs = irs)
override fun BuildFilePrinter.render() = when (this) {
is GradlePrinter -> {
indent(); sectionCall("dependencies", irsOfType<DependencyIR>())
}
is MavenPrinter -> node("dependencies") {
irsOfType<DependencyIR>().listNl()
sourcesets.forEach { sourceset ->
sourceset.irsOfType<DependencyIR>().listNl()
}
}
else -> Unit
}
}
data class SingleplatformSourcesetIR(
override val sourcesetType: SourcesetType,
override val path: Path,
override val irs: List<BuildSystemIR>,
override val template: Template?,
override val original: Sourceset
) : SourcesetIR, IrsOwner {
override fun withReplacedIrs(irs: List<BuildSystemIR>): SingleplatformSourcesetIR = copy(irs = irs)
override fun BuildFilePrinter.render() = Unit
}
data class SourcesetModuleIR(
override val name: String,
override val path: Path,
override val irs: List<BuildSystemIR>,
override val type: ModuleType,
override val sourcesetType: SourcesetType,
override val template: Template?,
override val original: Sourceset
) : SourcesetIR, GradleIR, ModuleIR() {
override fun withReplacedIrs(irs: List<BuildSystemIR>): SourcesetModuleIR = copy(irs = irs)
override fun GradlePrinter.renderGradle() = getting(name, prefix = null) {
val dependencies = irsOfType<DependencyIR>()
val needBody = dependencies.isNotEmpty() || dsl == GradlePrinter.GradleDsl.GROOVY
if (needBody) {
+" "
inBrackets {
if (dependencies.isNotEmpty()) {
indent()
sectionCall("dependencies", dependencies)
}
}
}
}
}
@@ -0,0 +1,25 @@
package org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.BuildFilePrinter
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.GradlePrinter
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.MavenPrinter
import org.jetbrains.kotlin.tools.projectWizard.settings.version.Version
data class PomIR(
val artifactId: String,
val groupId: String,
val version: Version
) : BuildSystemIR {
override fun BuildFilePrinter.render() = when (this) {
is GradlePrinter -> {
+"group = "; +groupId.quotified; nl()
+"version = "; +version.toString().quotified; nl()
}
is MavenPrinter -> {
singleLineNode("artifactId") { +artifactId }
singleLineNode("groupId") { +groupId }
singleLineNode("version") { +version.toString() }
}
else -> Unit
}
}
@@ -0,0 +1,28 @@
package org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.BuildFilePrinter
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.GradlePrinter
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.DefaultRepository
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Repository
data class RepositoryIR(val repository: Repository) : BuildSystemIR {
override fun BuildFilePrinter.render() = when (this) {
is GradlePrinter -> when (repository) {
is DefaultRepository -> {
+repository.type.asGradleName()
+"()"
}
else -> Unit
}
else -> Unit
}
companion object {
private fun DefaultRepository.Type.asGradleName() = when (this) {
DefaultRepository.Type.JCENTER -> "jcenter"
DefaultRepository.Type.MAVEN_CENTRAL -> "mavenCentral"
DefaultRepository.Type.GOOGLE -> "google"
DefaultRepository.Type.GRADLE_PLUGIN_PORTAL -> "gradlePluginPortal"
}
}
}
@@ -0,0 +1,32 @@
package org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.FreeIR
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.GradlePrinter
interface AndroidIR : GradleIR
//TODO parematrize
data class AndroidConfigIR(val artifactId: String) : AndroidIR, FreeIR {
override fun GradlePrinter.renderGradle() {
sectionCall("android", needIndent = true) {
call("compileSdkVersion") { +"29" }; nlIndented() // TODO dehardcode
sectionCall("defaultConfig", needIndent = true) {
assignmentOrCall("applicationId") { +artifactId.quotified }; nlIndented()
call("minSdkVersion") { +"24" }; nlIndented() // TODO dehardcode
call("targetSdkVersion") { +"29" }; nlIndented() // TODO dehardcode
assignmentOrCall("versionCode") { +"1" }; nlIndented()
assignmentOrCall("versionName") { +"1.0".quotified }
}
nlIndented()
sectionCall("buildTypes", needIndent = true) {
val sectionIdentifier = when (dsl) {
GradlePrinter.GradleDsl.KOTLIN -> """getByName("release")"""
GradlePrinter.GradleDsl.GROOVY -> "release".quotified
}
sectionCall(sectionIdentifier, needIndent = true) {
assignmentOrCall("isMinifyEnabled") { +"false" }
}
}
}
}
}
@@ -0,0 +1,29 @@
package org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle
import org.jetbrains.kotlin.tools.projectWizard.core.ListBuilder
import org.jetbrains.kotlin.tools.projectWizard.core.buildList
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.BuildSystemIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.render
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.GradlePrinter
data class BodyIR(
val body: List<BuildSystemIR>
) : GradleIR {
constructor(vararg body: BuildSystemIR) : this(body.toList())
override fun GradlePrinter.renderGradle() {
inBrackets {
body.listNl()
}
}
}
fun buildBody(builder: ListBuilder<BuildSystemIR>.() -> Unit) =
BodyIR(buildList(builder))
fun BodyIR.renderIfNotEmpty(printer: GradlePrinter) =
takeIf { it.isNotEmpty() }?.render(printer)
fun BodyIR.isEmpty() = body.isEmpty()
fun BodyIR.isNotEmpty() = body.isNotEmpty()
@@ -0,0 +1,97 @@
package org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.*
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.BuildFilePrinter
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.GradlePrinter
interface GradleIR : BuildSystemIR {
fun GradlePrinter.renderGradle()
override fun BuildFilePrinter.render() {
if (this is GradlePrinter) renderGradle()
}
}
data class RawGradleIR(
val renderer: GradlePrinter.() -> Unit
) : GradleIR {
override fun GradlePrinter.renderGradle() = renderer()
}
data class CreateGradleValueIR(val name: String, val body: GradleIR) : GradleIR {
override fun GradlePrinter.renderGradle() {
when (dsl) {
GradlePrinter.GradleDsl.KOTLIN -> {
+"val "
+name
+" = "
body.render(this)
}
GradlePrinter.GradleDsl.GROOVY -> {
+"def "
+name
+" = "
body.render(this)
}
}
}
}
data class GradleCallIr(
val name: String
) : GradleIR {
override fun GradlePrinter.renderGradle() {
sectionCall(name) {}
}
}
data class GradleSectionIR(
val name: String,
val body: BodyIR
) : GradleIR, FreeIR {
override fun GradlePrinter.renderGradle() {
+name
+" "
body.render(this)
}
}
data class GradleAssignmentIR(
val target: String,
val assignee: BuildSystemIR
) : GradleIR {
override fun GradlePrinter.renderGradle() {
+"$target = "
assignee.render(this)
}
}
data class GradleStringConstIR(
val text: String
) : GradleIR {
override fun GradlePrinter.renderGradle() {
+text.quotified
}
}
data class CompilationAccessIr(
val targetName: String,
val compilationName: String,
val property: String?
) : GradleIR {
override fun GradlePrinter.renderGradle() {
when (dsl) {
GradlePrinter.GradleDsl.KOTLIN -> +"kotlin.$targetName().compilations[\"$compilationName\"]"
GradlePrinter.GradleDsl.GROOVY -> +"kotlin.$targetName().compilations.$compilationName"
}
property?.let { +".$it" }
}
}
interface BuildScriptIR : BuildSystemIR
data class BuildScriptDependencyIR(val dependencyIR: GradleIR) : BuildScriptIR, BuildSystemIR by dependencyIR
data class BuildScriptRepositoryIR(val repositoryIR: RepositoryIR) : BuildScriptIR, BuildSystemIR by repositoryIR
@@ -0,0 +1,48 @@
package org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.FreeIR
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.GradlePrinter
interface GradleTaskIR : GradleIR, FreeIR {
val name: String
val taskClass: String
val body: BodyIR
}
data class CreateGradleTaskIR(
override val name: String,
override val taskClass: String,
override val body: BodyIR
) : GradleTaskIR {
override fun GradlePrinter.renderGradle() {
when (dsl) {
GradlePrinter.GradleDsl.GROOVY -> {
+"tasks.create('$name', $taskClass)"
}
GradlePrinter.GradleDsl.KOTLIN -> {
+"val $name by tasks.registering($taskClass::class)"
}
}
+" "
body.renderIfNotEmpty(this)
}
}
data class GetGradleTaskIR(
override val name: String,
override val taskClass: String,
override val body: BodyIR
) : GradleTaskIR {
override fun GradlePrinter.renderGradle() {
when (dsl) {
GradlePrinter.GradleDsl.GROOVY -> {
+"tasks.getByName('$name')"
}
GradlePrinter.GradleDsl.KOTLIN -> {
+"tasks.getByName<$taskClass>(${name.quotified})"
}
}
+" "
body.renderIfNotEmpty(this)
}
}
@@ -0,0 +1,25 @@
package org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.BuildSystemIR
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.BuildFilePrinter
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.MavenPrinter
interface MavenIR : BuildSystemIR {
fun MavenPrinter.render()
override fun BuildFilePrinter.render() {
if (this is MavenPrinter) render()
}
}
data class ModulesDependencyMavenIR(val dependencies: List<String>) : MavenIR {
override fun MavenPrinter.render() {
node("modules") {
dependencies.forEach { dependency ->
singleLineNode("module") { +dependency }
}
}
}
}
@@ -0,0 +1,71 @@
package org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.multiplatform
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.BuildSystemIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.IrsOwner
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.KotlinIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.GradleIR
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleSubType
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.GradlePrinter
interface MultiplatformIR : GradleIR, KotlinIR
interface TargetIR: MultiplatformIR
data class TargetAccessIR(
val type: ModuleSubType,
val nonDefaultName: String? // `null` stands for default target name
) : TargetIR {
override fun GradlePrinter.renderGradle() {
+type.toString()
par {
nonDefaultName?.let { +it.quotified }
}
}
}
interface TargetConfigurationIR : MultiplatformIR, IrsOwner
data class DefaultTargetConfigurationIR(
val targetAccess: TargetAccessIR,
override val irs: List<BuildSystemIR>
) : TargetConfigurationIR {
override fun withReplacedIrs(irs: List<BuildSystemIR>): DefaultTargetConfigurationIR =
copy(irs = irs)
override fun GradlePrinter.renderGradle() {
+targetAccess.type.toString()
if (irs.isEmpty() || targetAccess.nonDefaultName != null) par {
targetAccess.nonDefaultName?.let { +it.quotified }
}
if (irs.isNotEmpty()) {
+" "; inBrackets { irs.listNl() }
}
}
}
data class NonDefaultTargetConfigurationIR(
val name: String,
override val irs: List<BuildSystemIR>
) : TargetConfigurationIR {
override fun withReplacedIrs(irs: List<BuildSystemIR>): NonDefaultTargetConfigurationIR =
copy(irs = irs)
override fun GradlePrinter.renderGradle() {
+name
when (dsl) {
GradlePrinter.GradleDsl.KOTLIN -> +".apply"
GradlePrinter.GradleDsl.GROOVY-> +".with"
}
+" "; inBrackets { irs.listNl() }
}
}
data class CompilationIR(
val name: String,
override val irs: List<BuildSystemIR>
) : MultiplatformIR, IrsOwner {
override fun withReplacedIrs(irs: List<BuildSystemIR>) = copy(irs = irs)
override fun GradlePrinter.renderGradle() {
getting(name, "compilations") { irs.listNl() }
}
}
@@ -0,0 +1,19 @@
package org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.multiplatform
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.GradleIR
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.GradlePrinter
class NativeTargetInternalIR(
val mainClassFqName: String?
) : GradleIR {
override fun GradlePrinter.renderGradle() {
sectionCall("binaries") {
indent()
sectionCall("executable") {
mainClassFqName?.let { mainClass ->
indent(); assignment("entryPoint") { +mainClass.quotified }
}
}
}
}
}
@@ -0,0 +1,50 @@
package org.jetbrains.kotlin.tools.projectWizard.library
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Repository
import org.jetbrains.kotlin.tools.projectWizard.settings.version.Version
sealed class LibraryArtifact
data class MavenArtifact(
val repository: Repository,
val groupId: String,
val artifactId: String
) : LibraryArtifact()
data class NpmArtifact(
val name: String
) : LibraryArtifact()
sealed class LibraryDescriptor {
abstract val artifact: LibraryArtifact
abstract val type: LibraryType
abstract val version: Version
}
data class MavenLibraryDescriptor(
override val artifact: MavenArtifact,
override val type: LibraryType,
override val version: Version
) : LibraryDescriptor()
data class NpmLibraryDescriptor(
override val artifact: NpmArtifact,
override val version: Version
) : LibraryDescriptor() {
override val type: LibraryType get() = LibraryType.NPM
}
sealed class LibraryType(
val supportJvm: Boolean,
val supportJs: Boolean,
val supportNative: Boolean
) {
object JVM_ONLY : LibraryType(supportJvm = true, supportJs = false, supportNative = false)
object JS_ONLY : LibraryType(supportJvm = false, supportJs = true, supportNative = false)
object NPM : LibraryType(supportJvm = false, supportJs = true, supportNative = false)
object MULTIPLATFORM : LibraryType(supportJvm = true, supportJs = true, supportNative = true)
}
@@ -0,0 +1,149 @@
package org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators
import org.jetbrains.kotlin.tools.projectWizard.core.*
import org.jetbrains.kotlin.tools.projectWizard.core.entity.ModuleConfiguratorSetting
import org.jetbrains.kotlin.tools.projectWizard.core.entity.ValidationResult
import org.jetbrains.kotlin.tools.projectWizard.core.service.AndroidService
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.*
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.AndroidConfigIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.BuildScriptDependencyIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.BuildScriptRepositoryIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.RawGradleIR
import org.jetbrains.kotlin.tools.projectWizard.library.MavenArtifact
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.ModuleConfigurationData
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleType
import org.jetbrains.kotlin.tools.projectWizard.plugins.pomIR
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.DefaultRepository
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
import org.jetbrains.kotlin.tools.projectWizard.settings.javaPackage
import org.jetbrains.kotlin.tools.projectWizard.settings.version.Version
import org.jetbrains.kotlin.tools.projectWizard.templates.FileTemplate
import org.jetbrains.kotlin.tools.projectWizard.templates.FileTemplateDescriptor
import java.nio.file.Path
object AndroidSinglePlatformModuleConfigurator : ModuleConfiguratorWithSettings(),
SinglePlatformModuleConfigurator,
AndroidModuleConfigurator {
override val moduleType = ModuleType.jvm
override val id = "android"
override val suggestedModuleName = "android"
override val greyText = "Requires Android SDK"
val androidSdkPath by pathSetting("Android SDK Path", neededAtPhase = GenerationPhase.PROJECT_GENERATION) {
shouldExists()
validate { path ->
ValidationResult.create(
service<AndroidService>().isValidAndroidSdk(
path
),
"Path should point to valid Android SDK location"
)
}
}
override fun createBuildFileIRs(configurationData: ModuleConfigurationData, module: Module) =
buildList<BuildSystemIR> {
+GradleOnlyPluginByNameIR("com.android.application")
// it is explicitly here instead of by `createKotlinPluginIR` as it should be after `com.android.application`
+KotlinBuildSystemPluginIR(
KotlinBuildSystemPluginIR.Type.android,
version = null // Version is already present in the parent buildfile
)
+GradleOnlyPluginByNameIR("kotlin-android-extensions")
+AndroidConfigIR(configurationData.pomIr.artifactId)
+listOf(
DefaultRepository.GRADLE_PLUGIN_PORTAL,
DefaultRepository.GOOGLE,
DefaultRepository.JCENTER
).map(::RepositoryIR)
}
override fun createRootBuildFileIrs(configurationData: ModuleConfigurationData) = buildList<BuildSystemIR> {
+listOf(
DefaultRepository.GRADLE_PLUGIN_PORTAL,
DefaultRepository.GOOGLE,
DefaultRepository.JCENTER
).map { BuildScriptRepositoryIR(RepositoryIR(it)) }
+listOf(
RawGradleIR { call("classpath") { +"com.android.tools.build:gradle:3.2.1".quotified } },
RawGradleIR {
call("classpath") {
call("kotlin", forceBrackets = true) {
+"gradle-plugin".quotified
+", "
+configurationData.kotlinVersion.toString().quotified
}
}
}
).map(::BuildScriptDependencyIR)
}
override fun TaskRunningContext.runArbitraryTask(
configurationData: ModuleConfigurationData,
module: Module,
modulePath: Path
): TaskResult<Unit> = withSettingsOf(module) {
val path = androidSdkPath.reference.settingValue
GradlePlugin::localProperties.addValues(
"sdk.dir" to path
)
} andThen computeM {
val javaPackage = module.javaPackage(configurationData.pomIr)
TemplatesPlugin::addFileTemplates.execute(
listOf(
FileTemplate(FileTemplateDescriptors.activityMainXml, modulePath, "package" to javaPackage),
FileTemplate(FileTemplateDescriptors.androidManifestXml, modulePath, "package" to javaPackage),
FileTemplate(FileTemplateDescriptors.mainActivityKt(javaPackage), modulePath, "package" to javaPackage)
)
)
}
override fun createModuleIRs(configurationData: ModuleConfigurationData, module: Module): List<BuildSystemIR> =
buildList {
+ArtifactBasedLibraryDependencyIR(
MavenArtifact(DefaultRepository.GOOGLE, "androidx.appcompat", "appcompat"),
version = Version.fromString("1.1.0"),
dependencyType = DependencyType.MAIN
)
+ArtifactBasedLibraryDependencyIR(
MavenArtifact(DefaultRepository.GOOGLE, "androidx.core", "core-ktx"),
version = Version.fromString("1.1.0"),
dependencyType = DependencyType.MAIN
)
+KotlinLibraryDependencyIR("stdlib-jdk7", configurationData.kotlinVersion, DependencyType.MAIN)
+ArtifactBasedLibraryDependencyIR(
MavenArtifact(DefaultRepository.GOOGLE, "androidx.constraintlayout", "constraintlayout"),
version = Version.fromString("1.1.3"),
dependencyType = DependencyType.MAIN
)
}
override val settings: List<ModuleConfiguratorSetting<*, *>> =
listOf(androidSdkPath)
private object FileTemplateDescriptors {
val activityMainXml = FileTemplateDescriptor(
"android/activity_main.xml.vm",
"src" / "main" / "res" / "layout" / "activity_main.xml"
)
val androidManifestXml = FileTemplateDescriptor(
"android/AndroidManifest.xml.vm",
"src" / "main" / "AndroidManifest.xml"
)
fun mainActivityKt(javaPackage: JavaPackage) = FileTemplateDescriptor(
"android/MainActivity.kt.vm",
"src" / "main" / "java" / javaPackage.asPath() / "MainActivity.kt"
)
}
}
@@ -0,0 +1,221 @@
package org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators
import org.jetbrains.kotlin.tools.projectWizard.Identificator
import org.jetbrains.kotlin.tools.projectWizard.SettingsOwner
import org.jetbrains.kotlin.tools.projectWizard.core.*
import org.jetbrains.kotlin.tools.projectWizard.core.cached
import org.jetbrains.kotlin.tools.projectWizard.core.entity.*
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.BuildSystemIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.KotlinBuildSystemPluginIR
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleConfigurationData
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.*
import org.jetbrains.kotlin.tools.projectWizard.settings.version.Version
import java.nio.file.Path
import kotlin.properties.ReadOnlyProperty
class ModuleSettingsEnvironment(private val configurator: ModuleConfigurator, private val moduleId: Identificator) {
val <V : Any, T : SettingType<V>> ModuleConfiguratorSetting<V, T>.reference
get() = ModuleConfiguratorSettingReference(configurator, moduleId, this)
}
fun <T> withSettingsOf(
moduleId: Identificator,
configurator: ModuleConfigurator,
function: ModuleSettingsEnvironment.() -> T
): T = function(ModuleSettingsEnvironment(configurator, moduleId))
fun <T> withSettingsOf(
module: Module,
configurator: ModuleConfigurator = module.configurator,
function: ModuleSettingsEnvironment.() -> T
): T = function(ModuleSettingsEnvironment(configurator, module.identificator))
abstract class ModuleConfiguratorWithSettings : ModuleConfigurator, SettingsOwner {
override fun <V : Any, T : SettingType<V>> settingDelegate(
create: (path: String) -> SettingBuilder<V, T>
): ReadOnlyProperty<Any?, ModuleConfiguratorSetting<V, T>> = cached { name ->
ModuleConfiguratorSetting(create(name).buildInternal())
}
abstract val settings: List<ModuleConfiguratorSetting<*, *>>
@Suppress("UNCHECKED_CAST")
override fun <V : DisplayableSettingItem> dropDownSetting(
title: String,
neededAtPhase: GenerationPhase,
parser: Parser<V>,
init: DropDownSettingType.Builder<V>.() -> Unit
): ReadOnlyProperty<Any, ModuleConfiguratorSetting<V, DropDownSettingType<V>>> =
super.dropDownSetting(
title,
neededAtPhase,
parser,
init
) as ReadOnlyProperty<Any, ModuleConfiguratorSetting<V, DropDownSettingType<V>>>
@Suppress("UNCHECKED_CAST")
override fun stringSetting(
title: String,
neededAtPhase: GenerationPhase,
init: StringSettingType.Builder.() -> Unit
): ReadOnlyProperty<Any, ModuleConfiguratorSetting<String, StringSettingType>> =
super.stringSetting(
title,
neededAtPhase,
init
) as ReadOnlyProperty<Any, ModuleConfiguratorSetting<String, StringSettingType>>
@Suppress("UNCHECKED_CAST")
override fun booleanSetting(
title: String,
neededAtPhase: GenerationPhase,
init: BooleanSettingType.Builder.() -> Unit
): ReadOnlyProperty<Any, ModuleConfiguratorSetting<Boolean, BooleanSettingType>> =
super.booleanSetting(
title,
neededAtPhase,
init
) as ReadOnlyProperty<Any, ModuleConfiguratorSetting<Boolean, BooleanSettingType>>
@Suppress("UNCHECKED_CAST")
override fun <V : Any> valueSetting(
title: String,
neededAtPhase: GenerationPhase,
parser: Parser<V>,
init: ValueSettingType.Builder<V>.() -> Unit
): ReadOnlyProperty<Any, ModuleConfiguratorSetting<V, ValueSettingType<V>>> =
super.valueSetting(
title,
neededAtPhase,
parser,
init
) as ReadOnlyProperty<Any, ModuleConfiguratorSetting<V, ValueSettingType<V>>>
@Suppress("UNCHECKED_CAST")
override fun versionSetting(
title: String,
neededAtPhase: GenerationPhase,
init: VersionSettingType.Builder.() -> Unit
): ReadOnlyProperty<Any, ModuleConfiguratorSetting<Version, VersionSettingType>> =
super.versionSetting(
title,
neededAtPhase,
init
) as ReadOnlyProperty<Any, ModuleConfiguratorSetting<Version, VersionSettingType>>
@Suppress("UNCHECKED_CAST")
override fun <V : Any> listSetting(
title: String,
neededAtPhase: GenerationPhase,
parser: Parser<V>,
init: ListSettingType.Builder<V>.() -> Unit
): ReadOnlyProperty<Any, ModuleConfiguratorSetting<List<V>, ListSettingType<V>>> =
super.listSetting(
title,
neededAtPhase,
parser,
init
) as ReadOnlyProperty<Any, ModuleConfiguratorSetting<List<V>, ListSettingType<V>>>
@Suppress("UNCHECKED_CAST")
override fun pathSetting(
title: String,
neededAtPhase: GenerationPhase,
init: PathSettingType.Builder.() -> Unit
): ReadOnlyProperty<Any, ModuleConfiguratorSetting<Path, PathSettingType>> =
super.pathSetting(
title,
neededAtPhase,
init
) as ReadOnlyProperty<Any, ModuleConfiguratorSetting<Path, PathSettingType>>
inline fun <reified E> enumSetting(
title: String,
neededAtPhase: GenerationPhase,
crossinline init: DropDownSettingType.Builder<E>.() -> Unit = {}
) where E : Enum<E>, E : DisplayableSettingItem = dropDownSetting<E>(title, neededAtPhase, enumParser()) {
values = enumValues<E>().asList()
init()
}
}
val ModuleConfigurator.settings
get() = when (this) {
is ModuleConfiguratorWithSettings -> settings
else -> emptyList()
}
val Module.configuratorSettings
get() = configurator.settings.map { setting ->
ModuleConfiguratorSettingReference(configurator, this, setting)
}
interface ModuleConfigurator : DisplayableSettingItem, EntitiesOwnerDescriptor {
val moduleKind: ModuleKind
val moduleType: ModuleType
override val text: String
get() = id
val suggestedModuleName: String? get() = null
val canContainSubModules: Boolean get() = false
fun createBuildFileIRs(configurationData: ModuleConfigurationData, module: Module): List<BuildSystemIR> =
emptyList()
fun createModuleIRs(configurationData: ModuleConfigurationData, module: Module): List<BuildSystemIR> =
emptyList()
fun createRootBuildFileIrs(configurationData: ModuleConfigurationData): List<BuildSystemIR> = emptyList()
fun createKotlinPluginIR(configurationData: ModuleConfigurationData, module: Module): KotlinBuildSystemPluginIR? =
null
fun TaskRunningContext.runArbitraryTask(
configurationData: ModuleConfigurationData,
module: Module,
modulePath: Path
): TaskResult<Unit> = UNIT_SUCCESS
companion object {
val ALL = buildList<ModuleConfigurator> {
+RealNativeTargetConfigurator.configurators
+NativeForCurrentSystemTarget
+JsBrowserTargetConfigurator
+JsNodeTargetConfigurator
+CommonTargetConfigurator
+JvmTargetConfigurator
+AndroidTargetConfigurator
+MppModuleConfigurator
+JvmSinglePlatformModuleConfigurator
+AndroidSinglePlatformModuleConfigurator
+IOSSinglePlatformModuleConfigurator
}
init {
ALL.groupBy(ModuleConfigurator::id)
.forEach { (id, configurators) -> assert(configurators.size == 1) { id } }
}
val BY_ID = ALL.associateBy(ModuleConfigurator::id)
val BY_MODULE_KIND = ALL.groupBy(ModuleConfigurator::moduleKind)
fun getParser(moduleIdentificator: Identificator): Parser<ModuleConfigurator> = mapParser { map, path ->
val (id) = map.parseValue<String>(path, "name")
val (configurator) = BY_ID[id].toResult { ConfiguratorNotFoundError(id) }
val (settingsWithValues) = configurator.settings.mapComputeM { setting ->
val (settingValue) = map[setting.path].toResult { ParseError("No value was found for a key `$path.${setting.path}`") }
val reference = withSettingsOf(moduleIdentificator, configurator) { setting.reference }
setting.type.parse(this, settingValue, setting.path).map { reference to it }
}.sequence()
updateState { it.withSettings(settingsWithValues) }
configurator
} or valueParserM { value, path ->
val (id) = value.parseAs<String>(path)
BY_ID[id].toResult { ConfiguratorNotFoundError(id) }
}
}
}
@@ -0,0 +1,78 @@
package org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators
import org.jetbrains.kotlin.tools.projectWizard.core.buildList
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.BuildSystemIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.CreateGradleValueIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.RawGradleIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.multiplatform.NativeTargetInternalIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.multiplatform.NonDefaultTargetConfigurationIR
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleSubType
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleType
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.GradlePrinter
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
interface NativeTargetConfigurator : TargetConfigurator {
override fun createInnerTargetIrs(module: Module): List<BuildSystemIR> = buildList {
+NativeTargetInternalIR("MAIN CLASS")
}
}
class RealNativeTargetConfigurator private constructor(
override val moduleSubType: ModuleSubType
) : NativeTargetConfigurator, SimpleTargetConfigurator {
companion object {
val configurators = ModuleSubType.values()
.filter { it.moduleType == ModuleType.native }
.map(::RealNativeTargetConfigurator)
val configuratorsByModuleType = configurators.associateBy { it.moduleSubType }
}
}
object NativeForCurrentSystemTarget : NativeTargetConfigurator, SingleCoexistenceTargetConfigurator {
override val moduleType = ModuleType.native
override val id = "nativeForCurrentSystem"
override val text = "For Current System"
override fun createTargetIrs(module: Module): List<BuildSystemIR> {
val moduleName = module.name
return buildList {
+CreateGradleValueIR("hostOs", RawGradleIR { +"System.getProperty(\"os.name\")" })
+CreateGradleValueIR("isMingwX64", RawGradleIR { +"hostOs.startsWith(\"Windows\")" })
//TODO do not use RawGradleIR here
+RawGradleIR {
when (dsl) {
GradlePrinter.GradleDsl.KOTLIN -> {
+"val $DEFAULT_TARGET_VARIABLE_NAME = when "
inBrackets {
indent()
+"""hostOs == "Mac OS X" -> macosX64("$moduleName")"""; nlIndented()
+"""hostOs == "Linux" -> linuxX64("$moduleName")"""; nlIndented()
+"""isMingwX64 -> mingwX64("$moduleName")"""; nlIndented()
+"""else -> throw GradleException("Host OS is not supported in Kotlin/Native.")"""
}
}
GradlePrinter.GradleDsl.GROOVY -> {
+"""org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetWithTests $DEFAULT_TARGET_VARIABLE_NAME"""; nlIndented()
+"""if (hostOs == "Mac OS X") $DEFAULT_TARGET_VARIABLE_NAME = macosX64('$moduleName')"""; nlIndented()
+"""else if (hostOs == "Linux") $DEFAULT_TARGET_VARIABLE_NAME = linuxX64("$moduleName")"""; nlIndented()
+"""else if (isMingwX64) return $DEFAULT_TARGET_VARIABLE_NAME = mingwX64("$moduleName")"""; nlIndented()
+"""else throw new GradleException("Host OS is not supported in Kotlin/Native.")""";
}
}
nl()
}
+NonDefaultTargetConfigurationIR(
DEFAULT_TARGET_VARIABLE_NAME,
createInnerTargetIrs(module)
)
}
}
private const val DEFAULT_TARGET_VARIABLE_NAME = "nativeTarget"
}
@@ -0,0 +1,95 @@
package org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleSubType
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleType
object TargetConfigurationGroups {
val JVM = FinalTargetConfiguratorGroup(
ModuleType.jvm.projectTypeName,
ModuleType.jvm,
listOf(
JvmTargetConfigurator,
AndroidTargetConfigurator
)
)
val JS = FinalTargetConfiguratorGroup(
ModuleType.js.projectTypeName,
ModuleType.js,
listOf(
JsBrowserTargetConfigurator,
JsNodeTargetConfigurator
)
)
object NATIVE {
val LINUX = FinalTargetConfiguratorGroup(
"Linux",
ModuleType.native,
listOf(
RealNativeTargetConfigurator.configuratorsByModuleType.getValue(ModuleSubType.linuxX64),
RealNativeTargetConfigurator.configuratorsByModuleType.getValue(ModuleSubType.linuxArm32Hfp),
RealNativeTargetConfigurator.configuratorsByModuleType.getValue(ModuleSubType.linuxMips32),
RealNativeTargetConfigurator.configuratorsByModuleType.getValue(ModuleSubType.linuxMipsel32)
)
)
val WINDOWS = FinalTargetConfiguratorGroup(
"Windows (MinGW)",
ModuleType.native,
listOf(
RealNativeTargetConfigurator.configuratorsByModuleType.getValue(ModuleSubType.mingwX64),
RealNativeTargetConfigurator.configuratorsByModuleType.getValue(ModuleSubType.mingwX86)
)
)
val MAC = FinalTargetConfiguratorGroup(
"macOS",
ModuleType.native,
listOf(
RealNativeTargetConfigurator.configuratorsByModuleType.getValue(ModuleSubType.macosX64)
)
)
val IOS = FinalTargetConfiguratorGroup(
"iOS",
ModuleType.native,
listOf(
RealNativeTargetConfigurator.configuratorsByModuleType.getValue(ModuleSubType.iosArm32),
RealNativeTargetConfigurator.configuratorsByModuleType.getValue(ModuleSubType.iosArm64),
RealNativeTargetConfigurator.configuratorsByModuleType.getValue(ModuleSubType.iosX64)
)
)
val ANDROID_NATIVE = FinalTargetConfiguratorGroup(
"Android Native",
ModuleType.native,
listOf(
RealNativeTargetConfigurator.configuratorsByModuleType.getValue(ModuleSubType.androidNativeArm64),
RealNativeTargetConfigurator.configuratorsByModuleType.getValue(ModuleSubType.androidNativeArm32)
)
)
val ALL = StepTargetConfiguratorGroup(
ModuleType.native.projectTypeName,
ModuleType.native,
listOf(
NativeForCurrentSystemTarget,
LINUX,
WINDOWS,
MAC,
IOS,
ANDROID_NATIVE
)
)
}
val FIRST = FirstStepTargetConfiguratorGroup(
listOf(
CommonTargetConfigurator,
JVM,
NATIVE.ALL,
JS
)
)
}
@@ -0,0 +1,104 @@
package org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators
import org.jetbrains.kotlin.tools.projectWizard.core.buildList
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.BuildSystemIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.RawGradleIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.multiplatform.DefaultTargetConfigurationIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.multiplatform.TargetAccessIR
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleSubType
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleType
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.ModuleKind
interface TargetConfigurator : ModuleConfigurator {
override val moduleKind get() = ModuleKind.target
fun canCoexistsWith(other: List<TargetConfigurator>): Boolean = true
fun createTargetIrs(module: Module): List<BuildSystemIR>
fun createInnerTargetIrs(module: Module): List<BuildSystemIR> = emptyList()
}
interface SingleCoexistenceTargetConfigurator : TargetConfigurator {
override fun canCoexistsWith(other: List<TargetConfigurator>): Boolean =
other.none { it == this }
}
interface SimpleTargetConfigurator : TargetConfigurator {
val moduleSubType: ModuleSubType
override val moduleType get() = moduleSubType.moduleType
override val id get() = "${moduleSubType.name}Target"
override val text get() = moduleSubType.name.capitalize()
override val suggestedModuleName: String? get() = moduleSubType.name
override fun createTargetIrs(module: Module): List<BuildSystemIR> = buildList {
+DefaultTargetConfigurationIR(
module.createTargetAccessIr(moduleSubType),
createInnerTargetIrs(module)
)
}
}
private fun Module.createTargetAccessIr(moduleSubType: ModuleSubType) =
TargetAccessIR(
moduleSubType,
name.takeIf { it != moduleSubType.name }
)
interface JsTargetConfigurator : TargetConfigurator {
override val moduleType: ModuleType get() = ModuleType.js
}
object JsBrowserTargetConfigurator : JsTargetConfigurator {
override val id = "jsBrowser"
override val text = "Browser"
override fun createTargetIrs(module: Module): List<BuildSystemIR> = buildList {
+DefaultTargetConfigurationIR(
module.createTargetAccessIr(ModuleSubType.js),
buildList {
+RawGradleIR {
sectionCall("browser") {}
}
}
)
}
}
object JsNodeTargetConfigurator : JsTargetConfigurator {
override val id = "jsNode"
override val text = "Node.js"
override fun createTargetIrs(module: Module): List<BuildSystemIR> = buildList {
+DefaultTargetConfigurationIR(
module.createTargetAccessIr(ModuleSubType.js),
buildList {
+RawGradleIR {
sectionCall("nodejs") {}
}
}
)
}
}
object CommonTargetConfigurator : SimpleTargetConfigurator, SingleCoexistenceTargetConfigurator {
override val moduleSubType = ModuleSubType.common
}
object JvmTargetConfigurator : TargetConfigurator,
SimpleTargetConfigurator,
JvmModuleConfigurator,
SingleCoexistenceTargetConfigurator {
override val moduleSubType = ModuleSubType.jvm
}
object AndroidTargetConfigurator : TargetConfigurator,
SimpleTargetConfigurator,
AndroidModuleConfigurator,
SingleCoexistenceTargetConfigurator {
override val moduleSubType = ModuleSubType.android
}

Some files were not shown because too many files have changed in this diff Show More