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

org.jetbrains.kotlin.gradle.internal.kapt.Kapt3KotlinGradleSubplugin.kt Maven / Gradle / Ivy

There is a newer version: 2.1.0-RC
Show newest version
/*
 * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

@file:Suppress("PackageDirectoryMismatch")

package org.jetbrains.kotlin.gradle.internal

import com.android.build.gradle.BaseExtension
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.ExternalDependency
import org.gradle.api.attributes.Usage
import org.gradle.api.file.FileCollection
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.TaskDependency
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.compile.AbstractCompile
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.process.CommandLineArgumentProvider
import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin.Companion.isInfoAsWarnings
import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin.Companion.isKaptKeepKdocCommentsInStubs
import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin.Companion.isKaptVerbose
import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin.Companion.isUseK2
import org.jetbrains.kotlin.gradle.model.builder.KaptModelBuilder
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinWithJavaCompilation
import org.jetbrains.kotlin.gradle.tasks.*
import org.jetbrains.kotlin.gradle.tasks.configuration.KaptGenerateStubsConfig
import org.jetbrains.kotlin.gradle.tasks.configuration.KaptWithoutKotlincConfig
import org.jetbrains.kotlin.gradle.utils.*
import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeAsciiOnly
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.ObjectOutputStream
import java.util.*
import java.util.concurrent.Callable
import javax.inject.Inject

// apply plugin: 'kotlin-kapt'
class Kapt3GradleSubplugin @Inject internal constructor(private val registry: ToolingModelBuilderRegistry) :
    KotlinCompilerPluginSupportPlugin {

    override fun apply(target: Project) {
        target.extensions.create("kapt", KaptExtension::class.java)

        registry.register(KaptModelBuilder())
    }

    companion object {
        // Required for IDEA import
        @Suppress("unused")
        @JvmStatic
        fun getKaptGeneratedClassesDir(project: Project, sourceSetName: String): File =
            project.getKaptGeneratedClassesDirectory(sourceSetName).get().asFile

        internal fun Project.getKaptGeneratedClassesDirectory(sourceSetName: String) =
            layout.buildDirectory.dir("tmp/kapt3/classes/$sourceSetName")

        // Required for IDEA import
        @Suppress("unused")
        @JvmStatic
        fun getKaptGeneratedSourcesDir(project: Project, sourceSetName: String): File =
            project.getKaptGeneratedSourcesDirectory(sourceSetName).get().asFile

        internal fun Project.getKaptGeneratedSourcesDirectory(sourceSetName: String) =
            layout.buildDirectory.dir("generated/source/kapt/$sourceSetName")

        // Required for IDEA import
        @Suppress("unused")
        @JvmStatic
        fun getKaptGeneratedKotlinSourcesDir(project: Project, sourceSetName: String) =
            project.getKaptGeneratedKotlinSourcesDirectory(sourceSetName).get().asFile

        internal fun Project.getKaptGeneratedKotlinSourcesDirectory(sourceSetName: String) =
            layout.buildDirectory.dir("generated/source/kaptKotlin/$sourceSetName")

        const val KAPT_WORKER_DEPENDENCIES_CONFIGURATION_NAME = "kotlinKaptWorkerDependencies"

        val KAPT_KOTLIN_GENERATED = "kapt.kotlin.generated"

        private val CLASSLOADERS_CACHE_SIZE = "kapt.classloaders.cache.size"
        private val CLASSLOADERS_CACHE_DISABLE_FOR_PROCESSORS = "kapt.classloaders.cache.disableForProcessors"

        val MAIN_KAPT_CONFIGURATION_NAME = "kapt"

        const val KAPT_ARTIFACT_NAME = "kotlin-annotation-processing-gradle"
        val KAPT_SUBPLUGIN_ID = "org.jetbrains.kotlin.kapt3"

        fun getKaptConfigurationName(sourceSetName: String): String {
            return if (sourceSetName != SourceSet.MAIN_SOURCE_SET_NAME)
                "$MAIN_KAPT_CONFIGURATION_NAME${sourceSetName.capitalizeAsciiOnly()}"
            else
                MAIN_KAPT_CONFIGURATION_NAME
        }

        fun Project.findKaptConfiguration(sourceSetName: String): Configuration? {
            return project.configurations.findByName(getKaptConfigurationName(sourceSetName))
        }

        fun Project.isKaptVerbose(): Boolean {
            return getBooleanOptionValue(BooleanOption.KAPT_VERBOSE)
        }

        fun Project.isIncrementalKapt(): Boolean {
            return getBooleanOptionValue(BooleanOption.KAPT_INCREMENTAL_APT)
        }

        fun Project.isInfoAsWarnings(): Boolean {
            return getBooleanOptionValue(BooleanOption.KAPT_INFO_AS_WARNINGS)
        }

        fun Project.isIncludeCompileClasspath(): Boolean {
            return getBooleanOptionValue(BooleanOption.KAPT_INCLUDE_COMPILE_CLASSPATH)
        }

        fun Project.isKaptKeepKdocCommentsInStubs(): Boolean {
            return getBooleanOptionValue(BooleanOption.KAPT_KEEP_KDOC_COMMENTS_IN_STUBS)
        }

        fun Project.isUseK2(): Boolean {
            return getBooleanOptionValue(BooleanOption.KAPT_USE_K2)
        }

        fun Project.classLoadersCacheSize(): Int = findPropertySafe(CLASSLOADERS_CACHE_SIZE)?.toString()?.toInt() ?: 0

        fun Project.disableClassloaderCacheForProcessors(): Set {
            val value = findPropertySafe(CLASSLOADERS_CACHE_DISABLE_FOR_PROCESSORS)?.toString() ?: ""
            return value
                .split(",")
                .map { it.trim() }
                .filter { it.isNotEmpty() }
                .toSet()
        }

        /**
         * In case [Project.findProperty] can throw exception, this version catch it and return null
         */
        private fun Project.findPropertySafe(propertyName: String): Any? =
            try {
                findProperty(propertyName)
            } catch (ex: Exception) {
                logger.warn("Error getting property $propertyName", ex)
                null
            }

        fun findMainKaptConfiguration(project: Project) = project.findKaptConfiguration(SourceSet.MAIN_SOURCE_SET_NAME)

        fun createAptConfigurationIfNeeded(project: Project, sourceSetName: String): Configuration {
            val configurationName = getKaptConfigurationName(sourceSetName)

            project.configurations.findResolvable(configurationName)?.let { return it }
            val aptConfiguration = project.configurations.createResolvable(configurationName).apply {
                attributes.setAttribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage.JAVA_RUNTIME))
            }

            if (aptConfiguration.name != MAIN_KAPT_CONFIGURATION_NAME) {
                // The main configuration can be created after the current one. We should handle this case
                val mainConfiguration = findMainKaptConfiguration(project)
                    ?: createAptConfigurationIfNeeded(project, SourceSet.MAIN_SOURCE_SET_NAME)

                aptConfiguration.extendsFrom(mainConfiguration)
            }

            return aptConfiguration
        }

        fun isEnabled(project: Project) =
            project.plugins.any { it is Kapt3GradleSubplugin }

        private fun Project.getBooleanOptionValue(
            booleanOption: BooleanOption
        ): Boolean {
            val value = findProperty(booleanOption.optionName)
            return when (value) {
                is Boolean -> value
                is String -> when {
                    value.equals("true", ignoreCase = true) -> true
                    value.equals("false", ignoreCase = true) -> false
                    else -> {
                        project.logger.warn(
                            "Boolean option `${booleanOption.optionName}` was set to an invalid value: `$value`." +
                                    " Using default value `${booleanOption.defaultValue}` instead."
                        )
                        booleanOption.defaultValue
                    }
                }
                null -> booleanOption.defaultValue
                else -> {
                    project.logger.warn(
                        "Boolean option `${booleanOption.optionName}` was set to an invalid value: `$value`." +
                                " Using default value `${booleanOption.defaultValue}` instead."
                    )
                    booleanOption.defaultValue
                }
            }
        }

        /**
         * Kapt option that expects a Boolean value. It has a default value to be used when its value is not set.
         *
         * IMPORTANT: The default value should typically match those defined in org.jetbrains.kotlin.kapt3.base.KaptFlag.
         */
        private enum class BooleanOption(
            val optionName: String,
            val defaultValue: Boolean
        ) {
            KAPT_VERBOSE("kapt.verbose", false),
            KAPT_INCREMENTAL_APT(
                "kapt.incremental.apt",
                true // Currently doesn't match the default value of KaptFlag.INCREMENTAL_APT, but it's fine (see https://github.com/JetBrains/kotlin/pull/3942#discussion_r532578690).
            ),
            KAPT_INFO_AS_WARNINGS("kapt.info.as.warnings", false),
            KAPT_INCLUDE_COMPILE_CLASSPATH("kapt.include.compile.classpath", true),
            KAPT_KEEP_KDOC_COMMENTS_IN_STUBS("kapt.keep.kdoc.comments.in.stubs", true),
            KAPT_USE_K2("kapt.use.k2", false),
        }
    }

    override fun isApplicable(kotlinCompilation: KotlinCompilation<*>) =
        (kotlinCompilation.platformType == KotlinPlatformType.jvm || kotlinCompilation.platformType == KotlinPlatformType.androidJvm)

    private fun Kapt3SubpluginContext.getKaptStubsDir() = temporaryKaptDirectory("stubs")

    private fun Kapt3SubpluginContext.getKaptIncrementalDataDir() = temporaryKaptDirectory("incrementalData")

    private fun Kapt3SubpluginContext.getKaptIncrementalAnnotationProcessingCache() = temporaryKaptDirectory("incApCache")

    private fun Kapt3SubpluginContext.temporaryKaptDirectory(
        name: String
    ) = project.layout.buildDirectory.dir("tmp/kapt3/$name/$sourceSetName")

    internal inner class Kapt3SubpluginContext(
        val project: Project,
        val javaCompile: TaskProvider?,
        val variantData: Any?,
        val sourceSetName: String,
        val kotlinCompilation: KotlinCompilation<*>,
        val kaptExtension: KaptExtension,
        val kaptClasspathConfigurations: List
    ) {
        val sourcesOutputDir = project.getKaptGeneratedSourcesDirectory(sourceSetName)
        val kotlinSourcesOutputDir = project.getKaptGeneratedKotlinSourcesDirectory(sourceSetName)
        val classesOutputDir = project.getKaptGeneratedClassesDirectory(sourceSetName)
        val includeCompileClasspath =
            kaptExtension.includeCompileClasspath
                ?: project.isIncludeCompileClasspath()

        @Suppress("UNCHECKED_CAST")
        val kotlinCompile: TaskProvider
            // Can't use just kotlinCompilation.compileKotlinTaskProvider, as the latter is not statically-known to be KotlinCompile
            get() = kotlinCompilation.compileTaskProvider as TaskProvider
    }

    override fun applyToCompilation(
        kotlinCompilation: KotlinCompilation<*>
    ): Provider> {
        val project = kotlinCompilation.target.project

        val buildDependencies = arrayListOf()
        val kaptConfigurations = arrayListOf()

        fun handleSourceSet(sourceSetName: String) {
            project.findKaptConfiguration(sourceSetName)?.let { kaptConfiguration ->
                kaptConfigurations += kaptConfiguration
                buildDependencies += kaptConfiguration.buildDependencies
            }
        }

        @Suppress("TYPEALIAS_EXPANSION_DEPRECATION")
        val androidVariantData: DeprecatedAndroidBaseVariant? = (kotlinCompilation as? KotlinJvmAndroidCompilation)?.androidVariant

        val sourceSetName = if (androidVariantData != null) {
            for (provider in androidVariantData.sourceSets) {
                @Suppress("TYPEALIAS_EXPANSION_DEPRECATION")
                handleSourceSet((provider as DeprecatedAndroidSourceSet).name)
            }
            androidVariantData.name
        } else {
            handleSourceSet(kotlinCompilation.compilationName)
            kotlinCompilation.compilationName
        }

        val kaptExtension = project.extensions.getByType(KaptExtension::class.java)

        val javaCompileOrNull = findJavaTaskForKotlinCompilation(kotlinCompilation)

        val context = Kapt3SubpluginContext(
            project,
            javaCompileOrNull,
            androidVariantData,
            sourceSetName,
            kotlinCompilation,
            kaptExtension,
            kaptConfigurations,
        )

        val kaptGenerateStubsTaskProvider: TaskProvider = context.createKaptGenerateStubsTask()
        val kaptTaskProvider: TaskProvider = context.createKaptKotlinTask(
            kaptGenerateStubsTaskProvider
        )

        kaptGenerateStubsTaskProvider.configure { kaptGenerateStubsTask ->
            kaptGenerateStubsTask.dependsOn(*buildDependencies.toTypedArray())

            if (androidVariantData != null) {
                kaptGenerateStubsTask.additionalSources.from(
                    Callable {
                        // Avoid circular dependency: the stubs task need the Java sources, but the Java sources generated by Kapt should be
                        // excluded, as the Kapt tasks depend on the stubs ones, and having them in the input would lead to a cycle
                        val kaptJavaOutput = kaptTaskProvider.get().destinationDir.get().asFile
                        @Suppress("TYPEALIAS_EXPANSION_DEPRECATION")
                        androidVariantData.getSourceFolders(DeprecatedAndroidSourceKind.JAVA).filter { it.dir != kaptJavaOutput }
                    }
                )
            }
        }

        context.kotlinCompile.configure { it.dependsOn(kaptTaskProvider) }

        /** Plugin options are applied to kapt*Compile inside [createKaptKotlinTask] */
        return project.provider { emptyList() }
    }

    /* Returns AP options from static DSL. */
    private fun Kapt3SubpluginContext.getDslKaptApOptions(): Provider> = project.provider {
        val androidVariantData = KaptWithAndroid.androidVariantData(this)

        val androidExtension = androidVariantData?.let {
            project.extensions.findByName("android") as? BaseExtension
        }

        val androidOptions = androidVariantData?.annotationProcessorOptions ?: emptyMap()
        val androidSubpluginOptions = androidOptions.toList().map { SubpluginOption(it.first, it.second) }

        androidSubpluginOptions + getNonAndroidDslApOptions(
            kaptExtension, project, listOf(kotlinSourcesOutputDir.get().asFile), androidVariantData, androidExtension
        ).get()
    }

    private fun Kapt3SubpluginContext.createKaptKotlinTask(
        generateStubsTask: TaskProvider
    ): TaskProvider {
        val taskName = kotlinCompile.kaptTaskName
        val taskConfigAction = KaptWithoutKotlincConfig(
            kotlinCompilation.project,
            generateStubsTask,
            kaptExtension
        )

        val kaptClasspathConfiguration = project.configurations.createResolvable("kaptClasspath_$taskName")
            .setExtendsFrom(kaptClasspathConfigurations).also {
                it.isVisible = false
            }
        taskConfigAction.configureTaskProvider { taskProvider ->
            taskProvider.dependsOn(generateStubsTask)

            if (javaCompile != null) {
                val androidVariantData = KaptWithAndroid.androidVariantData(this)
                if (androidVariantData != null) {
                    KaptWithAndroid.registerGeneratedJavaSourceForAndroid(this, project, androidVariantData, taskProvider)
                    androidVariantData.addJavaSourceFoldersToModel(sourcesOutputDir.get().asFile)
                } else {
                    registerGeneratedJavaSource(taskProvider, javaCompile)
                }

                disableAnnotationProcessingInJavaTask()
            }

            // Workaround for changes in Gradle 7.3 causing eager task realization
            // For details check `KotlinSourceSetProcessor.prepareKotlinCompileTask()`
            if (kotlinCompilation is KotlinWithJavaCompilation<*, *>) {
                kotlinCompilation.output.classesDirs.from(classesOutputDir).builtBy(taskProvider)
            } else {
                kotlinCompilation.output.classesDirs.from(taskProvider.flatMap { it.classesDir })
            }

            kotlinCompilation.compileTaskProvider.configure { task ->
                with(task as AbstractKotlinCompile<*>) {
                    setSource(sourcesOutputDir, kotlinSourcesOutputDir)
                    libraries.from(classesOutputDir)
                }
            }
        }
        taskConfigAction.configureTask { task ->
            task.stubsDir.set(getKaptStubsDir())
            task.destinationDir.set(sourcesOutputDir)
            task.kotlinSourcesDestinationDir.set(kotlinSourcesOutputDir)
            task.classesDir.set(classesOutputDir)

            if (javaCompile != null) {
                task.defaultJavaSourceCompatibility.set(javaCompile.map { it.sourceCompatibility })
            }

            if (project.isIncrementalKapt()) {
                task.incAptCache.value(getKaptIncrementalAnnotationProcessingCache()).disallowChanges()
            }

            task.kaptClasspath.from(kaptClasspathConfiguration).disallowChanges()
            task.kaptExternalClasspath.from(kaptClasspathConfiguration.fileCollection { it is ExternalDependency })
            task.kaptClasspathConfigurationNames.value(kaptClasspathConfigurations.map { it.name }).disallowChanges()

            KaptWithAndroid.androidVariantData(this)?.annotationProcessorOptionProviders?.let {
                task.annotationProcessorOptionProviders.add(it)
            }

            val pluginOptions: Provider = getDslKaptApOptions().toCompilerPluginOptions()

            task.kaptPluginOptions.add(pluginOptions)
        }

        return project.registerTask(taskName, KaptWithoutKotlincTask::class.java, emptyList()).also {
            taskConfigAction.execute(it)
        }
    }

    private fun Kapt3SubpluginContext.createKaptGenerateStubsTask(): TaskProvider {
        val kaptTaskName = kotlinCompile.kaptGenerateStubsTaskName
        val kaptTaskProvider = project.registerTask(kaptTaskName, listOf(project))

        val taskConfig = KaptGenerateStubsConfig(kotlinCompilation)
        taskConfig.configureTask {
            it.stubsDir.set(getKaptStubsDir())
            it.destinationDirectory.set(getKaptIncrementalDataDir())
            it.kaptClasspath.from(kaptClasspathConfigurations)
        }

        taskConfig.execute(kaptTaskProvider)

        project.whenEvaluated {
            addCompilationSourcesToExternalCompileTask(kotlinCompilation, kaptTaskProvider)
        }

        return kaptTaskProvider
    }

    private fun Kapt3SubpluginContext.disableAnnotationProcessingInJavaTask() {
        javaCompile?.configure { javaCompileInstance ->
            if (javaCompileInstance !is JavaCompile)
                return@configure

            val options = javaCompileInstance.options
            // 'android-apt' (com.neenbedankt) adds a File instance to compilerArgs (List).
            // Although it's not our problem, we need to handle this case properly.
            val oldCompilerArgs: List = options.compilerArgs
            val newCompilerArgs = oldCompilerArgs.filterTo(mutableListOf()) {
                it !is CharSequence || !it.toString().startsWith("-proc:")
            }
            if (!kaptExtension.keepJavacAnnotationProcessors) {
                newCompilerArgs.add("-proc:none")
            }
            @Suppress("UNCHECKED_CAST")
            options.compilerArgs = newCompilerArgs as List

            // Filter out the argument providers that are related to annotation processing and therefore already used by Kapt.
            // This is done to avoid outputs intersections between Kapt and and javaCompile and make the up-to-date check for
            // javaCompile more granular as it does not perform annotation processing:
            KaptWithAndroid.androidVariantData(this)?.let { androidVariantData ->
                options.compilerArgumentProviders.removeAll(androidVariantData.annotationProcessorOptionProviders)
            }
        }
    }

    override fun getCompilerPluginId() = KAPT_SUBPLUGIN_ID

    override fun getPluginArtifact(): SubpluginArtifact =
        JetBrainsSubpluginArtifact(artifactId = KAPT_ARTIFACT_NAME)
}

