diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/mpp/MppJvmRunIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/mpp/MppJvmRunIT.kt new file mode 100644 index 00000000000..db51e587112 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/mpp/MppJvmRunIT.kt @@ -0,0 +1,78 @@ +/* + * Copyright 2010-2023 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.gradle.mpp + +import org.gradle.util.GradleVersion +import org.jetbrains.kotlin.gradle.testbase.* +import kotlin.io.path.appendText + +@MppGradlePluginTests +class MppJvmRunIT : KGPBaseTest() { + + @GradleTest + fun `test jvmRun`(version: GradleVersion) { + project("mppRunJvm", version) { + build("jvmRun", "-PmainClass=JvmMainKt") { + assertOutputContains("Jvm: OK!") + assertOutputContains("Executed: JvmMain") + } + + build("jvmRun", "-PmainClass=CommonMainKt") { + assertOutputContains("Jvm: OK!") + assertOutputContains("Executed: CommonMain") + } + + // No mainClass provided! + buildAndFail("jvmRun") + + /* Provided mainClass in buildscript */ + projectPath.resolve("multiplatform").resolve("build.gradle.kts").appendText( + """ + kotlin { + jvm().mainRun { + mainClass.set("JvmMainKt") + } + } + """.trimIndent() + ) + + build("jvmRun") { + assertOutputContains("Jvm: OK!") + assertOutputContains("Executed: JvmMain") + } + + /* Overwrite buildscript mainClass via property */ + build("jvmRun", "-PmainClass=CommonMainKt") { + assertOutputContains("Jvm: OK!") + assertOutputContains("Executed: CommonMain") + } + } + } + + @GradleTestVersions(minVersion = TestVersions.Gradle.G_7_4) + @GradleTest + fun `test - jvmRun - works with Gradle configuration cache`(version: GradleVersion) { + project("mppRunJvm", version, buildOptions = defaultBuildOptions.copy(configurationCache = true)) { + build("jvmRun", "-DmainClass=JvmMainKt") { + assertOutputContains("Jvm: OK!") + assertOutputContains("Executed: JvmMain") + assertConfigurationCacheStored() + } + + build("jvmRun", "-DmainClass=CommonMainKt") { + assertOutputContains("Jvm: OK!") + assertOutputContains("Executed: CommonMain") + assertConfigurationCacheReused() + } + + build("jvmRun", "-DmainClass=JvmMainKt") { + assertOutputContains("Jvm: OK!") + assertOutputContains("Executed: JvmMain") + assertConfigurationCacheReused() + } + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/mppRunJvm/jvm/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/mppRunJvm/jvm/build.gradle.kts new file mode 100644 index 00000000000..da54cebc7a0 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/mppRunJvm/jvm/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + kotlin("jvm") +} + +dependencies { + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1") +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/mppRunJvm/jvm/src/main/kotlin/Jvm.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/mppRunJvm/jvm/src/main/kotlin/Jvm.kt new file mode 100644 index 00000000000..108674a7cfd --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/mppRunJvm/jvm/src/main/kotlin/Jvm.kt @@ -0,0 +1,11 @@ +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext + +object Jvm { + operator fun invoke() = runBlocking { + withContext(Dispatchers.Default) { + println("Jvm: OK!") + } + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/mppRunJvm/multiplatform/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/mppRunJvm/multiplatform/build.gradle.kts new file mode 100644 index 00000000000..1c4dad9758b --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/mppRunJvm/multiplatform/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + kotlin("multiplatform") +} + +kotlin { + jvm() + sourceSets.getByName("jvmMain").dependencies { + implementation(project(":jvm")) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/mppRunJvm/multiplatform/src/commonMain/kotlin/CommonMain.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/mppRunJvm/multiplatform/src/commonMain/kotlin/CommonMain.kt new file mode 100644 index 00000000000..9328a6050b6 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/mppRunJvm/multiplatform/src/commonMain/kotlin/CommonMain.kt @@ -0,0 +1,4 @@ +fun main() { + Jvm.invoke() + println("Executed: CommonMain") +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/mppRunJvm/multiplatform/src/jvmMain/kotlin/JvmMain.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/mppRunJvm/multiplatform/src/jvmMain/kotlin/JvmMain.kt new file mode 100644 index 00000000000..903ce7fda4f --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/mppRunJvm/multiplatform/src/jvmMain/kotlin/JvmMain.kt @@ -0,0 +1,4 @@ +fun main() { + Jvm.invoke() + println("Executed: JvmMain") +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/mppRunJvm/settings.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/mppRunJvm/settings.gradle.kts new file mode 100644 index 00000000000..6b1101e4c9e --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/mppRunJvm/settings.gradle.kts @@ -0,0 +1,9 @@ +dependencyResolutionManagement { + repositories { + mavenLocal() + mavenCentral() + } +} + +include(":multiplatform") +include(":jvm") diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnostics.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnostics.kt index 5634634e4da..4aaebe93d16 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnostics.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnostics.kt @@ -178,4 +178,12 @@ object KotlinToolingDiagnostics { """.trimIndent() ) } + + object KotlinJvmMainRunTaskConflict : ToolingDiagnosticFactory(WARNING) { + operator fun invoke(targetName: String, taskName: String) = build( + """ + Target '$targetName': Unable to create run task '$taskName' as there is already such a task registered + """.trimIndent() + ) + } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinTargetPresets.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinTargetPresets.kt index 8b24f3a9cce..0616d953d4e 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinTargetPresets.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinTargetPresets.kt @@ -6,10 +6,13 @@ package org.jetbrains.kotlin.gradle.plugin.mpp import org.gradle.api.Project -import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.plugin.AbstractKotlinTargetConfigurator +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation +import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType +import org.jetbrains.kotlin.gradle.plugin.KotlinTargetPreset abstract class KotlinOnlyTargetPreset, T : KotlinCompilation<*>>( - protected val project: Project + protected val project: Project, ) : KotlinTargetPreset { protected abstract fun createKotlinTargetConfigurator(): AbstractKotlinTargetConfigurator diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/jvm/KotlinJvmTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/jvm/KotlinJvmTarget.kt index 40df67067da..8885b3793d0 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/jvm/KotlinJvmTarget.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/jvm/KotlinJvmTarget.kt @@ -18,6 +18,7 @@ import org.gradle.api.tasks.testing.Test import org.gradle.jvm.tasks.Jar import org.gradle.language.jvm.tasks.ProcessResources import org.gradle.util.GradleVersion +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.internal.JavaSourceSetsAccessor @@ -25,20 +26,69 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinOnlyTarget import org.jetbrains.kotlin.gradle.plugin.mpp.internal import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.copyAttributes +import org.jetbrains.kotlin.gradle.targets.jvm.tasks.KotlinJvmRunDsl +import org.jetbrains.kotlin.gradle.targets.jvm.tasks.KotlinJvmRunDslImpl +import org.jetbrains.kotlin.gradle.targets.jvm.tasks.registerMainRunTask import org.jetbrains.kotlin.gradle.tasks.withType +import org.jetbrains.kotlin.gradle.utils.Future import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation +import org.jetbrains.kotlin.gradle.utils.future import org.jetbrains.kotlin.utils.addToStdlib.cast import java.util.concurrent.Callable import javax.inject.Inject import kotlin.reflect.full.functions abstract class KotlinJvmTarget @Inject constructor( - project: Project + project: Project, ) : KotlinOnlyTarget(project, KotlinPlatformType.jvm), KotlinTargetWithTests { override lateinit var testRuns: NamedDomainObjectContainer + internal val mainRun: Future = project.future { registerMainRunTask() } + + /** + * ### ⚠️ KotlinJvmTarget 'mainRun' is experimental + * The [KotlinJvmTarget], by default, creates a 'run' task called {targetName}Run, which will allows simple + * execution of the targets 'main' code. + * + * e.g. + * ```kotlin + * // build.gradle.kts + * kotlin { + * jvm().mainRun { + * mainClass.set("FooKt") + * } + * } + * + * // src/jvmMain/Foo + * fun main() { + * println("Hello from foo") + * } + * ``` + * + * will be executable using + * ```text + * ./gradlew jvmRun + * > "Hello from foo" + * ``` + * + * ### Running a different 'mainClass' from CLI: + * The execution of the main code allows providing a different 'mainClass' via CLI. * + * It accepts System Properties and Gradle Properties. However, when Gradle Configuration Cache is used, + * System Properties are the preferred way. + * + * ```text + * ./gradlew jvmRun -DmainClass="BarKt" + * ^ + * Will execute the 'src/jvmMain/kotlin/Bar' main method. + * ``` + */ + @ExperimentalKotlinGradlePluginApi + fun mainRun(configure: KotlinJvmRunDsl.() -> Unit) = project.launch { + mainRun.await()?.configure() + } + var withJavaEnabled = false private set @@ -106,7 +156,7 @@ abstract class KotlinJvmTarget @Inject constructor( private fun setupJavaSourceSetSourcesAndResources( javaSourceSet: SourceSet, - compilation: KotlinJvmCompilation + compilation: KotlinJvmCompilation, ) { javaSourceSet.java.setSrcDirs(listOf("src/${compilation.defaultSourceSet.name}/java")) compilation.defaultSourceSet.kotlin.srcDirs(javaSourceSet.java.sourceDirectories) @@ -153,7 +203,7 @@ abstract class KotlinJvmTarget @Inject constructor( private fun setupDependenciesCrossInclusionForJava( compilation: KotlinJvmCompilation, - javaSourceSet: SourceSet + javaSourceSet: SourceSet, ) { // Make sure Kotlin compilation dependencies appear in the Java source set classpaths: diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/jvm/tasks/KotlinJvmRun.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/jvm/tasks/KotlinJvmRun.kt new file mode 100644 index 00000000000..edba6a78b3b --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/jvm/tasks/KotlinJvmRun.kt @@ -0,0 +1,162 @@ +package org.jetbrains.kotlin.gradle.targets.jvm.tasks + +import org.gradle.api.file.FileCollection +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.JavaExec +import org.gradle.api.tasks.TaskProvider +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.InternalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation +import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle +import org.jetbrains.kotlin.gradle.plugin.await +import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics.KotlinJvmMainRunTaskConflict +import org.jetbrains.kotlin.gradle.plugin.diagnostics.reportDiagnostic +import org.jetbrains.kotlin.gradle.plugin.internal.configurationTimePropertiesAccessor +import org.jetbrains.kotlin.gradle.plugin.internal.usedAtConfigurationTime +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmCompilation +import org.jetbrains.kotlin.gradle.plugin.mpp.internal +import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget +import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName +import org.jetbrains.kotlin.gradle.utils.property +import org.jetbrains.kotlin.tooling.core.UnsafeApi + +/** + * Run task for executing Kotlin/Jvm code. + * Shall be configured using the [KotlinJvmRunDsl] + * + * ### IDE carrier task: + * This task can act as 'carrier' task for IDE run gutters: + * It accepts configurable 'mainClass' when a Gradle property is provided as + * `.mainClass=...` + * + * e.g. + * ``` + * ./gradlew runJvm // <- executes the mainClass that is configured by default by the user + * ./gradlew runJvm -PrunJvm.mainClass="FooKt" // <- executes FooKt.main + * ./gradlew runJvm -PmainClass="FooKt" // <- excutes FooKt.main + * ``` + */ +@InternalKotlinGradlePluginApi +open class KotlinJvmRun : JavaExec() + +@ExperimentalKotlinGradlePluginApi +interface KotlinJvmRunDsl { + /** + * ## See [JavaExec.mainClass] + */ + val mainClass: Property + + /** + * ## See [JavaExec.args] + */ + fun args(vararg args: Any) + + /** + * ## See [JavaExec.args] + */ + fun args(args: Iterable<*>) + + /** + * ## See [JavaExec.setArgs] + */ + fun setArgs(args: Iterable<*>) + + /** + * ## See [JavaExec.classpath] + */ + fun classpath(vararg paths: Any) + + + /** + * ## See [JavaExec.setClasspath] + */ + fun setClasspath(classpath: FileCollection) + + /** + * Adds the runtime classpath of the given [compilation] to this run task + */ + fun classpath(compilation: KotlinCompilation<*>) +} + +internal suspend fun KotlinJvmTarget.registerMainRunTask(): KotlinJvmRunDslImpl? { + /* Await all inputs from user */ + KotlinPluginLifecycle.Stage.FinaliseDsl.await() + val mainCompilation = compilations.findByName("main") ?: return null + val taskName = lowerCamelCaseName(targetName, "run") + + if (taskName in project.tasks.names) { + /* Proper warning tbd */ + project.reportDiagnostic(KotlinJvmMainRunTaskConflict(targetName = name, taskName = taskName)) + return null + } + + return registerKotlinJvmRun(taskName, mainCompilation) +} + +private fun KotlinJvmTarget.registerKotlinJvmRun(taskName: String, compilation: KotlinJvmCompilation): KotlinJvmRunDslImpl { + val mainClass = project.objects.property() + val taskProvider = project.tasks.register(taskName, KotlinJvmRun::class.java) + + /* Convenience helper for telling older Gradle versions, that this provider is used at configuration time */ + val configurationTimePropertiesAccessor = project.configurationTimePropertiesAccessor + fun Provider.usedAtConfigurationTime() = usedAtConfigurationTime(configurationTimePropertiesAccessor) + + taskProvider.configure { task -> + task.group = "run" + task.description = "Jvm Run task for target '${compilation.target.name}' and compilation '${compilation.name}'. " + + "This task can act as carrier for the IDE to execute jvm based code" + + /** + * See [KotlinJvmRun]: This task will respect the '.mainClass' property over + * the DSL configuration + */ + task.mainClass.value( + project.providers.gradleProperty("$taskName.mainClass").usedAtConfigurationTime() + .orElse(project.providers.gradleProperty("mainClass").usedAtConfigurationTime()) + .orElse(project.providers.systemProperty("$taskName.mainClass").usedAtConfigurationTime()) + .orElse(project.providers.systemProperty("mainClass").usedAtConfigurationTime()) + .orElse(mainClass).usedAtConfigurationTime() + ) + } + + @OptIn(UnsafeApi::class) + return KotlinJvmRunDslImpl(taskProvider, mainClass) + .also { dsl -> dsl.classpath(compilation) } +} + +/** + * Use [registerKotlinJvmRun] to create an instance + */ +internal class KotlinJvmRunDslImpl @UnsafeApi constructor( + val task: TaskProvider, + override val mainClass: Property, +) : KotlinJvmRunDsl { + + override fun args(vararg args: Any) { + task.configure { it.args(*args) } + } + + override fun args(args: Iterable<*>) { + task.configure { it.args(args) } + } + + override fun setArgs(args: Iterable<*>) { + task.configure { it.setArgs(args) } + } + + override fun classpath(vararg paths: Any) { + task.configure { it.classpath(*paths) } + } + + override fun setClasspath(classpath: FileCollection) { + task.configure { it.setClasspath(classpath) } + } + + override fun classpath(compilation: KotlinCompilation<*>) { + task.configure { + it.classpath(compilation.output.allOutputs) + it.classpath(compilation.internal.configurations.runtimeDependencyConfiguration) + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/KotlinJvmRunTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/KotlinJvmRunTest.kt new file mode 100644 index 00000000000..7490a5f8e0b --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/KotlinJvmRunTest.kt @@ -0,0 +1,165 @@ +/* + * Copyright 2010-2023 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. + */ + +@file:Suppress("FunctionName") + +package org.jetbrains.kotlin.gradle.unitTests + +import org.gradle.api.tasks.JavaExec +import org.jetbrains.kotlin.gradle.dependencyResolutionTests.mavenCentralCacheRedirector +import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension +import org.jetbrains.kotlin.gradle.plugin.configurationResult +import org.jetbrains.kotlin.gradle.plugin.launch +import org.jetbrains.kotlin.gradle.util.* +import kotlin.test.* + +class KotlinJvmRunTest { + @Test + fun `test - simple jvm target`() = buildProjectWithMPP().runLifecycleAwareTest { + val kotlin = project.multiplatformExtension + assertNotNull(kotlin.jvm()) + project.configurationResult.await() + + val task = project.tasks.findByName("jvmRun") ?: fail("Missing 'jvmRun' run task") + if (task !is JavaExec) fail("Expected $task to implement '${JavaExec::class}'") + assertNoDiagnostics() + } + + @Test + fun `test - mainClass - set via DSL`() = buildProjectWithMPP().runLifecycleAwareTest { + val kotlin = project.multiplatformExtension + val mainRun = assertNotNull(kotlin.jvm().mainRun.await()) + val mainRunTask = mainRun.task.get() + + assertNull( + mainRunTask.mainClass.orNull, + "Expected mainClass to be null: No mainClass set via DSL or property" + ) + + // Set mainClass via DSL + mainRun.mainClass.set("Foo") + assertEquals("Foo", mainRunTask.mainClass.orNull) + } + + + @Test + fun `test - args`() = buildProjectWithMPP().runLifecycleAwareTest { + val kotlin = project.multiplatformExtension + val task = assertNotNull(kotlin.jvm().mainRun.await()).task.get() + + /* Set properties using 'args()' */ + kotlin.jvm().mainRun { + args("first", "second") + args(listOf("third", "fourth")) + } + + launch { + assertEquals(listOf("first", "second", "third", "fourth"), task.args) + } + + + /* Set property using .setArgs */ + kotlin.jvm().mainRun { + setArgs(listOf("1", "2")) + } + + launch { + assertEquals(listOf("1", "2"), task.args) + } + } + + @Test + fun `test - classpath - contains main compilation output by default`() = buildProjectWithMPP().runLifecycleAwareTest { + repositories.mavenLocal() + repositories.mavenCentralCacheRedirector() + + val kotlin = project.multiplatformExtension + val mainRunTask = assertNotNull(kotlin.jvm().mainRun.await()).task.get() + val mainCompilation = kotlin.jvm().compilations.main + + configurationResult.await() + + mainCompilation.output.allOutputs.files.ifEmpty { fail("Expected some file in 'allOutputs'") } + if (!mainRunTask.classpath.files.containsAll(mainCompilation.output.allOutputs.files)) { + fail("Missing output from main compilation in '$mainRunTask'") + } + } + + @Test + fun `test - classpath - contains main compilation runtime dependencies`() = buildProjectWithMPP().runLifecycleAwareTest { + repositories.mavenLocal() + repositories.mavenCentralCacheRedirector() + + val kotlin = project.multiplatformExtension + + kotlin.jvm().compilations.main.defaultSourceSet.dependencies { + implementation(project.files("implementation.jar")) + compileOnly(project.files("compileOnly.jar")) + runtimeOnly(project.files("runtimeOnly.jar")) + } + + + configurationResult.await() + val mainRunTask = assertNotNull(kotlin.jvm().mainRun.await()?.task?.get()) + + if (project.file("implementation.jar") !in mainRunTask.classpath) { + fail("Missing file from 'implementation' scope") + } + + if (project.file("runtimeOnly.jar") !in mainRunTask.classpath) { + fail("Missing file from 'runtimeOnly' scope") + } + + if (project.file("compileOnly.jar") in mainRunTask.classpath) { + fail("Unexpected file from 'compileOnly' scope") + } + } + + @Test + fun `test - classpath - add file`() = buildProjectWithMPP().runLifecycleAwareTest { + repositories.mavenLocal() + repositories.mavenCentralCacheRedirector() + val kotlin = project.multiplatformExtension + kotlin.jvm().mainRun { classpath(file("custom.jar")) } + + configurationResult.await() + + val task = assertNotNull(kotlin.jvm().mainRun.await()).task.get() + if (file("custom.jar") !in task.classpath.files) { + fail("Missing custom.jar in classpath") + } + + if (task.classpath.files.size <= 1) { + fail("Expected more files in classpath than just custom.jar") + } + } + + @Test + fun `test - setClasspath`() = buildProjectWithMPP().runLifecycleAwareTest { + repositories.mavenLocal() + repositories.mavenCentralCacheRedirector() + val kotlin = project.multiplatformExtension + kotlin.jvm().mainRun { + setClasspath(project.files("a.jar", "b.jar")) + } + + project.configurationResult.await() + assertEquals( + project.files("a.jar", "b.jar").files, + kotlin.jvm().mainRun.await()?.task?.get()?.classpath?.files + ) + } + + @Test + fun `test - jvmRun task is already registered`() = buildProjectWithMPP().runLifecycleAwareTest { + val kotlin = multiplatformExtension + kotlin.jvm() + tasks.register("jvmRun") + configurationResult.await() + checkDiagnostics("jvmRunTask-conflict") + } + +} + diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/resources/expectedDiagnostics/jvmRunTask-conflict.txt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/resources/expectedDiagnostics/jvmRunTask-conflict.txt new file mode 100644 index 00000000000..a4e9e0f726d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/resources/expectedDiagnostics/jvmRunTask-conflict.txt @@ -0,0 +1 @@ +[KotlinJvmMainRunTaskConflict | WARNING] Target 'jvm': Unable to create run task 'jvmRun' as there is already such a task registered