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

proguard.gradle.plugin.android.AndroidPlugin.kt Maven / Gradle / Ivy

Go to download

Gradle plugin for ProGuard, the free shrinker, optimizer, obfuscator, and preverifier for Java bytecode

There is a newer version: 7.6.0
Show newest version
/*
 * ProGuard -- shrinking, optimization, obfuscation, and preverification
 *             of Java bytecode.
 *
 * Copyright (c) 2002-2021 Guardsquare NV
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

package proguard.gradle.plugin.android

import com.android.build.api.variant.VariantInfo
import com.android.build.gradle.AppExtension
import com.android.build.gradle.BaseExtension
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.api.BaseVariant
import com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask
import com.android.build.gradle.internal.tasks.factory.dependsOn
import java.io.File
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Configuration
import org.gradle.api.attributes.Attribute
import org.gradle.api.tasks.TaskProvider
import org.gradle.util.VersionNumber
import proguard.gradle.plugin.android.AndroidProjectType.ANDROID_APPLICATION
import proguard.gradle.plugin.android.AndroidProjectType.ANDROID_LIBRARY
import proguard.gradle.plugin.android.dsl.ProGuardAndroidExtension
import proguard.gradle.plugin.android.dsl.ProGuardConfiguration
import proguard.gradle.plugin.android.dsl.UserProGuardConfiguration
import proguard.gradle.plugin.android.dsl.VariantConfiguration
import proguard.gradle.plugin.android.tasks.CollectConsumerRulesTask
import proguard.gradle.plugin.android.tasks.PrepareProguardConfigDirectoryTask
import proguard.gradle.plugin.android.transforms.AndroidConsumerRulesTransform
import proguard.gradle.plugin.android.transforms.ArchiveConsumerRulesTransform

class AndroidPlugin(private val androidExtension: BaseExtension) : Plugin {

    override fun apply(project: Project) {
        val collectConsumerRulesTask = project.tasks.register(COLLECT_CONSUMER_RULES_TASK_NAME)
        registerDependencyTransforms(project)
        val proguardBlock = project.extensions.create("proguard", ProGuardAndroidExtension::class.java, project)

        val projectType = when (androidExtension) {
            is AppExtension -> ANDROID_APPLICATION
            is LibraryExtension -> ANDROID_LIBRARY
            else -> throw GradleException("The ProGuard Gradle plugin can only be used on Android application and library projects")
        }

        configureAapt(project)

        warnOldProguardVersion(project)

        androidExtension.registerTransform(
                ProGuardTransform(project, proguardBlock, projectType, androidExtension),
                collectConsumerRulesTask)

        project.afterEvaluate {
            if (proguardBlock.configurations.isEmpty())
                throw GradleException("There are no configured variants in the 'proguard' block")

            val matchedConfigurations = mutableListOf()

            when (androidExtension) {
                is AppExtension -> androidExtension.applicationVariants.all { applicationVariant ->
                    setupVariant(proguardBlock, applicationVariant, collectConsumerRulesTask, project)?.let {
                        matchedConfigurations.add(it)
                    }
                }
                is LibraryExtension -> androidExtension.libraryVariants.all { libraryVariant ->
                    setupVariant(proguardBlock, libraryVariant, collectConsumerRulesTask, project)?.let {
                        matchedConfigurations.add(it)
                    }
                }
            }

            proguardBlock.configurations.forEach {
                checkConfigurationFile(project, it.configurations)
            }

            (proguardBlock.configurations - matchedConfigurations).apply {
                if (isNotEmpty()) when (size) {
                    1 -> throw GradleException("The configured variant '${first().name}' does not exist")
                    else -> throw GradleException("The configured variants ${joinToString(separator = "', '", prefix = "'", postfix = "'") { it.name }} do not exist")
                }
            }
        }
    }

    private fun configureAapt(project: Project) {
        val createDirectoryTask = project.tasks.register("prepareProguardConfigDirectory", PrepareProguardConfigDirectoryTask::class.java)
        project.tasks.withType(LinkApplicationAndroidResourcesTask::class.java) {
            it.dependsOn(createDirectoryTask)
        }
        if (!androidExtension.aaptAdditionalParameters.contains("--proguard")) {
            androidExtension.aaptAdditionalParameters.addAll(listOf(
                    "--proguard",
                    "${project.buildDir.absolutePath}/intermediates/proguard/configs/aapt_rules.pro"))
        }

        if (!androidExtension.aaptAdditionalParameters.contains("--proguard-conditional-keep-rules")) {
            androidExtension.aaptAdditionalParameters.add("--proguard-conditional-keep-rules")
        }
    }

    private fun setupVariant(proguardBlock: ProGuardAndroidExtension, variant: BaseVariant, collectConsumerRulesTask: TaskProvider, project: Project): VariantConfiguration? {
        val matchingConfiguration = proguardBlock.configurations.findVariantConfiguration(variant.name)
        if (matchingConfiguration != null) {
            verifyNotMinified(variant)
            disableAaptOutputCaching(project, variant)

            collectConsumerRulesTask.dependsOn(createCollectConsumerRulesTask(
                    project,
                    variant,
                    createConsumerRulesConfiguration(project, variant),
                    File("${project.buildDir}/intermediates/proguard/configs")))
        }
        return matchingConfiguration
    }

    private fun createCollectConsumerRulesTask(
        project: Project,
        variant: BaseVariant,
        inputConfiguration: Configuration,
        outputDir: File
    ): TaskProvider =
        project.tasks.register(COLLECT_CONSUMER_RULES_TASK_NAME + variant.name.capitalize(), CollectConsumerRulesTask::class.java) {
            it.consumerRulesConfiguration = inputConfiguration
            it.outputFile = File(File(outputDir, variant.dirName), CONSUMER_RULES_PRO)
            it.dependsOn(inputConfiguration.buildDependencies)
        }

    private fun createConsumerRulesConfiguration(project: Project, variant: BaseVariant): Configuration =
        project.configurations.create("${variant.name}ProGuardConsumerRulesArtifacts") {
            it.isCanBeResolved = true
            it.isCanBeConsumed = false
            it.isTransitive = true

            it.extendsFrom(variant.runtimeConfiguration)
            copyConfigurationAttributes(it, variant.runtimeConfiguration)

            it.attributes.attribute(ATTRIBUTE_ARTIFACT_TYPE, ARTIFACT_TYPE_CONSUMER_RULES)
        }

    private fun checkConfigurationFile(project: Project, files: List) {
        files.filterIsInstance().forEach {
            val file = project.file(it.path)
            if (!file.exists()) throw GradleException("ProGuard configuration file ${file.absolutePath} was set but does not exist.")
        }
    }

    private fun verifyNotMinified(variant: BaseVariant) {
        if (variant.buildType.isMinifyEnabled) {
            throw GradleException(
                    "The option 'minifyEnabled' is set to 'true' for variant '${variant.name}', but should be 'false' for variants processed by ProGuard")
        }
    }

    private fun copyConfigurationAttributes(destConfiguration: Configuration, srcConfiguration: Configuration) {
        srcConfiguration.attributes.keySet().forEach { attribute ->
            val attributeValue = srcConfiguration.attributes.getAttribute(attribute)
            destConfiguration.attributes.attribute(attribute as Attribute, attributeValue)
        }
    }

    private fun registerDependencyTransforms(project: Project) {
        project.dependencies.registerTransform(ArchiveConsumerRulesTransform::class.java) {
            it.from.attribute(ATTRIBUTE_ARTIFACT_TYPE, "aar")
            it.to.attribute(ATTRIBUTE_ARTIFACT_TYPE, ARTIFACT_TYPE_CONSUMER_RULES)
        }
        project.dependencies.registerTransform(ArchiveConsumerRulesTransform::class.java) {
            it.from.attribute(ATTRIBUTE_ARTIFACT_TYPE, "jar")
            it.to.attribute(ATTRIBUTE_ARTIFACT_TYPE, ARTIFACT_TYPE_CONSUMER_RULES)
        }
        project.dependencies.registerTransform(AndroidConsumerRulesTransform::class.java) {
            it.from.attribute(ATTRIBUTE_ARTIFACT_TYPE, "android-consumer-proguard-rules")
            it.to.attribute(ATTRIBUTE_ARTIFACT_TYPE, ARTIFACT_TYPE_CONSUMER_RULES)
        }
    }

    // TODO: improve loading AAPT rules so that we don't rely on this
    private fun disableAaptOutputCaching(project: Project, variant: BaseVariant) {
        val cachingEnabled = project.hasProperty("org.gradle.caching") &&
                (project.findProperty("org.gradle.caching") as String).toBoolean()

        if (cachingEnabled) {
            // ensure that the aapt_rules.pro has been generated, so ProGuard can use it
            val processResourcesTask = project.tasks.findByName("process${variant.name.capitalize()}Resources")
            processResourcesTask?.outputs?.doNotCacheIf("We need to regenerate the aapt_rules.pro file, sorry!") {
                project.logger.debug("Disabling AAPT caching for ${variant.name}")
                !File("${project.buildDir.absolutePath}/intermediates/proguard/configs/aapt_rules.pro").exists()
            }
        }
    }

    private fun warnOldProguardVersion(project: Project) {
        if (agpVersion.major >= 7) return

        val message =
"""An older version of ProGuard has been detected on the classpath which can clash with ProGuard Gradle Plugin.
This is likely due to a transitive dependency introduced by Android Gradle plugin.

Please update your configuration to exclude the old version of ProGuard, for example:

buildscript {
    // ... 
    dependencies {
        // ...
        classpath("com.android.tools.build:gradle:x.y.z") {
            exclude group: "net.sf.proguard", module: "proguard-gradle"
            // or for kotlin (build.gradle.kts):
            // exclude(group = "net.sf.proguard", module = "proguard-gradle")
        }
   }
}"""
        val proguardTask = Class.forName("proguard.gradle.ProGuardTask")
        // This method does not exist in the ProGuard version distributed with AGP.
        // It's used by `ProGuardTransform`, so throw an exception if it doesn't exist.
        if (proguardTask.methods.count { it.name == "extraJar" } == 0) {
            throw GradleException(message)
        }

        // Otherwise, only print a warning since it may or may not cause a problem
        project.rootProject.buildscript.configurations.all {
            it.resolvedConfiguration.resolvedArtifacts.find {
                it.moduleVersion.id.module.group.equals("net.sf.proguard") && it.moduleVersion.id.module.name.equals("proguard-gradle")
            }?.let {
                project.logger.warn(message)
            }
        }
    }

    companion object {
        const val COLLECT_CONSUMER_RULES_TASK_NAME = "collectConsumerRules"

        private const val CONSUMER_RULES_PRO = "consumer-rules.pro"
        private const val ARTIFACT_TYPE_CONSUMER_RULES = "proguard-consumer-rules"
        private val ATTRIBUTE_ARTIFACT_TYPE = Attribute.of("artifactType", String::class.java)
    }
}

enum class AndroidProjectType {
    ANDROID_APPLICATION,
    ANDROID_LIBRARY;
}

fun Iterable.findVariantConfiguration(variant: VariantInfo) =
    find { it.name == variant.fullVariantName } ?: find { it.name == variant.buildTypeName }

fun Iterable.findVariantConfiguration(variantName: String) =
    find { it.name == variantName } ?: find { variantName.endsWith(it.name.capitalize()) }

fun Iterable.hasVariantConfiguration(variantName: String) =
        this.findVariantConfiguration(variantName) != null

val agpVersion: VersionNumber
    get() { // TODO update to use AGP7 version API
        return try {
            val clazz = Class.forName("com.android.builder.model.Version")
            val version = clazz.fields.first { it.name == "ANDROID_GRADLE_PLUGIN_VERSION" }.get(null) as String
            return VersionNumber.parse(version)
        } catch (e: ClassNotFoundException) {
            VersionNumber.UNKNOWN
        }
    }

/**
 * Extension property that wraps the aapt additional parameters, to take into account
 * API changes.
 */
@Suppress("UNCHECKED_CAST")
val BaseExtension.aaptAdditionalParameters: MutableCollection
    get() {
        val aaptOptionsGetter = if (agpVersion.major >= 7) "getAndroidResources" else "getAaptOptions"
        val aaptOptions = this.javaClass.methods.first { it.name == aaptOptionsGetter }.invoke(this)
        val additionalParameters = aaptOptions.javaClass.methods.first { it.name == "getAdditionalParameters" }.invoke(aaptOptions)
        return if (additionalParameters != null) {
            additionalParameters as MutableCollection
        } else {
            // additionalParameters may be null because AGP 4.0.0 does not set a default empty list
            val newAdditionalParameters = ArrayList()
            aaptOptions.javaClass.methods.first { it.name == "setAdditionalParameters" }.invoke(aaptOptions, newAdditionalParameters)
            newAdditionalParameters
        }
    }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy