From 62f293280c7440d3e457939d31c37d9baaeda54d Mon Sep 17 00:00:00 2001 From: Alexey Tsvetkov Date: Fri, 15 Sep 2017 16:35:30 +0300 Subject: [PATCH] Gradle: compile tests incrementally when main is changed #KT-17674 fixed --- .../daemon/common/CompilationOptions.kt | 4 +- .../kotlin/daemon/CompileServiceImpl.kt | 9 +- .../kotlin/incremental/BuildDiffsStorage.kt | 132 ++++++++++++++++++ .../jetbrains/kotlin/incremental/BuildInfo.kt | 2 +- .../incremental/IncrementalCompilerRunner.kt | 25 ++-- .../IncrementalJvmCompilerRunner.kt | 96 ++++++++++--- .../incremental/BuildDiffsStorageTest.kt | 102 ++++++++++++++ .../kotlin/gradle/KotlinGradlePluginIT.kt | 26 +++- .../kotlin/gradle/TestRootAffectedIT.kt | 27 +--- .../GradleCompilerEnvironment.kt | 4 +- .../GradleKotlinCompilerRunner.kt | 4 +- .../jetbrains/kotlin/gradle/tasks/Tasks.kt | 6 +- 12 files changed, 371 insertions(+), 66 deletions(-) create mode 100644 compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/BuildDiffsStorage.kt create mode 100644 compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/BuildDiffsStorageTest.kt diff --git a/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/CompilationOptions.kt b/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/CompilationOptions.kt index 3ebd25bc0b5..373cb9569dd 100644 --- a/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/CompilationOptions.kt +++ b/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/CompilationOptions.kt @@ -48,7 +48,9 @@ class IncrementalCompilationOptions( /** @See [ReportSeverity] */ reportSeverity: Int, /** @See [CompilationResultCategory]] */ - requestedCompilationResults: Array + requestedCompilationResults: Array, + val resultDifferenceFile: File? = null, + val friendDifferenceFile: File? = null ) : CompilationOptions(compilerMode, targetPlatform, reportCategories, reportSeverity, requestedCompilationResults) { companion object { const val serialVersionUID: Long = 0 diff --git a/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt b/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt index f95b8bf708d..578f6fc84e3 100644 --- a/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt +++ b/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt @@ -507,9 +507,12 @@ class CompileServiceImpl( workingDir, enabled = true) - return IncrementalJvmCompilerRunner(workingDir, javaSourceRoots, versions, reporter, annotationFileUpdater, - artifactChanges, changesRegistry) - .compile(allKotlinFiles, k2jvmArgs, compilerMessageCollector, { changedFiles }) + val compiler = IncrementalJvmCompilerRunner(workingDir, javaSourceRoots, versions, + reporter, annotationFileUpdater, + artifactChanges, changesRegistry, + buildHistoryFile = incrementalCompilationOptions.resultDifferenceFile, + friendBuildHistoryFile = incrementalCompilationOptions.friendDifferenceFile) + return compiler.compile(allKotlinFiles, k2jvmArgs, compilerMessageCollector, { changedFiles }) } override fun leaseReplSession( diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/BuildDiffsStorage.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/BuildDiffsStorage.kt new file mode 100644 index 00000000000..1ba8074d4b1 --- /dev/null +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/BuildDiffsStorage.kt @@ -0,0 +1,132 @@ +/* + * Copyright 2010-2017 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 org.jetbrains.kotlin.incremental + +import org.jetbrains.annotations.TestOnly +import org.jetbrains.kotlin.name.FqName +import java.io.File +import java.io.IOException +import java.io.ObjectInputStream +import java.io.ObjectOutputStream + +data class BuildDifference(val ts: Long, val isIncremental: Boolean, val dirtyData: DirtyData) + +// todo: storage format can be optimized by compressing fq-names +data class BuildDiffsStorage(val buildDiffs: List) { + companion object { + fun readFromFile(file: File, reporter: ICReporter?): BuildDiffsStorage? { + fun reportFail(reason: String) { + reporter?.report { "Could not read diff from file $file: $reason" } + } + + if (!file.exists()) return null + + try { + ObjectInputStream(file.inputStream().buffered()).use { input -> + val version = input.readInt() + if (version != CURRENT_VERSION) { + reportFail("incompatible version $version, actual version is $CURRENT_VERSION") + return null + } + + val size = input.readInt() + val result = ArrayList(size) + repeat(size) { + result.add(input.readBuildDifference()) + } + return BuildDiffsStorage(result) + } + } + catch (e: IOException) { + reportFail(e.toString()) + } + + return null + } + + fun writeToFile(file: File, storage: BuildDiffsStorage, reporter: ICReporter?) { + file.parentFile.mkdirs() + + try { + ObjectOutputStream(file.outputStream().buffered()).use { output -> + output.writeInt(CURRENT_VERSION) + + val diffsToWrite = storage.buildDiffs.sortedBy { it.ts }.takeLast(MAX_DIFFS_ENTRIES) + output.writeInt(diffsToWrite.size) + for (diff in diffsToWrite) { + output.writeBuildDifference(diff) + } + } + } + catch (e: IOException) { + reporter?.report { "Could not write diff to file $file: $e" } + } + } + + private fun ObjectInputStream.readBuildDifference(): BuildDifference { + val ts = readLong() + val isIncremental = readBoolean() + val dirtyData = readDirtyData() + return BuildDifference(ts, isIncremental, dirtyData) + } + + private fun ObjectOutputStream.writeBuildDifference(diff: BuildDifference) { + writeLong(diff.ts) + writeBoolean(diff.isIncremental) + writeDirtyData(diff.dirtyData) + } + + private fun ObjectInputStream.readDirtyData(): DirtyData { + val lookupSymbolSize = readInt() + val lookupSymbols = ArrayList(lookupSymbolSize) + repeat(lookupSymbolSize) { + val name = readUTF() + val scope = readUTF() + lookupSymbols.add(LookupSymbol(name = name, scope = scope)) + } + + val dirtyClassesSize = readInt() + val dirtyClassesFqNames = ArrayList(dirtyClassesSize) + repeat(dirtyClassesSize) { + val fqNameString = readUTF() + dirtyClassesFqNames.add(FqName(fqNameString)) + } + + return DirtyData(lookupSymbols, dirtyClassesFqNames) + } + + private fun ObjectOutputStream.writeDirtyData(dirtyData: DirtyData) { + val lookupSymbols = dirtyData.dirtyLookupSymbols + writeInt(lookupSymbols.size) + for ((name, scope) in lookupSymbols) { + writeUTF(name) + writeUTF(scope) + } + + val dirtyClassesFqNames = dirtyData.dirtyClassesFqNames + writeInt(dirtyClassesFqNames.size) + for (fqName in dirtyClassesFqNames) { + writeUTF(fqName.asString()) + } + } + + internal val MAX_DIFFS_ENTRIES: Int = 10 + + @set:TestOnly + var CURRENT_VERSION: Int = 0 + } +} \ No newline at end of file diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/BuildInfo.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/BuildInfo.kt index d5cfc2ded94..d5149dfadb9 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/BuildInfo.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/BuildInfo.kt @@ -18,7 +18,7 @@ package org.jetbrains.kotlin.incremental import java.io.* -internal data class BuildInfo(val startTS: Long) : Serializable { +data class BuildInfo(val startTS: Long) : Serializable { companion object { fun read(file: File): BuildInfo? = try { diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt index fcd9688ea0b..dfa187249b7 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt @@ -255,16 +255,9 @@ abstract class IncrementalCompilerRunner< if (exitCode == ExitCode.OK && compilationMode is CompilationMode.Incremental) { buildDirtyLookupSymbols.addAll(additionalDirtyLookupSymbols()) } - if (changesRegistry != null) { - if (compilationMode is CompilationMode.Incremental) { - val dirtyData = DirtyData(buildDirtyLookupSymbols, buildDirtyFqNames) - changesRegistry.registerChanges(currentBuildInfo.startTS, dirtyData) - } - else { - assert(compilationMode is CompilationMode.Rebuild) { "Unexpected compilation mode: ${compilationMode::class.java}" } - changesRegistry.unknownChanges(currentBuildInfo.startTS) - } - } + + val dirtyData = DirtyData(buildDirtyLookupSymbols, buildDirtyFqNames) + processChangesAfterBuild(compilationMode, currentBuildInfo, dirtyData) if (exitCode == ExitCode.OK) { cacheVersions.forEach { it.saveIfNeeded() } @@ -273,6 +266,18 @@ abstract class IncrementalCompilerRunner< return exitCode } + protected open fun processChangesAfterBuild(compilationMode: CompilationMode, currentBuildInfo: BuildInfo, dirtyData: DirtyData) { + if (changesRegistry == null) return + + if (compilationMode is CompilationMode.Incremental) { + changesRegistry.registerChanges(currentBuildInfo.startTS, dirtyData) + } + else { + assert(compilationMode is CompilationMode.Rebuild) { "Unexpected compilation mode: ${compilationMode::class.java}" } + changesRegistry.unknownChanges(currentBuildInfo.startTS) + } + } + companion object { const val DIRTY_SOURCES_FILE_NAME = "dirty-sources.txt" const val LAST_BUILD_INFO_FILE_NAME = "last-build.bin" diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJvmCompilerRunner.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJvmCompilerRunner.kt index 881e14df20c..45ab3f6d3f8 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJvmCompilerRunner.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJvmCompilerRunner.kt @@ -16,10 +16,10 @@ package org.jetbrains.kotlin.incremental +import com.intellij.openapi.util.io.FileUtil import org.jetbrains.kotlin.annotation.AnnotationFileUpdater import org.jetbrains.kotlin.build.GeneratedFile import org.jetbrains.kotlin.build.GeneratedJvmClass -import org.jetbrains.kotlin.build.isModuleMappingFile import org.jetbrains.kotlin.build.JvmSourceRoot import org.jetbrains.kotlin.cli.common.ExitCode import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments @@ -38,6 +38,7 @@ import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.resolve.jvm.JvmClassName import java.io.File import java.util.* +import kotlin.collections.HashSet fun makeIncrementally( cachesDir: File, @@ -89,7 +90,9 @@ class IncrementalJvmCompilerRunner( reporter: ICReporter, private var kaptAnnotationsFileUpdater: AnnotationFileUpdater? = null, artifactChangesProvider: ArtifactChangesProvider? = null, - changesRegistry: ChangesRegistry? = null + changesRegistry: ChangesRegistry? = null, + private val buildHistoryFile: File? = null, + private val friendBuildHistoryFile: File? = null ) : IncrementalCompilerRunner( workingDir, "caches-jvm", @@ -110,16 +113,58 @@ class IncrementalJvmCompilerRunner( private var javaFilesProcessor = ChangedJavaFilesProcessor(reporter) override fun calculateSourcesToCompile(caches: IncrementalJvmCachesManager, changedFiles: ChangedFiles.Known, args: K2JVMCompilerArguments): CompilationMode { - val removedClassFiles = changedFiles.removed.filter(File::isClassFile) - if (removedClassFiles.any()) return CompilationMode.Rebuild { "Removed class files: ${reporter.pathsAsString(removedClassFiles)}" } + val dirtyFiles = getDirtyFiles(changedFiles) - val modifiedClassFiles = changedFiles.modified.filter(File::isClassFile) - if (modifiedClassFiles.any()) return CompilationMode.Rebuild { "Modified class files: ${reporter.pathsAsString(modifiedClassFiles)}" } + fun markDirtyBy(lookupSymbols: Collection) { + if (lookupSymbols.isEmpty()) return + + val dirtyFilesFromLookups = mapLookupSymbolsToFiles(caches.lookupCache, lookupSymbols, reporter) + dirtyFiles.addAll(dirtyFilesFromLookups) + } + + fun markDirtyBy(dirtyClassesFqNames: Collection) { + if (dirtyClassesFqNames.isEmpty()) return + + val fqNamesWithSubtypes = dirtyClassesFqNames.flatMap { withSubtypes(it, listOf(caches.platformCache)) } + val dirtyFilesFromFqNames = mapClassesFqNamesToFiles(listOf(caches.platformCache), fqNamesWithSubtypes, reporter) + dirtyFiles.addAll(dirtyFilesFromFqNames) + } + + val lastBuildInfo = BuildInfo.read(lastBuildInfoFile) + reporter.report { "Last Kotlin Build info -- $lastBuildInfo" } + + val changesFromFriend by lazy { + val myLastTS = lastBuildInfo?.startTS ?: return@lazy ChangesEither.Unknown() + val storage = friendBuildHistoryFile?.let { BuildDiffsStorage.readFromFile(it, reporter) } ?: return@lazy ChangesEither.Unknown() + + val (prevDiffs, newDiffs) = storage.buildDiffs.partition { it.ts < myLastTS } + if (prevDiffs.isEmpty()) return@lazy ChangesEither.Unknown() + + val dirtyLookupSymbols = HashSet() + val dirtyClassesFqNames = HashSet() + for ((_, isIncremental, dirtyData) in newDiffs) { + if (!isIncremental) return@lazy ChangesEither.Unknown() + + dirtyLookupSymbols.addAll(dirtyData.dirtyLookupSymbols) + dirtyClassesFqNames.addAll(dirtyData.dirtyClassesFqNames) + } + + markDirtyBy(dirtyLookupSymbols) + markDirtyBy(dirtyClassesFqNames) + ChangesEither.Known(dirtyLookupSymbols, dirtyClassesFqNames) + } + val friendDirs = args.friendPaths?.map { File(it) } ?: emptyList() + for (file in changedFiles.removed.asSequence() + changedFiles.modified.asSequence()) { + if (!file.isClassFile()) continue + + val isFriendClassFile = friendDirs.any { FileUtil.isAncestor(it, file, false) } + if (isFriendClassFile && changesFromFriend is ChangesEither.Known) continue + + return CompilationMode.Rebuild { "Cannot get changes from modified or removed class file: ${reporter.pathsAsString(file)}" } + } val classpathSet = args.classpathAsList.toHashSet() val modifiedClasspathEntries = changedFiles.modified.filter { it in classpathSet } - val lastBuildInfo = BuildInfo.read(lastBuildInfoFile) - reporter.report { "Last Kotlin Build info -- $lastBuildInfo" } val classpathChanges = getClasspathChanges(modifiedClasspathEntries, lastBuildInfo) if (classpathChanges !is ChangesEither.Known) { return CompilationMode.Rebuild { "could not get changes from modified classpath entries: ${reporter.pathsAsString(modifiedClasspathEntries)}" } @@ -131,21 +176,9 @@ class IncrementalJvmCompilerRunner( is ChangesEither.Unknown -> return CompilationMode.Rebuild { "Could not get changes for java files" } } - val dirtyFiles = getDirtyFiles(changedFiles) - val lookupSymbols = HashSet() - lookupSymbols.addAll(affectedJavaSymbols) - lookupSymbols.addAll(classpathChanges.lookupSymbols) - - if (lookupSymbols.any()) { - val dirtyFilesFromLookups = mapLookupSymbolsToFiles(caches.lookupCache, lookupSymbols, reporter) - dirtyFiles.addAll(dirtyFilesFromLookups) - } - - val dirtyClassesFqNames = classpathChanges.fqNames.flatMap { withSubtypes(it, listOf(caches.platformCache)) } - if (dirtyClassesFqNames.any()) { - val dirtyFilesFromFqNames = mapClassesFqNamesToFiles(listOf(caches.platformCache), dirtyClassesFqNames, reporter) - dirtyFiles.addAll(dirtyFilesFromFqNames) - } + markDirtyBy(affectedJavaSymbols) + markDirtyBy(classpathChanges.lookupSymbols) + markDirtyBy(classpathChanges.fqNames) return CompilationMode.Incremental(dirtyFiles) } @@ -263,6 +296,23 @@ class IncrementalJvmCompilerRunner( override fun additionalDirtyLookupSymbols(): Iterable = javaFilesProcessor.allChangedSymbols + override fun processChangesAfterBuild(compilationMode: CompilationMode, currentBuildInfo: BuildInfo, dirtyData: DirtyData) { + super.processChangesAfterBuild(compilationMode, currentBuildInfo, dirtyData) + + if (buildHistoryFile == null) return + + val prevDiffs = BuildDiffsStorage.readFromFile(buildHistoryFile, reporter)?.buildDiffs ?: emptyList() + val newDiff = if (compilationMode is CompilationMode.Incremental) { + BuildDifference(currentBuildInfo.startTS, true, dirtyData) + } + else { + val emptyDirtyData = DirtyData() + BuildDifference(currentBuildInfo.startTS, false, emptyDirtyData) + } + + BuildDiffsStorage.writeToFile(buildHistoryFile, BuildDiffsStorage(prevDiffs + newDiff), reporter) + } + override fun makeServices( args: K2JVMCompilerArguments, lookupTracker: LookupTracker, diff --git a/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/BuildDiffsStorageTest.kt b/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/BuildDiffsStorageTest.kt new file mode 100644 index 00000000000..66f34b3d09a --- /dev/null +++ b/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/BuildDiffsStorageTest.kt @@ -0,0 +1,102 @@ +/* + * Copyright 2010-2017 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 org.jetbrains.kotlin.incremental + +import org.jetbrains.kotlin.name.FqName +import org.junit.After +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import java.io.File +import java.io.OutputStream +import java.util.* + +class BuildDiffsStorageTest { + lateinit var storageFile: File + private val random = Random(System.currentTimeMillis()) + + @Before + fun setUp() { + storageFile = File.createTempFile("BuildDiffsStorageTest", "storage") + } + + @After + fun tearDown() { + storageFile.delete() + } + + @Test + fun testToString() { + val lookupSymbols = listOf(LookupSymbol("foo", "bar")) + val fqNames = listOf(FqName("fizz.Buzz")) + val diff = BuildDifference(100, true, DirtyData(lookupSymbols, fqNames)) + val diffs = BuildDiffsStorage(listOf(diff)) + Assert.assertEquals("BuildDiffsStorage(buildDiffs=[BuildDifference(ts=100, isIncremental=true, dirtyData=DirtyData(dirtyLookupSymbols=[LookupSymbol(name=foo, scope=bar)], dirtyClassesFqNames=[fizz.Buzz]))])", + diffs.toString()) + } + + @Test + fun writeReadSimple() { + val diffs = BuildDiffsStorage(listOf(getRandomDiff())) + BuildDiffsStorage.writeToFile(storageFile, diffs, reporter = null) + + val diffsDeserialized = BuildDiffsStorage.readFromFile(storageFile, reporter = null) + Assert.assertEquals(diffs.toString(), diffsDeserialized.toString()) + } + + @Test + fun writeReadMany() { + val generated = Array(20) { getRandomDiff() }.toList() + val diffs = BuildDiffsStorage(generated) + BuildDiffsStorage.writeToFile(storageFile, diffs, reporter = null) + + val diffsDeserialized = BuildDiffsStorage.readFromFile(storageFile, reporter = null) + val expected = generated.sortedBy { it.ts }.takeLast(BuildDiffsStorage.MAX_DIFFS_ENTRIES).toTypedArray() + Assert.assertArrayEquals(expected, diffsDeserialized?.buildDiffs?.toTypedArray()) + } + + @Test + fun readFileNotExist() { + storageFile.delete() + + val diffsDeserialized = BuildDiffsStorage.readFromFile(storageFile, reporter = null) + Assert.assertEquals(null, diffsDeserialized) + } + + @Test + fun versionChanged() { + val diffs = BuildDiffsStorage(listOf(getRandomDiff())) + BuildDiffsStorage.writeToFile(storageFile, diffs, reporter = null) + + val versionBackup = BuildDiffsStorage.CURRENT_VERSION + try { + BuildDiffsStorage.CURRENT_VERSION++ + val diffsDeserialized = BuildDiffsStorage.readFromFile(storageFile, reporter = null) + Assert.assertEquals(null, diffsDeserialized) + } + finally { + BuildDiffsStorage.CURRENT_VERSION = versionBackup + } + } + + private fun getRandomDiff(): BuildDifference { + val ts = random.nextLong() + val lookupSymbols = listOf(LookupSymbol("foo", "bar")) + val fqNames = listOf(FqName("fizz.Buzz")) + return BuildDifference(ts, true, DirtyData(lookupSymbols, fqNames)) + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinGradlePluginIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinGradlePluginIT.kt index d8079c024bd..eb2e4f17567 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinGradlePluginIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinGradlePluginIT.kt @@ -20,10 +20,7 @@ import org.gradle.api.logging.LogLevel import com.intellij.openapi.util.io.FileUtil import org.jetbrains.kotlin.gradle.plugin.CopyClassesToJavaOutputStatus import org.jetbrains.kotlin.gradle.tasks.USING_INCREMENTAL_COMPILATION_MESSAGE -import org.jetbrains.kotlin.gradle.util.checkBytecodeContains -import org.jetbrains.kotlin.gradle.util.getFileByName -import org.jetbrains.kotlin.gradle.util.getFilesByNames -import org.jetbrains.kotlin.gradle.util.modify +import org.jetbrains.kotlin.gradle.util.* import org.junit.Test import java.io.File import java.util.zip.ZipFile @@ -490,6 +487,27 @@ class KotlinGradleIT: BaseGradleIT() { } } + @Test + fun testIncrementalTestCompile() { + val project = Project("kotlinProject", GRADLE_VERSION) + val options = defaultBuildOptions().copy(incremental = true) + + project.build("build", options = options) { + assertSuccessful() + } + + val joinerKt = project.projectDir.getFileByName("KotlinGreetingJoiner.kt") + joinerKt.modify { + it.replace("class KotlinGreetingJoiner", "internal class KotlinGreetingJoiner") + } + + project.build("build", options = options) { + assertSuccessful() + val testJoinerKt = project.projectDir.getFileByName("TestKotlinGreetingJoiner.kt") + assertCompiledKotlinSources(project.relativize(joinerKt, testJoinerKt)) + } + } + @Test fun testLanguageVersionApiVersionExplicit() { val project = Project("kotlinProject", "3.3") diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/TestRootAffectedIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/TestRootAffectedIT.kt index 04b9d34cfb9..0eab7d30ac5 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/TestRootAffectedIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/TestRootAffectedIT.kt @@ -1,15 +1,13 @@ package org.jetbrains.kotlin.gradle -import org.jetbrains.kotlin.gradle.util.allKotlinFiles import org.jetbrains.kotlin.gradle.util.getFileByName import org.jetbrains.kotlin.gradle.util.modify import org.junit.Test -import java.io.File class TestRootAffectedIT : BaseGradleIT() { @Test fun testSourceRootClassIsModifiedIC() { - val project = Project("kotlinProject", "2.10") + val project = Project("kotlinProject", "4.1") val buildOptions = defaultBuildOptions().copy(incremental = true) project.build("build", options = buildOptions) { @@ -26,8 +24,8 @@ class TestRootAffectedIT : BaseGradleIT() { project.build("build", options = buildOptions) { assertSuccessful() - val expectedToCompile = project.relativize(listOf(kotlinGreetingJoinerFile) + project.allTestKotlinFiles()) - assertCompiledKotlinSources(expectedToCompile) + val testKotlinGreetingJoinerFile = project.projectDir.getFileByName("TestKotlinGreetingJoiner.kt") + assertCompiledKotlinSources(project.relativize(kotlinGreetingJoinerFile, testKotlinGreetingJoinerFile)) } project.build("build", options = buildOptions) { @@ -38,7 +36,8 @@ class TestRootAffectedIT : BaseGradleIT() { @Test fun testSourceRootClassIsRemovedIC() { - val project = Project("kotlinProject", "2.10") + // todo: update Gradle after https://github.com/gradle/gradle/issues/3051 is resolved + val project = Project("kotlinProject", "3.0") val buildOptions = defaultBuildOptions().copy(incremental = true) project.build("build", options = buildOptions) { @@ -48,12 +47,6 @@ class TestRootAffectedIT : BaseGradleIT() { val dummyFile = project.projectDir.getFileByName("Dummy.kt") dummyFile.delete() - project.build("build", options = buildOptions) { - assertSuccessful() - val expectedToCompile = project.relativize(project.allTestKotlinFiles()) - assertCompiledKotlinSources(expectedToCompile) - } - project.build("build", options = buildOptions) { assertSuccessful() assertCompiledKotlinSources(emptyList()) @@ -62,7 +55,7 @@ class TestRootAffectedIT : BaseGradleIT() { @Test fun testTestRootClassIsRemovedIC() { - val project = Project("kotlinProject", "2.10") + val project = Project("kotlinProject", "4.1") val buildOptions = defaultBuildOptions().copy(incremental = true) project.build("build", options = buildOptions) { @@ -76,13 +69,5 @@ class TestRootAffectedIT : BaseGradleIT() { assertSuccessful() assertCompiledKotlinSources(emptyList()) } - - project.build("build", options = buildOptions) { - assertSuccessful() - assertCompiledKotlinSources(emptyList()) - } } - - private fun Project.allTestKotlinFiles(): Iterable = - File(projectDir, "src/test").allKotlinFiles() } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleCompilerEnvironment.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleCompilerEnvironment.kt index b37381c5b28..640e6e799f3 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleCompilerEnvironment.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleCompilerEnvironment.kt @@ -36,5 +36,7 @@ internal class GradleIncrementalCompilerEnvironment( compilerArgs: CommonCompilerArguments, val kaptAnnotationsFileUpdater: AnnotationFileUpdater? = null, val artifactDifferenceRegistryProvider: ArtifactDifferenceRegistryProvider? = null, - val artifactFile: File? = null + val artifactFile: File? = null, + val buildHistoryFile: File? = null, + val friendBuildHistoryFile: File? = null ) : GradleCompilerEnvironment(compilerClasspath, messageCollector, outputItemsCollector, compilerArgs) \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerRunner.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerRunner.kt index 52f0f88b03d..a117ea566c3 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerRunner.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerRunner.kt @@ -255,7 +255,9 @@ internal class GradleCompilerRunner(private val project: Project) : KotlinCompil reportSeverity = reportSeverity(verbose), requestedCompilationResults = arrayOf(CompilationResultCategory.IC_COMPILE_ITERATION.code), compilerMode = CompilerMode.INCREMENTAL_COMPILER, - targetPlatform = targetPlatform + targetPlatform = targetPlatform, + resultDifferenceFile = environment.buildHistoryFile, + friendDifferenceFile = environment.friendBuildHistoryFile ) val servicesFacade = GradleIncrementalCompilerServicesFacadeImpl(project, environment) val argsArray = ArgumentUtils.convertArgumentsToStringList(environment.compilerArgs).toTypedArray() diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt index 376edf79f86..7f6c9f133db 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt @@ -243,6 +243,7 @@ open class KotlinCompile : AbstractKotlinCompile(), Kotl internal open val sourceRootsContainer = FilteringSourceRootsContainer() private var kaptAnnotationsFileUpdater: AnnotationFileUpdater? = null + val buildHistoryFile: File = File(taskBuildDirectory, "build-history.bin") val kaptOptions = KaptOptions() @@ -316,10 +317,13 @@ open class KotlinCompile : AbstractKotlinCompile(), Kotl !incremental -> GradleCompilerEnvironment(computedCompilerClasspath, messageCollector, outputItemCollector, args) else -> { logger.warn(USING_INCREMENTAL_COMPILATION_MESSAGE) + val friendTask = friendTaskName?.let { project.tasks.findByName(it) as? KotlinCompile } GradleIncrementalCompilerEnvironment(computedCompilerClasspath, changedFiles, reporter, taskBuildDirectory, messageCollector, outputItemCollector, args, kaptAnnotationsFileUpdater, artifactDifferenceRegistryProvider, - artifactFile) + artifactFile = artifactFile, + buildHistoryFile = buildHistoryFile, + friendBuildHistoryFile = friendTask?.buildHistoryFile) } }