diff --git a/libraries/tools/kotlin-gradle-plugin-core/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/AnnotationProcessingManager.kt b/libraries/tools/kotlin-gradle-plugin-core/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/AnnotationProcessingManager.kt new file mode 100644 index 00000000000..12c20344b25 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-core/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/AnnotationProcessingManager.kt @@ -0,0 +1,185 @@ +/* + * 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 org.jetbrains.kotlin.gradle.tasks + +import org.gradle.api.tasks.compile.JavaCompile +import org.jetbrains.org.objectweb.asm.ClassWriter +import java.io.File +import java.io.IOException +import java.util.zip.ZipFile +import org.jetbrains.org.objectweb.asm.* +import org.jetbrains.org.objectweb.asm.Opcodes.* +import java.lang.ref.WeakReference +import kotlin.properties.Delegates + +public class AnnotationProcessingManager(private val task: KotlinCompile) { + + private val project = task.getProject() + + private companion object { + val JAVA_FQNAME_PATTERN = "^([\\p{L}_$][\\p{L}\\p{N}_$]*\\.)*[\\p{L}_$][\\p{L}\\p{N}_$]*$".toRegex() + val ANNOTATIONS_FILENAME = "annotations.txt" + } + + fun getAnnotationFile(outputDirFile: String): File { + val aptDir = File(outputDirFile, "0apt") + aptDir.mkdirs() + return File(aptDir, ANNOTATIONS_FILENAME) + } + + fun afterKotlinCompile(outputDirFile: File) { + [suppress("UNCHECKED_CAST")] + val javaTask = (task.getExtensions().getExtraProperties().get("javaTask") as? WeakReference)?.get() + val aptFiles = task.aptFiles + if (javaTask == null || aptFiles.isEmpty()) return + + val aptDir = File(outputDirFile, "0apt") + val annotationDeclarationsFile = File(aptDir, ANNOTATIONS_FILENAME) + + generateJavaHackFile(aptDir, javaTask) + + javaTask.appendClasspath(aptFiles) + + val annotationProcessorFqNames = lookupAnnotationProcessors(aptFiles) + generateAnnotationProcessorStubs(aptDir, javaTask, annotationProcessorFqNames) + } + + private fun generateJavaHackFile(aptDir: File, javaTask: JavaCompile) { + val javaAptSourceDir = File(aptDir, "java_src") + val javaHackPackageDir = File(javaAptSourceDir, "__gen/annotation") + + javaHackPackageDir.mkdirs() + + val javaHackClFile = File(javaHackPackageDir, "Cl.java") + javaHackClFile.writeText( + "package __gen.annotation;" + + "class Cl { @javax.inject.Inject boolean v; }") + + javaTask.source(javaAptSourceDir) + } + + private fun generateAnnotationProcessorStubs(aptDir: File, javaTask: JavaCompile, apFqNames: Set) { + val stubOutputDir = File(aptDir, "wrappers") + + val stubOutputPackageDir = File(stubOutputDir, "__gen") + stubOutputPackageDir.mkdirs() + + for (processor in apFqNames) { + generateAnnotationProcessorWrapper(processor, "__gen", stubOutputPackageDir) + } + + val annotationProcessorWrapperFqNames = apFqNames + .map { "__gen.AnnotationProcessorWrapper_${it.replace('.', '_')}" } + .joinToString(",") + + javaTask.appendClasspath(stubOutputDir) + addWrappersToCompilerArgs(javaTask, annotationProcessorWrapperFqNames) + } + + private fun JavaCompile.appendClasspath(file: File) = setClasspath(getClasspath() + project.files(file)) + + private fun JavaCompile.appendClasspath(files: Iterable) = setClasspath(getClasspath() + project.files(files)) + + private fun addWrappersToCompilerArgs(javaTask: JavaCompile, wrapperFqNames: String) { + val compilerArgs = javaTask.getOptions().getCompilerArgs() + val processorArgIndex = compilerArgs.indexOfFirst { "-processor" == it } + + // Already has a "-processor" argument (and it is not the last one) + if (processorArgIndex >= 0 && compilerArgs.size() > (processorArgIndex + 1)) { + compilerArgs[processorArgIndex + 1] = + compilerArgs[processorArgIndex + 1] + "," + wrapperFqNames + } + else { + compilerArgs.add("-processor") + compilerArgs.add(wrapperFqNames) + } + + javaTask.getOptions().setCompilerArgs(compilerArgs) + } + + private fun generateAnnotationProcessorWrapper(processorFqName: String, packageName: String, outputDirectory: File) { + val className = "AnnotationProcessorWrapper_${processorFqName.replace('.', '_')}" + val classFqName = "$packageName/$className" + + val bytes = with (ClassWriter(0)) { + val superClass = "org/kotlin/annotations/AnnotationProcessorWrapper" + + visit(49, ACC_PUBLIC + ACC_SUPER, classFqName, null, + superClass, null) + + visitSource(null, null) + + with (visitMethod(ACC_PUBLIC, "", "()V", null, null)) { + visitVarInsn(ALOAD, 0) + visitLdcInsn(processorFqName) + visitMethodInsn(INVOKESPECIAL, superClass, "", "(Ljava/lang/String;)V", false) + visitInsn(RETURN) + visitMaxs(2, 1) + visitEnd() + } + + visitEnd() + toByteArray() + } + File(outputDirectory, "$className.class").writeBytes(bytes) + } + + private fun lookupAnnotationProcessors(files: Set): Set { + fun withZipFile(file: File, job: (ZipFile) -> Unit) { + var zipFile: ZipFile? = null + try { + zipFile = ZipFile(file) + job(zipFile) + } + catch (e: IOException) { + // Do nothing (do not continue to search for annotation processors on error) + } + catch (e: IllegalStateException) { + // ZipFile was already closed for some reason + } + finally { + try { + zipFile?.close() + } + catch (e: IOException) {} + } + } + + val annotationProcessors = hashSetOf() + + fun processLines(lines: Sequence) { + for (line in lines) { + if (line.isBlank() || !JAVA_FQNAME_PATTERN.matcher(line).matches()) continue + annotationProcessors.add(line) + } + } + + for (file in files) { + withZipFile(file) { zipFile -> + val entry = zipFile.getEntry("META-INF/services/javax.annotation.processing.Processor") + if (entry != null) { + zipFile.getInputStream(entry).reader().useLines { lines -> + processLines(lines) + } + } + } + } + + return annotationProcessors + } + +} \ No newline at end of file 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 6a1241612fb..52311046854 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 @@ -36,16 +36,11 @@ import org.gradle.api.tasks.compile.JavaCompile import org.jetbrains.org.objectweb.asm.ClassWriter import java.io.IOException import java.lang.ref.WeakReference -import java.util.zip.ZipFile -import org.jetbrains.org.objectweb.asm.* -import org.jetbrains.org.objectweb.asm.Opcodes.* val DEFAULT_ANNOTATIONS = "org.jebrains.kotlin.gradle.defaultAnnotations" val ANNOTATIONS_PLUGIN_NAME = "org.jetbrains.kotlin.kapt" -val JAVA_FQNAME_PATTERN = "^([\\p{L}_$][\\p{L}\\p{N}_$]*\\.)*[\\p{L}_$][\\p{L}\\p{N}_$]*$".toRegex() - abstract class AbstractKotlinCompile() : AbstractCompile() { abstract protected val compiler: CLICompiler abstract protected fun createBlankArgs(): T @@ -114,6 +109,8 @@ public open class KotlinCompile() : AbstractKotlinCompile() + val annotationProcessingManager = AnnotationProcessingManager(this) + override fun populateTargetSpecificArgs(args: K2JVMCompilerArguments) { // show kotlin compiler where to look for java source files args.freeArgs = (args.freeArgs + getJavaSourceRoots().map { it.getAbsolutePath() }).toSet().toList() @@ -135,7 +132,7 @@ public open class KotlinCompile() : AbstractKotlinCompile)?.get() - if (javaTask != null && aptFiles.isNotEmpty()) { - val aptStubsDir = File(outputDirFile, "0apt") - - val annotationDeclarationsFile = File(aptStubsDir, "annotations.txt") - - val javaAptSourceDir = File(aptStubsDir, "java_src") - val stubOutputDir = File(outputDirFile, "wrappers") - javaAptSourceDir.mkdirs() - stubOutputDir.mkdirs() - - javaTask.source(javaAptSourceDir) - javaTask.setClasspath(javaTask.getClasspath() + getProject().files(stubOutputDir)) - - // Generate a simple Java source file with annotation to launch Java APT even if there's no Java files - javaAptSourceDir.mkdirs() - val javaHackPackageDir = File(javaAptSourceDir, "__gen/annotation") - val javaHackClFile = File(javaHackPackageDir, "Cl.java") - javaHackClFile.writeText("package __gen.annotation;" + "class Cl { @javax.inject.Inject boolean v; }") - - val annotationProcessors = lookupAnnotationProcessors(aptFiles) - for (processor in annotationProcessors) { - generateAnnotationProcessorWrapper(processor, stubOutputDir) - } - - val annotationProcessorWrappers = annotationProcessors - .map { "__gen.AnnotationProcessorWrapper_${it.replace('.', '_')}" } - .joinToString(",") - - val compilerArgs = javaTask.getOptions().getCompilerArgs() - val processorArgIndex = compilerArgs.indexOfFirst { "-processor" == it } - - // Already has a "-processor" argument (and it is not the last one) - if (processorArgIndex >= 0 && compilerArgs.size() > (processorArgIndex + 1)) { - compilerArgs[processorArgIndex + 1] = - compilerArgs[processorArgIndex + 1] + "," + annotationProcessorWrappers - } - else { - compilerArgs.add("-processor") - compilerArgs.add(annotationProcessorWrappers) - } - - javaTask.getOptions().setCompilerArgs(compilerArgs) - - javaTask.doLast { - annotationDeclarationsFile.delete() - javaAptSourceDir.deleteRecursively() - } - } - } - - private fun lookupAnnotationProcessors(files: Set): Set { - fun withZipFile(file: File, job: (ZipFile) -> Unit) { - var zipFile: ZipFile? = null - try { - zipFile = ZipFile(file) - job(zipFile) - } - catch (e: IOException) { - // Do nothing (do not continue to search for annotation processors on error) - } - catch (e: IllegalStateException) { - // ZipFile was already closed for some reason - } - finally { - try { - zipFile?.close() - } - catch (e: IOException) {} - } - } - - val annotationProcessors = hashSetOf() - - fun processLines(lines: Sequence) { - for (line in lines) { - if (line.isBlank() || !JAVA_FQNAME_PATTERN.matcher(line).matches()) continue - annotationProcessors.add(line) - } - } - - for (file in files) { - withZipFile(file) { zipFile -> - val entry = zipFile.getEntry("META-INF/services/javax.annotation.processing.Processor") - if (entry != null) { - zipFile.getInputStream(entry).reader().useLines { lines -> - processLines(lines) - } - } - } - } - - return annotationProcessors - } - - private fun generateAnnotationProcessorWrapper(processorFqName: String, outputDirectory: File) { - val className = "AnnotationProcessorWrapper_${processorFqName.replace('.', '_')}" - - val bytes = with (ClassWriter(0)) { - val superClass = "org/kotlin/annotations/AnnotationProcessorWrapper" - - visit(49, ACC_PUBLIC + ACC_SUPER, className, null, - superClass, null) - - visitSource("$className.java", null) - - with (visitMethod(ACC_PUBLIC, "", "()V", null, null)) { - visitVarInsn(ALOAD, 0) - visitLdcInsn(processorFqName) - visitMethodInsn(INVOKESPECIAL, superClass, "", "(Ljava.lang.String;)V", false) - visitInsn(RETURN) - visitMaxs(2, 1) - visitEnd() - } - - visitEnd() - toByteArray() - } - File(outputDirectory, "$className.class").writeBytes(bytes) + annotationProcessingManager.afterKotlinCompile(outputDirFile) } // override setSource to track source directory sets