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.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.testbase.* import org.jetbrains.kotlin.gradle.testbase.*
import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import java.io.File import java.io.File
class KotlinJavaToolchainTest : KGPBaseTest() { 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( private fun BuildResult.assertDaemonIsUsingJdk(
javaexecPath: String javaexecPath: String
) = assertOutputContains("i: connected to the daemon. Daemon is using following 'java' executable to run itself: $javaexecPath") ) = 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 { plugins {
id "org.jetbrains.kotlin.jvm" version "${'$'}kotlin_version" id "org.jetbrains.kotlin.jvm" version "${'$'}kotlin_version"
id "org.jetbrains.kotlin.kapt" version "${'$'}kotlin_version"
} }
} }
""".trimIndent() """.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.DirectoryProperty
import org.gradle.api.file.FileCollection import org.gradle.api.file.FileCollection
import org.gradle.api.internal.ConventionTask import org.gradle.api.internal.ConventionTask
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.ListProperty import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property import org.gradle.api.provider.Property
import org.gradle.api.provider.ProviderFactory import org.gradle.api.provider.ProviderFactory
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.* import org.gradle.api.tasks.*
import org.gradle.api.tasks.incremental.IncrementalTaskInputs import org.gradle.api.tasks.incremental.IncrementalTaskInputs
import org.jetbrains.kotlin.build.report.metrics.BuildMetricsReporter 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.kapt.incremental.UnknownSnapshot
import org.jetbrains.kotlin.gradle.internal.tasks.TaskConfigurator import org.jetbrains.kotlin.gradle.internal.tasks.TaskConfigurator
import org.jetbrains.kotlin.gradle.internal.tasks.TaskWithLocalState 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.cacheOnlyIfEnabledForKotlin
import org.jetbrains.kotlin.gradle.tasks.clearLocalState import org.jetbrains.kotlin.gradle.tasks.clearLocalState
import org.jetbrains.kotlin.gradle.utils.getValue import org.jetbrains.kotlin.gradle.utils.getValue
import org.jetbrains.kotlin.gradle.utils.propertyWithNewInstance
import java.io.File import java.io.File
import java.util.concurrent.Callable import java.util.concurrent.Callable
import java.util.jar.JarFile import java.util.jar.JarFile
@@ -28,9 +31,10 @@ import javax.inject.Inject
@CacheableTask @CacheableTask
abstract class KaptTask @Inject constructor( abstract class KaptTask @Inject constructor(
@get:Internal objectFactory: ObjectFactory
protected val providerFactory: ProviderFactory ) : ConventionTask(),
): ConventionTask(), TaskWithLocalState { TaskWithLocalState,
UsesKotlinJavaToolchain {
open class Configurator<T: KaptTask>(protected val kotlinCompileTask: KotlinCompile) : TaskConfigurator<T> { open class Configurator<T: KaptTask>(protected val kotlinCompileTask: KotlinCompile) : TaskConfigurator<T> {
override fun configure(task: T) { override fun configure(task: T) {
@@ -117,6 +121,9 @@ abstract class KaptTask @Inject constructor(
@get:Internal @get:Internal
abstract val classpath: ConfigurableFileCollection abstract val classpath: ConfigurableFileCollection
final override val kotlinJavaToolchainProvider: Provider<KotlinJavaToolchainProvider> =
objectFactory.propertyWithNewInstance()
@Suppress("unused", "DeprecatedCallableAddReplaceWith") @Suppress("unused", "DeprecatedCallableAddReplaceWith")
@Deprecated( @Deprecated(
message = "Don't use directly. Used only for up-to-date checks", 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.file.ConfigurableFileCollection
import org.gradle.api.model.ObjectFactory import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property import org.gradle.api.provider.Property
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.gradle.api.tasks.* import org.gradle.api.tasks.*
import org.gradle.api.tasks.incremental.IncrementalTaskInputs 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.logging.GradlePrintingMessageCollector
import org.jetbrains.kotlin.gradle.report.ReportingSettings import org.jetbrains.kotlin.gradle.report.ReportingSettings
import org.jetbrains.kotlin.gradle.tasks.* import org.jetbrains.kotlin.gradle.tasks.*
import org.jetbrains.kotlin.gradle.utils.*
import org.jetbrains.kotlin.gradle.utils.toSortedPathsArray import org.jetbrains.kotlin.gradle.utils.toSortedPathsArray
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
abstract class KaptWithKotlincTask @Inject constructor( abstract class KaptWithKotlincTask @Inject constructor(
objectFactory: ObjectFactory, objectFactory: ObjectFactory
providerFactory: ProviderFactory ) : KaptTask(objectFactory),
) : KaptTask(providerFactory), CompilerArgumentAwareWithInput<K2JVMCompilerArguments> {
CompilerArgumentAwareWithInput<K2JVMCompilerArguments>,
UsesKotlinJavaToolchain{
class Configurator(kotlinCompileTask: KotlinCompile): KaptTask.Configurator<KaptWithKotlincTask>(kotlinCompileTask) { class Configurator(kotlinCompileTask: KotlinCompile): KaptTask.Configurator<KaptWithKotlincTask>(kotlinCompileTask) {
override fun configure(task: KaptWithKotlincTask) { override fun configure(task: KaptWithKotlincTask) {
@@ -56,9 +52,6 @@ abstract class KaptWithKotlincTask @Inject constructor(
@get:Internal @get:Internal
val taskProvider by lazy { GradleCompileTaskProvider(this) } val taskProvider by lazy { GradleCompileTaskProvider(this) }
final override val kotlinJavaToolchainProvider: Provider<KotlinJavaToolchainProvider> =
objectFactory.propertyWithNewInstance()
override fun createCompilerArgs(): K2JVMCompilerArguments = K2JVMCompilerArguments() override fun createCompilerArgs(): K2JVMCompilerArguments = K2JVMCompilerArguments()
@get:Internal @get:Internal
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.gradle.internal package org.jetbrains.kotlin.gradle.internal
import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property import org.gradle.api.provider.Property
import org.gradle.api.provider.ProviderFactory import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.* 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.CompilerPluginOptions
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.tasks.findKotlinStdlibClasspath 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.gradle.utils.isGradleVersionAtLeast
import org.jetbrains.kotlin.utils.PathUtil import org.jetbrains.kotlin.utils.PathUtil
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@@ -34,9 +34,10 @@ import java.net.URLClassLoader
import javax.inject.Inject import javax.inject.Inject
abstract class KaptWithoutKotlincTask @Inject constructor( abstract class KaptWithoutKotlincTask @Inject constructor(
providerFactory: ProviderFactory, objectFactory: ObjectFactory,
private val providerFactory: ProviderFactory,
private val workerExecutor: WorkerExecutor private val workerExecutor: WorkerExecutor
) : KaptTask(providerFactory) { ) : KaptTask(objectFactory) {
class Configurator(kotlinCompileTask: KotlinCompile): KaptTask.Configurator<KaptWithoutKotlincTask>(kotlinCompileTask) { class Configurator(kotlinCompileTask: KotlinCompile): KaptTask.Configurator<KaptWithoutKotlincTask>(kotlinCompileTask) {
override fun configure(task: KaptWithoutKotlincTask) { override fun configure(task: KaptWithoutKotlincTask) {
@@ -157,15 +158,9 @@ abstract class KaptWithoutKotlincTask @Inject constructor(
} }
val kaptClasspath = kaptJars.toList() + kotlinStdlibClasspath val kaptClasspath = kaptJars.toList() + kotlinStdlibClasspath
val isolationMode = getWorkerIsolationMode()
//TODO for gradle < 6.5 logger.info("Using workers $isolationMode isolation mode to run kapt")
val isolationModeStr = getValue("kapt.workers.isolation") ?: "none" val toolsJarURLSpec = kotlinJavaToolchainProvider.get().jdkToolsJar.orNull?.toURI()?.toURL()?.toString().orEmpty()
val isolationMode = when (isolationModeStr.toLowerCase()) {
"process" -> IsolationMode.PROCESS
"none" -> IsolationMode.NONE
else -> IsolationMode.NONE
}
val toolsJarURLSpec = findToolsJar()?.toURI()?.toURL()?.toString().orEmpty()
submitWork( submitWork(
isolationMode, 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( private fun submitWork(
isolationMode: IsolationMode, isolationMode: IsolationMode,
optionsForWorker: KaptOptionsForWorker, optionsForWorker: KaptOptionsForWorker,
@@ -187,14 +201,13 @@ abstract class KaptWithoutKotlincTask @Inject constructor(
// for tests // for tests
it.forkOptions.jvmArgs("-verbose:class") it.forkOptions.jvmArgs("-verbose:class")
} }
logger.info("Kapt worker classpath: ${it.classpath}") it.forkOptions.executable = kotlinJavaToolchainProvider.get().javaExecutable.asFile.get().absolutePath
}
IsolationMode.CLASSLOADER -> workerExecutor.classLoaderIsolation() {
logger.info("Kapt worker classpath: ${it.classpath}") logger.info("Kapt worker classpath: ${it.classpath}")
} }
IsolationMode.NONE -> workerExecutor.noIsolation() IsolationMode.NONE -> workerExecutor.noIsolation()
IsolationMode.AUTO -> throw UnsupportedOperationException( IsolationMode.AUTO, IsolationMode.CLASSLOADER -> throw UnsupportedOperationException(
"Kapt worker compilation does not support $isolationMode" "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, objects: ObjectFactory,
files: ProjectLayout files: ProjectLayout
) : KotlinJavaToolchain { ) : KotlinJavaToolchain {
private val currentJvm: Property<Jvm> = objects @get:Internal
internal val currentJvm: Property<Jvm> = objects
.property<Jvm>(Jvm.current()) .property<Jvm>(Jvm.current())
.chainedFinalizeValueOnRead() .chainedFinalizeValueOnRead()
@@ -16,7 +16,6 @@
package org.jetbrains.kotlin.gradle.tasks package org.jetbrains.kotlin.gradle.tasks
import org.gradle.api.GradleException
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ResolvedDependency import org.gradle.api.artifacts.ResolvedDependency
@@ -104,21 +103,6 @@ internal fun findKotlinScriptRuntimeClasspath(project: Project): List<File> =
internal fun findKotlinReflectClasspath(project: Project): List<File> = internal fun findKotlinReflectClasspath(project: Project): List<File> =
findKotlinModuleJar(project, KOTLIN_REFLECT_EXPECTED_CLASS, KOTLIN_REFLECT) 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? { internal fun findJarByClass(klass: Class<*>): File? {
val classFileName = klass.name.substringAfterLast(".") + ".class" val classFileName = klass.name.substringAfterLast(".") + ".class"
val resource = klass.getResource(classFileName) ?: return null val resource = klass.getResource(classFileName) ?: return null