diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinJavaToolchainTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinJavaToolchainTest.kt index 02d5b08ab53..b0174c6c6ca 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinJavaToolchainTest.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinJavaToolchainTest.kt @@ -34,28 +34,6 @@ class KotlinJavaToolchainTest : KGPBaseTest() { } } - @GradleTestVersions(minVersion = "6.7.1") - @GradleTest - @DisplayName("Default Kotlin toolchain should still allow to set Java source and target compatibility") - internal fun shouldNotFailWithDefaultJdkAndCompatibility(gradleVersion: GradleVersion) { - project( - projectName = "simple".fullProjectName, - gradleVersion = gradleVersion - ) { - //language=Groovy - rootBuildGradle.append( - """ - - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - """.trimIndent() - ) - - build("assemble") - } - } - - @GradleTestVersions(maxVersion = "6.6.1") @GradleTest @DisplayName("Should use provided jdk location to compile Kotlin sources") internal fun customJdkHomeLocation(gradleVersion: GradleVersion) { @@ -63,6 +41,8 @@ class KotlinJavaToolchainTest : KGPBaseTest() { projectName = "simple".fullProjectName, gradleVersion = gradleVersion, ) { + setJavaCompilationCompatibility(JavaVersion.VERSION_1_9) + useJdkToCompile( getJdk9Path(), JavaVersion.VERSION_1_9 @@ -88,6 +68,7 @@ class KotlinJavaToolchainTest : KGPBaseTest() { if (shouldUseToolchain(gradleVersion)) { useToolchainExtension(11) } else { + setJavaCompilationCompatibility(JavaVersion.VERSION_1_9) useJdkToCompile( getJdk9Path(), JavaVersion.VERSION_1_9 @@ -107,6 +88,7 @@ class KotlinJavaToolchainTest : KGPBaseTest() { if (shouldUseToolchain(gradleVersion)) { useToolchainExtension(11) } else { + setJavaCompilationCompatibility(JavaVersion.VERSION_1_9) useJdkToCompile( getJdk9Path(), JavaVersion.VERSION_1_9 @@ -134,6 +116,7 @@ class KotlinJavaToolchainTest : KGPBaseTest() { if (shouldUseToolchain(gradleVersion)) { useToolchainExtension(11) } else { + setJavaCompilationCompatibility(JavaVersion.VERSION_1_9) useJdkToCompile( getJdk9Path(), JavaVersion.VERSION_1_9 @@ -440,6 +423,173 @@ class KotlinJavaToolchainTest : KGPBaseTest() { } } + @DisplayName("Should produce warning if java and kotlin jvm targets are different") + @GradleTestVersions(minVersion = "6.7.1") + @GradleTest + internal fun shouldWarnIfJavaAndKotlinJvmTargetsAreDifferent(gradleVersion: GradleVersion) { + project( + projectName = "kotlinJavaProject".fullProjectName, + gradleVersion = gradleVersion, + buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.WARN) + ) { + setJavaCompilationCompatibility(JavaVersion.VERSION_1_8) + useToolchainToCompile(11) + + build("assemble") { + assertOutputContains( + "'compileJava' task (current target is 1.8) and 'compileKotlin' task (current target is 11) jvm target compatibility " + + "should be set to the same Java version." + ) + } + } + } + + @DisplayName("Should fail the build if verification mode is 'error' and kotlin and java targets are different") + @GradleTestVersions(minVersion = "6.7.1") + @GradleTest + internal fun shouldFailBuildIfJavaAndKotlinJvmTargetsAreDifferent(gradleVersion: GradleVersion) { + project( + projectName = "kotlinJavaProject".fullProjectName, + gradleVersion = gradleVersion + ) { + setJavaCompilationCompatibility(JavaVersion.VERSION_1_8) + useToolchainToCompile(11) + //language=properties + gradleProperties.append( + """ + kotlin.jvm.target.validation.mode = error + """.trimIndent() + ) + + buildAndFail("assemble") { + assertOutputContains( + "'compileJava' task (current target is 1.8) and 'compileKotlin' task (current target is 11) jvm target compatibility " + + "should be set to the same Java version." + ) + } + } + } + + @DisplayName("Should ignore if verification mode is 'ignore' and kotlin and java targets are different") + @GradleTestVersions(minVersion = "6.7.1") + @GradleTest + internal fun shouldNotPrintAnythingIfJavaAndKotlinJvmTargetsAreDifferent( + gradleVersion: GradleVersion + ) { + project( + projectName = "kotlinJavaProject".fullProjectName, + gradleVersion = gradleVersion + ) { + setJavaCompilationCompatibility(JavaVersion.VERSION_1_8) + useToolchainToCompile(11) + //language=properties + gradleProperties.append( + """ + kotlin.jvm.target.validation.mode = ignore + """.trimIndent() + ) + + build("assemble") { + assertOutputDoesNotContain( + "'compileJava' task (current target is 1.8) and 'compileKotlin' task (current target is 11) jvm target compatibility " + + "should be set to the same Java version." + ) + } + } + } + + @DisplayName("Should not produce warning when java and kotlin jvm targets are the same") + @GradleTestVersions(minVersion = "6.7.1") + @GradleTest + internal fun shouldNotWarnOnJavaAndKotlinSameJvmTargets(gradleVersion: GradleVersion) { + project( + projectName = "kotlinJavaProject".fullProjectName, + gradleVersion = gradleVersion + ) { + useToolchainToCompile(11) + + build("build") { + assertOutputDoesNotContain( + "'compileJava' task (current target is 1.8) and 'compileKotlin' task (current target is 11) jvm target compatibility " + + "should be set to the same Java version." + ) + } + } + } + + @DisplayName("Should produce Java-Kotlin jvm target incompatibility warning only for related tasks") + @GradleTestVersions(minVersion = "6.7.1") + @GradleTest + internal fun shouldProduceJavaKotlinJvmTargetDifferenceWarningOnlyForRelatedTasks( + gradleVersion: GradleVersion + ) { + project( + projectName = "kotlinJavaProject".fullProjectName, + gradleVersion = gradleVersion + ) { + useToolchainToCompile(11) + + JavaVersion.VERSION_1_8 + //language=Groovy + rootBuildGradle.append( + """ + + tasks + .matching { + it instanceof JavaCompile && it.name == "compileTestJava" + } + .configureEach { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + """.trimIndent() + ) + + build("build") { + assertOutputContains( + "'compileTestJava' task (current target is 1.8) and 'compileTestKotlin' task (current target is 11) jvm target " + + "compatibility should be set to the same Java version." + ) + assertOutputDoesNotContain( + "'compileJava' task (current target is 1.8) and 'compileKotlin' task (current target is 11) jvm target compatibility " + + "should be set to the same Java version." + ) + } + } + } + + @DisplayName("Setting toolchain via java extension should also affect Kotlin compilations") + @GradleTestVersions(minVersion = "6.7.1") + @GradleTest + internal fun settingToolchainViaJavaShouldAlsoWork(gradleVersion: GradleVersion) { + project( + projectName = "kotlinJavaProject".fullProjectName, + gradleVersion = gradleVersion + ) { + //language=groovy + rootBuildGradle.append( + """ + + java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(11)) + } + } + + """.trimIndent() + ) + //language=properties + gradleProperties.append( + """ + kotlin.jvm.target.validation.mode = error + """.trimIndent() + ) + + build("build") + } + } + private fun BuildResult.assertJdkHomeIsUsingJdk( javaexecPath: String ) = assertOutputContains("[KOTLIN] Kotlin compilation 'jdkHome' argument: $javaexecPath") @@ -475,6 +625,22 @@ class KotlinJavaToolchainTest : KGPBaseTest() { ) } + private fun TestProject.setJavaCompilationCompatibility( + target: JavaVersion + ) { + //language=Groovy + rootBuildGradle.append( + """ + + tasks.withType(JavaCompile.class).configureEach { + sourceCompatibility = JavaVersion.${target.name} + targetCompatibility = JavaVersion.${target.name} + } + + """.trimIndent() + ) + } + private fun TestProject.useJdkToCompile( jdkPath: String, jdkVersion: JavaVersion diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-java-toolchain/kotlinJavaProject/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-java-toolchain/kotlinJavaProject/build.gradle new file mode 100644 index 00000000000..dc3ad8c8a0d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-java-toolchain/kotlinJavaProject/build.gradle @@ -0,0 +1,19 @@ +plugins { + id "org.jetbrains.kotlin.jvm" + id "java" +} + +repositories { + mavenLocal() + mavenCentral() +} + +dependencies { + implementation 'com.google.guava:guava:12.0' + + testImplementation 'org.testng:testng:6.8' +} + +test { + useTestNG() +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-java-toolchain/kotlinJavaProject/src/main/java/demo/Greeter.java b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-java-toolchain/kotlinJavaProject/src/main/java/demo/Greeter.java new file mode 100644 index 00000000000..ec2831d8d21 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-java-toolchain/kotlinJavaProject/src/main/java/demo/Greeter.java @@ -0,0 +1,17 @@ +package demo; + +/** + * Created by Nikita.Skvortsov + * Date: 3/1/13, 10:53 AM + */ +public class Greeter { + private final String myGreeting; + + public Greeter(String greeting) { + myGreeting = greeting; + } + + public String getGreeting() { + return myGreeting; + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-java-toolchain/kotlinJavaProject/src/main/java/demo/HelloWorld.java b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-java-toolchain/kotlinJavaProject/src/main/java/demo/HelloWorld.java new file mode 100644 index 00000000000..2bc6463319b --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-java-toolchain/kotlinJavaProject/src/main/java/demo/HelloWorld.java @@ -0,0 +1,18 @@ +package demo; + +/** + * Created by Nikita.Skvortsov + * Date: 3/1/13, 10:50 AM + */ +public class HelloWorld { + public static void main(String[] args) { + final KotlinGreetingJoiner example = new KotlinGreetingJoiner(new Greeter("Hi")); + + example.addName("Harry"); + example.addName("Ron"); + example.addName(null); + example.addName("Hermione"); + + System.out.println(example.getJoinedGreeting()); + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-java-toolchain/kotlinJavaProject/src/main/kotlin/helloWorld.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-java-toolchain/kotlinJavaProject/src/main/kotlin/helloWorld.kt new file mode 100644 index 00000000000..b3e99bbdd59 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-java-toolchain/kotlinJavaProject/src/main/kotlin/helloWorld.kt @@ -0,0 +1,20 @@ +package demo + +import com.google.common.primitives.Ints +import com.google.common.base.Joiner +import java.util.ArrayList + +class KotlinGreetingJoiner(val greeter : Greeter) { + + val names = ArrayList() + + fun addName(name : String?): Unit{ + names.add(name) + } + + fun getJoinedGreeting() : String? { + val joiner = Joiner.on(" and ").skipNulls(); + return "${greeter.getGreeting()} ${joiner.join(names)}" + } +} + diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-java-toolchain/kotlinJavaProject/src/test/java/demo/HelloWorldTest.java b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-java-toolchain/kotlinJavaProject/src/test/java/demo/HelloWorldTest.java new file mode 100644 index 00000000000..e6c590751ac --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-java-toolchain/kotlinJavaProject/src/test/java/demo/HelloWorldTest.java @@ -0,0 +1,4 @@ +package demo; + +public class HelloWorldTest { +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-java-toolchain/kotlinJavaProject/src/test/kotlin/tests.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-java-toolchain/kotlinJavaProject/src/test/kotlin/tests.kt new file mode 100644 index 00000000000..1a7a3a23b80 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-java-toolchain/kotlinJavaProject/src/test/kotlin/tests.kt @@ -0,0 +1,21 @@ +package demo + +import com.google.common.primitives.Ints +import com.google.common.base.Joiner +import org.testng.Assert.* +import org.testng.annotations.AfterMethod +import org.testng.annotations.BeforeMethod +import org.testng.annotations.Test as test + +class TestSource() { + @test fun f() { + val example : KotlinGreetingJoiner = KotlinGreetingJoiner(Greeter("Hi")) + example.addName("Harry") + example.addName("Ron") + example.addName(null) + example.addName("Hermione") + + assertEquals(example.getJoinedGreeting(), "Hi Harry and Ron and Hermione") + } +} + diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt index cd52c1ad844..9061daf6fd4 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt @@ -42,6 +42,7 @@ internal fun PropertiesProvider.mapKotlinTaskProperties(task: AbstractKotlinComp task.kotlinOptions.useFir = true } } + task.jvmTargetValidationMode.set(jvmTargetValidationMode) } if (task is Kotlin2JsCompile) { @@ -340,6 +341,13 @@ internal class PropertiesProvider private constructor(private val project: Proje val kotlinTestInferJvmVariant: Boolean get() = booleanProperty("kotlin.test.infer.jvm.variant") ?: true + enum class JvmTargetValidationMode { + IGNORE, WARNING, ERROR + } + + val jvmTargetValidationMode: JvmTargetValidationMode + get() = enumProperty("kotlin.jvm.target.validation.mode", JvmTargetValidationMode.WARNING) + private fun propertyWithDeprecatedVariant(propName: String, deprecatedPropName: String): String? { val deprecatedProperty = property(deprecatedPropName) if (deprecatedProperty != null) { @@ -351,6 +359,11 @@ internal class PropertiesProvider private constructor(private val project: Proje private fun booleanProperty(propName: String): Boolean? = property(propName)?.toBoolean() + private inline fun > enumProperty( + propName: String, + defaultValue: T + ): T = property(propName)?.let { enumValueOf(it.toUpperCase()) } ?: defaultValue + private fun property(propName: String): String? = if (project.hasProperty(propName)) { project.property(propName) as? String diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/DefaultKotlinJavaToolchain.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/DefaultKotlinJavaToolchain.kt index 47d696390f5..4242587b6c1 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/DefaultKotlinJavaToolchain.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/DefaultKotlinJavaToolchain.kt @@ -119,7 +119,6 @@ internal abstract class DefaultKotlinJavaToolchain @Inject constructor( get() = defaultJavaToolchainSetter ?: throw GradleException("Toolchain support is available from $TOOLCHAIN_SUPPORTED_VERSION") - private abstract class JvmTargetUpdater( private val kotlinCompileTaskProvider: () -> KotlinCompileTask? ) { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt index c779fdf4a79..cc6c3de5793 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.gradle.tasks +import org.gradle.api.GradleException import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.file.* @@ -46,6 +47,7 @@ import org.jetbrains.kotlin.gradle.logging.GradleKotlinLogger import org.jetbrains.kotlin.gradle.logging.GradlePrintingMessageCollector import org.jetbrains.kotlin.gradle.logging.kotlinDebug import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.plugin.mpp.* import org.jetbrains.kotlin.gradle.plugin.mpp.associateWithTransitiveClosure import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinCompilationData import org.jetbrains.kotlin.gradle.report.ReportingSettings @@ -428,6 +430,25 @@ abstract class KotlinCompile @Inject constructor( UsesKotlinJavaToolchain { class Configurator(kotlinCompilation: KotlinCompilationData<*>) : AbstractKotlinCompile.Configurator(kotlinCompilation) { + override fun configure(task: KotlinCompile) { + super.configure(task) + + val compileJavaTaskProvider = when (compilation) { + is KotlinJvmCompilation -> compilation.compileJavaTaskProvider + is KotlinJvmAndroidCompilation -> compilation.compileJavaTaskProvider + is KotlinWithJavaCompilation -> compilation.compileJavaTaskProvider + else -> null + } + + if (compileJavaTaskProvider != null) { + task.associatedJavaCompileTaskTargetCompatibility.set( + compileJavaTaskProvider.map { it.targetCompatibility } + ) + task.associatedJavaCompileTaskName.set( + compileJavaTaskProvider.map { it.name } + ) + } + } } @get:Internal @@ -482,6 +503,15 @@ abstract class KotlinCompile @Inject constructor( } ) + @get:Internal + internal abstract val associatedJavaCompileTaskTargetCompatibility: Property + + @get:Internal + internal abstract val associatedJavaCompileTaskName: Property + + @get:Internal + internal abstract val jvmTargetValidationMode: Property + init { incremental = true } @@ -511,6 +541,8 @@ abstract class KotlinCompile @Inject constructor( override fun callCompilerAsync(args: K2JVMCompilerArguments, sourceRoots: SourceRoots, changedFiles: ChangedFiles) { sourceRoots as SourceRoots.ForJvm + validateKotlinAndJavaHasSameTargetCompatibility(args) + val messageCollector = GradlePrintingMessageCollector(logger, args.allWarningsAsErrors) val outputItemCollector = OutputItemsCollectorImpl() val compilerRunner = compilerRunner.get() @@ -544,6 +576,23 @@ abstract class KotlinCompile @Inject constructor( ) } + private fun validateKotlinAndJavaHasSameTargetCompatibility(args: K2JVMCompilerArguments) { + associatedJavaCompileTaskTargetCompatibility.orNull?.let { targetCompatibility -> + val normalizedJavaVersion = if (targetCompatibility == "1.9") "9" else targetCompatibility + if (normalizedJavaVersion != args.jvmTarget) { + val javaTaskName = associatedJavaCompileTaskName.get() + val errorMessage = "'$javaTaskName' task (current target is $targetCompatibility) and " + + "'$name' task (current target is ${args.jvmTarget}) " + + "jvm target compatibility should be set to the same Java version." + when (jvmTargetValidationMode.get()) { + PropertiesProvider.JvmTargetValidationMode.ERROR -> throw GradleException(errorMessage) + PropertiesProvider.JvmTargetValidationMode.WARNING -> logger.warn(errorMessage) + else -> Unit + } + } + } + } + @get:Input val disableMultiModuleIC: Boolean by lazy { if (!isIncrementalCompilationEnabled() || !javaOutputDir.isPresent) {