[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
@@ -7,14 +7,13 @@ package org.jetbrains.kotlin.incremental
import org.jetbrains.kotlin.build.report.DoNothingICReporter
import org.jetbrains.kotlin.build.report.ICReporter
import org.jetbrains.kotlin.incremental.storage.FileToAbsolutePathConverter
import org.jetbrains.kotlin.incremental.storage.FileToPathConverter
import org.jetbrains.kotlin.incremental.storage.IncrementalFileToPathConverter
import java.io.File
private fun createDefaultPathConverter(rootProjectDir: File?) = IncrementalFileToPathConverter(rootProjectDir)
class IncrementalCompilationContext(
val pathConverter: FileToPathConverter,
// The root directories of source files and class files are different, so we may need different `FileToPathConverter`s
val pathConverterForSourceFiles: FileToPathConverter = FileToAbsolutePathConverter,
val pathConverterForOutputFiles: FileToPathConverter = FileToAbsolutePathConverter,
val storeFullFqNamesInLookupCache: Boolean = false,
val transaction: CompilationTransaction = NonRecoverableCompilationTransaction(),
val reporter: ICReporter = DoNothingICReporter,
@@ -29,19 +28,7 @@ class IncrementalCompilationContext(
*/
val keepIncrementalCompilationCachesInMemory: Boolean = false,
) {
constructor(
rootProjectDir: File?,
storeFullFqNamesInLookupCache: Boolean = false,
transaction: CompilationTransaction = NonRecoverableCompilationTransaction(),
reporter: ICReporter = DoNothingICReporter,
trackChangesInLookupCache: Boolean = false,
keepIncrementalCompilationCachesInMemory: Boolean = false,
) : this(
createDefaultPathConverter(rootProjectDir),
storeFullFqNamesInLookupCache,
transaction,
reporter,
trackChangesInLookupCache,
keepIncrementalCompilationCachesInMemory
)
// FIXME: Remove `pathConverter` and require its users to decide whether to use `pathConverterForSourceFiles` or
// `pathConverterForClassFiles`
val pathConverter = pathConverterForSourceFiles
}
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.incremental.storage
import java.io.File
/** Converts a [File] to a path of type [String] to store in IC caches, and vice versa. */
interface FileToPathConverter {
fun toPath(file: File): String
fun toFile(path: String): File
@@ -1,34 +0,0 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.incremental.storage
import java.io.File
open class IncrementalFileToPathConverter(val rootProjectDir: File?) : FileToPathConverter {
//project root dir
private val projectDirPath = rootProjectDir?.normalize()?.absolutePath
override fun toPath(file: File): String {
val path = file.normalize().absolutePath
return when {
projectDirPath == null || !path.startsWith(projectDirPath) -> path
else -> PROJECT_DIR_PLACEHOLDER + path.substring(projectDirPath.length)
}
}
override fun toFile(path: String): File =
when {
rootProjectDir != null && path.startsWith(PROJECT_DIR_PLACEHOLDER) -> rootProjectDir.resolve(path.substring(PATH_PREFIX.length))
else -> File(path)
}
private companion object {
private const val PROJECT_DIR_PLACEHOLDER = "${'$'}PROJECT_DIR$"
//use only for prefix length because it OS dependent
private const val PATH_PREFIX = "$PROJECT_DIR_PLACEHOLDER/"
}
}
@@ -16,7 +16,9 @@
package org.jetbrains.kotlin.incremental.storage
interface LazyStorage<K, V> {
import java.io.Closeable
interface LazyStorage<K, V> : Closeable {
val keys: Collection<K>
operator fun contains(key: K): Boolean
operator fun get(key: K): V?
@@ -24,7 +26,7 @@ interface LazyStorage<K, V> {
fun remove(key: K)
fun clean()
fun flush(memoryCachesOnly: Boolean)
fun close()
override fun close()
}
interface AppendableLazyStorage<K, V> : LazyStorage<K, V> {
@@ -0,0 +1,54 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.incremental.storage
import java.io.File
/**
* [FileToPathConverter] which converts a [File] to a Unix-style relative path, which is relative to a base directory ([baseDir]), and vice
* versa.
*
* This is to support build cache relocatability
* (https://docs.gradle.org/current/userguide/build_cache_concepts.html#relocatability).
*/
class RelocatableFileToPathConverter(private val baseDir: File) : FileToPathConverter {
private val unixStyleBaseDirPathPrefix = "${baseDir.invariantSeparatorsPath}/"
override fun toPath(file: File): String {
check(file.invariantSeparatorsPath.startsWith(unixStyleBaseDirPathPrefix)) {
"The given file '${file.path}' is located outside the base directory '${baseDir.path}'"
}
return file.relativeTo(baseDir).invariantSeparatorsPath
}
override fun toFile(path: String): File {
// Note: The given path is Unix-style but baseDir could be Windows-style; call normalize() so that the style of the returned file's
// path is consistent with baseDir.
return baseDir.resolve(path).normalize()
}
}
/**
* File locations to support build cache relocatability (see [RelocatableFileToPathConverter]).
*
* These are the root directories of
* - Source files
* - Output files, which include .class files and possibly additional output files such as `.java` stub files for KaptGenerateStubs tasks.
*
* Ideally, they should be the most specific root directories (e.g., `/path/to/MyApplication/app/src/main/kotlin` for source files and
* `/path/to/MyApplication/app/build/tmp/kotlin-classes/debug` for output .class files). However, for simplicity we are using the root
* project directory ([rootProjectDir]) for source files and ([buildDir]) for output files.
*/
class FileLocations(
val rootProjectDir: File,
val buildDir: File,
) {
fun getRelocatablePathConverterForSourceFiles() = RelocatableFileToPathConverter(rootProjectDir)
fun getRelocatablePathConverterForOutputFiles() = RelocatableFileToPathConverter(buildDir)
}