[Gradle] Implement KotlinJvmWithJavaAndroidCompatibilityChecker to fail when 'withJava' is used together with AGP

As both (AGP and 'withJava') are using project global names like
'main' and 'test' which will lead to clashes  (e.g. configuration names
are the same for both)

^KT-59595 Verification Pending
This commit is contained in:
Sebastian Sellmair
2023-07-04 13:00:10 +02:00
committed by Space Team
parent 1e87ba3485
commit 7f3970fb0f
7 changed files with 114 additions and 6 deletions
@@ -113,7 +113,7 @@ internal interface KotlinGradleProjectChecker {
KotlinSourceSetTreeDependsOnMismatchChecker,
PlatformSourceSetConventionsChecker,
AndroidMainSourceSetConventionsChecker,
IosSourceSetConventionChecker
IosSourceSetConventionChecker,
)
}
}
@@ -530,6 +530,22 @@ object KotlinToolingDiagnostics {
""".trimIndent()
)
}
object JvmWithJavaIsIncompatibleWithAndroid : ToolingDiagnosticFactory(FATAL) {
operator fun invoke(androidPluginId: String, trace: Throwable?) = build(
"""
'withJava()' is not compatible with Android Plugins
Incompatible Android Plugin applied: '$androidPluginId'
kotlin {
jvm {
withJava() /* <- cannot be used when the Android Plugin is present */
}
}
""".trimIndent(),
throwable = trace
)
}
}
private fun String.indentLines(nSpaces: Int = 4, skipFirstLine: Boolean = true): String {
@@ -68,12 +68,15 @@ internal abstract class KotlinToolingDiagnosticsCollector : BuildService<BuildSe
return
}
if (diagnostic.severity == ToolingDiagnostic.Severity.FATAL) {
throw InvalidUserCodeException(diagnostic.message)
}
rawDiagnosticsFromProject.compute(project.path) { _, previousListIfAny ->
previousListIfAny?.apply { add(diagnostic) } ?: mutableListOf(diagnostic)
}
if (diagnostic.severity == ToolingDiagnostic.Severity.FATAL) {
if (diagnostic.throwable != null)
throw InvalidUserCodeException(diagnostic.message, diagnostic.throwable)
else throw InvalidUserCodeException(diagnostic.message)
}
}
}
@@ -10,9 +10,7 @@ import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.Project
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.internal.tasks.JvmConstants
import org.gradle.api.plugins.JavaBasePlugin
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.compile.AbstractCompile
import org.gradle.language.jvm.tasks.ProcessResources
@@ -21,6 +19,8 @@ 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.KotlinPluginLifecycle.Stage.AfterFinaliseDsl
import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics
import org.jetbrains.kotlin.gradle.plugin.diagnostics.reportDiagnostic
import org.jetbrains.kotlin.gradle.plugin.internal.JavaSourceSetsAccessor
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinOnlyTarget
@@ -32,6 +32,7 @@ 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.findAppliedAndroidPluginIdOrNull
import org.jetbrains.kotlin.gradle.utils.future
import org.jetbrains.kotlin.utils.addToStdlib.cast
import java.util.concurrent.Callable
@@ -92,6 +93,7 @@ abstract class KotlinJvmTarget @Inject constructor(
var withJavaEnabled = false
private set
@Suppress("unused") // user DSL
fun withJava() {
if (withJavaEnabled)
@@ -105,6 +107,25 @@ abstract class KotlinJvmTarget @Inject constructor(
)
}
/**
* Reports diagnostic in the case of
* ```kotlin
* kotlin {
* jvm().withJava()
* }
* ```
*
* is used together with the Android Gradle Plugin.
* This case is incompatible so far, as the 'withJava' implementation is still using 'global' namespaces
* (like main/test, etc), which will clash with the global names used by AGP (also occupying main, test, etc).
*/
val trace = Throwable()
project.launchInStage(AfterFinaliseDsl) check@{
val androidPluginId = project.findAppliedAndroidPluginIdOrNull() ?: return@check
project.reportDiagnostic(KotlinToolingDiagnostics.JvmWithJavaIsIncompatibleWithAndroid(androidPluginId, trace))
}
withJavaEnabled = true
project.plugins.apply(JavaBasePlugin::class.java)
@@ -0,0 +1,52 @@
/*
* 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.diagnosticsTests
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.util.androidApplication
import org.jetbrains.kotlin.gradle.util.androidLibrary
import org.jetbrains.kotlin.gradle.util.buildProjectWithMPP
import org.jetbrains.kotlin.gradle.util.checkDiagnostics
import kotlin.test.Test
import kotlin.test.assertFails
class JvmWithJavaIsIncompatibleWithAndroidDiagnosticTest {
@Test
fun `test - withJava and android library`() {
val project = buildProjectWithMPP()
project.androidLibrary { compileSdk = 33 }
project.multiplatformExtension.jvm().withJava()
project.multiplatformExtension.androidTarget()
assertFails { project.evaluate() }
project.checkDiagnostics("JvmWithJavaIsIncompatibleWithAndroid-withJava-withAndroidLibrary")
}
@Test
fun `test - withJava and android application`() {
val project = buildProjectWithMPP()
project.androidApplication { compileSdk = 33 }
project.multiplatformExtension.jvm().withJava()
project.multiplatformExtension.androidTarget()
assertFails { project.evaluate() }
project.checkDiagnostics("JvmWithJavaIsIncompatibleWithAndroid-withJava-withAndroidApplication")
}
@Test
fun `test - withJava - without android plugin`() {
val project = buildProjectWithMPP()
project.androidLibrary { compileSdk = 33 }
project.multiplatformExtension.jvm()
project.multiplatformExtension.androidTarget()
project.evaluate()
project.checkDiagnostics("JvmWithJavaIsIncompatibleWithAndroid-withJava-withoutAndroidPlugin")
}
}
@@ -0,0 +1,8 @@
[JvmWithJavaIsIncompatibleWithAndroid | FATAL] 'withJava()' is not compatible with Android Plugins
Incompatible Android Plugin applied: 'com.android.application'
kotlin {
jvm {
withJava() /* <- cannot be used when the Android Plugin is present */
}
}
@@ -0,0 +1,8 @@
[JvmWithJavaIsIncompatibleWithAndroid | FATAL] 'withJava()' is not compatible with Android Plugins
Incompatible Android Plugin applied: 'com.android.library'
kotlin {
jvm {
withJava() /* <- cannot be used when the Android Plugin is present */
}
}