diff --git a/libraries/tools/kotlin-gradle-plugin-core/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt b/libraries/tools/kotlin-gradle-plugin-core/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt index 55825efb8ef..6b95e64ebcb 100644 --- a/libraries/tools/kotlin-gradle-plugin-core/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt +++ b/libraries/tools/kotlin-gradle-plugin-core/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt @@ -14,7 +14,6 @@ import org.apache.commons.lang.StringUtils import org.codehaus.groovy.runtime.MethodClosure import org.gradle.api.GradleException import org.gradle.api.file.SourceDirectorySet -import org.gradle.api.internal.AbstractTask import org.gradle.api.logging.Logger import org.gradle.api.logging.Logging import org.gradle.api.plugins.ExtraPropertiesExtension @@ -51,12 +50,10 @@ import java.util.* const val DEFAULT_ANNOTATIONS = "org.jebrains.kotlin.gradle.defaultAnnotations" const val ANNOTATIONS_PLUGIN_NAME = "org.jetbrains.kotlin.kapt" -const val KOTLIN_CACHES_DIR_NAME = "kotlin-caches" +const val KOTLIN_BUILD_DIR_NAME = "kotlin" +const val CACHES_DIR_NAME = "caches" const val DIRTY_SOURCES_FILE_NAME = "dirty-sources.txt" -val AbstractTask.kotlinCachesDir: File - get() = File(File(project.buildDir, KOTLIN_CACHES_DIR_NAME), name) - abstract class AbstractKotlinCompile() : AbstractCompile() { abstract protected val compiler: CLICompiler abstract protected fun createBlankArgs(): T @@ -82,7 +79,6 @@ abstract class AbstractKotlinCompile() : AbstractCo @TaskAction fun execute(inputs: IncrementalTaskInputs): Unit { - logger.debug("Starting ${javaClass} task") logger.kotlinDebug("all sources ${getSource().joinToString { it.path }}") logger.kotlinDebug("is incremental == ${inputs.isIncremental}") val modified = arrayListOf() @@ -107,7 +103,7 @@ abstract class AbstractKotlinCompile() : AbstractCo beforeCompileHook(args) try { - callCompiler(args, sources, inputs.isIncremental, modified, removed, kotlinCachesDir) + callCompiler(args, sources, inputs.isIncremental, modified, removed) } finally { afterCompileHook(args) @@ -130,8 +126,7 @@ abstract class AbstractKotlinCompile() : AbstractCo args.noInline = kotlinOptions.noInline } - protected open fun callCompiler(args: T, sources: List, isIncremental: Boolean, modified: List, removed: List, cachesBaseDir: File) { - + protected open fun callCompiler(args: T, sources: List, isIncremental: Boolean, modified: List, removed: List) { populateSources(args, sources) val messageCollector = GradleMessageCollector(logger) @@ -164,6 +159,17 @@ open class KotlinCompile() : AbstractKotlinCompile() { // TODO: find out whether we really need to be able to override destination dir here, and how it should work with destinationDir property private val compilerDestinationDir: String get() = if (StringUtils.isEmpty(kotlinOptions.destination)) { kotlinDestinationDir?.path.orEmpty() } else { kotlinOptions.destination } + // lazy because name is probably not available when constructor is called + private val taskBuildDirectory: File by lazy { File(File(project.buildDir, KOTLIN_BUILD_DIR_NAME), name) } + private val cacheDirectory: File by lazy { File(taskBuildDirectory, CACHES_DIR_NAME) } + private val dirtySourcesSinceLastTimeFile: File by lazy { File(taskBuildDirectory, DIRTY_SOURCES_FILE_NAME) } + private val cacheVersions by lazy { + listOf(normalCacheVersion(taskBuildDirectory), + experimentalCacheVersion(taskBuildDirectory), + dataContainerCacheVersion(taskBuildDirectory), + gradleCacheVersion(taskBuildDirectory)) + } + override fun populateTargetSpecificArgs(args: K2JVMCompilerArguments) { // show kotlin compiler where to look for java source files // args.freeArgs = (args.freeArgs + getJavaSourceRoots().map { it.getAbsolutePath() }).toSet().toList() @@ -216,7 +222,7 @@ open class KotlinCompile() : AbstractKotlinCompile() { logger.kotlinDebug("args.moduleName = ${args.moduleName}") } - override fun callCompiler(args: K2JVMCompilerArguments, sources: List, isIncrementalRequested: Boolean, modified: List, removed: List, cachesBaseDir: File) { + override fun callCompiler(args: K2JVMCompilerArguments, sources: List, isIncrementalRequested: Boolean, modified: List, removed: List) { fun projectRelativePath(f: File) = f.toRelativeString(project.projectDir) @@ -232,15 +238,14 @@ open class KotlinCompile() : AbstractKotlinCompile() { val targets = listOf(TargetId(moduleName, targetType)) val outputDir = File(compilerDestinationDir) val caches = hashMapOf() - val lookupStorage = LookupStorage(File(cachesBaseDir, "lookups")) + val lookupStorage = LookupStorage(File(cacheDirectory, "lookups")) val lookupTracker = LookupTrackerImpl(LookupTracker.DO_NOTHING) var currentRemoved = removed.filter { it.isKotlinFile() } val allGeneratedFiles = hashSetOf>() val logAction = { logStr: String -> logger.kotlinInfo(logStr) } - val dirtySourcesSinceLastTimeFile = File(cachesBaseDir, DIRTY_SOURCES_FILE_NAME) fun getOrCreateIncrementalCache(target: TargetId): GradleIncrementalCacheImpl { - val cacheDir = File(cachesBaseDir, "increCache.${target.name}") + val cacheDir = File(cacheDirectory, "increCache.${target.name}") cacheDir.mkdirs() return GradleIncrementalCacheImpl(targetDataRoot = cacheDir, targetOutputDir = outputDir, target = target) } @@ -294,58 +299,21 @@ open class KotlinCompile() : AbstractKotlinCompile() { return changedClasspath.any() } - fun allCachesVersions() = allCachesVersions(cachesBaseDir, listOf(cachesBaseDir)) - fun calculateSourcesToCompile(): Pair, Boolean> { - - if (!experimentalIncremental || - !isIncrementalRequested || - // TODO: more precise will be not to rebuild unconditionally on classpath changes, but retrieve lookup info and try to find out which sources are affected by cp changes - isClassPathChanged() || - // so far considering it not incremental TODO: store java files in the cache and extract removed symbols from it here - removed.any { it.isJavaFile() } + if (!experimentalIncremental + || !isIncrementalRequested + // TODO: more precise will be not to rebuild unconditionally on classpath changes, but retrieve lookup info and try to find out which sources are affected by cp changes + || isClassPathChanged() + // so far considering it not incremental TODO: store java files in the cache and extract removed symbols from it here + || removed.any { it.isJavaFile() } + || cacheVersions.any { it.checkVersion() != CacheVersion.Action.DO_NOTHING } ) { logger.kotlinInfo(if (!isIncrementalRequested) "clean caches on rebuild" else "classpath changed, rebuilding all kotlin files") targets.forEach { getIncrementalCache(it).clean() } lookupStorage.clean() + dirtySourcesSinceLastTimeFile.delete() return Pair(sources.toSet(), false) } - val actions = if (isIncrementalRequested) allCachesVersions().map { it.checkVersion() } - else listOf(CacheVersion.Action.REBUILD_ALL_KOTLIN) - // TODO: find out whether these flags should be emulated too -// val hasKotlin = HasKotlinMarker(dataManager) -// val rebuildAfterCacheVersionChanged = RebuildAfterCacheVersionChangeMarker(dataManager) - - for (status in actions.distinct().sorted()) { - when (status) { - CacheVersion.Action.REBUILD_ALL_KOTLIN -> { - logger.kotlinInfo("Kotlin global lookup map format changed, rebuilding all kotlin files") - targets.forEach { getIncrementalCache(it).clean() } - lookupStorage.clean() - return Pair(sources.toSet(), false) - } - CacheVersion.Action.REBUILD_CHUNK -> { - logger.kotlinInfo("Clearing caches for " + targets.joinToString { it.name }) - targets.forEach { getIncrementalCache(it).clean() } - } - CacheVersion.Action.CLEAN_NORMAL_CACHES -> { - logger.kotlinInfo("Clearing caches for all targets") - targets.forEach { getIncrementalCache(it).clean() } - } - CacheVersion.Action.CLEAN_EXPERIMENTAL_CACHES -> { - logger.kotlinInfo("Clearing experimental caches for all targets") - targets.forEach { getIncrementalCache(it).cleanExperimental() } - } - CacheVersion.Action.CLEAN_DATA_CONTAINER -> { - logger.kotlinInfo("Clearing lookup cache") - lookupStorage.clean() - dataContainerCacheVersion(cachesBaseDir).clean() - } - else -> { - assert(status == CacheVersion.Action.DO_NOTHING) { "Unknown version status $status" } - } - } - } val dirtyFiles = dirtyKotlinSourcesFromGradle() if (dirtySourcesSinceLastTimeFile.exists()) { @@ -426,7 +394,7 @@ open class KotlinCompile() : AbstractKotlinCompile() { getIncrementalCache = { caches[it]!! }) lookupStorage.update(lookupTracker, sourcesToCompile, currentRemoved) - allCachesVersions().forEach { it.saveIfNeeded() } + cacheVersions.forEach { it.saveIfNeeded() } processCompilerExitCode(exitCode) if (!isIncrementalDecided) break; diff --git a/libraries/tools/kotlin-gradle-plugin-core/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/gradleCacheVersion.kt b/libraries/tools/kotlin-gradle-plugin-core/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/gradleCacheVersion.kt new file mode 100644 index 00000000000..5f9778ddb92 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-core/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/gradleCacheVersion.kt @@ -0,0 +1,16 @@ +package org.jetbrains.kotlin.gradle.tasks + +import org.jetbrains.kotlin.config.IncrementalCompilation +import org.jetbrains.kotlin.incremental.CacheVersion +import java.io.File + +const val GRADLE_CACHE_VERSION = 0 +const val GRADLE_CACHE_VERSION_FILE_NAME = "gradle-format-version.txt" + +fun gradleCacheVersion(dataRoot: File): CacheVersion = + CacheVersion(ownVersion = GRADLE_CACHE_VERSION, + versionFile = File(dataRoot, GRADLE_CACHE_VERSION_FILE_NAME), + whenVersionChanged = CacheVersion.Action.REBUILD_ALL_KOTLIN, + whenTurnedOn = CacheVersion.Action.REBUILD_ALL_KOTLIN, + whenTurnedOff = CacheVersion.Action.REBUILD_ALL_KOTLIN, + isEnabled = { IncrementalCompilation.isExperimental() }) diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/CacheVersionChangedIT.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/CacheVersionChangedIT.kt new file mode 100644 index 00000000000..ba3cc399320 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/CacheVersionChangedIT.kt @@ -0,0 +1,67 @@ +package org.jetbrains.kotlin.gradle + +import org.jetbrains.kotlin.gradle.util.findFileByName +import org.jetbrains.kotlin.gradle.util.modify +import org.junit.Test +import java.io.File +import kotlin.test.assertNotEquals +import kotlin.test.assertTrue + +class CacheVersionChangedIT : BaseGradleIT() { + @Test + fun testGradleCacheVersionChanged() { + compileIncrementallyWithChangedVersion("gradle-format-version.txt") + } + + @Test + fun testNormalCacheVersionChanged() { + compileIncrementallyWithChangedVersion("format-version.txt") + } + + @Test + fun testExperimentalCacheVersionChanged() { + compileIncrementallyWithChangedVersion("experimental-format-version.txt") + } + + @Test + fun testDataContainerCacheVersionChanged() { + compileIncrementallyWithChangedVersion("data-container-format-version.txt") + } + + private fun compileIncrementallyWithChangedVersion(versionFileName: String) { + val project = Project("kotlinProject", "2.10") + val options = defaultBuildOptions().copy(incremental = true) + + fun File.projectPath() = relativeTo(project.projectDir).path + + project.build("build", options = options) { + assertSuccessful() + } + + val versionFile = File(project.projectDir, "build/kotlin/compileKotlin/$versionFileName") + assertTrue(versionFile.exists(), "${versionFile.projectPath()} does not exist!") + val modifiedVersion = "777" + versionFile.writeText(modifiedVersion) + + // gradle won't call kotlin task if no file is changed + val dummyKtFile = project.projectDir.findFileByName("Dummy.kt") + dummyKtFile.modify { it + " " } + + project.build("build", options = options) { + assertSuccessful() + assertNotEquals(modifiedVersion, versionFile.readText(), "${versionFile.projectPath()} was not rewritten by build") + + val mainDir = File(project.projectDir, "src/main") + val mainKotlinFiles = mainDir.walk().filter { it.isFile && it.extension.equals("kt", ignoreCase = true) } + val mainKotlinRelativePaths = mainKotlinFiles.map { it.projectPath() }.toList() + assertCompiledKotlinSources(mainKotlinRelativePaths) + } + + dummyKtFile.modify { it + " " } + + project.build("build", options = options) { + assertSuccessful() + assertCompiledKotlinSources(listOf(dummyKtFile.projectPath())) + } + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/util/fileUtil.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/util/fileUtil.kt new file mode 100644 index 00000000000..98beadf4dda --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/util/fileUtil.kt @@ -0,0 +1,11 @@ +package org.jetbrains.kotlin.gradle.util + +import java.io.File + +fun File.findFileByName(name: String): File = + walk().filter { it.isFile && it.name.equals(name, ignoreCase = true) }.firstOrNull() + ?: throw AssertionError("Could not find file with name '$name' in $this") + +fun File.modify(transform: (String)->String) { + writeText(transform(readText())) +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/kotlinProject/gradle.properties b/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/kotlinProject/gradle.properties new file mode 100644 index 00000000000..93ce6a94e63 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/kotlinProject/gradle.properties @@ -0,0 +1 @@ +org.gradle.jvmargs=-Xmx1024m -XX:MaxPermSize=512m \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/kotlinProject/src/main/kotlin/Dummy.kt b/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/kotlinProject/src/main/kotlin/Dummy.kt new file mode 100644 index 00000000000..9c033c2608b --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/kotlinProject/src/main/kotlin/Dummy.kt @@ -0,0 +1,3 @@ +package demo + +class Dummy