Support multi-project IC for android projects
This commit is contained in:
+2
-19
@@ -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")
|
||||
}
|
||||
|
||||
+39
-2
@@ -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
|
||||
}
|
||||
|
||||
+2
-1
@@ -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) {
|
||||
|
||||
+51
@@ -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]
|
||||
}
|
||||
}
|
||||
+10
-10
@@ -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
|
||||
}
|
||||
|
||||
+26
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -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 {
|
||||
|
||||
+3
@@ -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 {
|
||||
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
fun foo() {
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package lib
|
||||
|
||||
fun libUtil(): String = "libUtil"
|
||||
Reference in New Issue
Block a user