[IC] Relocatable IC caches for projects with custom buildDir

IC caches often contain file paths. To make them relocatable, we need
to convert these file paths into relative paths, relative to a base
directory.
  - If the file paths are source files, we can use the root project
    directory as base.
  - If the file paths are class files, we should use the classes
    directory as base (before this commit, we used the root project
    directory in both cases, that's why we hit KT-58547).

The key changes in this commit include:
  - RelocatableFileToPathConverter: converts paths to relative paths
  - IncrementalCompilationContext: contains 2 different path converters,
    one for source files and one for class files
  - SourceToOutputFilesMap: maps source files to class files using the
    above path converters
  - IncrementalCompilerRunner: creates the path converters based on file
    locations

Test: RelocatableFileToPathConverterTest unit test
      SourceToOutputFilesMapTest unit test
      BuildCacheRelocationIT.testCustomBuildDirectory integration test
^KT-58547 Fixed
This commit is contained in:
Hung Nguyen
2023-08-10 11:05:55 +01:00
committed by Space Team
parent 432c9fe592
commit 894ba9ab80
30 changed files with 329 additions and 293 deletions
@@ -17,13 +17,15 @@
package org.jetbrains.kotlin.gradle
import org.gradle.api.logging.LogLevel
import org.gradle.api.logging.configuration.WarningMode
import org.gradle.testkit.runner.BuildResult
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.testbase.*
import org.junit.jupiter.api.DisplayName
import java.io.File
import kotlin.io.path.createDirectory
import kotlin.io.path.isRegularFile
import kotlin.io.path.readText
import kotlin.io.path.walk
@DisplayName("Build cache relocation")
class BuildCacheRelocationIT : KGPBaseTest() {
@@ -400,4 +402,29 @@ class BuildCacheRelocationIT : KGPBaseTest() {
assertTasksFromCache(":app:kaptGenerateStubsKotlin", ":app:kaptKotlin")
}
}
@JvmGradlePluginTests
@DisplayName("test relocatability for projects using custom build directory") // Regression test for KT-58547
@GradleTest
fun testCustomBuildDirectory(gradleVersion: GradleVersion) {
val (firstProject, secondProject) = prepareTestProjects("buildCacheSimple", gradleVersion)
firstProject.buildGradle.append("buildDir = \"../BUILD_DIR_1\"")
secondProject.buildGradle.append("buildDir = \"../BUILD_DIR_2\"")
firstProject.build(":compileKotlin")
val outputFilesContainingNonRelocatablePaths =
firstProject.projectPath.resolve("../BUILD_DIR_1/kotlin/compileKotlin").walk()
.filter {
// Use readText() even for binary files as we don't have a better way for now
it.isRegularFile() && it.readText().contains("BUILD_DIR_1")
}.toList()
assert(outputFilesContainingNonRelocatablePaths.isEmpty()) {
"The following output files contain non-relocatable paths:\n" + outputFilesContainingNonRelocatablePaths.joinToString("\n")
}
secondProject.build(":compileKotlin") {
assertTasksFromCache(":compileKotlin")
}
}
}
@@ -7,6 +7,8 @@ package org.jetbrains.kotlin.compilerRunner
import org.gradle.api.logging.Logger
import org.jetbrains.kotlin.build.report.metrics.*
import org.jetbrains.kotlin.build.report.statistics.StatTag
import org.jetbrains.kotlin.buildtools.api.KotlinLogger
import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.config.Services
@@ -15,13 +17,8 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
import org.jetbrains.kotlin.gradle.logging.*
import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskExecutionResults
import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskLoggers
import org.jetbrains.kotlin.build.report.statistics.StatTag
import org.jetbrains.kotlin.buildtools.api.KotlinLogger
import org.jetbrains.kotlin.gradle.report.*
import org.jetbrains.kotlin.gradle.tasks.*
import org.jetbrains.kotlin.gradle.tasks.OOMErrorException
import org.jetbrains.kotlin.gradle.tasks.cleanOutputsAndLocalState
import org.jetbrains.kotlin.gradle.tasks.kotlinDaemonOOMHelperMessage
import org.jetbrains.kotlin.gradle.utils.stackTraceAsString
import org.jetbrains.kotlin.incremental.ChangedFiles
import org.jetbrains.kotlin.incremental.ClasspathChanges
@@ -38,7 +35,6 @@ import java.util.*
import java.util.concurrent.Callable
import java.util.concurrent.Executors
import javax.inject.Inject
import kotlin.collections.HashSet
internal class ProjectFilesForCompilation(
val projectRootFile: File,
@@ -331,6 +327,7 @@ internal class GradleKotlinCompilerWork @Inject constructor(
multiModuleICSettings = icEnv.multiModuleICSettings,
modulesInfo = incrementalModuleInfo!!,
rootProjectDir = icEnv.rootProjectDir,
buildDir = icEnv.buildDir,
kotlinScriptExtensions = kotlinScriptExtensions,
withAbiSnapshot = icEnv.withAbiSnapshot,
preciseCompilationResultsBackup = icEnv.preciseCompilationResultsBackup,
@@ -16,6 +16,7 @@ internal class IncrementalCompilationEnvironment(
val classpathChanges: ClasspathChanges,
val workingDir: File,
val rootProjectDir: File,
val buildDir: File,
val usePreciseJavaTracking: Boolean = false,
val disableMultiModuleIC: Boolean = false,
val multiModuleICSettings: MultiModuleICSettings,
@@ -96,7 +96,8 @@ internal abstract class BuildToolsApiCompilationWork : WorkAction<BuildToolsApiC
val classpathChanges = icEnv?.classpathChanges
if (classpathChanges is ClasspathChanges.ClasspathSnapshotEnabled) {
val classpathSnapshotsConfig = jvmCompilationConfig.makeClasspathSnapshotBasedIncrementalCompilationConfiguration()
.useProjectDir(icEnv.rootProjectDir)
.setRootProjectDir(icEnv.rootProjectDir)
.setBuildDir(icEnv.buildDir)
.usePreciseJavaTracking(icEnv.usePreciseJavaTracking)
.usePreciseCompilationResultsBackup(icEnv.preciseCompilationResultsBackup)
.keepIncrementalCompilationCachesInMemory(icEnv.keepIncrementalCompilationCachesInMemory)
@@ -328,7 +328,8 @@ abstract class Kotlin2JsCompile @Inject constructor(
getChangedFiles(inputChanges, incrementalProps),
ClasspathChanges.NotAvailableForJSCompiler,
taskBuildCacheableOutputDirectory.get().asFile,
projectRootDir,
rootProjectDir = rootProjectDir,
buildDir = buildDir,
multiModuleICSettings = multiModuleICSettings,
preciseCompilationResultsBackup = preciseCompilationResultsBackup.get(),
keepIncrementalCompilationCachesInMemory = keepIncrementalCompilationCachesInMemory.get(),
@@ -351,16 +352,17 @@ abstract class Kotlin2JsCompile @Inject constructor(
}
private val projectRootDir = project.rootDir
private val rootProjectDir = project.rootDir
private val buildDir = project.buildDir
private fun validateOutputDirectory() {
val outputFile = outputFileProperty.get()
val outputDir = outputFile.parentFile
if (outputDir.isParentOf(projectRootDir)) {
if (outputDir.isParentOf(rootProjectDir)) {
throw InvalidUserDataException(
"The output directory '$outputDir' (defined by outputFile of ':$name') contains or " +
"matches the project root directory '${projectRootDir}'.\n" +
"matches the project root directory '${rootProjectDir}'.\n" +
"Gradle will not be able to build the project because of the root directory lock.\n" +
"To fix this, consider using the default outputFile location instead of providing it explicitly."
)
@@ -28,9 +28,7 @@ import org.jetbrains.kotlin.compilerRunner.IncrementalCompilationEnvironment
import org.jetbrains.kotlin.compilerRunner.OutputItemsCollectorImpl
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.*
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptionsHelper
import org.jetbrains.kotlin.gradle.dsl.jvm.JvmTargetValidationMode
import org.jetbrains.kotlin.gradle.dsl.usesK2
import org.jetbrains.kotlin.gradle.internal.tasks.allOutputFiles
import org.jetbrains.kotlin.gradle.logging.GradleErrorMessageCollector
import org.jetbrains.kotlin.gradle.logging.GradlePrintingMessageCollector
@@ -44,10 +42,11 @@ import org.jetbrains.kotlin.gradle.report.BuildReportMode
import org.jetbrains.kotlin.gradle.tasks.internal.KotlinJvmOptionsCompat
import org.jetbrains.kotlin.gradle.utils.*
import org.jetbrains.kotlin.incremental.ClasspathChanges
import org.jetbrains.kotlin.incremental.ClasspathChanges.*
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnabled.*
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotDisabled
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnabled.IncrementalRun.NoChanges
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnabled.IncrementalRun.ToBeComputedByIncrementalCompiler
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnabled.NotAvailableDueToMissingClasspathSnapshot
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnabled.NotAvailableForNonIncrementalRun
import org.jetbrains.kotlin.incremental.ClasspathSnapshotFiles
import org.jetbrains.kotlin.incremental.classpathAsList
import org.jetbrains.kotlin.incremental.destinationAsFile
@@ -302,6 +301,7 @@ abstract class KotlinCompile @Inject constructor(
}
private val projectRootDir = project.rootDir
private val buildDir = project.buildDir
override fun callCompilerAsync(
args: K2JVMCompilerArguments,
@@ -325,6 +325,7 @@ abstract class KotlinCompile @Inject constructor(
classpathChanges = getClasspathChanges(inputChanges),
workingDir = taskBuildCacheableOutputDirectory.get().asFile,
rootProjectDir = projectRootDir,
buildDir = buildDir,
usePreciseJavaTracking = usePreciseJavaTracking,
disableMultiModuleIC = disableMultiModuleIC,
multiModuleICSettings = multiModuleICSettings,