internal const val KAPT_GENERATE_STUBS_PREFIX = "kaptGenerateStubs"
internal const val KAPT_PREFIX = "kapt"

internal val TaskProvider.kaptGenerateStubsTaskName
    get() = getKaptTaskName(name, KAPT_GENERATE_STUBS_PREFIX)

internal val TaskProvider.kaptTaskName
    get() = getKaptTaskName(name, KAPT_PREFIX)

internal fun getKaptTaskName(
    kotlinCompileName: String,
    prefix: String
): String {
    return if (kotlinCompileName.startsWith("compile")) {
        // Replace compile*Kotlin to kapt*Kotlin
        kotlinCompileName.replaceFirst("compile", prefix)
    } else {
        // Task was created via exposed apis (KotlinJvmFactory or MPP) with random name
        // in such case adding 'kapt' prefix to name
        "$prefix${kotlinCompileName.capitalizeAsciiOnly()}"
    }
}

internal fun buildKaptSubpluginOptions(
    kaptExtension: KaptExtension,
    project: Project,
    javacOptions: Map,
    aptMode: String,
    generatedSourcesDir: Iterable,
    generatedClassesDir: Iterable,
    incrementalDataDir: Iterable,
    includeCompileClasspath: Boolean,
    kaptStubsDir: Iterable,
): List {
    if (kaptExtension.generateStubs) {
        project.logger.warn("'kapt.generateStubs' is not used by the 'kotlin-kapt' plugin")
    }

    val pluginOptions = mutableListOf()

    pluginOptions += SubpluginOption("aptMode", aptMode)

    pluginOptions += FilesSubpluginOption("sources", generatedSourcesDir)
    pluginOptions += FilesSubpluginOption("classes", generatedClassesDir)
    pluginOptions += FilesSubpluginOption("incrementalData", incrementalDataDir)

    @Suppress("DEPRECATION") val annotationProcessors = kaptExtension.processors
    if (annotationProcessors.isNotEmpty()) {
        pluginOptions += SubpluginOption("processors", annotationProcessors)
    }
    pluginOptions += SubpluginOption("javacArguments", encodeList(javacOptions))
    pluginOptions += SubpluginOption("includeCompileClasspath", includeCompileClasspath.toString())

    // These option names must match those defined in org.jetbrains.kotlin.kapt.cli.KaptCliOption.
    pluginOptions += SubpluginOption("useLightAnalysis", "${kaptExtension.useLightAnalysis}")
    pluginOptions += SubpluginOption("correctErrorTypes", "${kaptExtension.correctErrorTypes}")
    pluginOptions += SubpluginOption("dumpDefaultParameterValues", "${kaptExtension.dumpDefaultParameterValues}")
    pluginOptions += SubpluginOption("mapDiagnosticLocations", "${kaptExtension.mapDiagnosticLocations}")
    pluginOptions += SubpluginOption(
        "strictMode", // Currently doesn't match KaptCliOption.STRICT_MODE_OPTION, is it a typo introduced in https://github.com/JetBrains/kotlin/commit/c83581e6b8155c6d89da977be6e3cd4af30562e5?
        "${kaptExtension.strictMode}"
    )
    pluginOptions += SubpluginOption("stripMetadata", "${kaptExtension.stripMetadata}")
    pluginOptions += SubpluginOption("keepKdocCommentsInStubs", "${project.isKaptKeepKdocCommentsInStubs()}")
    pluginOptions += SubpluginOption("showProcessorTimings", "${kaptExtension.showProcessorStats}")
    pluginOptions += SubpluginOption("detectMemoryLeaks", kaptExtension.detectMemoryLeaks)
    pluginOptions += SubpluginOption("useK2", "${project.isUseK2()}")
    pluginOptions += SubpluginOption("infoAsWarnings", "${project.isInfoAsWarnings()}")
    pluginOptions += FilesSubpluginOption("stubs", kaptStubsDir)

    if (project.isKaptVerbose()) {
        pluginOptions += SubpluginOption("verbose", "true")
    }

    return pluginOptions
}

