diff --git a/build-common/src/org/jetbrains/kotlin/build/report/metrics/BuildAttribute.kt b/build-common/src/org/jetbrains/kotlin/build/report/metrics/BuildAttribute.kt index 9115e6daa35..4ed65b21d7c 100644 --- a/build-common/src/org/jetbrains/kotlin/build/report/metrics/BuildAttribute.kt +++ b/build-common/src/org/jetbrains/kotlin/build/report/metrics/BuildAttribute.kt @@ -16,6 +16,7 @@ enum class BuildAttributeKind : Serializable { } enum class BuildAttribute(val kind: BuildAttributeKind, val readableString: String) : Serializable { + CACHE_DIRECTORY_NOT_POPULATED(BuildAttributeKind.REBUILD_REASON, "Cache directory not populated"), NO_BUILD_HISTORY(BuildAttributeKind.REBUILD_REASON, "Build history file not found"), NO_ABI_SNAPSHOT(BuildAttributeKind.REBUILD_REASON, "ABI snapshot not found"), CLASSPATH_SNAPSHOT_NOT_FOUND(BuildAttributeKind.REBUILD_REASON, "Classpath snapshot not found"), diff --git a/build-common/src/org/jetbrains/kotlin/incremental/LookupStorage.kt b/build-common/src/org/jetbrains/kotlin/incremental/LookupStorage.kt index 7d7d939f4ce..742dffe806c 100644 --- a/build-common/src/org/jetbrains/kotlin/incremental/LookupStorage.kt +++ b/build-common/src/org/jetbrains/kotlin/incremental/LookupStorage.kt @@ -288,13 +288,13 @@ data class LookupSymbol(val name: String, val scope: String) : Comparable() else null val removedKeys = if (trackChanges) mutableSetOf() else null 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 6a51b9f446c..405dc5e8bcd 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 @@ -129,7 +129,15 @@ abstract class IncrementalCompilerRunner< else -> providedChangedFiles } - val compilationMode = sourcesToCompile(caches, changedFiles, args, messageCollector, classpathAbiSnapshot) + // Check whether the cache directory is populated (note that it may be deleted upon a Gradle build cache hit if the directory is + // marked as @LocalState in the Gradle task). + val cacheDirectoryNotPopulated = cacheDirectory.walk().none { it.isFile } + + val compilationMode = if (cacheDirectoryNotPopulated) { + CompilationMode.Rebuild(BuildAttribute.CACHE_DIRECTORY_NOT_POPULATED) + } else { + sourcesToCompile(caches, changedFiles, args, messageCollector, classpathAbiSnapshot) + } val exitCode = when (compilationMode) { is CompilationMode.Incremental -> { 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 d464d0dc9cb..e0bb95cf638 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 @@ -256,7 +256,7 @@ class IncrementalJvmCompilerRunner( is NotAvailableDueToMissingClasspathSnapshot -> ChangesEither.Unknown(BuildAttribute.CLASSPATH_SNAPSHOT_NOT_FOUND) is NotAvailableForNonIncrementalRun -> ChangesEither.Unknown(BuildAttribute.UNKNOWN_CHANGES_IN_GRADLE_INPUTS) is ClasspathSnapshotDisabled -> reporter.measure(BuildTime.IC_ANALYZE_CHANGES_IN_DEPENDENCIES) { - val lastBuildInfo = BuildInfo.read(lastBuildInfoFile) ?: return CompilationMode.Rebuild(BuildAttribute.IC_IS_NOT_ENABLED) + val lastBuildInfo = BuildInfo.read(lastBuildInfoFile) ?: return CompilationMode.Rebuild(BuildAttribute.NO_BUILD_HISTORY) reporter.reportVerbose { "Last Kotlin Build info -- $lastBuildInfo" } val scopes = caches.lookupCache.lookupSymbols.map { it.scope.ifBlank { it.name } }.distinct() diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/AbstractKotlinAndroidGradleTests.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/AbstractKotlinAndroidGradleTests.kt index f5ce9205cb8..9e7dcaf8453 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/AbstractKotlinAndroidGradleTests.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/AbstractKotlinAndroidGradleTests.kt @@ -1030,8 +1030,7 @@ fun getSomething() = 10 .filter { it.contains("[KOTLIN] compile iteration:") } .drop(1) .joinToString(separator = "/n") - val actualSources = getCompiledKotlinSources(filteredOutput).projectRelativePaths(project) - assertSameFiles(project.relativize(androidModuleKt), actualSources, "Compiled Kotlin files differ:\n ") + assertCompiledKotlinSources(project.relativize(androidModuleKt), output = filteredOutput) } } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt index 645244938a3..7d390f122f2 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt @@ -11,26 +11,19 @@ import org.gradle.api.logging.configuration.WarningMode import org.gradle.tooling.GradleConnector import org.gradle.util.GradleVersion import org.intellij.lang.annotations.Language +import org.jetbrains.kotlin.cli.common.CompilerSystemProperties.COMPILE_INCREMENTAL_WITH_ARTIFACT_TRANSFORM import org.jetbrains.kotlin.gradle.model.ModelContainer import org.jetbrains.kotlin.gradle.model.ModelFetcherBuildAction import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType import org.jetbrains.kotlin.gradle.report.BuildReportType -import org.jetbrains.kotlin.gradle.testbase.applyAndroidTestFixes -import org.jetbrains.kotlin.gradle.testbase.enableCacheRedirector -import org.jetbrains.kotlin.gradle.testbase.extractJavaCompiledSources -import org.jetbrains.kotlin.gradle.testbase.extractKotlinCompiledSources -import org.jetbrains.kotlin.gradle.testbase.prettyPrintXml -import org.jetbrains.kotlin.gradle.testbase.readAndCleanupTestResults -import org.jetbrains.kotlin.gradle.testbase.addPluginManagementToSettings +import org.jetbrains.kotlin.gradle.testbase.* import org.jetbrains.kotlin.gradle.util.* import org.jetbrains.kotlin.gradle.util.modify import org.jetbrains.kotlin.konan.target.HostManager import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.test.RunnerWithMuteInDatabase -import org.jetbrains.kotlin.test.util.trimTrailingWhitespaces import org.junit.After import org.junit.AfterClass -import org.junit.Assert import org.junit.Before import org.junit.runner.RunWith import java.io.File @@ -386,14 +379,7 @@ abstract class BaseGradleIT { } } - class CompiledProject(val project: Project, val output: String, val resultCode: Int) { - fun getCompiledKotlinSources(output: String) = - extractKotlinCompiledSources(project.projectDir.toPath(), output).map { sourcePath -> sourcePath.toFile() } - - val compiledJavaSources: Iterable by lazy { - extractJavaCompiledSources(output).map { sourcePath -> sourcePath.toFile() } - } - } + class CompiledProject(val project: Project, val output: String, val resultCode: Int) // Basically the same as `Project.build`, tells gradle to wait for debug on 5005 port // Faster to type than `project.build("-Dorg.gradle.debug=true")` or `project.build(options = defaultBuildOptions().copy(debug = true))` @@ -593,27 +579,6 @@ abstract class BaseGradleIT { return map { it.canonicalFile.toRelativeString(project.projectDir) } } - fun CompiledProject.assertSameFiles(expected: Iterable, actual: Iterable, messagePrefix: String): CompiledProject { - val expectedSet = expected.map { it.normalizePath() }.toSortedSet().joinToString("\n") - val actualSet = actual.map { it.normalizePath() }.toSortedSet().joinToString("\n") - Assert.assertEquals(messagePrefix, expectedSet, actualSet) - return this - } - - fun CompiledProject.assertContainFiles( - expected: Iterable, - actual: Iterable, - messagePrefix: String = "" - ): CompiledProject { - val expectedNormalized = expected.map(::normalizePath).toSortedSet() - val actualNormalized = actual.map(::normalizePath).toSortedSet() - assertTrue( - actualNormalized.containsAll(expectedNormalized), - messagePrefix + "expected files: ${expectedNormalized.joinToString()}\n !in actual files: ${actualNormalized.joinToString()}" - ) - return this - } - fun CompiledProject.findTasksByPattern(pattern: String): Set { return "task '($pattern)'".toRegex().findAll(output).mapTo(HashSet()) { it.groupValues[1] } } @@ -778,13 +743,14 @@ Finished executing task ':$taskName'| output: String = this.output, suffix: String = "" ): CompiledProject { - val messagePrefix = "Compiled Kotlin files differ${suffix}:\n " - val actualSources = getCompiledKotlinSources(output).projectRelativePaths(this.project) - return if (weakTesting) { - assertContainFiles(expectedSourcesRelativePaths, actualSources, messagePrefix) + val messagePrefix = "Compiled Kotlin files differ${suffix}:\n" + val actualSources = extractCompiledKotlinFiles(output) + if (weakTesting) { + assertContainsFiles(expectedSourcesRelativePaths.toPaths(), actualSources, messagePrefix) } else { - assertSameFiles(expectedSourcesRelativePaths, actualSources, messagePrefix) + assertSameFiles(expectedSourcesRelativePaths.toPaths(), actualSources, messagePrefix) } + return this } fun CompiledProject.assertCompiledKotlinFiles(expectedFiles: Iterable): CompiledProject = @@ -799,11 +765,15 @@ Finished executing task ':$taskName'| fun CompiledProject.assertCompiledJavaSources( sources: Iterable, weakTesting: Boolean = false - ): CompiledProject = + ): CompiledProject { + val messagePrefix = "Compiled Java files differ:\n" + val actualSources = extractCompiledJavaFiles(project.projectDir, output) if (weakTesting) - assertContainFiles(sources, compiledJavaSources.projectRelativePaths(this.project), "Compiled Java files differ:\n ") + assertContainsFiles(sources.toPaths(), actualSources, messagePrefix) else - assertSameFiles(sources, compiledJavaSources.projectRelativePaths(this.project), "Compiled Java files differ:\n ") + assertSameFiles(sources.toPaths(), actualSources, messagePrefix) + return this + } fun Project.resourcesDir(subproject: String? = null, sourceSet: String = "main"): String = (subproject?.plus("/") ?: "") + "build/resources/$sourceSet/" @@ -904,7 +874,7 @@ Finished executing task ':$taskName'| options.incrementalJsKlib?.let { add("-Pkotlin.incremental.js.klib=$it") } options.jsIrBackend?.let { add("-Pkotlin.js.useIrBackend=$it") } options.usePreciseJavaTracking?.let { add("-Pkotlin.incremental.usePreciseJavaTracking=$it") } - options.useClasspathSnapshot?.let { add("-Pkotlin.incremental.useClasspathSnapshot=$it") } + options.useClasspathSnapshot?.let { add("-P${COMPILE_INCREMENTAL_WITH_ARTIFACT_TRANSFORM.property}=$it") } options.androidGradlePluginVersion?.let { add("-Pandroid_tools_version=$it") } if (options.debug) { add("-Dorg.gradle.debug=true") diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildCacheIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildCacheIT.kt index b6ff82643bf..40e0cae3beb 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildCacheIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildCacheIT.kt @@ -103,33 +103,6 @@ class BuildCacheIT : KGPBaseTest() { } } - @DisplayName("Incremental compilation works with cache") - @GradleTest - fun testKotlinCompileIncrementalBuildWithoutRelocation(gradleVersion: GradleVersion) { - project("buildCacheSimple", gradleVersion) { - enableLocalBuildCache(localBuildCacheDir) - - build("assemble") { - assertTasksPackedToCache(":compileKotlin") - } - - build("clean", "assemble") { - assertTasksFromCache(":compileKotlin") - } - - val fooKtSourceFile = kotlinSourcesDir().resolve("foo.kt") - fooKtSourceFile.modify { it.replace("Int = 1", "String = \"abc\"") } - build("assemble") { - assertIncrementalCompilation(modifiedFiles = setOf(fooKtSourceFile)) - } - - fooKtSourceFile.modify { it.replace("String = \"abc\"", "Int = 1") } - build("clean", "assemble") { - assertTasksFromCache(":compileKotlin") - } - } - } - @DisplayName("Debug log level should not break build cache") @GradleTest fun testDebugLogLevelCaching(gradleVersion: GradleVersion) { diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildCacheRelocationIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildCacheRelocationIT.kt index 3dfc5bd045f..7975b588ecb 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildCacheRelocationIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildCacheRelocationIT.kt @@ -16,11 +16,13 @@ package org.jetbrains.kotlin.gradle +import org.gradle.api.logging.LogLevel import org.gradle.api.logging.configuration.WarningMode import org.gradle.util.GradleVersion import org.jetbrains.kotlin.gradle.testbase.* import org.junit.jupiter.api.DisplayName import kotlin.io.path.createDirectory +import kotlin.io.path.pathString @DisplayName("Build cache relocation") @SimpleGradlePluginTests @@ -260,20 +262,6 @@ class BuildCacheRelocationIT : KGPBaseTest() { ) } - @DisplayName("Incremental compilation build cache does not break relocated cache") - @GradleTest - fun testKotlinCompileCachingIncrementalBuildWithRelocation(gradleVersion: GradleVersion) { - val firstProject = project("buildCacheSimple", gradleVersion) { - enableLocalBuildCache(localBuildCacheDir) - } - - val secondProject = project("buildCacheSimple", gradleVersion) { - enableLocalBuildCache(localBuildCacheDir) - } - - checkKotlinCompileCachingIncrementalBuild(firstProject, secondProject) - } - @DisplayName("Kapt incremental compilation works with cache") @GradleTest fun testKaptCachingIncrementalBuildWithoutRelocation(gradleVersion: GradleVersion) { @@ -332,10 +320,22 @@ class BuildCacheRelocationIT : KGPBaseTest() { } } - private fun checkKotlinCompileCachingIncrementalBuild( - firstProject: TestProject, - secondProject: TestProject - ) { + @DisplayName("Kotlin incremental compilation should work correctly") + @GradleTest + fun testKotlinIncrementalCompilation(gradleVersion: GradleVersion) { + checkKotlinIncrementalCompilation(gradleVersion) + } + + @DisplayName("Kotlin incremental compilation with `kotlin.incremental.useClasspathSnapshot` feature should work correctly") + @GradleTest + fun testKotlinIncrementalCompilation_withClasspathSnapshot(gradleVersion: GradleVersion) { + checkKotlinIncrementalCompilation(gradleVersion, useClasspathSnapshot = true) + } + + private fun checkKotlinIncrementalCompilation(gradleVersion: GradleVersion, useClasspathSnapshot: Boolean? = null) { + val buildOptions = defaultBuildOptions.copy(useClasspathSnapshot = useClasspathSnapshot) + val (firstProject, secondProject) = prepareTestProjects("buildCacheSimple", gradleVersion, buildOptions) + // First build, should be stored into the build cache: firstProject.build("assemble") { assertTasksPackedToCache(":compileKotlin") @@ -348,9 +348,12 @@ class BuildCacheRelocationIT : KGPBaseTest() { // Change the return type of foo() from Int to String in foo.kt, and check that fooUsage.kt is recompiled as well: val fooKtSourceFile = secondProject.kotlinSourcesDir().resolve("foo.kt") + val fooUsageKtSourceFile = secondProject.kotlinSourcesDir().resolve("fooUsage.kt") fooKtSourceFile.modify { it.replace("Int = 1", "String = \"abc\"") } - secondProject.build("assemble") { - assertIncrementalCompilation(modifiedFiles = setOf(fooKtSourceFile)) + secondProject.build("assemble", buildOptions = buildOptions.copy(logLevel = LogLevel.DEBUG)) { + assertIncrementalCompilation( + listOf(fooKtSourceFile, fooUsageKtSourceFile).relativizeTo(secondProject.projectPath).map { it.pathString } + ) } // Revert the change to the return type of foo(), and check if we get a cache hit diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/BuildOptions.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/BuildOptions.kt index c66b363604b..2eec4a153a1 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/BuildOptions.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/BuildOptions.kt @@ -8,6 +8,7 @@ package org.jetbrains.kotlin.gradle.testbase import org.gradle.api.logging.LogLevel import org.gradle.api.logging.configuration.WarningMode import org.gradle.util.GradleVersion +import org.jetbrains.kotlin.cli.common.CompilerSystemProperties.COMPILE_INCREMENTAL_WITH_ARTIFACT_TRANSFORM import org.jetbrains.kotlin.gradle.BaseGradleIT import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType import org.jetbrains.kotlin.gradle.report.BuildReportType @@ -21,6 +22,7 @@ data class BuildOptions( val configurationCacheProblems: BaseGradleIT.ConfigurationCacheProblems = BaseGradleIT.ConfigurationCacheProblems.FAIL, val parallel: Boolean = true, val incremental: Boolean? = null, + val useClasspathSnapshot: Boolean? = null, val maxWorkers: Int = (Runtime.getRuntime().availableProcessors() / 4 - 1).coerceAtLeast(2), val fileSystemWatchEnabled: Boolean = false, val buildCacheEnabled: Boolean = false, @@ -81,6 +83,8 @@ data class BuildOptions( arguments.add("-Pkotlin.incremental=$incremental") } + useClasspathSnapshot?.let { arguments.add("-P${COMPILE_INCREMENTAL_WITH_ARTIFACT_TRANSFORM.property}=$it") } + if (gradleVersion >= GradleVersion.version("6.5")) { if (fileSystemWatchEnabled) { arguments.add("--watch-fs") diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/compilationAssertions.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/compilationAssertions.kt index 8f71dedd674..c9135558332 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/compilationAssertions.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/compilationAssertions.kt @@ -5,14 +5,16 @@ package org.jetbrains.kotlin.gradle.testbase -import java.nio.file.Path -import java.nio.file.Paths -import kotlin.io.path.relativeTo import org.gradle.api.logging.LogLevel import org.gradle.testkit.runner.BuildResult import org.intellij.lang.annotations.Language +import java.io.File +import java.nio.file.Path +import java.nio.file.Paths -private val kotlinSrcRegex by lazy { Regex("\\[KOTLIN\\] compile iteration: ([^\\r\\n]*)") } +private val kotlinSrcRegex by lazy { Regex("\\[KOTLIN] compile iteration: ([^\\r\\n]*)") } + +private val javaSrcRegex by lazy { Regex("\\[DEBUG] \\[[^]]*JavaCompiler] Compiler arguments: ([^\\r\\n]*)") } @Language("RegExp") private fun taskOutputRegex( @@ -42,45 +44,56 @@ fun BuildResult.getOutputForTask(taskName: String): String = taskOutputRegex(tas ?: error("Could not find output for task $taskName") /** - * Extracts list of compiled .kt files from task output + * Extracts the list of compiled .kt files from the build output. * - * Note: log level of output should be set to [LogLevel.DEBUG] + * The returned paths are relative to the project directory. + * + * Note: Log level of output must be set to [LogLevel.DEBUG]. */ -fun extractKotlinCompiledSources(projectDirectory: Path, output: String) = - kotlinSrcRegex.findAll(output) - .asIterable() - .flatMap { result -> - result.groups[1]!!.value.split(", ") - .map { source -> projectDirectory.resolve(source).normalize() } - } - -private val javaSrcRegex by lazy { Regex("\\[DEBUG\\] \\[[^\\]]*JavaCompiler\\] Compiler arguments: ([^\\r\\n]*)") } +fun extractCompiledKotlinFiles(output: String): List { + return kotlinSrcRegex.findAll(output).asIterable() + .flatMap { matchResult -> matchResult.groups[1]!!.value.split(", ") } + .toPaths() +} /** - * Extracts list of compiled .java files from task output + * Extracts the list of compiled .java files from the build output. * - * Note: log level of output should be set to [LogLevel.DEBUG] + * The returned paths are relative to the project directory. + * + * Note: Log level of output must be set to [LogLevel.DEBUG]. */ -fun extractJavaCompiledSources(output: String): List = - javaSrcRegex.findAll(output).asIterable().flatMap { - it.groups[1]!!.value - .split(" ") - .filter { source -> source.endsWith(".java", ignoreCase = true) } - .map { source -> Paths.get(source).normalize() } - } +fun extractCompiledJavaFiles(projectDir: File, output: String): List { + return javaSrcRegex.findAll(output).asIterable() + .flatMap { matchResult -> matchResult.groups[1]!!.value.split(" ") } + .filter { filePath -> filePath.endsWith(".java", ignoreCase = true) } + .map { javaFilePath -> projectDir.toPath().relativize(Paths.get(javaFilePath)) } +} /** * Asserts all the .kt files from [expectedSources] and only they are compiled * * Note: log level of output should be set to [LogLevel.DEBUG] */ -fun GradleProject.assertCompiledKotlinSources( +fun assertCompiledKotlinSources( expectedSources: Iterable, output: String, errorMessageSuffix: String = "" ) { - val actualSources = extractKotlinCompiledSources(projectPath, output).map { - it.relativeTo(projectPath) - } + val actualSources = extractCompiledKotlinFiles(output) assertSameFiles(expectedSources, actualSources, "Compiled Kotlin files differ${errorMessageSuffix}:\n") -} \ No newline at end of file +} + +/** + * Asserts that compilation was incremental and the set of compiled .kt files exactly match [expectedCompiledKotlinFiles]. + * + * [expectedCompiledKotlinFiles] are relative to the project directory. + * + * Note: Log level of output must be set to [LogLevel.DEBUG]. + */ +fun BuildResult.assertIncrementalCompilation(expectedCompiledKotlinFiles: Iterable) { + assertOutputDoesNotContain("Non-incremental compilation will be performed") + + val actualCompiledKotlinFiles = extractCompiledKotlinFiles(output) + assertSameFiles(expectedCompiledKotlinFiles.toPaths(), actualCompiledKotlinFiles, "Compiled Kotlin files differ:\n") +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/configurationCacheHelpers.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/configurationCacheHelpers.kt index b597f0fce05..3d5c166439c 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/configurationCacheHelpers.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/configurationCacheHelpers.kt @@ -55,7 +55,7 @@ fun TestProject.assertSimpleConfigurationCacheScenarioWorks( */ @OptIn(ExperimentalPathApi::class) private fun copyReportToTempDir(htmlReportFile: Path): Path = - createTempDir("report").let { tempDir -> + createTempDirDeleteOnExit("report").let { tempDir -> htmlReportFile.parent.toFile().copyRecursively(tempDir.toFile()) tempDir.resolve(htmlReportFile.name) } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/fileAssertions.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/fileAssertions.kt index 8f5a4ceaebe..37d8ad5ca5e 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/fileAssertions.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/fileAssertions.kt @@ -10,7 +10,7 @@ import java.nio.file.Path import kotlin.io.path.exists import kotlin.io.path.isDirectory import kotlin.io.path.readText -import kotlin.test.assertEquals +import kotlin.test.asserter /** * Asserts file under [file] path exists and is a regular file. @@ -189,8 +189,24 @@ fun GradleProject.assertFileDoesNotContain( } } -fun GradleProject.assertSameFiles(expected: Iterable, actual: Iterable, messagePrefix: String) { - val expectedSet = expected.map { it.toString().normalizePath() }.toSortedSet().joinToString("\n") - val actualSet = actual.map { it.toString().normalizePath() }.toSortedSet().joinToString("\n") - assertEquals(expectedSet, actualSet, messagePrefix) -} \ No newline at end of file +fun assertSameFiles(expected: Iterable, actual: Iterable, messagePrefix: String) { + val expectedSet = expected.map { it.toString().normalizePath() }.toSet() + val actualSet = actual.map { it.toString().normalizePath() }.toSet() + asserter.assertTrue(lazyMessage = { + messagePrefix + + "Actual set does not exactly match expected set.\n" + + "Expected set: ${expectedSet.sorted().joinToString(", ")}\n" + + "Actual set: ${actualSet.sorted().joinToString(", ")}\n" + }, actualSet.size == expectedSet.size && actualSet.containsAll(expectedSet)) +} + +fun assertContainsFiles(expected: Iterable, actual: Iterable, messagePrefix: String) { + val expectedSet = expected.map { it.toString().normalizePath() }.toSet() + val actualSet = actual.map { it.toString().normalizePath() }.toSet() + asserter.assertTrue(lazyMessage = { + messagePrefix + + "Actual set does not contain all of expected set.\n" + + "Expected set: ${expectedSet.sorted().joinToString(", ")}\n" + + "Actual set: ${actualSet.sorted().joinToString(", ")}\n" + }, actualSet.containsAll(expectedSet)) +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/outputAssertions.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/outputAssertions.kt index 91cb4e80082..b5911124605 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/outputAssertions.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/outputAssertions.kt @@ -6,7 +6,6 @@ package org.jetbrains.kotlin.gradle.testbase import org.gradle.testkit.runner.BuildResult -import java.nio.file.Path /** * Asserts Gradle output contains [expectedSubString] string. @@ -127,70 +126,6 @@ fun BuildResult.assertNoBuildWarnings( } } -fun BuildResult.assertIncrementalCompilation( - modifiedFiles: Set = emptySet(), - deletedFiles: Set = emptySet() -) { - val incrementalCompilationOptions = output - .lineSequence() - .filter { it.trim().startsWith("Options for KOTLIN DAEMON: IncrementalCompilationOptions(") } - .map { - it - .removePrefix("Options for KOTLIN DAEMON: IncrementalCompilationOptions(") - .removeSuffix(")") - } - .toList() - - assert(incrementalCompilationOptions.isNotEmpty()) { - printBuildOutput() - "No incremental compilation options were found in the build" - } - - val modifiedFilesPath = modifiedFiles.map { it.toAbsolutePath().toString() } - val deletedFilesPath = deletedFiles.map { it.toAbsolutePath().toString() } - val hasMatch = incrementalCompilationOptions - .firstOrNull { - val optionModifiedFiles = it - .substringAfter("modifiedFiles=[") - .substringBefore("]") - .split(",") - .filter(String::isNotEmpty) - - val modifiedFilesFound = if (modifiedFilesPath.isEmpty()) { - optionModifiedFiles.isEmpty() - } else { - modifiedFilesPath.subtract(optionModifiedFiles).isEmpty() - } - - val optionDeletedFiles = it - .substringAfter("deletedFiles=[") - .substringBefore("]") - .split(",") - .filter(String::isNotEmpty) - - val deletedFilesFound = if (deletedFilesPath.isEmpty()) { - optionDeletedFiles.isEmpty() - } else { - deletedFilesPath.subtract(optionDeletedFiles).isEmpty() - } - - modifiedFilesFound && deletedFilesFound - } != null - - assert(hasMatch) { - printBuildOutput() - - """ - |Expected incremental compilation options with: - |- modified files: ${modifiedFilesPath.joinToString()} - |- deleted files: ${deletedFilesPath.joinToString()} - | - |but none of following compilation options match: - |${incrementalCompilationOptions.joinToString(separator = "\n")} - """.trimMargin() - } -} - /** * Asserts compilation is running via Kotlin daemon with given jvm arguments. */ diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/pathHelpers.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/pathHelpers.kt index 4c8362c81a6..a42a4ca0dce 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/pathHelpers.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/pathHelpers.kt @@ -6,10 +6,7 @@ package org.jetbrains.kotlin.gradle.testbase import java.io.IOException -import java.nio.file.FileVisitResult -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.SimpleFileVisitor +import java.nio.file.* import java.nio.file.attribute.BasicFileAttributes import kotlin.io.path.* import kotlin.streams.asSequence @@ -50,7 +47,7 @@ internal val Path.allJavaSources: List * * Prefer using JUnit5 `@TempDir` over this method when possible. */ -internal fun createTempDir(prefix: String): Path = Files +internal fun createTempDirDeleteOnExit(prefix: String): Path = Files .createTempDirectory(prefix) .apply { toFile().deleteOnExit() } @@ -108,3 +105,5 @@ internal fun Path.deleteRecursively() { } }) } + +internal fun Iterable.toPaths(): List = map { Paths.get(it) } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/util/fileUtil.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/util/fileUtil.kt index 9bc97fbe76f..c2db85299ea 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/util/fileUtil.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/util/fileUtil.kt @@ -5,10 +5,8 @@ package org.jetbrains.kotlin.gradle.util +import org.jetbrains.kotlin.gradle.testbase.createTempDirDeleteOnExit import java.io.File -import java.nio.file.Files -import java.nio.file.Path -import kotlin.io.path.relativeTo fun File.getFileByName(name: String): File = findFileByName(name) ?: throw AssertionError("Could not find file with name '$name' in $this") @@ -36,10 +34,7 @@ fun File.addNewLine() { modify { "$it\n" } } -fun createTempDir(prefix: String): File = - Files.createTempDirectory(prefix).toFile().apply { - deleteOnExit() - } +fun createTempDir(prefix: String): File = createTempDirDeleteOnExit(prefix).toFile() /** * converts back slashes to forward slashes diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/buildCacheSimple/src/main/kotlin/StandAloneClass.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/buildCacheSimple/src/main/kotlin/StandAloneClass.kt new file mode 100644 index 00000000000..234bd3b1560 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/buildCacheSimple/src/main/kotlin/StandAloneClass.kt @@ -0,0 +1,3 @@ +// This class is used so that the set of compiled source files in an incremental compilation is a proper subset of the set of compiled +// source files in a non-incremental compilation, so that the two cases are not ambiguous when we check the set of compiled source files. +class StandAloneClass \ No newline at end of file