Support multi-project IC for android projects

This commit is contained in:
Alexey Tsvetkov
2016-08-23 22:59:19 +03:00
parent 9f6509cd8e
commit 0b017aa3ca
10 changed files with 138 additions and 34 deletions
@@ -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<K2JVMCompilerArguments>() {
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<K2JVMCompilerArguments>() {
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<K2JVMCompilerArguments>() {
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")
}
@@ -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<Project> {
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<SourceProvider>
get() = variantConfiguration.sortedSourceProviders
}
@@ -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) {
@@ -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<File, File> = 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<ArtifactDifference>? {
val mappedFile = jarToLibraryArtifactMap[artifact] ?: return null
return registry[mappedFile]
}
}
@@ -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
}
@@ -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)
}
}
}
@@ -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 {
@@ -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 {
@@ -0,0 +1,3 @@
package lib
fun libUtil(): String = "libUtil"