Move conditional task caching setup into the task constructors;

Add 'kotlin.caching.enabled' flag to switch the caching for all tasks;
Rename and change semantics of cache support checks: make two instead
    of one, `isBuildCacheSupported` and `isBuildCacheEnabledForKotlin`;
Remove FileOptionKind entries that are redundant at the moment;
Improvements in BuildCacheIT.kt and other refactoring  suggestions from
    the review
    https://upsource.jetbrains.com/kotlin/review/KOTLIN-CR-1647
This commit is contained in:
Sergey Igushkin
2017-12-29 18:59:34 +03:00
parent fd066ea4d5
commit 1be9e04797
16 changed files with 121 additions and 106 deletions
@@ -21,22 +21,29 @@ import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.compile.AbstractCompile
import java.io.File
open class SubpluginOption(val key: String, open val value: String)
open class SubpluginOption(val key: String, val value: String)
class FilesSubpluginOption(
key: String,
val kind: FileOptionKind,
val files: List<File>,
val kind: FilesOptionKind = FilesOptionKind.INTERNAL,
value: String = files.joinToString(File.pathSeparator) { it.canonicalPath })
: SubpluginOption(key, value)
class WrapperSubpluginOption(
class CompositeSubpluginOption(
key: String,
value: String,
val originalOptions: List<SubpluginOption>)
: SubpluginOption(key, value)
enum class FileOptionKind { INPUT_FILES, CLASSPATH_INPUT, OUTPUT_FILES, OUTPUT_DIRS, INTERNAL }
/** Defines how the files option should be handled with regard to Gradle model */
enum class FilesOptionKind {
/** The files option is an implementation detail and should not be treated as an input or an output. */
INTERNAL
// More options might be added when use cases appear for them,
// such as output directories, inputs or classpath options.
}
interface KotlinGradleSubplugin<in KotlinCompile : AbstractCompile> {
fun isApplicable(project: Project, task: AbstractCompile): Boolean
@@ -16,12 +16,9 @@
package org.jetbrains.kotlin.gradle
import org.jetbrains.kotlin.gradle.util.checkBytecodeContains
import org.jetbrains.kotlin.gradle.util.modify
import org.junit.Test
import java.io.File
import java.nio.file.Files
import kotlin.test.assertEquals
class BuildCacheIT : BaseGradleIT() {
override fun defaultBuildOptions(): BuildOptions =
@@ -32,7 +29,7 @@ class BuildCacheIT : BaseGradleIT() {
}
@Test
fun noCacheWithGradlePre43() = with(Project("simpleProject", "4.2")) {
fun testNoCacheWithGradlePre43() = with(Project("simpleProject", "4.2")) {
// Check that even with the build cache enabled, the Kotlin tasks are not cacheable with Gradle < 4.3:
val optionsWithCache = defaultBuildOptions().copy(withBuildCache = true)
@@ -47,6 +44,21 @@ class BuildCacheIT : BaseGradleIT() {
}
}
@Test
fun testKotlinCachingEnabledFlag() = with(Project("simpleProject", GRADLE_VERSION)) {
prepareLocalBuildCache()
build("assemble") {
assertSuccessful()
assertContains("Packing task ':compileKotlin'")
}
build("clean", "assemble", "-Dkotlin.caching.enabled=false") {
assertSuccessful()
assertNotContains(":compileKotlin FROM-CACHE")
}
}
@Test
fun testCacheHitAfterClean() = with(Project("simpleProject", GRADLE_VERSION)) {
prepareLocalBuildCache()
@@ -99,20 +111,9 @@ class BuildCacheIT : BaseGradleIT() {
}
@Test
fun testCorrectBuildAfterCacheHit() = with(Project("simpleProject", GRADLE_VERSION)) {
fun testCorrectBuildAfterCacheHit() = with(Project("buildCacheSimple", GRADLE_VERSION)) {
prepareLocalBuildCache()
val fooKt = File(projectDir, "src/main/kotlin/foo/foo.kt").apply {
parentFile.mkdirs()
writeText("package foo; fun foo(i: Int): Int = 1")
}
// bar.kt references foo.kt; we expect it to recompile during the first build after a cache hit.
File(projectDir, "src/main/kotlin/bar/bar.kt").apply {
parentFile.mkdirs()
writeText("package bar; import foo.*; fun bar() = foo(1)")
}
// First build, should be stored into the build cache:
build("assemble") {
assertSuccessful()
@@ -125,13 +126,11 @@ class BuildCacheIT : BaseGradleIT() {
assertContains(":compileKotlin FROM-CACHE")
}
// Change the return type Int to String, so that bar.kt should be recompiled, and check that:
fooKt.modify { it.replace("Int = 1", "String = \"abc\"") }
// Change the return type of foo() from Int to String in foo.kt, and check that fooUsage.kt is recompiled as well:
File(projectDir, "src/main/kotlin/foo.kt").modify { it.replace("Int = 1", "String = \"abc\"") }
build("assemble") {
assertSuccessful()
checkBytecodeContains(
File(projectDir, "build/classes/kotlin/main/bar/BarKt.class"),
"INVOKESTATIC foo/FooKt.foo (I)Ljava/lang/String;")
assertCompiledKotlinSources(relativize(allKotlinFiles))
}
}
@@ -166,7 +165,7 @@ class BuildCacheIT : BaseGradleIT() {
}
}
fun BaseGradleIT.Project.prepareLocalBuildCache(directory: File = Files.createTempDirectory("GradleTestBuildCache").toFile()): File {
fun BaseGradleIT.Project.prepareLocalBuildCache(directory: File = File(projectDir.parentFile, "buildCache").apply { mkdir() }): File {
if (!projectDir.exists()) {
setupWorkingDir()
}
@@ -0,0 +1,21 @@
buildscript {
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: "kotlin"
apply plugin: 'java'
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}
@@ -0,0 +1,5 @@
package bar
import foo.*
fun bar() = foo(1)
@@ -118,10 +118,10 @@ class AndroidSubplugin : KotlinGradleSubplugin<KotlinCompile> {
fun addVariant(sourceSet: AndroidSourceSet) {
val optionValue = sourceSet.name + ';' +
sourceSet.res.srcDirs.joinToString(";") { it.absolutePath }
pluginOptions += WrapperSubpluginOption("variant", optionValue, listOf(
pluginOptions += CompositeSubpluginOption("variant", optionValue, listOf(
SubpluginOption("sourceSetName", sourceSet.name),
//use the INTERNAL option kind since the resources are tracked as sources (see below)
FilesSubpluginOption("resDirs", FileOptionKind.INTERNAL, sourceSet.res.srcDirs.toList())
FilesSubpluginOption("resDirs", sourceSet.res.srcDirs.toList())
))
kotlinCompile.source(project.files(getLayoutDirectories(sourceSet.res.srcDirs)))
}
@@ -170,10 +170,10 @@ class AndroidSubplugin : KotlinGradleSubplugin<KotlinCompile> {
append(';')
resDirectories.joinTo(this, separator = ";") { it.canonicalPath }
}
pluginOptions += WrapperSubpluginOption("variant", optionValue, listOf(
pluginOptions += CompositeSubpluginOption("variant", optionValue, listOf(
SubpluginOption("variantName", name),
// use INTERNAL option kind since the resources are tracked as sources (see below)
FilesSubpluginOption("resDirs", FileOptionKind.INTERNAL, resDirectories)))
FilesSubpluginOption("resDirs", resDirectories)))
kotlinCompile.source(project.files(getLayoutDirectories(resDirectories)))
}
@@ -36,7 +36,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider
import org.jetbrains.kotlin.gradle.tasks.kapt.generateAnnotationProcessorWrapper
import org.jetbrains.kotlin.gradle.tasks.kapt.generateKotlinAptAnnotation
import org.jetbrains.kotlin.gradle.tasks.shouldEnableGradleCache
import org.jetbrains.kotlin.gradle.tasks.isBuildCacheSupported
import java.io.File
import java.io.IOException
import java.util.zip.ZipFile
@@ -80,7 +80,7 @@ internal fun Project.initKapt(
// moveGeneratedJavaFilesToCorrespondingDirectories(kaptManager.aptOutputDir)
// }
if (shouldEnableGradleCache()) {
if (isBuildCacheSupported()) {
// Since Kapt1 is about to be dropped, disable the cache for it:
kotlinAfterJavaTask.outputs.doNotCacheIf("Caching is not supported with deprecated Kapt1") { true }
}
@@ -110,7 +110,7 @@ internal fun Project.initKapt(
kotlinTask.kaptOptions.annotationsFile = kaptManager.getAnnotationFile()
if (shouldEnableGradleCache()) {
if (isBuildCacheSupported()) {
// Since Kapt1 is about to be dropped, disable the cache for it:
kotlinTask.outputs.doNotCacheIf("Caching is not supported with deprecated Kapt1") { true }
}
@@ -35,8 +35,6 @@ import java.io.File
import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin.Companion.getKaptGeneratedSourcesDir
import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin.Companion.getKaptGeneratedClassesDir
import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin.Companion.getKaptGeneratedKotlinSourcesDir
import org.jetbrains.kotlin.gradle.tasks.shouldEnableGradleCache
import org.jetbrains.kotlin.gradle.tasks.useBuildCacheIfSupported
import java.io.ByteArrayOutputStream
import java.io.ObjectOutputStream
import java.util.*
@@ -210,14 +208,14 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
pluginOptions += SubpluginOption("aptMode", aptMode)
disableAnnotationProcessingInJavaTask()
kaptClasspath.forEach { pluginOptions += FilesSubpluginOption("apclasspath", FileOptionKind.INTERNAL, listOf(it)) }
kaptClasspath.forEach { pluginOptions += FilesSubpluginOption("apclasspath", listOf(it)) }
javaCompile.source(generatedFilesDir)
pluginOptions += FilesSubpluginOption("sources", FileOptionKind.INTERNAL, listOf(generatedFilesDir))
pluginOptions += FilesSubpluginOption("classes", FileOptionKind.INTERNAL, listOf(getKaptGeneratedClassesDir(project, sourceSetName)))
pluginOptions += FilesSubpluginOption("sources", listOf(generatedFilesDir))
pluginOptions += FilesSubpluginOption("classes", listOf(getKaptGeneratedClassesDir(project, sourceSetName)))
pluginOptions += FilesSubpluginOption("incrementalData", FileOptionKind.INTERNAL, listOf(getKaptIncrementalDataDir()))
pluginOptions += FilesSubpluginOption("incrementalData", listOf(getKaptIncrementalDataDir()))
val annotationProcessors = kaptExtension.processors
if (annotationProcessors.isNotEmpty()) {
@@ -235,9 +233,9 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
val apOptions =
(kaptExtension.getAdditionalArguments(project, kaptVariantData?.variantData, androidPlugin) + androidOptions)
.map { SubpluginOption(it.key, it.value) } +
FilesSubpluginOption("kapt.kotlin.generated", FileOptionKind.INTERNAL, listOf(kotlinSourcesOutputDir))
FilesSubpluginOption("kapt.kotlin.generated", listOf(kotlinSourcesOutputDir))
pluginOptions += WrapperSubpluginOption("apoptions", encodeList(apOptions.associate { it.key to it.value }), apOptions)
pluginOptions += CompositeSubpluginOption("apoptions", encodeList(apOptions.associate { it.key to it.value }), apOptions)
pluginOptions += SubpluginOption("javacArguments", encodeList(kaptExtension.getJavacOptions()))
@@ -284,7 +282,7 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
pluginOptions += SubpluginOption("useLightAnalysis", "${kaptExtension.useLightAnalysis}")
pluginOptions += SubpluginOption("correctErrorTypes", "${kaptExtension.correctErrorTypes}")
pluginOptions += FilesSubpluginOption("stubs", FileOptionKind.INTERNAL, listOf(getKaptStubsDir()))
pluginOptions += FilesSubpluginOption("stubs", listOf(getKaptStubsDir()))
if (project.hasProperty(VERBOSE_OPTION_NAME) && project.property(VERBOSE_OPTION_NAME) == "true") {
pluginOptions += SubpluginOption("verbose", "true")
@@ -296,14 +294,6 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
getKaptTaskName("kapt"),
KaptTask::class.java)
kaptTask.useBuildCacheIfSupported()
if (shouldEnableGradleCache()) {
val reason = "Caching is disabled by default for kapt because of arbitrary behavior of external " +
"annotation processors. You can enable it by adding 'kapt.useBuildCache = true' to the build script."
kaptTask.outputs.doNotCacheIf(reason) { !kaptTask.useBuildCache }
}
kaptTask.useBuildCache = kaptExtension.useBuildCache
kaptTask.kotlinCompileTask = kotlinCompile
@@ -342,8 +332,6 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
getKaptTaskName("kaptGenerateStubs"),
KaptGenerateStubsTask::class.java)
kaptTask.useBuildCacheIfSupported()
kaptTask.sourceSetName = sourceSetName
kaptTask.kotlinCompileTask = kotlinCompile
kotlinToKaptGenerateStubsTasksMap[kotlinCompile] = kaptTask
@@ -16,6 +16,16 @@ import java.io.File
@CacheableTask
open class KaptTask : ConventionTask(), CompilerArgumentAware<K2JVMCompilerArguments> {
init {
cacheOnlyIfEnabledForKotlin()
if (isBuildCacheSupported()) {
val reason = "Caching is disabled by default for kapt because of arbitrary behavior of external " +
"annotation processors. You can enable it by adding 'kapt.useBuildCache = true' to the build script."
outputs.cacheIf(reason) { useBuildCache }
}
}
@get:Internal
internal val pluginOptions = CompilerPluginOptions()
@@ -17,7 +17,7 @@
package org.jetbrains.kotlin.gradle.internal
import org.jetbrains.kotlin.gradle.plugin.SubpluginOption
import org.jetbrains.kotlin.gradle.plugin.WrapperSubpluginOption
import org.jetbrains.kotlin.gradle.plugin.CompositeSubpluginOption
import java.io.ByteArrayOutputStream
import java.io.ObjectOutputStream
import java.util.*
@@ -43,6 +43,6 @@ fun encodePluginOptions(options: Map<String, List<String>>): String {
fun wrapPluginOptions(options: List<SubpluginOption>, newOptionName: String): List<SubpluginOption> {
val groupedOptions = options.groupBy { it.key }.mapValues { opt -> opt.value.map { it.value } }
val encodedOptions = encodePluginOptions(groupedOptions)
val singleOption = WrapperSubpluginOption(newOptionName, encodedOptions, options)
val singleOption = CompositeSubpluginOption(newOptionName, encodedOptions, options)
return listOf(singleOption)
}
@@ -18,15 +18,10 @@ package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.FileTree
import org.gradle.api.internal.file.UnionFileCollection
import org.gradle.api.internal.file.UnionFileTree
import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.api.tasks.SourceSet
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinJsDce
import org.jetbrains.kotlin.gradle.tasks.useBuildCacheIfSupported
import java.io.File
class KotlinJsDcePlugin : Plugin<Project> {
@@ -46,18 +41,15 @@ class KotlinJsDcePlugin : Plugin<Project> {
project.tasks.findByName("build")!!.dependsOn(it)
}
dceTask.useBuildCacheIfSupported()
project.afterEvaluate {
val outputDir = File(File(project.buildDir, DEFAULT_OUT_DIR), sourceSet.name)
val configuration = project.configurations.findByName(sourceSet.compileConfigurationName)!!
val dceInputTree = project.fileTree(kotlinTask.outputFile)
with (dceTask) {
classpath = configuration
destinationDir = dceTask.dceOptions.outputDirectory?.let { File(it) } ?: outputDir
source(dceInputTree)
source(kotlinTask.outputFile)
}
}
}
@@ -689,8 +689,8 @@ internal fun configureJavaTask(kotlinTask: KotlinCompile, javaTask: AbstractComp
// Make Gradle check if the javaTask is up-to-date based on the Kotlin classes
javaTask.inputsCompatible.run {
if (shouldEnableGradleCache()) {
dir(kotlinTask.destinationDir).withNormalizer(ClasspathNormalizer::class.java)
if (isBuildCacheSupported()) {
dir(kotlinTask.destinationDir).withNormalizer(CompileClasspathNormalizer::class.java)
}
else {
dirCompatible(kotlinTask.destinationDir)
@@ -839,30 +839,11 @@ internal class SubpluginEnvironment(
internal fun Task.registerSubpluginOptionAsInput(subpluginId: String, option: SubpluginOption) {
when (option) {
is WrapperSubpluginOption -> {
is CompositeSubpluginOption -> {
val subpluginIdWithWrapperKey = "$subpluginId.${option.key}"
option.originalOptions.forEach { registerSubpluginOptionAsInput(subpluginIdWithWrapperKey, it) }
}
is FilesSubpluginOption -> {
when (option.kind) {
FileOptionKind.INTERNAL -> Unit
FileOptionKind.OUTPUT_FILES -> outputsCompatible.filesCompatible(*option.files.toTypedArray())
FileOptionKind.OUTPUT_DIRS -> option.files.forEach { outputsCompatible.dirCompatible(it) }
FileOptionKind.INPUT_FILES, FileOptionKind.CLASSPATH_INPUT -> {
if (!shouldEnableGradleCache()) {
// Normalization makes no sense when we don't mean to use the cache,
// and moreover, Gradle lacks some of the APIs in the earlier versions.
inputsCompatible.filesCompatible(option.files)
}
else {
if (option.kind == FileOptionKind.CLASSPATH_INPUT)
inputs.files(option.files).withNormalizer(ClasspathNormalizer::class.java)
else
inputs.files(option.files).withPathSensitivity(PathSensitivity.RELATIVE)
}
}
}
}
is FilesSubpluginOption -> Unit
else -> {
// Since there might be multiple subplugin options with the same key,
// use the properties count to resolve duplication:
@@ -18,21 +18,24 @@ package org.jetbrains.kotlin.gradle.tasks
import org.gradle.api.Task
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.utils.outputsCompatible
internal fun isBuildCacheSupported(): Boolean =
gradleVersion >= GradleVersion.version("4.3")
internal fun isBuildCacheEnabledForKotlin(): Boolean =
isBuildCacheSupported() &&
System.getProperty(KOTLIN_CACHING_ENABLED_PROPERTY)?.toBoolean() ?: true
internal fun <T : Task> T.cacheOnlyIfEnabledForKotlin() {
// The `cacheIf` method may be missing if the Gradle version is too low:
try {
outputsCompatible.cacheIf { isBuildCacheEnabledForKotlin() }
}
catch (_: NoSuchMethodError) {
}
}
private val gradleVersion = GradleVersion.current()
internal fun shouldEnableGradleCache(): Boolean =
gradleVersion >= GradleVersion.version("4.3")
internal fun <T : Task> T.useBuildCacheIfSupported() {
when {
gradleVersion >= GradleVersion.version("3.4") ->
outputs.cacheIf("Gradle version is at least 4.3") { shouldEnableGradleCache() }
// There was no 'cacheIf' method accepting a reason string before 3.4:
gradleVersion >= GradleVersion.version("3.0") -> outputs.cacheIf { false }
// Before 3.0, there was no build cache at all, no need to restrict anything
else -> Unit
}
}
private const val KOTLIN_CACHING_ENABLED_PROPERTY = "kotlin.caching.enabled"
@@ -35,6 +35,10 @@ import java.io.File
@CacheableTask
open class KotlinJsDce : AbstractKotlinCompileTool<K2JSDceArguments>(), KotlinJsDce {
init {
cacheOnlyIfEnabledForKotlin()
}
override fun createCompilerArgs(): K2JSDceArguments = K2JSDceArguments()
override fun setupCompilerArgs(args: K2JSDceArguments, defaultsOnly: Boolean) {
@@ -80,6 +80,11 @@ abstract class AbstractKotlinCompileTool<T : CommonToolArguments>() : AbstractCo
}
abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractKotlinCompileTool<T>() {
init {
cacheOnlyIfEnabledForKotlin()
}
@get:LocalState
internal val taskBuildDirectory: File
get() = File(File(project.buildDir, KOTLIN_BUILD_DIR_NAME), name).apply { mkdirs() }
@@ -412,11 +417,11 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
kaptAnnotationsFileUpdater = AnnotationFileUpdaterImpl(kaptAnnotationsFile)
}
addPluginArgument(ANNOTATIONS_PLUGIN_NAME, FilesSubpluginOption("output", FileOptionKind.INTERNAL, listOf(kaptAnnotationsFile)))
addPluginArgument(ANNOTATIONS_PLUGIN_NAME, FilesSubpluginOption("output", listOf(kaptAnnotationsFile)))
}
if (kaptOptions.generateStubs) {
addPluginArgument(ANNOTATIONS_PLUGIN_NAME, FilesSubpluginOption("stubs", FileOptionKind.INTERNAL, listOf(destinationDir)))
addPluginArgument(ANNOTATIONS_PLUGIN_NAME, FilesSubpluginOption("stubs", listOf(destinationDir)))
}
if (kaptOptions.supportInheritedAnnotations) {
@@ -24,19 +24,16 @@ import org.jetbrains.kotlin.gradle.plugin.mapKotlinTaskProperties
internal open class KotlinTasksProvider {
fun createKotlinJVMTask(project: Project, name: String, sourceSetName: String): KotlinCompile =
project.tasks.create(name, KotlinCompile::class.java).apply {
useBuildCacheIfSupported()
configure(project, sourceSetName)
}
fun createKotlinJSTask(project: Project, name: String, sourceSetName: String): Kotlin2JsCompile =
project.tasks.create(name, Kotlin2JsCompile::class.java).apply {
useBuildCacheIfSupported()
configure(project, sourceSetName)
}
fun createKotlinCommonTask(project: Project, name: String, sourceSetName: String): KotlinCompileCommon =
project.tasks.create(name, KotlinCompileCommon::class.java).apply {
useBuildCacheIfSupported()
configure(project, sourceSetName)
}