/* Returns AP options from KAPT static DSL. */
internal fun getNonAndroidDslApOptions(
    kaptExtension: KaptExtension,
    project: Project,
    kotlinSourcesOutputDir: Iterable,
    @Suppress("TYPEALIAS_EXPANSION_DEPRECATION") variantData: DeprecatedAndroidBaseVariant?,
    androidExtension: BaseExtension?
): Provider> {
    return project.provider {
        kaptExtension.getAdditionalArguments(project, variantData, androidExtension).toList()
            .map { SubpluginOption(it.first, it.second) } +
                FilesSubpluginOption(Kapt3GradleSubplugin.KAPT_KOTLIN_GENERATED, kotlinSourcesOutputDir)
    }
}

private fun encodeList(options: Map): String {
    val os = ByteArrayOutputStream()
    val oos = ObjectOutputStream(os)

    oos.writeInt(options.size)
    for ((key, value) in options.entries) {
        oos.writeUTF(key)
        oos.writeUTF(value)
    }

    oos.flush()
    return Base64.getEncoder().encodeToString(os.toByteArray())
}

// Don't reference the BaseVariant type in the Kapt plugin signatures, as those type references will fail to link when there's no Android
// Gradle plugin on the project's plugin classpath
private object KaptWithAndroid {
    // Avoid loading the BaseVariant type at call sites and instead lazily load it when evaluation reaches it in the body using inline:
    @Suppress("NOTHING_TO_INLINE", "TYPEALIAS_EXPANSION_DEPRECATION")
    inline fun androidVariantData(
        context: Kapt3GradleSubplugin.Kapt3SubpluginContext
    ): DeprecatedAndroidBaseVariant? = context.variantData as? DeprecatedAndroidBaseVariant

    @Suppress("NOTHING_TO_INLINE")
    // Avoid loading the BaseVariant type at call sites and instead lazily load it when evaluation reaches it in the body using inline:
    inline fun registerGeneratedJavaSourceForAndroid(
        kapt3SubpluginContext: Kapt3GradleSubplugin.Kapt3SubpluginContext,
        project: Project,
        @Suppress("TYPEALIAS_EXPANSION_DEPRECATION") variantData: DeprecatedAndroidBaseVariant,
        kaptTask: TaskProvider
    ) {
        val kaptSourceOutput = project.fileTree(kapt3SubpluginContext.sourcesOutputDir).builtBy(kaptTask)
        kaptSourceOutput.include("**/*.java")
        variantData.registerExternalAptJavaOutput(kaptSourceOutput)
        kaptTask.configure { kaptTaskInstance ->
            variantData.dataBindingDependencyArtifactsIfSupported?.let { dataBindingArtifacts ->
                kaptTaskInstance.dependsOn(dataBindingArtifacts)
            }
        }
    }
}

internal fun registerGeneratedJavaSource(kaptTask: TaskProvider, javaTaskProvider: TaskProvider) {
    javaTaskProvider.configure { javaTask ->
        val generatedJavaSources = javaTask.project.fileTree(kaptTask.flatMap { it.destinationDir })
        generatedJavaSources.include("**/*.java")
        javaTask.source(generatedJavaSources)
    }
}

