Locate build history when file is outside of root project dir

Support use-cases when the build dir is outside of root project dir.
Many projects set up output dir as <root_project>/../out/<project_name>/build
to be able to clean build dir more easily. This commit adds support
to build history file detection and it tries to find build history
files for artifacts that are:
- under root project dir (a default structure)
- under root project's build dir parent (to support typical custom setup)

Fixes https://youtrack.jetbrains.com/issue/KT-40875

Test: BaseIncrementalCompilationMultiProjectIT
This commit is contained in:
Ivan Gavrilovic
2021-02-01 15:40:29 +00:00
committed by nataliya.valtman
parent 5de34b052f
commit 2fce6a4af9
6 changed files with 48 additions and 7 deletions
@@ -21,6 +21,7 @@ data class IncrementalModuleEntry(
class IncrementalModuleInfo(
val projectRoot: File,
val rootProjectBuildDir: File,
val dirToModule: Map<File, IncrementalModuleEntry>,
val nameToModules: Map<String, Set<IncrementalModuleEntry>>,
val jarToClassListFile: Map<File, File>,
@@ -28,6 +29,6 @@ class IncrementalModuleInfo(
val jarToModule: Map<File, IncrementalModuleEntry>
) : Serializable {
companion object {
private const val serialVersionUID = 0L
private const val serialVersionUID = 1L
}
}
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.incremental.multiproject
import org.jetbrains.kotlin.incremental.IncrementalModuleEntry
import org.jetbrains.kotlin.incremental.IncrementalModuleInfo
import org.jetbrains.kotlin.incremental.util.Either
import java.io.File
@@ -23,7 +22,14 @@ object EmptyModulesApiHistory : ModulesApiHistory {
}
abstract class ModulesApiHistoryBase(protected val modulesInfo: IncrementalModuleInfo) : ModulesApiHistory {
protected val projectRootPath: Path = Paths.get(modulesInfo.projectRoot.absolutePath)
// All project build dirs should have this dir as their parent. For a default project setup, this will
// be the same as root project path. Some projects map output outside of the root project dir, typically
// with <some_dir>/<project_path>/build, and in that case, this path will be <some_dir>.
// This is using set in order to de-dup paths, and avoid duplicate checks when possible.
protected val possibleParentsToBuildDirs: Set<Path> = setOf(
Paths.get(modulesInfo.rootProjectBuildDir.parentFile.absolutePath),
Paths.get(modulesInfo.projectRoot.absolutePath)
)
private val dirToHistoryFileCache = HashMap<File, Set<File>>()
override fun historyFilesForChangedFiles(changedFiles: Set<File>): Either<Set<File>> {
@@ -76,7 +82,7 @@ abstract class ModulesApiHistoryBase(protected val modulesInfo: IncrementalModul
when {
module != null ->
setOf(module.buildHistoryFile)
parent != null && projectRootPath.isParentOf(parent) -> {
parent != null && isInProjectBuildDir(parent) -> {
val parentHistory = getBuildHistoryForDir(parent)
when (parentHistory) {
is Either.Success<Set<File>> -> parentHistory.value
@@ -90,6 +96,10 @@ abstract class ModulesApiHistoryBase(protected val modulesInfo: IncrementalModul
return Either.Success(history)
}
protected fun isInProjectBuildDir(file: File): Boolean {
return possibleParentsToBuildDirs.any { it.isParentOf(file) }
}
protected abstract fun getBuildHistoryFilesForJar(jar: File): Either<Set<File>>
}
@@ -145,14 +155,14 @@ class ModulesApiHistoryAndroid(modulesInfo: IncrementalModuleInfo) : ModulesApiH
override fun getBuildHistoryFilesForJar(jar: File): Either<Set<File>> {
// Module detection is expensive, so we don't don it for jars outside of project dir
if (!projectRootPath.isParentOf(jar)) return Either.Error("Non-project jar is modified $jar")
if (!isInProjectBuildDir(jar)) return Either.Error("Non-project jar is modified $jar")
val jarPath = Paths.get(jar.absolutePath)
return getHistoryForModuleNames(jarPath, getPossibleModuleNamesFromJar(jarPath))
}
override fun getBuildHistoryForDir(file: File): Either<Set<File>> {
if (!projectRootPath.isParentOf(file)) return Either.Error("Non-project file while looking for history $file")
if (!isInProjectBuildDir(file)) return Either.Error("Non-project file while looking for history $file")
// check both meta-inf and META-INF directories
val moduleNames =
@@ -32,7 +32,7 @@ abstract class AbstractIncrementalMultiModuleCompilerRunnerTest<Args : CommonCom
private val jarToModule = mutableMapOf<File, IncrementalModuleEntry>()
protected val incrementalModuleInfo: IncrementalModuleInfo by lazy {
IncrementalModuleInfo(workingDir, dirToModule, nameToModules, jarToClassListFile, jarToModule)
IncrementalModuleInfo(workingDir, workingDir, dirToModule, nameToModules, jarToClassListFile, jarToModule)
}
protected abstract val modulesApiHistory: ApiHistory
@@ -56,6 +56,7 @@ class ModulesApiHistoryAndroidTest {
val info = IncrementalModuleInfo(
projectRoot = projectRoot,
rootProjectBuildDir = projectRoot.resolve("build"),
dirToModule = mapOf(appKotlinDestination to appEntry, libKotlinDestination to libEntry),
nameToModules = mapOf("app" to setOf(appEntry), "lib" to setOf(libEntry)),
jarToClassListFile = mapOf(),
@@ -347,5 +347,33 @@ open class A {
assertCompiledKotlinSources(project.relativize(aaKt))
}
}
/** Regression test for KT-40875. */
@Test
fun testMoveFunctionFromLibWithRemappedBuildDirs() {
val project = defaultProject()
project.setupWorkingDir()
project.projectDir.resolve("build.gradle").appendText("""
allprojects {
it.buildDir = new File(rootDir, "../out" + it.path.replace(":", "/") + "/build")
}
""".trimIndent())
project.build("build") {
assertSuccessful()
}
val barUseABKt = project.projectDir.getFileByName("barUseAB.kt")
val barInApp = File(project.projectDir, "app/src/main/kotlin/bar").apply { mkdirs() }
barUseABKt.copyTo(File(barInApp, barUseABKt.name))
barUseABKt.delete()
project.build("build") {
assertSuccessful()
val affectedSources = project.projectDir.getFilesByNames("fooCallUseAB.kt", "barUseAB.kt")
val relativePaths = project.relativize(affectedSources)
assertCompiledKotlinSources(relativePaths)
}
}
}
@@ -257,6 +257,7 @@ internal open class GradleCompilerRunner(protected val taskProvider: GradleCompi
return IncrementalModuleInfo(
projectRoot = gradle.rootProject.projectDir,
rootProjectBuildDir = gradle.rootProject.buildDir,
dirToModule = dirToModule,
nameToModules = nameToModules,
jarToClassListFile = jarToClassListFile,