diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/AnnotationProcessingManager.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/AnnotationProcessingManager.kt index 58e573ee3ee..71f140699a1 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/AnnotationProcessingManager.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/AnnotationProcessingManager.kt @@ -46,13 +46,13 @@ fun Project.initKapt( val stubsDir = File(buildDir, "tmp/kapt/$variantName/classFileStubs") kotlinTask.extensions.extraProperties.set("kaptStubsDir", stubsDir) + javaTask.appendClasspathDynamically(stubsDir) - javaTask.doFirst { - javaTask.classpath += files(stubsDir) - } - - kotlinTask.doFirst { + javaTask.doLast { kotlinAfterJavaTask.source(kotlinTask.source) + kotlinAfterJavaTask.source(kaptManager.javaAptSourceDir) + // we don't want kotlinAfterJavaTask to track modifications in generated class + kotlinAfterJavaTask.classpath -= project.files(javaTask.destinationDir) } subpluginEnvironment.addSubpluginArguments(this, kotlinAfterJavaTask) @@ -61,6 +61,9 @@ fun Project.initKapt( kotlinTask.logger.kotlinDebug("kapt: Class file stubs are not used") } + javaTask.appendClasspathDynamically(kaptManager.wrappersDirectory) + javaTask.source(kaptManager.javaAptSourceDir) + if (kaptExtension.inheritedAnnotations) { kotlinTask.extensions.extraProperties.set("kaptInheritedAnnotations", true) } @@ -70,13 +73,16 @@ fun Project.initKapt( kotlinAfterJavaTask?.source(kaptManager.getGeneratedKotlinSourceDir()) } + var originalJavaCompilerArgs: List? = null javaTask.doFirst { + originalJavaCompilerArgs = (javaTask as JavaCompile).options.compilerArgs kaptManager.setupKapt() kaptManager.generateJavaHackFile() kotlinAfterJavaTask?.source(kaptManager.getGeneratedKotlinSourceDir()) } javaTask.doLast { + (javaTask as JavaCompile).options.compilerArgs = originalJavaCompilerArgs kaptManager.afterJavaCompile() } @@ -92,8 +98,8 @@ private fun Project.createKotlinAfterJavaTask( ): AbstractCompile { val kotlinAfterJavaTask = with (taskFactory(KOTLIN_AFTER_JAVA_TASK_SUFFIX)) { kotlinDestinationDir = kotlinTask.kotlinDestinationDir - destinationDir = javaTask.destinationDir - classpath = javaTask.classpath + destinationDir = kotlinTask.destinationDir + classpath = kotlinTask.classpath - project.files(javaTask.destinationDir) this } @@ -124,10 +130,11 @@ public class AnnotationProcessingManager( private val project = task.project private val random = Random() + val wrappersDirectory = File(aptWorkingDir, "wrappers") + val javaAptSourceDir = File(aptWorkingDir, "java_src") private companion object { val JAVA_FQNAME_PATTERN = "^([\\p{L}_$][\\p{L}\\p{N}_$]*\\.)*[\\p{L}_$][\\p{L}\\p{N}_$]*$".toRegex() - val WRAPPERS_DIRECTORY = "wrappers" val GEN_ANNOTATION = "__gen/annotation" private val ANDROID_APT_PLUGIN_ID = "com.neenbedankt.android-apt" @@ -135,7 +142,7 @@ public class AnnotationProcessingManager( fun getAnnotationFile(): File { if (!aptWorkingDir.exists()) aptWorkingDir.mkdirs() - return File(aptWorkingDir, "$WRAPPERS_DIRECTORY/annotations.$taskQualifier.txt") + return File(wrappersDirectory, "annotations.$taskQualifier.txt") } fun getGeneratedKotlinSourceDir(): File { @@ -153,16 +160,13 @@ public class AnnotationProcessingManager( val annotationProcessorFqNames = lookupAnnotationProcessors(aptFiles) - val stubOutputDir = File(aptWorkingDir, WRAPPERS_DIRECTORY) - generateAnnotationProcessorStubs(javaTask, annotationProcessorFqNames, stubOutputDir) + generateAnnotationProcessorStubs(javaTask, annotationProcessorFqNames, wrappersDirectory) - val processorPath = setOf(stubOutputDir) + aptFiles + val processorPath = setOf(wrappersDirectory) + aptFiles setProcessorPath(javaTask, (processorPath + javaTask.classpath).joinToString(File.pathSeparator)) - javaTask.appendClasspath(stubOutputDir) addGeneratedSourcesOutputToCompilerArgs(javaTask, aptOutputDir) - appendAnnotationsArguments() appendAdditionalComplerArgs() } @@ -177,7 +181,6 @@ public class AnnotationProcessingManager( } fun generateJavaHackFile() { - val javaAptSourceDir = File(aptWorkingDir, "java_src") val javaHackPackageDir = File(javaAptSourceDir, GEN_ANNOTATION) if (!javaHackPackageDir.exists()) javaHackPackageDir.mkdirs() @@ -193,10 +196,6 @@ public class AnnotationProcessingManager( project.logger.kotlinDebug("kapt: Java file stub generated: $javaHackClFile " + "(previously existed: $previouslyExisted)") - - if (!javaTask.source.contains(javaHackClFile)) { - javaTask.source(javaAptSourceDir) - } } private fun appendAnnotationsArguments() { @@ -241,10 +240,6 @@ public class AnnotationProcessingManager( addWrappersToCompilerArgs(javaTask, annotationProcessorWrapperFqNames) } - private fun JavaCompile.appendClasspath(file: File) { - classpath += project.files(file) - } - private fun addWrappersToCompilerArgs(javaTask: JavaCompile, wrapperFqNames: String) { javaTask.addCompilerArgument("-processor") { prevValue -> if (prevValue != null) "$prevValue,$wrapperFqNames" else wrapperFqNames 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 79bbb80f5d4..388e89c9fef 100644 --- 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 @@ -134,7 +134,7 @@ class Kotlin2JvmSourceSetProcessor( val javaTask = project.tasks.findByName(sourceSet.compileJavaTaskName) as AbstractCompile? if (javaTask != null) { - setUpKotlinToJavaDependency(project, kotlinTask, javaTask, logger) + setUpKotlinToJavaDependency(kotlinTask, javaTask, logger) } val kotlinAnnotationProcessingDep = cachedKotlinAnnotationProcessingDep ?: run { @@ -367,7 +367,6 @@ open class KotlinAndroidPlugin @Inject constructor(val scriptHandler: ScriptHand kotlinTask.kotlinDestinationDir = File(project.buildDir, "tmp/kotlin-classes/$variantDataName") kotlinTask.destinationDir = javaTask.destinationDir kotlinTask.description = "Compiles the ${variantDataName} kotlin." - kotlinTask.classpath = javaTask.classpath kotlinTask.setDependsOn(javaTask.dependsOn) fun SourceDirectorySet.addSourceDirectories(additionalSourceFiles: Collection) { @@ -408,13 +407,17 @@ open class KotlinAndroidPlugin @Inject constructor(val scriptHandler: ScriptHand subpluginEnvironment.addSubpluginArguments(project, kotlinTask) - kotlinTask.doFirst { + // should not be evaluated until right before compileKotlin evaluation since android can change + // java classpath during project evaluation (see prepareComAndroidSupportSupportV42311Library) + val fullClasspath = lazy { val androidRT = project.files(AndroidGradleWrapper.getRuntimeJars(androidPlugin, androidExt)) - val fullClasspath = (javaTask.classpath + androidRT) - project.files(kotlinTask.kotlinDestinationDir) - (it as AbstractCompile).classpath = fullClasspath - + (javaTask.classpath + androidRT) - project.files(kotlinTask.kotlinDestinationDir) + } + kotlinTask.classpath = javaTask.classpath + kotlinTask.updateClasspathBeforeTask { fullClasspath.value } + kotlinTask.doFirst { for (task in project.getTasksByName(kotlinTaskName + KOTLIN_AFTER_JAVA_TASK_SUFFIX, false)) { - (task as AbstractCompile).classpath = project.files(fullClasspath, javaTask.destinationDir) + (task as AbstractCompile).classpath = project.files(fullClasspath.value, javaTask.destinationDir) } } @@ -432,7 +435,7 @@ open class KotlinAndroidPlugin @Inject constructor(val scriptHandler: ScriptHand } } - setUpKotlinToJavaDependency(project, kotlinTask, javaTask, logger) + setUpKotlinToJavaDependency(kotlinTask, javaTask, logger) } } @@ -448,7 +451,7 @@ open class KotlinAndroidPlugin @Inject constructor(val scriptHandler: ScriptHand } } -private fun setUpKotlinToJavaDependency(project: Project, kotlinTask: AbstractCompile, javaTask: AbstractCompile, logger: Logger) { +private fun setUpKotlinToJavaDependency(kotlinTask: AbstractCompile, javaTask: AbstractCompile, logger: Logger) { // Since we cannot update classpath statically, java not able to detect changes in the classpath after kotlin compiler. // Therefore this (probably inefficient since java cannot decide "uptodateness" by the list of changed class files, but told // explicitly being out of date whenever any kotlin files are compiled @@ -465,20 +468,15 @@ private fun setUpKotlinToJavaDependency(project: Project, kotlinTask: AbstractCo } javaTask.dependsOn(kotlinTask.name) - javaTask.doFirst { - /* - * It's important to modify javaTask.classpath only in doFirst, - * because Android plugin uses ConventionMapping to modify it too (see JavaCompileConfigAction.execute), - * and setting classpath explicitly prevents usage of Android mappings. - * Also classpath setted by Android can be modified after excecution of some tasks (see VarianConfiguration.getCompileClasspath) - * ex. it adds some support libraries jars after execution of prepareComAndroidSupportSupportV42311Library task, - * so it's only safe to modify javaTask.classpath right before its usage - */ - javaTask.classpath += project.files(kotlinTask.property("kotlinDestinationDir")) - } - javaTask.doLast { - javaTask.classpath -= project.files(kotlinTask.property("kotlinDestinationDir")) - } + /* + * It's important to modify javaTask.classpath only in doFirst, + * because Android plugin uses ConventionMapping to modify it too (see JavaCompileConfigAction.execute), + * and setting classpath explicitly prevents usage of Android mappings. + * Also classpath setted by Android can be modified after excecution of some tasks (see VarianConfiguration.getCompileClasspath) + * ex. it adds some support libraries jars after execution of prepareComAndroidSupportSupportV42311Library task, + * so it's only safe to modify javaTask.classpath right before its usage + */ + javaTask.appendClasspathDynamically(kotlinTask.kotlinDestinationDir!!) } private fun loadSubplugins(project: Project): SubpluginEnvironment { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/taskUtils.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/taskUtils.kt index c37aa8ea3a0..669118457a4 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/taskUtils.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/taskUtils.kt @@ -1,10 +1,56 @@ package org.jetbrains.kotlin.gradle.plugin +import org.gradle.api.file.FileCollection import org.gradle.api.internal.AbstractTask import org.gradle.api.tasks.compile.AbstractCompile import java.io.File import kotlin.reflect.KProperty +/* + * Adding fake up-to-date check to do something before execution, and before input snapshots are taken. + * Note, this hack is sensitive to up-to-date checks order, so it should be added before any custom up-to-date checks + * that could return false. + * + * Should be used only for compileKotlinTask. + * + * // todo: could add configureKotlinTask that always executes before compileKotlin instead + */ +internal fun AbstractCompile.updateClasspathBeforeTask(newClassPath: (oldClassPath: FileCollection) -> FileCollection) { + // Gradle can take inputs snapshots before task run (normally) or after task run (--rerun-tasks or some up-to-date when returned false). + // Fake up-to-date check to update classpath dynamically before inputs snapshot is taken. + // Won't be called in case of some other up-to-date check returned false before this one evaluation + // Won't be called in case of --rerun-tasks + var classpathIsUpdated = false + outputs.upToDateWhen { + classpath = newClassPath(classpath) + classpathIsUpdated = true + true + } + + // in case fake up-to-date check was not called (see comment above) + doFirst { + if (!classpathIsUpdated) { + classpath = newClassPath(classpath) + } + } +} + +internal fun AbstractCompile.appendClasspathDynamically(file: File) { + var added = false + + doFirst { + if (file !in classpath) { + classpath += project.files(file) + added = true + } + } + doLast { + if (added) { + classpath -= project.files(file) + } + } +} + internal var AbstractTask.anyClassesCompiled: Boolean? by TaskPropertyDelegate("anyClassesCompiled") internal var AbstractTask.kotlinDestinationDir: File? by TaskPropertyDelegate("kotlinDestinationDir") diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/AbstractKotlinAndroidGradleTests.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/AbstractKotlinAndroidGradleTests.kt index cc89b2a7c99..0d1a42aac12 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/AbstractKotlinAndroidGradleTests.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/AbstractKotlinAndroidGradleTests.kt @@ -86,6 +86,27 @@ fun getSomething() = 10 } } + @Test + fun testIncrementalBuildWithNoChanges() { + val project = Project("AndroidIncrementalSingleModuleProject", gradleVersion) + val tasksToExecute = arrayOf( + ":app:prepareComAndroidSupportAppcompatV72311Library", + ":app:prepareComAndroidSupportSupportV42311Library", + ":app:compileDebugKotlin", + ":app:compileDebugJavaWithJavac" + ) + + project.build("assembleDebug") { + assertSuccessful() + assertContains(*tasksToExecute) + } + + project.build("assembleDebug") { + assertSuccessful() + assertContains(*tasksToExecute.map { it + " UP-TO-DATE" }.toTypedArray()) + } + } + @Test fun testModuleNameAndroid() { val project = Project("AndroidProject", gradleVersion) diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinGradlePluginIT.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinGradlePluginIT.kt index 3075662cd60..ea0272d593c 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinGradlePluginIT.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinGradlePluginIT.kt @@ -3,6 +3,7 @@ package org.jetbrains.kotlin.gradle import org.gradle.api.logging.LogLevel import org.jetbrains.kotlin.gradle.plugin.CleanUpBuildListener import org.jetbrains.kotlin.gradle.tasks.USING_EXPERIMENTAL_INCREMENTAL_MESSAGE +import org.jetbrains.kotlin.gradle.util.getFileByName import org.junit.Test import java.io.File import kotlin.test.assertTrue @@ -221,23 +222,49 @@ class KotlinGradleIT: BaseGradleIT() { } } + @Test + fun testKaptSimpleIncrementalBuild() { + doTestKaptIncrementalBuild("kaptSimple", arrayOf(":compileKotlin", ":compileJava")) + } + @Test fun testKaptStubsIncrementalBuild() { - val project = Project("kaptStubs", GRADLE_VERSION) + doTestKaptIncrementalBuild("kaptStubs", arrayOf(":compileKotlin", ":compileJava", ":compileKotlinAfterJava")) + } + + private fun doTestKaptIncrementalBuild(projectName: String, compileTasks: Array) { + val compileTasksUpToDate = compileTasks.map { it + " UP-TO-DATE" }.toTypedArray() + val project = Project(projectName, GRADLE_VERSION) project.build("build") { assertSuccessful() - - // Modify the Kotlin source file somehow - val someJavaFile = fileInWorkingDir("src/main/java/test.kt") - someJavaFile.appendText(" ") } + project.projectDir.getFileByName("test.kt").appendText(" ") project.build("build") { assertSuccessful() - assertContains(":compileKotlin") - assertContains(":compileJava") - assertNotContains(":compileJava UP-TO-DATE") + assertContains(*compileTasks) + assertNotContains(*compileTasksUpToDate) + } + + repeat(2) { + project.build("build") { + assertSuccessful() + assertContains(*compileTasksUpToDate) + } + } + + project.build("clean", "build") { + assertSuccessful() + assertContains(*compileTasks) + assertNotContains(*compileTasksUpToDate) + } + + repeat(2) { + project.build("build") { + assertSuccessful() + assertContains(*compileTasksUpToDate) + } } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/AndroidIncrementalSingleModuleProject/app/build.gradle b/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/AndroidIncrementalSingleModuleProject/app/build.gradle index 58da37606a2..c38414820d9 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/AndroidIncrementalSingleModuleProject/app/build.gradle +++ b/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/AndroidIncrementalSingleModuleProject/app/build.gradle @@ -50,5 +50,6 @@ repositories { } dependencies { + compile 'com.android.support:appcompat-v7:23.1.1' compile "org.jetbrains.kotlin:kotlin-stdlib:1.1-SNAPSHOT" } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/AndroidIncrementalSingleModuleProject/gradle.properties b/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/AndroidIncrementalSingleModuleProject/gradle.properties new file mode 100644 index 00000000000..98780069b89 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/AndroidIncrementalSingleModuleProject/gradle.properties @@ -0,0 +1 @@ +org.gradle.jvmargs=-XX:MaxPermSize=512M \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/kaptStubs/src/main/java/javaPackage/JavaClass.java b/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/kaptStubs/src/main/java/javaPackage/JavaClass.java new file mode 100644 index 00000000000..2af6a92fd02 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/test/resources/testProject/kaptStubs/src/main/java/javaPackage/JavaClass.java @@ -0,0 +1,25 @@ +/* + * Copyright 2010-2015 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 javaPackage; + +public class JavaClass { + + public void test() { + System.out.println(example.TestClassGenerated.class.getName()); + } + +} \ No newline at end of file