All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.kotlin.gradle.internal.AnnotationProcessingManager.kt Maven / Gradle / Ivy

/*
 * 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.internal

import com.android.build.gradle.BaseExtension
import org.gradle.api.Project
import org.gradle.api.UnknownDomainObjectException
import org.gradle.api.tasks.compile.AbstractCompile
import org.gradle.api.tasks.compile.JavaCompile
import org.jetbrains.kotlin.gradle.plugin.*
import java.io.File
import java.io.IOException
import java.util.*
import java.util.zip.ZipFile

fun Project.initKapt(
        kotlinTask: AbstractCompile,
        javaTask: AbstractCompile,
        kaptManager: AnnotationProcessingManager,
        variantName: String,
        kotlinOptions: Any?,
        subpluginEnvironment: SubpluginEnvironment,
        taskFactory: (suffix: String) -> AbstractCompile
): AbstractCompile? {
    val kaptExtension = extensions.getByType(KaptExtension::class.java)
    val kotlinAfterJavaTask: AbstractCompile?

    if (kaptExtension.generateStubs) {
        kotlinAfterJavaTask = createKotlinAfterJavaTask(javaTask, kotlinTask, kotlinOptions, taskFactory)
        mapKotlinTaskProperties(this, kotlinAfterJavaTask)

        kotlinTask.logger.kotlinDebug("kapt: Using class file stubs")

        val stubsDir = File(buildDir, "tmp/kapt/$variantName/classFileStubs")
        kotlinTask.extensions.extraProperties.set("kaptStubsDir", stubsDir)
        javaTask.appendClasspathDynamically(stubsDir)
        kotlinTask.appendClasspathDynamically(stubsDir)

        val javaDestinationDir = project.files(javaTask.destinationDir)
        javaTask.doLast {
            kotlinAfterJavaTask.source(kotlinTask.source)
            // we don't want kotlinAfterJavaTask to track modifications in generated class
            kotlinAfterJavaTask.classpath -= javaDestinationDir
        }
        kotlinAfterJavaTask.doFirst {
            kotlinAfterJavaTask.classpath += javaDestinationDir
        }
        kotlinAfterJavaTask.doLast {
            kotlinAfterJavaTask.classpath -= javaDestinationDir
        }

        subpluginEnvironment.addSubpluginArguments(this, kotlinAfterJavaTask)
    } else {
        kotlinAfterJavaTask = null
        kotlinTask.logger.kotlinDebug("kapt: Class file stubs are not used")
    }

    javaTask.appendClasspathDynamically(kaptManager.wrappersDirectory)
    javaTask.source(kaptManager.hackAnnotationDir)

    if (kaptExtension.inheritedAnnotations) {
        kotlinTask.extensions.extraProperties.set("kaptInheritedAnnotations", true)
    }

    kotlinTask.doFirst {
        kaptManager.generateJavaHackFile()
        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()
    }

    kotlinTask.storeKaptAnnotationsFile(kaptManager)
    return kotlinAfterJavaTask
}

private fun Project.createKotlinAfterJavaTask(
        javaTask: AbstractCompile,
        kotlinTask: AbstractCompile,
        kotlinOptions: Any?,
        taskFactory: (suffix: String) -> AbstractCompile
): AbstractCompile {
    val kotlinAfterJavaTask = with (taskFactory(KOTLIN_AFTER_JAVA_TASK_SUFFIX)) {
        kotlinDestinationDir = kotlinTask.kotlinDestinationDir
        destinationDir = kotlinTask.destinationDir
        classpath = kotlinTask.classpath - project.files(javaTask.destinationDir)
        this
    }

    getAllTasks(false)
            .flatMap { it.value }
            .filter { javaTask in it.taskDependencies.getDependencies(it) }
            .forEach { it.dependsOn(kotlinAfterJavaTask) }

    kotlinAfterJavaTask.dependsOn(javaTask)

    kotlinAfterJavaTask.extensions.extraProperties.set("defaultModuleName", "${project.name}-${kotlinTask.name}")
    if (kotlinOptions != null) {
        kotlinAfterJavaTask.setProperty("kotlinOptions", kotlinOptions)
    }

    return kotlinAfterJavaTask
}

public class AnnotationProcessingManager(
        private val task: AbstractCompile,
        private val javaTask: JavaCompile,
        private val taskQualifier: String,
        private val aptFiles: Set,
        val aptOutputDir: File,
        private val aptWorkingDir: File,
        private val coreClassLoader: ClassLoader,
        private val androidVariant: Any? = null) {

    private val project = task.project
    private val random = Random()
    val wrappersDirectory = File(aptWorkingDir, "wrappers")
    val hackAnnotationDir = 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 GEN_ANNOTATION = "__gen/annotation"

        private val ANDROID_APT_PLUGIN_ID = "com.neenbedankt.android-apt"
    }

    fun getAnnotationFile(): File {
        if (!aptWorkingDir.exists()) aptWorkingDir.mkdirs()
        return File(wrappersDirectory, "annotations.$taskQualifier.txt")
    }

    fun getGeneratedKotlinSourceDir(): File {
        val kotlinGeneratedDir = File(aptWorkingDir, "kotlinGenerated")
        if (!kotlinGeneratedDir.exists()) kotlinGeneratedDir.mkdirs()
        return kotlinGeneratedDir
    }

    fun setupKapt() {
        if (aptFiles.isEmpty()) return

        if (project.plugins.findPlugin(ANDROID_APT_PLUGIN_ID) != null) {
            project.logger.warn("Please do not use `$ANDROID_APT_PLUGIN_ID` with kapt.")
        }

        val annotationProcessorFqNames = lookupAnnotationProcessors(aptFiles)

        generateAnnotationProcessorStubs(javaTask, annotationProcessorFqNames, wrappersDirectory)

        val processorPath = setOf(wrappersDirectory) + aptFiles
        setProcessorPath(javaTask, (processorPath + javaTask.classpath).joinToString(File.pathSeparator))

        if (aptOutputDir.exists()) {
            aptOutputDir.deleteRecursively()
        }
        addGeneratedSourcesOutputToCompilerArgs(javaTask, aptOutputDir)

        appendAnnotationsArguments()
        appendAdditionalComplerArgs()
    }

    fun afterJavaCompile() {
        val generatedFile = File(javaTask.destinationDir, "$GEN_ANNOTATION/Cl.class")
        if (generatedFile.exists()) {
            generatedFile.delete()
        } else {
            project.logger.kotlinDebug("kapt: Java file stub was not found at $generatedFile")
        }
    }

    fun generateJavaHackFile() {
        val javaHackPackageDir = File(hackAnnotationDir, GEN_ANNOTATION)

        if (!javaHackPackageDir.exists()) javaHackPackageDir.mkdirs()

        val javaHackClFile = File(javaHackPackageDir, "Cl.java")
        val previouslyExisted = javaHackClFile.exists()
        val comment = System.currentTimeMillis().toString() + "-" + random.nextInt()

        javaHackClFile.writeText(
                "// $comment\n" +
                        "package __gen.annotation;\n" +
                        "class Cl { @__gen.KotlinAptAnnotation boolean v; }")

        project.logger.kotlinDebug("kapt: Java file stub generated: $javaHackClFile " +
                "(previously existed: $previouslyExisted)")
    }

    private fun appendAnnotationsArguments() {
        javaTask.modifyCompilerArguments { list ->
            list.add("-Akapt.annotations=" + getAnnotationFile())
            list.add("-Akapt.kotlin.generated=" + getGeneratedKotlinSourceDir())
        }
    }

    private fun appendAdditionalComplerArgs() {
        val kaptExtension = project.extensions.getByType(KaptExtension::class.java)
        val args = kaptExtension.getAdditionalArguments(project, androidVariant, getAndroidExtension())
        if (args.isEmpty()) return

        javaTask.modifyCompilerArguments { list ->
            list.addAll(args)
        }
    }

    private fun generateAnnotationProcessorStubs(javaTask: JavaCompile, processorFqNames: Set, outputDir: File) {
        val aptAnnotationFile = invokeCoreKaptMethod("generateKotlinAptAnnotation", outputDir) as File
        project.logger.kotlinDebug("kapt: Stub annotation generated: $aptAnnotationFile")

        val stubOutputPackageDir = File(outputDir, "__gen")
        stubOutputPackageDir.mkdirs()

        for (processorFqName in processorFqNames) {
            val wrapperFile = invokeCoreKaptMethod("generateAnnotationProcessorWrapper",
                    processorFqName,
                    "__gen",
                    stubOutputPackageDir,
                    getProcessorStubClassName(processorFqName),
                    taskQualifier) as File

            project.logger.kotlinDebug("kapt: Wrapper for $processorFqName generated: $wrapperFile")
        }

        val annotationProcessorWrapperFqNames = processorFqNames
                .map { fqName -> "__gen." + getProcessorStubClassName(fqName) }
                .joinToString(",")

        addWrappersToCompilerArgs(javaTask, annotationProcessorWrapperFqNames)
    }

    private fun addWrappersToCompilerArgs(javaTask: JavaCompile, wrapperFqNames: String) {
        javaTask.addCompilerArgument("-processor") { prevValue ->
            if (prevValue != null) "$prevValue,$wrapperFqNames" else wrapperFqNames
        }
    }

    private fun getAndroidExtension(): BaseExtension? {
        try {
            return project.extensions.getByName("android") as BaseExtension
        } catch (e: UnknownDomainObjectException) {
            return null
        }
    }

    private fun addGeneratedSourcesOutputToCompilerArgs(javaTask: JavaCompile, outputDir: File) {
        outputDir.mkdirs()

        javaTask.addCompilerArgument("-s") { prevValue ->
            if (prevValue != null)
                javaTask.logger.warn("Destination for generated sources was modified by kapt. Previous value = $prevValue")
            outputDir.absolutePath
        }
    }

    private fun setProcessorPath(javaTask: JavaCompile, path: String) {
        javaTask.addCompilerArgument("-processorpath") { prevValue ->
            if (prevValue != null)
                javaTask.logger.warn("Processor path was modified by kapt. Previous value = $prevValue")
            path
        }
    }

    private fun getProcessorStubClassName(processorFqName: String): String {
        return "AnnotationProcessorWrapper_${taskQualifier}_${processorFqName.replace('.', '_')}"
    }

    private inline fun JavaCompile.addCompilerArgument(name: String, value: (String?) -> String) {
        modifyCompilerArguments { args ->
            val argIndex = args.indexOfFirst { name == it }

            if (argIndex >= 0 && args.size > (argIndex + 1)) {
                args[argIndex + 1] = value(args[argIndex + 1])
            }
            else {
                args.add(name)
                args.add(value(null))
            }
        }
    }

    private inline fun JavaCompile.modifyCompilerArguments(modifier: (MutableList) -> Unit) {
        val compilerArgs: List = this.options.compilerArgs
        val newCompilerArgs = compilerArgs.mapTo(arrayListOf()) { it.toString() }
        modifier(newCompilerArgs)
        options.compilerArgs = newCompilerArgs
    }

    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.matches(line)) 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)
                    }
                }
            }
        }

        project.logger.kotlinDebug("kapt: Discovered annotation processors: ${annotationProcessors.joinToString()}")
        return annotationProcessors
    }

    private fun invokeCoreKaptMethod(methodName: String, vararg args: Any): Any {
        val array = arrayOfNulls>(args.size)
        args.forEachIndexed { i, arg -> array[i] = arg.javaClass }
        val method = getCoreKaptPackageClass().getMethod(methodName, *array)
        return method.invoke(null, *args)
    }

    private fun getCoreKaptPackageClass(): Class<*> {
        return Class.forName("org.jetbrains.kotlin.gradle.tasks.kapt.KaptStubGeneratorUtilsKt", false, coreClassLoader)
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy