Enable kotlin java toolchain support for kapt task.

With this change 'KaptWithoutKotlincTask' will also support overriding
default Gradle JDK to run kapt. In such case only 'in-process' kapt
 worker isolation mode is supported.

If user does not provide custom JDK, kapt worker isolation mode will
be 'no-isolation' as before.

^KT-45611 In Progress
This commit is contained in:
Yahor Berdnikau
2021-05-04 17:21:36 +02:00
parent afa1b8bfdc
commit c6fc393417
16 changed files with 255 additions and 48 deletions
@@ -11,6 +11,7 @@ import org.gradle.testkit.runner.BuildResult
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.testbase.*
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import java.io.File
class KotlinJavaToolchainTest : KGPBaseTest() {
@@ -106,6 +107,112 @@ class KotlinJavaToolchainTest : KGPBaseTest() {
}
}
@DisplayName("Kapt task should use only process worker isolation when kotlin java toolchain is set")
@GradleTest
internal fun kaptTasksShouldUseProcessWorkersIsolation(gradleVersion: GradleVersion) {
project(
projectName = "simpleWithKapt".fullProjectName,
gradleVersion = gradleVersion,
) {
useJdk9ToCompile()
gradleProperties.append(
"kapt.workers.isolation = none"
)
build("assemble") {
assertDaemonIsUsingJdk(getJdk9().javaExecutableRealPath)
assertOutputContains("Using workers PROCESS isolation mode to run kapt")
assertOutputContains("Using non-default Kotlin java toolchain - 'kapt.workers.isolation == none' property is ignored!")
}
}
}
@DisplayName("Kapt task should use worker no-isolation mode when build is using Gradle JDK")
@GradleTest
internal fun kaptTasksShouldUseNoIsolationModeOnDefaultJvm(gradleVersion: GradleVersion) {
project(
projectName = "simpleWithKapt".fullProjectName,
gradleVersion = gradleVersion,
) {
build("assemble") {
assertOutputContains("Using workers NONE isolation mode to run kapt")
assertOutputDoesNotContain("Using non-default Kotlin java toolchain - 'kapt.workers.isolation == none' property is ignored!")
}
}
}
@DisplayName("Kapt tasks with custom JDK should be cacheable")
@GradleTest
internal fun kaptTasksWithCustomJdkCacheable(gradleVersion: GradleVersion) {
val buildCache = workingDir.resolve("custom-jdk-build-cache")
project(
projectName = "simpleWithKapt".fullProjectName,
gradleVersion = gradleVersion,
projectPathAdditionalSuffix = "1/cache-test",
buildOptions = defaultBuildOptions.copy(buildCacheEnabled = true)
) {
enableLocalBuildCache(buildCache)
useJdk9ToCompile()
build("assemble")
}
project(
projectName = "simpleWithKapt".fullProjectName,
gradleVersion = gradleVersion,
projectPathAdditionalSuffix = "2/cache-test",
buildOptions = defaultBuildOptions.copy(buildCacheEnabled = true),
) {
enableLocalBuildCache(buildCache)
useJdk9ToCompile()
build("assemble") {
assertTasksFromCache(
":kaptGenerateStubsKotlin",
":kaptKotlin",
":compileKotlin"
)
}
}
}
@DisplayName("Kapt tasks with default JDK and different isolation modes should be cacheable")
@GradleTest
internal fun kaptCacheableOnSwitchingIsolationModeAndDefaultJDK(gradleVersion: GradleVersion) {
val buildCache = workingDir.resolve("custom-jdk-build-cache")
project(
projectName = "simpleWithKapt".fullProjectName,
gradleVersion = gradleVersion,
projectPathAdditionalSuffix = "1/cache-test",
buildOptions = defaultBuildOptions.copy(buildCacheEnabled = true)
) {
enableLocalBuildCache(buildCache)
build("assemble")
}
project(
projectName = "simpleWithKapt".fullProjectName,
gradleVersion = gradleVersion,
projectPathAdditionalSuffix = "2/cache-test",
buildOptions = defaultBuildOptions.copy(buildCacheEnabled = true),
) {
enableLocalBuildCache(buildCache)
gradleProperties.append(
"kapt.workers.isolation = process"
)
build("assemble") {
assertTasksFromCache(
":kaptGenerateStubsKotlin",
":kaptKotlin",
":compileKotlin"
)
}
}
}
private fun BuildResult.assertDaemonIsUsingJdk(
javaexecPath: String
) = assertOutputContains("i: connected to the daemon. Daemon is using following 'java' executable to run itself: $javaexecPath")
@@ -18,6 +18,7 @@ internal val DEFAULT_GROOVY_SETTINGS_FILE =
plugins {
id "org.jetbrains.kotlin.jvm" version "${'$'}kotlin_version"
id "org.jetbrains.kotlin.kapt" version "${'$'}kotlin_version"
}
}
""".trimIndent()
@@ -0,0 +1,18 @@
plugins {
id "org.jetbrains.kotlin.jvm"
id "org.jetbrains.kotlin.kapt"
id "java"
}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
implementation "org.jetbrains.kotlin:annotation-processor-example:$kotlin_version"
kapt "org.jetbrains.kotlin:annotation-processor-example:$kotlin_version"
testImplementation 'junit:junit:4.12'
}
compileKotlin.kotlinOptions.allWarningsAsErrors = true
@@ -0,0 +1,5 @@
package foo
internal class InternalDummy {
internal val x = "InternalDummy.x"
}
@@ -0,0 +1,7 @@
package foo
internal class InternalDummyUser {
internal fun use(dummy: InternalDummy) {
if (dummy.x != "InternalDummy.x") throw AssertionError("dummy.x = ${dummy.x}")
}
}
@@ -0,0 +1,28 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package javaPackage;
public class JavaClass {
public void test() {
System.out.println(example.TestClassGenerated.class.getName());
System.out.println(example.SourceAnnotatedTestClassGenerated.class.getName());
System.out.println(example.BinaryAnnotatedTestClassGenerated.class.getName());
System.out.println(example.RuntimeAnnotatedTestClassGenerated.class.getName());
}
}
@@ -0,0 +1,15 @@
package example
@example.ExampleAnnotation
@example.ExampleSourceAnnotation
@example.ExampleBinaryAnnotation
@example.ExampleRuntimeAnnotation
public class TestClass {
@example.ExampleAnnotation
public val testVal: String = "text"
@example.ExampleAnnotation
public fun testFunction(): TestClassGenerated = TestClassGenerated()
}
@@ -0,0 +1,12 @@
package example;
import org.junit.Assert;
import org.junit.Test;
public class JavaTest {
@Test
public void test() {
TestClass testClass = new TestClass();
Assert.assertEquals("text", testClass.getTestVal());
}
}
@@ -0,0 +1,12 @@
package foo
import org.junit.Test
class InternalDummyTest {
@Test
fun testDummy() {
val dummy = InternalDummy()
val dummyUser = InternalDummyUser()
dummyUser.use(dummy)
}
}
@@ -4,9 +4,11 @@ import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileCollection
import org.gradle.api.internal.ConventionTask
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.gradle.api.tasks.incremental.IncrementalTaskInputs
import org.jetbrains.kotlin.build.report.metrics.BuildMetricsReporter
@@ -17,10 +19,11 @@ import org.jetbrains.kotlin.gradle.internal.kapt.incremental.KaptIncrementalChan
import org.jetbrains.kotlin.gradle.internal.kapt.incremental.UnknownSnapshot
import org.jetbrains.kotlin.gradle.internal.tasks.TaskConfigurator
import org.jetbrains.kotlin.gradle.internal.tasks.TaskWithLocalState
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.tasks.*
import org.jetbrains.kotlin.gradle.tasks.cacheOnlyIfEnabledForKotlin
import org.jetbrains.kotlin.gradle.tasks.clearLocalState
import org.jetbrains.kotlin.gradle.utils.getValue
import org.jetbrains.kotlin.gradle.utils.propertyWithNewInstance
import java.io.File
import java.util.concurrent.Callable
import java.util.jar.JarFile
@@ -28,9 +31,10 @@ import javax.inject.Inject
@CacheableTask
abstract class KaptTask @Inject constructor(
@get:Internal
protected val providerFactory: ProviderFactory
): ConventionTask(), TaskWithLocalState {
objectFactory: ObjectFactory
) : ConventionTask(),
TaskWithLocalState,
UsesKotlinJavaToolchain {
open class Configurator<T: KaptTask>(protected val kotlinCompileTask: KotlinCompile) : TaskConfigurator<T> {
override fun configure(task: T) {
@@ -117,6 +121,9 @@ abstract class KaptTask @Inject constructor(
@get:Internal
abstract val classpath: ConfigurableFileCollection
final override val kotlinJavaToolchainProvider: Provider<KotlinJavaToolchainProvider> =
objectFactory.propertyWithNewInstance()
@Suppress("unused", "DeprecatedCallableAddReplaceWith")
@Deprecated(
message = "Don't use directly. Used only for up-to-date checks",
@@ -8,7 +8,6 @@ package org.jetbrains.kotlin.gradle.internal
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.gradle.api.tasks.incremental.IncrementalTaskInputs
@@ -22,17 +21,14 @@ import org.jetbrains.kotlin.gradle.logging.GradleKotlinLogger
import org.jetbrains.kotlin.gradle.logging.GradlePrintingMessageCollector
import org.jetbrains.kotlin.gradle.report.ReportingSettings
import org.jetbrains.kotlin.gradle.tasks.*
import org.jetbrains.kotlin.gradle.utils.*
import org.jetbrains.kotlin.gradle.utils.toSortedPathsArray
import java.io.File
import javax.inject.Inject
abstract class KaptWithKotlincTask @Inject constructor(
objectFactory: ObjectFactory,
providerFactory: ProviderFactory
) : KaptTask(providerFactory),
CompilerArgumentAwareWithInput<K2JVMCompilerArguments>,
UsesKotlinJavaToolchain{
objectFactory: ObjectFactory
) : KaptTask(objectFactory),
CompilerArgumentAwareWithInput<K2JVMCompilerArguments> {
class Configurator(kotlinCompileTask: KotlinCompile): KaptTask.Configurator<KaptWithKotlincTask>(kotlinCompileTask) {
override fun configure(task: KaptWithKotlincTask) {
@@ -56,9 +52,6 @@ abstract class KaptWithKotlincTask @Inject constructor(
@get:Internal
val taskProvider by lazy { GradleCompileTaskProvider(this) }
final override val kotlinJavaToolchainProvider: Provider<KotlinJavaToolchainProvider> =
objectFactory.propertyWithNewInstance()
override fun createCompilerArgs(): K2JVMCompilerArguments = K2JVMCompilerArguments()
@get:Internal
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.gradle.internal
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.*
@@ -23,7 +24,6 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinAndroidPluginWrapper
import org.jetbrains.kotlin.gradle.tasks.CompilerPluginOptions
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.tasks.findKotlinStdlibClasspath
import org.jetbrains.kotlin.gradle.tasks.findToolsJar
import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast
import org.jetbrains.kotlin.utils.PathUtil
import org.slf4j.LoggerFactory
@@ -34,9 +34,10 @@ import java.net.URLClassLoader
import javax.inject.Inject
abstract class KaptWithoutKotlincTask @Inject constructor(
providerFactory: ProviderFactory,
objectFactory: ObjectFactory,
private val providerFactory: ProviderFactory,
private val workerExecutor: WorkerExecutor
) : KaptTask(providerFactory) {
) : KaptTask(objectFactory) {
class Configurator(kotlinCompileTask: KotlinCompile): KaptTask.Configurator<KaptWithoutKotlincTask>(kotlinCompileTask) {
override fun configure(task: KaptWithoutKotlincTask) {
@@ -157,15 +158,9 @@ abstract class KaptWithoutKotlincTask @Inject constructor(
}
val kaptClasspath = kaptJars.toList() + kotlinStdlibClasspath
//TODO for gradle < 6.5
val isolationModeStr = getValue("kapt.workers.isolation") ?: "none"
val isolationMode = when (isolationModeStr.toLowerCase()) {
"process" -> IsolationMode.PROCESS
"none" -> IsolationMode.NONE
else -> IsolationMode.NONE
}
val toolsJarURLSpec = findToolsJar()?.toURI()?.toURL()?.toString().orEmpty()
val isolationMode = getWorkerIsolationMode()
logger.info("Using workers $isolationMode isolation mode to run kapt")
val toolsJarURLSpec = kotlinJavaToolchainProvider.get().jdkToolsJar.orNull?.toURI()?.toURL()?.toString().orEmpty()
submitWork(
isolationMode,
@@ -175,6 +170,25 @@ abstract class KaptWithoutKotlincTask @Inject constructor(
)
}
private fun getWorkerIsolationMode(): IsolationMode {
val kotlinJavaToolchain = kotlinJavaToolchainProvider.get()
val gradleJvm = kotlinJavaToolchain.currentJvm.get()
// Ensuring Gradle build JDK is set to kotlin toolchain by also comparing javaExecutable paths,
// as user may set JDK with same major Java version, but from different vendor
val isRunningOnGradleJvm = gradleJvm.javaVersion == kotlinJavaToolchain.javaVersion.get() &&
gradleJvm.javaExecutable.absolutePath == kotlinJavaToolchain.javaExecutable.get().asFile.absolutePath
val isolationModeStr = getValue("kapt.workers.isolation")?.toLowerCase()
return when {
(isolationModeStr == null || isolationModeStr == "none") && isRunningOnGradleJvm -> IsolationMode.NONE
else -> {
if (isolationModeStr == "none") {
logger.warn("Using non-default Kotlin java toolchain - 'kapt.workers.isolation == none' property is ignored!")
}
IsolationMode.PROCESS
}
}
}
private fun submitWork(
isolationMode: IsolationMode,
optionsForWorker: KaptOptionsForWorker,
@@ -187,14 +201,13 @@ abstract class KaptWithoutKotlincTask @Inject constructor(
// for tests
it.forkOptions.jvmArgs("-verbose:class")
}
logger.info("Kapt worker classpath: ${it.classpath}")
}
IsolationMode.CLASSLOADER -> workerExecutor.classLoaderIsolation() {
it.forkOptions.executable = kotlinJavaToolchainProvider.get().javaExecutable.asFile.get().absolutePath
logger.info("Kapt worker classpath: ${it.classpath}")
}
IsolationMode.NONE -> workerExecutor.noIsolation()
IsolationMode.AUTO -> throw UnsupportedOperationException(
"Kapt worker compilation does not support $isolationMode"
IsolationMode.AUTO, IsolationMode.CLASSLOADER -> throw UnsupportedOperationException(
"Kapt worker compilation does not support class loader isolation. " +
"Please use either \"none\" or \"process\" in gradle.properties."
)
}
@@ -24,7 +24,8 @@ abstract class KotlinJavaToolchainProvider @Inject constructor(
objects: ObjectFactory,
files: ProjectLayout
) : KotlinJavaToolchain {
private val currentJvm: Property<Jvm> = objects
@get:Internal
internal val currentJvm: Property<Jvm> = objects
.property<Jvm>(Jvm.current())
.chainedFinalizeValueOnRead()
@@ -16,7 +16,6 @@
package org.jetbrains.kotlin.gradle.tasks
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ResolvedDependency
@@ -104,21 +103,6 @@ internal fun findKotlinScriptRuntimeClasspath(project: Project): List<File> =
internal fun findKotlinReflectClasspath(project: Project): List<File> =
findKotlinModuleJar(project, KOTLIN_REFLECT_EXPECTED_CLASS, KOTLIN_REFLECT)
internal fun findToolsJar(): File? {
val javacUtilContextClass =
try {
Class.forName("com.sun.tools.javac.util.Context")
} catch (classNotFound: ClassNotFoundException) {
val javaHome = System.getProperty("java.home") // current Java installation path
throw GradleException(
"Kotlin could not find the required JDK tools in the Java installation ${javaHome?.let { "'$it' " }.orEmpty()}" +
"used by Gradle. Make sure Gradle is running on a JDK, not JRE.",
classNotFound
)
}
return javacUtilContextClass?.let(::findJarByClass)
}
internal fun findJarByClass(klass: Class<*>): File? {
val classFileName = klass.name.substringAfterLast(".") + ".class"
val resource = klass.getResource(classFileName) ?: return null