diff --git a/libraries/tools/kotlin-gradle-plugin-core/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt b/libraries/tools/kotlin-gradle-plugin-core/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt index 849e67d28eb..d199d184eaf 100644 --- a/libraries/tools/kotlin-gradle-plugin-core/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt +++ b/libraries/tools/kotlin-gradle-plugin-core/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt @@ -3,7 +3,6 @@ package org.jetbrains.kotlin.gradle.tasks import org.apache.commons.io.FilenameUtils import org.codehaus.groovy.runtime.MethodClosure import org.gradle.api.GradleException -import org.gradle.api.Project import org.gradle.api.file.SourceDirectorySet import org.gradle.api.logging.Logger import org.gradle.api.logging.Logging @@ -177,6 +176,7 @@ open class KotlinCompile() : AbstractKotlinCompile() { val kaptOptions = KaptOptions() val pluginOptions = CompilerPluginOptions() var artifactDifferenceRegistry: ArtifactDifferenceRegistry? = null + var artifactFile: File? = null override fun populateTargetSpecificArgs(args: K2JVMCompilerArguments) { logger.kotlinDebug("args.freeArgs = ${args.freeArgs}") @@ -435,7 +435,6 @@ open class KotlinCompile() : AbstractKotlinCompile() { kaptAnnotationsFileUpdater = null } - val artifactFile = project.tryGetSingleArtifact() logger.kotlinDebug { "Artifact to register difference for task $path: $artifactFile" } val currentBuildInfo = BuildInfo(startTS = System.currentTimeMillis()) BuildInfo.write(currentBuildInfo, lastBuildInfoFile) @@ -506,7 +505,7 @@ open class KotlinCompile() : AbstractKotlinCompile() { if (artifactFile != null && artifactDifferenceRegistry != null) { val dirtyData = DirtyData(buildDirtyLookupSymbols, buildDirtyFqNames) val artifactDifference = ArtifactDifference(currentBuildInfo.startTS, dirtyData) - artifactDifferenceRegistry!!.add(artifactFile, artifactDifference) + artifactDifferenceRegistry!!.add(artifactFile!!, artifactDifference) logger.kotlinDebug { val dirtySymbolsSorted = buildDirtyLookupSymbols.map { it.scope + "#" + it.name }.sorted() "Added artifact difference for $artifactFile (ts: ${currentBuildInfo.startTS}): " + @@ -802,22 +801,6 @@ class GradleMessageCollector(val logger: Logger, val outputCollector: OutputItem } } -fun Project.tryGetSingleArtifact(): File? { - val log = logger - log.kotlinDebug { "Trying to determine single artifact for project $path" } - - val archives = configurations.findByName("archives") - if (archives == null) { - log.kotlinDebug { "Could not find 'archives' configuration for project $path" } - return null - } - - val artifacts = archives.artifacts.files.files - log.kotlinDebug { "All artifacts for project $path: [${artifacts.joinToString()}]" } - - return if (artifacts.size == 1) artifacts.first() else null -} - internal fun Logger.kotlinInfo(message: String) { this.info("[KOTLIN] $message") } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt index 893941f4b3d..1261adcd2ce 100755 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt @@ -26,6 +26,7 @@ import org.jetbrains.kotlin.gradle.internal.Kapt2KotlinGradleSubplugin import org.jetbrains.kotlin.gradle.internal.initKapt import org.jetbrains.kotlin.gradle.plugin.android.AndroidGradleWrapper import org.jetbrains.kotlin.gradle.tasks.* +import org.jetbrains.kotlin.gradle.tasks.incremental.android.ArtifactDifferenceRegistryAndroidWrapper import org.jetbrains.kotlin.gradle.tasks.incremental.configureMultiProjectIncrementalCompilation import java.io.File import java.net.URL @@ -141,10 +142,28 @@ class Kotlin2JvmSourceSetProcessor( kotlinAfterJavaTask?.let { it.source(kotlinSourceSet.kotlin) } configureJavaTask(kotlinTask, javaTask, logger) createSyncOutputTask(project, kotlinTask, javaTask, kotlinAfterJavaTask, sourceSetName) - configureMultiProjectIncrementalCompilation(project, kotlinTask, javaTask, kotlinAfterJavaTask, artifactDifferenceRegistry) + val artifactFile = project.tryGetSingleArtifact() + configureMultiProjectIncrementalCompilation(project, kotlinTask, javaTask, kotlinAfterJavaTask, + artifactDifferenceRegistry, artifactFile) } } } + + private fun Project.tryGetSingleArtifact(): File? { + val log = logger + log.kotlinDebug { "Trying to determine single artifact for project $path" } + + val archives = configurations.findByName("archives") + if (archives == null) { + log.kotlinDebug { "Could not find 'archives' configuration for project $path" } + return null + } + + val artifacts = archives.artifacts.files.files + log.kotlinDebug { "All artifacts for project $path: [${artifacts.joinToString()}]" } + + return if (artifacts.size == 1) artifacts.first() else null + } } class Kotlin2JsSourceSetProcessor( @@ -242,7 +261,8 @@ open class Kotlin2JsPlugin( open class KotlinAndroidPlugin( val tasksProvider: KotlinTasksProvider, private val kotlinSourceSetProvider: KotlinSourceSetProvider, - private val kotlinPluginVersion: String + private val kotlinPluginVersion: String, + private val artifactDifferenceRegistry: ArtifactDifferenceRegistry ) : Plugin { private val log = Logging.getLogger(this.javaClass) @@ -363,6 +383,10 @@ open class KotlinAndroidPlugin( configureJavaTask(kotlinTask, javaTask, logger) createSyncOutputTask(project, kotlinTask, javaTask, kotlinAfterJavaTask, variantDataName) + val artifactFile = project.tryGetSingleArtifact(variantData) + val artifactDifferenceRegistryWrapper = ArtifactDifferenceRegistryAndroidWrapper(artifactDifferenceRegistry, variantData) + configureMultiProjectIncrementalCompilation(project, kotlinTask, javaTask, kotlinAfterJavaTask, + artifactDifferenceRegistryWrapper, artifactFile) } } @@ -380,6 +404,19 @@ open class KotlinAndroidPlugin( } } + private fun Project.tryGetSingleArtifact(variantData: BaseVariantData<*>): File? { + val log = logger + log.kotlinDebug { "Trying to determine single artifact for project $path" } + + val outputs = variantData.outputs + if (outputs.size != 1) { + log.kotlinDebug { "Output count != 1 for variant: ${outputs.map { it.outputFile.relativeTo(rootDir).path }.joinToString() }" } + return null + } + + return variantData.outputs.first().outputFile + } + private val BaseVariantData<*>.sourceProviders: List get() = variantConfiguration.sortedSourceProviders } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt index 7b6a22531e0..2ea4a437e76 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt @@ -36,7 +36,8 @@ open class KotlinPluginWrapper @Inject constructor(fileResolver: FileResolver): open class KotlinAndroidPluginWrapper @Inject constructor(fileResolver: FileResolver): KotlinBasePluginWrapper(fileResolver) { override fun getPlugin(kotlinGradleBuildServices: KotlinGradleBuildServices) = - KotlinAndroidPlugin(AndroidTasksProvider(), KotlinSourceSetProviderImpl(fileResolver), kotlinPluginVersion) + KotlinAndroidPlugin(AndroidTasksProvider(), KotlinSourceSetProviderImpl(fileResolver), kotlinPluginVersion, + kotlinGradleBuildServices.artifactDifferenceRegistry) } open class Kotlin2JsPluginWrapper @Inject constructor(fileResolver: FileResolver): KotlinBasePluginWrapper(fileResolver) { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/incremental/android/ArtifactDifferenceRegistryAndroidWrapper.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/incremental/android/ArtifactDifferenceRegistryAndroidWrapper.kt new file mode 100644 index 00000000000..6cadb4ea312 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/incremental/android/ArtifactDifferenceRegistryAndroidWrapper.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.gradle.tasks.incremental.android + +import com.android.build.gradle.internal.variant.BaseVariantData +import org.jetbrains.kotlin.gradle.tasks.ArtifactDifference +import org.jetbrains.kotlin.gradle.tasks.ArtifactDifferenceRegistry +import java.io.File +import java.util.* + +// When lib is compiled, changes are associated with .aar files. +// However when app is compiled, there is just .jar in classpath. +// This class maps jar to aar via BaseVariantData +internal class ArtifactDifferenceRegistryAndroidWrapper( + private val registry: ArtifactDifferenceRegistry, + variantData: BaseVariantData<*> +): ArtifactDifferenceRegistry by registry { + private val jarToLibraryArtifactMap: MutableMap = HashMap() + + init { + for (lib in variantData.variantDependency.libraries) { + jarToLibraryArtifactMap[lib.jarFile] = lib.bundle + + // local dependencies are detected as changed by gradle, because they are seem to be + // rewritten every time when bundle changes + // when local dep will actually change, record for bundle will be removed from registry + for (localDep in lib.localDependencies) { + jarToLibraryArtifactMap[localDep.jarFile] = lib.bundle + } + } + } + + override fun get(artifact: File): Iterable? { + val mappedFile = jarToLibraryArtifactMap[artifact] ?: return null + return registry[mappedFile] + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/incremental/multiProjectIncrementalCompilationUtil.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/incremental/multiProjectIncrementalCompilationUtil.kt index 40164db69eb..22c6e7cf980 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/incremental/multiProjectIncrementalCompilationUtil.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/incremental/multiProjectIncrementalCompilationUtil.kt @@ -8,24 +8,24 @@ import org.jetbrains.kotlin.com.intellij.openapi.util.io.FileUtil import org.jetbrains.kotlin.gradle.plugin.kotlinDebug import org.jetbrains.kotlin.gradle.tasks.ArtifactDifferenceRegistry import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import org.jetbrains.kotlin.gradle.tasks.tryGetSingleArtifact import java.io.File fun configureMultiProjectIncrementalCompilation( project: Project, kotlinTask: KotlinCompile, - javaTask: JavaCompile, + javaTask: AbstractCompile, kotlinAfterJavaTask: KotlinCompile?, - artifactDifferenceRegistry: ArtifactDifferenceRegistry + artifactDifferenceRegistry: ArtifactDifferenceRegistry, + artifactFile: File? ) { val log = kotlinTask.logger log.kotlinDebug { "Configuring multi-project incremental compilation for project ${project.path}" } - fun cannotPerformMultiProjectIC(artifact: File, reason: String) { + fun cannotPerformMultiProjectIC(reason: String) { log.kotlinDebug { - "Multi-project kotlin incremental compilation cannot be performed for project ${project.path}: $reason" + "Multi-project kotlin incremental compilation won't be performed for projects that depend on ${project.path}: $reason" } - artifactDifferenceRegistry.remove(artifact) + artifactFile?.let { artifactDifferenceRegistry.remove(it) } } fun isUnknownTaskOutputtingToJavaDestination(task: Task): Boolean { @@ -35,17 +35,17 @@ fun configureMultiProjectIncrementalCompilation( FileUtil.isAncestor(javaTask.destinationDir, task.destinationDir, /* strict = */ false) } - val artifact = project.tryGetSingleArtifact() ?: return if (!kotlinTask.incremental) { - return cannotPerformMultiProjectIC(artifact, reason = "incremental compilation is not enabled") + return cannotPerformMultiProjectIC(reason = "incremental compilation is not enabled") } + // todo: split registry for reading and writing changes val illegalTask = project.tasks.find(::isUnknownTaskOutputtingToJavaDestination) if (illegalTask != null) { - return cannotPerformMultiProjectIC(artifact, - reason = "unknown task outputs to java destination dir ${illegalTask.path} $(${illegalTask.javaClass})") + return cannotPerformMultiProjectIC(reason = "unknown task outputs to java destination dir ${illegalTask.path} $(${illegalTask.javaClass})") } val kotlinCompile = kotlinAfterJavaTask ?: kotlinTask kotlinCompile.artifactDifferenceRegistry = artifactDifferenceRegistry + kotlinCompile.artifactFile = artifactFile } diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/IncrementalCompilationMultiProjectIT.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/IncrementalCompilationMultiProjectIT.kt index fa89849a6a9..1a5274b5961 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/IncrementalCompilationMultiProjectIT.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/IncrementalCompilationMultiProjectIT.kt @@ -10,8 +10,15 @@ import java.io.File class IncrementalCompilationMultiProjectIT : BaseGradleIT() { companion object { private val GRADLE_VERSION = "2.10" + private val ANDROID_GRADLE_PLUGIN_VERSION = "1.5.+" } + private fun androidBuildOptions() = + BuildOptions(withDaemon = true, + androidHome = File("../../../dependencies/android-sdk-for-tests"), + androidGradlePluginVersion = ANDROID_GRADLE_PLUGIN_VERSION, + incremental = true) + override fun defaultBuildOptions(): BuildOptions = super.defaultBuildOptions().copy(withDaemon = true, incremental = true) @@ -185,4 +192,23 @@ open class A { assertCompiledKotlinSources(relativePaths, weakTesting = false) } } + + @Test + fun testAndroid() { + val project = Project("AndroidProject", GRADLE_VERSION) + val options = androidBuildOptions() + + project.build("assembleDebug", options = options) { + assertSuccessful() + } + + val libUtilKt = project.projectDir.getFileByName("libUtil.kt") + libUtilKt.modify { it.replace("fun libUtil(): String", "fun libUtil(): Any") } + + project.build("assembleDebug", options = options) { + assertSuccessful() + val affectedSources = project.projectDir.getFilesByNames("libUtil.kt", "MainActivity2.kt") + assertCompiledKotlinSources(project.relativize(affectedSources), weakTesting = false) + } + } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/AndroidProject/Android/root/kotlin/org/jetbrains/kotlin/gradle/test/androidalfa/MainActivity2.kt b/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/AndroidProject/Android/root/kotlin/org/jetbrains/kotlin/gradle/test/androidalfa/MainActivity2.kt index c9d1191e16f..3bb3b0230a3 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/AndroidProject/Android/root/kotlin/org/jetbrains/kotlin/gradle/test/androidalfa/MainActivity2.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/AndroidProject/Android/root/kotlin/org/jetbrains/kotlin/gradle/test/androidalfa/MainActivity2.kt @@ -7,12 +7,14 @@ import android.view.Menu import android.view.View import android.widget.Button import org.jetbrains.kotlin.gradle.test.androidalfa.R +import lib.* open class MainActivity2: Activity() { protected override fun onCreate(savedInstanceState: Bundle?): Unit { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main2) + libUtil() var next: Button = findViewById(R.id.Button02) as Button next.setOnClickListener(object: View.OnClickListener { diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/AndroidProject/Lib/build.gradle b/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/AndroidProject/Lib/build.gradle index eebe0fce4a0..a53b33453e2 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/AndroidProject/Lib/build.gradle +++ b/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/AndroidProject/Lib/build.gradle @@ -4,6 +4,9 @@ apply plugin: 'kotlin-android' dependencies { compile files('libs/android-support-v4.jar') + // unused but needed for IncrementalCompilationMultiProjectIT.testAndroid to check if non-local dependency affects IC + compile 'io.reactivex:rxjava:1.1.9' + compile 'com.loopj.android:android-async-http:1.4.9' } android { diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/AndroidProject/Lib/src/main/kotlin/Foo.kt b/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/AndroidProject/Lib/src/main/kotlin/Foo.kt deleted file mode 100644 index f23d7f69d2f..00000000000 --- a/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/AndroidProject/Lib/src/main/kotlin/Foo.kt +++ /dev/null @@ -1,2 +0,0 @@ -fun foo() { -} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/AndroidProject/Lib/src/main/kotlin/lib/libUtil.kt b/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/AndroidProject/Lib/src/main/kotlin/lib/libUtil.kt new file mode 100644 index 00000000000..58f8e7d0120 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/AndroidProject/Lib/src/main/kotlin/lib/libUtil.kt @@ -0,0 +1,3 @@ +package lib + +fun libUtil(): String = "libUtil" \ No newline at end of file