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

family.amma.deep_link.gradle_plugin.DeepLinksPlugin.kt Maven / Gradle / Ivy

package family.amma.deep_link.gradle_plugin

import com.android.build.api.extension.AndroidComponentsExtension
import com.android.build.api.variant.DynamicFeatureVariant
import com.android.build.gradle.AppExtension
import com.android.build.gradle.BaseExtension
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.api.ApplicationVariant
import com.android.build.gradle.api.BaseVariant
import groovy.xml.XmlSlurper
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.file.FileCollection
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import java.io.File
import javax.inject.Inject

private const val PLUGIN_DIRNAME = "navigation-deep-links"
private const val GENERATED_PATH = "generated/source/$PLUGIN_DIRNAME"
private const val PLUGIN_PATH = "$PLUGIN_DIRNAME/src/main/kotlin"

@Suppress("unused")
class DeepLinksPlugin @Inject constructor(val providerFactory: ProviderFactory) : Plugin {
    override fun apply(project: Project) {
        val pluginExtension = project.extensions.create(DeepLinksPluginExtension.NAME, DeepLinksPluginExtension::class.java)

        val baseExtension = project.extensions.findByType(BaseExtension::class.java)
            ?: throw GradleException("deep links plugin must be used with android plugin")
        val outputDir = File(project.rootDir, PLUGIN_PATH)
        val pluginDir = File(project.rootDir, PLUGIN_DIRNAME)
        val buildDir = File(project.buildDir, GENERATED_PATH)

        val applicationIds = mutableMapOf>()
        val variantExtension =
            project.extensions.findByType(AndroidComponentsExtension::class.java)
                ?: throw GradleException("deeplinks plugin must be used with android plugin")
        variantExtension.onVariants { variant ->
            when (variant) {
                is ApplicationVariant, is DynamicFeatureVariant ->
                    // Using reflection for AGP 7.0+ cause it can't resolve that
                    // DynamicFeatureVariant implements GeneratesApk so the `applicationId`
                    // property is actually available. Once we upgrade to 7.0 we will use
                    // getNamespace().
                    variant::class.java.getDeclaredMethod("getApplicationId").let { method ->
                        method.trySetAccessible()
                        applicationIds.getOrPut(variant.name) {
                            @kotlin.Suppress("UNCHECKED_CAST")
                            method.invoke(variant) as Provider
                        }
                    }
            }
        }

        forEachVariants(baseExtension) { variant: BaseVariant ->
            val generateDeepLinksTask = project
                .tasks
                .create("generateDeepLinks${variant.name.capitalize()}", GenerateDeepLinksTask::class.java) { task ->
                    task.rFilePackage.set(variant.rFilePackage())
                    task.applicationId.set(
                        // this will only put in the case where the extension is a Library module
                        // and should be superseded by `getNamespace()` in agp 7.0+
                        applicationIds.getOrPut(variant.name) {
                            providerFactory.provider { variant.applicationId }
                        }
                    )
                    task.generatorParams = pluginExtension.toGeneratorParams()
                    task.navigationFiles.setFrom(navigationFiles(variant, project))
                    task.outputDir = outputDir
                    task.pluginDir = pluginDir
                    task.buildDir = buildDir
                }
            variant.registerJavaGeneratingTask(generateDeepLinksTask, buildDir)
        }
    }

    private fun navigationFiles(variant: BaseVariant, project: Project): FileCollection {
        val fileProvider = providerFactory.provider {
            variant.sourceSets
                .flatMap { it.resDirectories }
                .mapNotNull { resDir ->
                    File(resDir, "navigation").takeIf { it.exists() && it.isDirectory }
                }
                .flatMap { navFolder -> navFolder.listFiles().orEmpty().asIterable() }
                .groupBy(File::getName)
                .values
                .map { it.last() }
        }
        return project.files(fileProvider)
    }

    private fun BaseVariant.rFilePackage() = providerFactory.provider {
        val mainSourceSet = sourceSets.find { it.name == "main" }
        val sourceSet = mainSourceSet ?: sourceSets[0]
        val manifest = sourceSet.manifestFile
        val parsed = XmlSlurper(false, false).parse(manifest)
        parsed.getProperty("@package").toString()
    }
}

private fun forEachVariants(extension: BaseExtension, action: (BaseVariant) -> Unit) {
    when (extension) {
        is AppExtension -> extension.applicationVariants.all(action)
        is LibraryExtension -> extension.libraryVariants.all(action)
        else -> throw GradleException("deep links plugin must be used with android app or library")
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy