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

org.jetbrains.kotlin.gradle.targets.android.Android25ProjectHandler.kt Maven / Gradle / Ivy

There is a newer version: 2.1.0-Beta1
Show newest version
/*
 * Copyright 2010-2019 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") // Old package for compatibility
package org.jetbrains.kotlin.gradle.plugin

import com.android.build.api.attributes.BuildTypeAttr
import com.android.build.gradle.*
import com.android.build.gradle.api.*
import com.android.build.gradle.tasks.MergeResources
import org.gradle.api.Project
import org.gradle.api.artifacts.ArtifactCollection
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.gradle.api.attributes.Attribute
import org.gradle.api.attributes.Category
import org.gradle.api.file.FileCollection
import org.gradle.api.specs.Spec
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import org.gradle.api.tasks.compile.AbstractCompile
import org.gradle.api.tasks.compile.JavaCompile
import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin
import org.jetbrains.kotlin.gradle.plugin.android.AndroidGradleWrapper
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.tasks.thisTaskProvider
import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation
import java.io.File
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.io.Serializable
import java.util.concurrent.Callable

class Android25ProjectHandler(
    kotlinConfigurationTools: KotlinConfigurationTools
) : AbstractAndroidProjectHandler(kotlinConfigurationTools) {

    override fun wireKotlinTasks(
        project: Project,
        compilation: KotlinJvmAndroidCompilation,
        androidPlugin: BasePlugin<*>,
        androidExt: BaseExtension,
        variantData: BaseVariant,
        javaTask: TaskProvider,
        kotlinTask: TaskProvider
    ) {
        val preJavaKotlinOutput = project.files(project.provider {
            mutableListOf().apply {
                add(kotlinTask.get().destinationDir)
                if (Kapt3GradleSubplugin.isEnabled(project)) {
                    // Add Kapt3 output as well, since there's no SyncOutputTask with the new API
                    val kaptClasssesDir = Kapt3GradleSubplugin.getKaptGeneratedClassesDir(project, getVariantName(variantData))
                    add(kaptClasssesDir)
                }
            }
        }).builtBy(kotlinTask)

        val preJavaClasspathKey = variantData.registerPreJavacGeneratedBytecode(preJavaKotlinOutput)
        kotlinTask.configure { kotlinTaskInstance ->
            kotlinTaskInstance.source(
                variantData.getSourceFolders(SourceKind.JAVA)
            )

            kotlinTaskInstance.classpath = project.files()
                .from(variantData.getCompileClasspath(preJavaClasspathKey))
                .from(Callable { AndroidGradleWrapper.getRuntimeJars(androidPlugin, androidExt) })

            kotlinTaskInstance.javaOutputDir.set(javaTask.flatMap { it.destinationDirectory })
        }

        // Find the classpath entries that come from the tested variant and register them as the friend paths, lazily
        val originalArtifactCollection = variantData.getCompileClasspathArtifacts(preJavaClasspathKey)
        val testedVariantDataIsNotNull = getTestedVariantData(variantData) != null
        val projectPath = project.path
        compilation.testedVariantArtifacts.set(
            originalArtifactCollection.artifactFiles.filter(
                AndroidTestedVariantArtifactsFilter(
                    originalArtifactCollection,
                    testedVariantDataIsNotNull,
                    projectPath
                )
            )
        )

        compilation.output.classesDirs.from(
            kotlinTask.flatMap { it.destinationDirectory },
            javaTask.flatMap { it.destinationDirectory }
        )
    }

    override fun getFlavorNames(variant: BaseVariant): List = variant.productFlavors.map { it.name }

    override fun getBuildTypeName(variant: BaseVariant): String = variant.buildType.name

    // TODO the return type is actually `AbstractArchiveTask | TaskProvider`;
    //      change the signature once the Android Gradle plugin versions that don't support task providers are dropped
    override fun getLibraryOutputTask(variant: BaseVariant): Any? {
        val getPackageLibraryProvider = variant.javaClass.methods
            .find { it.name == "getPackageLibraryProvider" && it.parameterCount == 0 }

        return if (getPackageLibraryProvider != null) {
            @Suppress("UNCHECKED_CAST")
            getPackageLibraryProvider(variant) as TaskProvider
        } else {
            (variant as? LibraryVariant)?.packageLibrary
        }
    }

    override fun setUpDependencyResolution(variant: BaseVariant, compilation: KotlinJvmAndroidCompilation) {
        val project = compilation.target.project

        AbstractKotlinTargetConfigurator.defineConfigurationsForCompilation(compilation)

        compilation.compileDependencyFiles = variant.compileConfiguration.apply {
            usesPlatformOf(compilation.target)
            project.addExtendsFromRelation(name, compilation.compileDependencyConfigurationName)
        }

        compilation.runtimeDependencyFiles = variant.runtimeConfiguration.apply {
            usesPlatformOf(compilation.target)
            project.addExtendsFromRelation(name, compilation.runtimeDependencyConfigurationName)
        }

        val buildTypeAttrValue = project.objects.named(BuildTypeAttr::class.java, variant.buildType.name)
        listOf(compilation.compileDependencyConfigurationName, compilation.runtimeDependencyConfigurationName).forEach {
            project.configurations.findByName(it)?.attributes?.attribute(Attribute.of(BuildTypeAttr::class.java), buildTypeAttrValue)
        }

        // TODO this code depends on the convention that is present in the Android plugin as there's no public API
        // We should request such API in the Android plugin
        val apiElementsConfigurationName = "${variant.name}ApiElements"
        val runtimeElementsConfigurationName = "${variant.name}RuntimeElements"

        // KT-29476, the Android *Elements configurations need Kotlin MPP dependencies:
        if (project.configurations.findByName(apiElementsConfigurationName) != null) {
            project.addExtendsFromRelation(apiElementsConfigurationName, compilation.apiConfigurationName)
        }
        if (project.configurations.findByName(runtimeElementsConfigurationName) != null) {
            project.addExtendsFromRelation(runtimeElementsConfigurationName, compilation.implementationConfigurationName)
            project.addExtendsFromRelation(runtimeElementsConfigurationName, compilation.runtimeOnlyConfigurationName)
        }

        listOf(apiElementsConfigurationName, runtimeElementsConfigurationName).forEach { outputConfigurationName ->
            project.configurations.findByName(outputConfigurationName)?.let { configuration ->
                configuration.usesPlatformOf(compilation.target)
                configuration.attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.categoryByName(Category.LIBRARY))
            }
        }
    }
}

internal fun getTestedVariantData(variantData: BaseVariant): BaseVariant? = when (variantData) {
    is TestVariant -> variantData.testedVariant
    is UnitTestVariant -> variantData.testedVariant as? BaseVariant
    else -> null
}

internal fun getVariantName(variant: BaseVariant): String = variant.name

@Suppress("UNCHECKED_CAST")
internal fun BaseVariant.getJavaTaskProvider(): TaskProvider =
    this::class.java.methods.firstOrNull { it.name == "getJavaCompileProvider" }
        ?.invoke(this) as? TaskProvider
        ?: @Suppress("DEPRECATION") javaCompile.thisTaskProvider

internal fun forEachVariant(project: Project, action: (BaseVariant) -> Unit) {
    val androidExtension = project.extensions.getByName("android")
    when (androidExtension) {
        is AppExtension -> androidExtension.applicationVariants.all(action)
        is LibraryExtension -> {
            androidExtension.libraryVariants.all(action)
            if (androidExtension is FeatureExtension) {
                androidExtension.featureVariants.all(action)
            }
        }
        is TestExtension -> androidExtension.applicationVariants.all(action)
    }
    if (androidExtension is TestedExtension) {
        androidExtension.testVariants.all(action)
        androidExtension.unitTestVariants.all(action)
    }
}

internal fun BaseVariant.getResDirectories(): FileCollection {
    val getAllResourcesMethod =
        this::class.java.methods.firstOrNull { it.name == "getAllRawAndroidResources" }
    if (getAllResourcesMethod != null) {
        val allResources = getAllResourcesMethod.invoke(this) as FileCollection
        return allResources
    }

    val project = mergeResources.project
    return project.files(Callable { mergeResources?.computeResourceSetList0() ?: emptyList() })
}

//TODO A public API is expected for this purpose. Once it is available, use the public API
private fun MergeResources.computeResourceSetList0(): List? {
    val computeResourceSetListMethod = MergeResources::class.java.declaredMethods
        .firstOrNull { it.name == "computeResourceSetList" && it.parameterCount == 0 } ?: return null

    val oldIsAccessible = computeResourceSetListMethod.isAccessible
    try {
        computeResourceSetListMethod.isAccessible = true

        val resourceSets = computeResourceSetListMethod.invoke(this) as? Iterable<*>

        return resourceSets
            ?.mapNotNull { resourceSet ->
                val getSourceFiles = resourceSet?.javaClass?.methods?.find { it.name == "getSourceFiles" && it.parameterCount == 0 }
                val files = getSourceFiles?.invoke(resourceSet)
                @Suppress("UNCHECKED_CAST")
                files as? Iterable
            }
            ?.flatten()

    } finally {
        computeResourceSetListMethod.isAccessible = oldIsAccessible
    }
}

/** Filter for the AGP test variant classpath artifacts. */
class AndroidTestedVariantArtifactsFilter(
    private val artifactCollection: ArtifactCollection,
    private val testedVariantDataIsNotNull: Boolean,
    private val projectPath: String
) : Serializable, Spec {

    /** Make transient as it should be derived from the [artifactCollection] property which may change in configuration cached runs. */
    @Transient
    private var filteredFiles = initFilteredFiles()

    private fun initFilteredFiles(): Lazy> {
        return lazy {
            artifactCollection.filter {
                it.id.componentIdentifier is TestedComponentIdentifier ||
                        // If tests depend on the main classes transitively, through a test dependency on another module which
                        // depends on this module, then there's no artifact with a TestedComponentIdentifier, so consider the artifact of the
                        // current module a friend path, too:
                        testedVariantDataIsNotNull &&
                        (it.id.componentIdentifier as? ProjectComponentIdentifier)?.projectPath == projectPath
            }
                .mapTo(mutableSetOf()) { it.file }
        }
    }

    private fun writeObject(objectOutputStream: ObjectOutputStream) {
        objectOutputStream.defaultWriteObject()
    }

    private fun readObject(objectInputStream: ObjectInputStream) {
        objectInputStream.defaultReadObject()
        filteredFiles = initFilteredFiles()
    }

    override fun isSatisfiedBy(element: File): Boolean {
        return element in filteredFiles.value
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy