Implement inter-project JS IC with Gradle

#KT-25025 fixed
This commit is contained in:
Alexey Tsvetkov
2018-07-10 15:18:32 +03:00
parent 02af631e14
commit 30f3622b27
30 changed files with 408 additions and 271 deletions
@@ -529,7 +529,10 @@ abstract class BaseGradleIT {
}
add("-Pkotlin_version=" + options.kotlinVersion)
options.incremental?.let { add("-Pkotlin.incremental=$it") }
options.incremental?.let {
add("-Pkotlin.incremental=$it")
add("-Pkotlin.incremental.js=$it")
}
options.usePreciseJavaTracking?.let { add("-Pkotlin.incremental.usePreciseJavaTracking=$it") }
options.androidGradlePluginVersion?.let { add("-Pandroid_tools_version=$it") }
if (options.debug) {
@@ -8,10 +8,35 @@ import org.junit.Assert
import org.junit.Test
import java.io.File
class IncrementalCompilationMultiProjectIT : BaseGradleIT() {
override fun defaultBuildOptions(): BuildOptions =
super.defaultBuildOptions().copy(withDaemon = true, incremental = true)
class IncrementalCompilationJsMultiProjectIT : BaseIncrementalCompilationMultiProjectIT() {
override fun defaultProject(): Project {
val project = Project("incrementalMultiproject")
project.setupWorkingDir()
for (subProject in arrayOf("app", "lib")) {
val subProjectDir = project.projectDir.resolve(subProject)
subProjectDir.resolve("src/main/java").deleteRecursively()
val buildGradle = subProjectDir.resolve("build.gradle")
val buildJsGradle = subProjectDir.resolve("build-js.gradle")
buildJsGradle.copyTo(buildGradle, overwrite = true)
buildJsGradle.delete()
}
return project
}
override val additionalLibDependencies: String =
"implementation \"org.jetbrains.kotlin:kotlin-test-js:${'$'}kotlin_version\""
}
class IncrementalCompilationJvmMultiProjectIT : BaseIncrementalCompilationMultiProjectIT() {
override val additionalLibDependencies: String =
"implementation \"org.jetbrains.kotlin:kotlin-stdlib:${'$'}kotlin_version\""
override fun defaultProject(): Project =
Project("incrementalMultiproject")
// todo: do the same for js backend
@Test
fun testDuplicatedClass() {
val project = Project("duplicatedClass")
@@ -28,174 +53,12 @@ class IncrementalCompilationMultiProjectIT : BaseGradleIT() {
}
}
@Test
fun testMoveFunctionFromLib() {
val project = Project("incrementalMultiproject")
project.build("build") {
assertSuccessful()
}
val barUseABKt = project.projectDir.getFileByName("barUseAB.kt")
val barInApp = File(project.projectDir, "app/src/main/java/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, weakTesting = false)
}
}
@Test
fun testAddNewMethodToLib() {
val project = Project("incrementalMultiproject")
project.build("build") {
assertSuccessful()
}
val aKt = project.projectDir.getFileByName("A.kt")
aKt.writeText(
"""
package bar
open class A {
fun a() {}
fun newA() {}
}
"""
)
project.build("build") {
assertSuccessful()
val affectedSources = project.projectDir.getFilesByNames("A.kt", "B.kt", "AA.kt", "BB.kt")
val relativePaths = project.relativize(affectedSources)
assertCompiledKotlinSources(relativePaths, weakTesting = false)
}
}
@Test
fun testLibClassBecameFinal() {
val project = Project("incrementalMultiproject")
project.build("build") {
assertSuccessful()
}
val bKt = project.projectDir.getFileByName("B.kt")
bKt.modify { it.replace("open class", "class") }
project.build("build") {
assertFailed()
val affectedSources = project.projectDir.getFilesByNames(
"B.kt", "barUseAB.kt", "barUseB.kt",
"BB.kt", "fooCallUseAB.kt", "fooUseB.kt"
)
val relativePaths = project.relativize(affectedSources)
assertCompiledKotlinSources(relativePaths, weakTesting = false)
}
}
@Test
fun testCleanBuildLib() {
val project = Project("incrementalMultiproject")
project.build("build") {
assertSuccessful()
}
project.build(":lib:clean") {
assertSuccessful()
}
// Change file so Gradle won't skip :app:compile
project.projectFile("BarDummy.kt").modify {
it.replace("class BarDummy", "open class BarDummy")
}
project.build("build") {
assertSuccessful()
val affectedSources = project.projectDir.allKotlinFiles()
val relativePaths = project.relativize(affectedSources)
assertCompiledKotlinSources(relativePaths, weakTesting = false)
}
val aaKt = project.projectFile("AA.kt")
aaKt.modify { "$it " }
project.build("build") {
assertSuccessful()
assertCompiledKotlinSources(project.relativize(aaKt), weakTesting = false)
}
}
@Test
fun testAddDependencyToLib() {
val project = Project("incrementalMultiproject")
project.build("build") {
assertSuccessful()
}
val libBuildGradle = File(project.projectDir, "lib/build.gradle")
Assert.assertTrue("$libBuildGradle does not exist", libBuildGradle.exists())
libBuildGradle.modify {
"""
$it
dependencies {
implementation 'junit:junit:4.12'
}
""".trimIndent()
}
// Change file so Gradle won't skip :app:compile
project.projectFile("BarDummy.kt").modify {
it.replace("class BarDummy", "open class BarDummy")
}
project.build("build") {
assertSuccessful()
val affectedSources = project.projectDir.allKotlinFiles()
val relativePaths = project.relativize(affectedSources)
assertCompiledKotlinSources(relativePaths, weakTesting = false)
}
val aaKt = project.projectFile("AA.kt")
aaKt.modify { "$it " }
project.build("build") {
assertSuccessful()
assertCompiledKotlinSources(project.relativize(aaKt), weakTesting = false)
}
}
@Test
fun testCompileErrorInLib() {
val project = Project("incrementalMultiproject")
project.build("build") {
assertSuccessful()
}
val bKt = project.projectDir.getFileByName("B.kt")
bKt.delete()
project.build("build") {
assertFailed()
}
project.projectDir.getFileByName("barUseB.kt").delete()
project.projectDir.getFileByName("barUseAB.kt").delete()
project.build("build") {
assertFailed()
val affectedSources = project.projectDir.allKotlinFiles()
val relativePaths = project.relativize(affectedSources)
assertCompiledKotlinSources(relativePaths, weakTesting = false)
}
}
// checks that multi-project ic is disabled when there is a task that outputs to javaDestination dir
// that is not JavaCompile or KotlinCompile
@Test
fun testCompileLibWithGroovy() {
val project = Project("incrementalMultiproject")
val project = defaultProject()
project.setupWorkingDir()
val lib = File(project.projectDir, "lib")
val libBuildGradle = File(lib, "build.gradle")
@@ -234,3 +97,175 @@ open class A {
}
}
abstract class BaseIncrementalCompilationMultiProjectIT : BaseGradleIT() {
override fun defaultBuildOptions(): BuildOptions =
super.defaultBuildOptions().copy(withDaemon = true, incremental = true)
protected abstract fun defaultProject(): Project
@Test
fun testMoveFunctionFromLib() {
val project = defaultProject()
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, weakTesting = false)
}
}
@Test
fun testAddNewMethodToLib() {
val project = defaultProject()
project.build("build") {
assertSuccessful()
}
val aKt = project.projectDir.getFileByName("A.kt")
aKt.writeText(
"""
package bar
open class A {
fun a() {}
fun newA() {}
}
"""
)
project.build("build") {
assertSuccessful()
val affectedSources = project.projectDir.getFilesByNames("A.kt", "B.kt", "AA.kt", "BB.kt")
val relativePaths = project.relativize(affectedSources)
assertCompiledKotlinSources(relativePaths, weakTesting = false)
}
}
@Test
fun testLibClassBecameFinal() {
val project = defaultProject()
project.build("build") {
assertSuccessful()
}
val bKt = project.projectDir.getFileByName("B.kt")
bKt.modify { it.replace("open class", "class") }
project.build("build") {
assertFailed()
val affectedSources = project.projectDir.getFilesByNames(
"B.kt", "barUseAB.kt", "barUseB.kt",
"BB.kt", "fooCallUseAB.kt", "fooUseB.kt"
)
val relativePaths = project.relativize(affectedSources)
assertCompiledKotlinSources(relativePaths, weakTesting = false)
}
}
@Test
fun testCleanBuildLib() {
val project = defaultProject()
project.build("build") {
assertSuccessful()
}
project.build(":lib:clean") {
assertSuccessful()
}
// Change file so Gradle won't skip :app:compile
project.projectFile("BarDummy.kt").modify {
it.replace("class BarDummy", "open class BarDummy")
}
project.build("build") {
assertSuccessful()
val affectedSources = project.projectDir.allKotlinFiles()
val relativePaths = project.relativize(affectedSources)
assertCompiledKotlinSources(relativePaths, weakTesting = false)
}
val aaKt = project.projectFile("AA.kt")
aaKt.modify { "$it " }
project.build("build") {
assertSuccessful()
assertCompiledKotlinSources(project.relativize(aaKt), weakTesting = false)
}
}
protected abstract val additionalLibDependencies: String
@Test
fun testAddDependencyToLib() {
val project = defaultProject()
project.build("build") {
assertSuccessful()
}
val libBuildGradle = File(project.projectDir, "lib/build.gradle")
Assert.assertTrue("$libBuildGradle does not exist", libBuildGradle.exists())
libBuildGradle.modify {
"""
$it
dependencies {
$additionalLibDependencies
}
""".trimIndent()
}
// Change file so Gradle won't skip :app:compile
project.projectFile("BarDummy.kt").modify {
it.replace("class BarDummy", "open class BarDummy")
}
project.build("build") {
assertSuccessful()
val affectedSources = project.projectDir.allKotlinFiles()
val relativePaths = project.relativize(affectedSources)
assertCompiledKotlinSources(relativePaths, weakTesting = false)
}
val aaKt = project.projectFile("AA.kt")
aaKt.modify { "$it " }
project.build("build") {
assertSuccessful()
assertCompiledKotlinSources(project.relativize(aaKt), weakTesting = false)
}
}
@Test
fun testCompileErrorInLib() {
val project = defaultProject()
project.build("build") {
assertSuccessful()
}
val bKt = project.projectDir.getFileByName("B.kt")
bKt.delete()
project.build("build") {
assertFailed()
}
project.projectDir.getFileByName("barUseB.kt").delete()
project.projectDir.getFileByName("barUseAB.kt").delete()
project.build("build") {
assertFailed()
val affectedSources = project.projectDir.allKotlinFiles()
val relativePaths = project.relativize(affectedSources)
assertCompiledKotlinSources(relativePaths, weakTesting = false)
}
}
}
@@ -3,6 +3,7 @@ package org.jetbrains.kotlin.gradle
import org.gradle.api.logging.LogLevel
import org.jetbrains.kotlin.gradle.tasks.USING_EXPERIMENTAL_JS_INCREMENTAL_COMPILATION_MESSAGE
import org.jetbrains.kotlin.gradle.util.getFileByName
import org.jetbrains.kotlin.gradle.util.getFilesByNames
import org.jetbrains.kotlin.gradle.util.modify
import org.junit.Test
import java.io.File
@@ -334,7 +335,8 @@ class Kotlin2JsGradlePluginIT : BaseGradleIT() {
build("build") {
assertSuccessful()
assertContains(USING_EXPERIMENTAL_JS_INCREMENTAL_COMPILATION_MESSAGE)
assertCompiledKotlinSources(project.relativize(allKotlinFiles - projectFile("DummyInLibMain.kt")))
val affectedFiles = project.projectDir.getFilesByNames("A.kt", "useAInLibMain.kt", "useAInAppMain.kt", "useAInAppTest.kt")
assertCompiledKotlinSources(project.relativize(affectedFiles))
}
}
}
@@ -0,0 +1,7 @@
apply plugin: 'kotlin2js'
dependencies {
compile project(':lib')
// do not remove until KT-25488 is fixed
compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
}
@@ -0,0 +1,12 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package foo
import bar.*
class AAA : A() {
fun aa() {}
}
@@ -0,0 +1,6 @@
apply plugin: 'kotlin2js'
dependencies {
// do not remove until KT-25488 is fixed
compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
}
@@ -28,6 +28,8 @@ import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
import org.gradle.api.invocation.Gradle
import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.jvm.tasks.Jar
import org.jetbrains.kotlin.config.Services
import org.jetbrains.kotlin.daemon.client.CompileServiceSession
import org.jetbrains.kotlin.daemon.common.*
@@ -37,8 +39,9 @@ import org.jetbrains.kotlin.gradle.incremental.GRADLE_CACHE_VERSION
import org.jetbrains.kotlin.gradle.incremental.GRADLE_CACHE_VERSION_FILE_NAME
import org.jetbrains.kotlin.gradle.utils.relativeToRoot
import org.jetbrains.kotlin.gradle.plugin.kotlinDebug
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
import org.jetbrains.kotlin.gradle.tasks.InspectClassesForMultiModuleIC
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
import org.jetbrains.kotlin.gradle.utils.newTmpFile
import org.jetbrains.kotlin.incremental.*
import java.io.ByteArrayOutputStream
@@ -358,15 +361,22 @@ internal class GradleCompilerRunner(private val project: Project) : KotlinCompil
val dirToModule = HashMap<File, IncrementalModuleEntry>()
val nameToModules = HashMap<String, HashSet<IncrementalModuleEntry>>()
val jarToClassListFile = HashMap<File, File>()
val jarToModule = HashMap<File, IncrementalModuleEntry>()
for (project in gradle.rootProject.allprojects) {
for (task in project.tasks) {
when (task) {
is KotlinCompile -> {
is AbstractKotlinCompile<*> -> {
val module = IncrementalModuleEntry(project.path, task.moduleName, project.buildDir, task.buildHistoryFile)
dirToModule[task.destinationDir] = module
task.javaOutputDir?.let { dirToModule[it] = module }
nameToModules.getOrPut(module.name) { HashSet() }.add(module)
if (task is Kotlin2JsCompile) {
jarForSourceSet(project, task.sourceSetName)?.let {
jarToModule[it] = module
}
}
}
is InspectClassesForMultiModuleIC -> {
jarToClassListFile[File(task.archivePath)] = task.classesListFile
@@ -375,11 +385,24 @@ internal class GradleCompilerRunner(private val project: Project) : KotlinCompil
}
}
return IncrementalModuleInfo(gradle.rootProject.projectDir, dirToModule, nameToModules, jarToClassListFile)
.also {
cachedGradle = WeakReference(gradle)
cachedModulesInfo = it
}
return IncrementalModuleInfo(
projectRoot = gradle.rootProject.projectDir,
dirToModule = dirToModule,
nameToModules = nameToModules,
jarToClassListFile = jarToClassListFile,
jarToModule = jarToModule
).also {
cachedGradle = WeakReference(gradle)
cachedModulesInfo = it
}
}
private fun jarForSourceSet(project: Project, sourceSetName: String): File? {
val javaConvention = project.convention.findPlugin(JavaPluginConvention::class.java)
?: return null
val sourceSet = javaConvention.sourceSets.findByName(sourceSetName) ?: return null
val jarTask = project.tasks.findByName(sourceSet.jarTaskName) as? Jar
return jarTask?.archivePath
}
@Synchronized
@@ -527,7 +527,9 @@ open class Kotlin2JsCompile() : AbstractKotlinCompile<K2JSCompilerArguments>(),
computedCompilerClasspath,
if (hasFilesInTaskBuildDirectory()) changedFiles else ChangedFiles.Unknown(),
taskBuildDirectory,
messageCollector, outputItemCollector, args,
messageCollector,
outputItemCollector,
args,
multiModuleICSettings = multiModuleICSettings
)
}