internal fun Configuration.getNamedDependencies(): List = allDependencies.filter { it.group != null }

private val ANNOTATION_PROCESSOR = "annotationProcessor"
private val ANNOTATION_PROCESSOR_CAP = ANNOTATION_PROCESSOR.capitalizeAsciiOnly()

internal fun checkAndroidAnnotationProcessorDependencyUsage(project: Project) {
    if (project.hasProperty("kapt.dont.warn.annotationProcessor.dependencies")) {
        return
    }

    val isKapt3Enabled = Kapt3GradleSubplugin.isEnabled(project)

    val apConfigurations = project.configurations
        .filter { it.name == ANNOTATION_PROCESSOR || (it.name.endsWith(ANNOTATION_PROCESSOR_CAP) && !it.name.startsWith("_")) }

    val problemDependencies = mutableListOf()

    for (apConfiguration in apConfigurations) {
        val apConfigurationName = apConfiguration.name

        val kaptConfigurationName = when (apConfigurationName) {
            ANNOTATION_PROCESSOR -> "kapt"
            else -> {
                val configurationName = apConfigurationName.dropLast(ANNOTATION_PROCESSOR_CAP.length)
                Kapt3GradleSubplugin.getKaptConfigurationName(configurationName)
            }
        }

        val kaptConfiguration = project.configurations.findByName(kaptConfigurationName) ?: continue
        val kaptConfigurationDependencies = kaptConfiguration.getNamedDependencies()

        problemDependencies += apConfiguration.getNamedDependencies().filter { a ->
            // Ignore annotationProcessor dependencies if they are also declared as 'kapt'
            kaptConfigurationDependencies.none { k -> a.group == k.group && a.name == k.name && a.version == k.version }
        }
    }

    if (problemDependencies.isNotEmpty()) {
        val artifactsRendered = problemDependencies.joinToString { "'${it.group}:${it.name}:${it.version}'" }
        val andApplyKapt = if (isKapt3Enabled) "" else " and apply the kapt plugin: \"apply plugin: 'kotlin-kapt'\""

        project.logger.warn(
            "${project.name}: " +
                    "'annotationProcessor' dependencies won't be recognized as kapt annotation processors. " +
                    "Please change the configuration name to 'kapt' for these artifacts: $artifactsRendered$andApplyKapt."
        )
    }
}

@Suppress("TYPEALIAS_EXPANSION_DEPRECATION")
private val DeprecatedAndroidBaseVariant.annotationProcessorOptions: Map?
    get() = javaCompileOptions.annotationProcessorOptions.arguments

@Suppress("TYPEALIAS_EXPANSION_DEPRECATION")
private val DeprecatedAndroidBaseVariant.annotationProcessorOptionProviders: List
    get() = javaCompileOptions.annotationProcessorOptions.compilerArgumentProviders

//TODO once the Android plugin reaches its 3.0.0 release, consider compiling against it (remove the reflective call)
@Suppress("TYPEALIAS_EXPANSION_DEPRECATION")
private val DeprecatedAndroidBaseVariant.dataBindingDependencyArtifactsIfSupported: FileCollection?
    get() = this::class.java.methods
        .find { it.name == "getDataBindingDependencyArtifacts" }
        ?.also { it.isAccessible = true }
        ?.invoke(this) as? FileCollection




© 2015 - 2024 Weber Informatics LLC | Privacy Policy