Fix collection of AAPT rules

This commit is contained in:
James Hamilton
2021-06-14 14:35:12 +02:00
parent 601affb88b
commit efd665afac
6 changed files with 222 additions and 2 deletions

View File

@@ -197,6 +197,11 @@ deprecated:
}
```
!!! warning "Known issue with library projects and AAPT rules"
In library projects, the ProGuard Gradle plugin will not apply any keep rules that would have been generated by
AAPT when using the AGP integration. Therefore, you may need to apply some extra keep rules for classes referenced from
resources in your own ProGuard configuration.
7. You can then build your application as usual and ProGuard will be automatically executed on the configured variants as before:
=== "Linux/macOS"

View File

@@ -121,6 +121,7 @@ class AndroidPlugin(private val androidExtension: BaseExtension) : Plugin<Projec
val matchingConfiguration = proguardBlock.configurations.findVariantConfiguration(variant.name)
if (matchingConfiguration != null) {
verifyNotMinified(variant)
disableAaptOutputCaching(project, variant)
collectConsumerRulesTask.dependsOn(createCollectConsumerRulesTask(
project,
@@ -191,6 +192,21 @@ class AndroidPlugin(private val androidExtension: BaseExtension) : Plugin<Projec
}
}
// 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

View File

@@ -76,7 +76,12 @@ class ProGuardTransform(
proguardTask.configuration(project.tasks.getByPath(COLLECT_CONSUMER_RULES_TASK_NAME + variantName.capitalize()).outputs.files)
proguardTask.configuration(variantBlock.configurations.map { project.file(it.path) })
getAaptRulesFile()?.let { proguardTask.configuration(it) }
val aaptRulesFile = getAaptRulesFile()
if (aaptRulesFile != null && File(aaptRulesFile).exists()) {
proguardTask.configuration(aaptRulesFile)
} else {
project.logger.warn("AAPT rules file not found: you may need to apply some extra keep rules for classes referenced from resources in your own ProGuard configuration.")
}
val mappingDir = File("${project.buildDir.absolutePath}/outputs/proguard/$variantName/mapping")
if (!mappingDir.exists()) mappingDir.mkdirs()
@@ -147,7 +152,7 @@ class ProGuardTransform(
private fun getAaptRulesFile() = androidExtension.aaptAdditionalParameters
.zipWithNext { cmd, param -> if (cmd == "--proguard") param else null }
.filterNotNull()
.firstOrNull()
.firstOrNull { File(it).exists() }
}
typealias ProGuardIOEntry = Pair<File, File>

View File

@@ -8,11 +8,15 @@
package proguard.gradle
import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import org.gradle.testkit.runner.TaskOutcome
import testutils.AndroidProject
import testutils.SourceFile
import testutils.applicationModule
import testutils.createGradleRunner
import testutils.createTestKitDir
import testutils.libraryModule
class ProGuardPluginTest : FreeSpec({
val testKitDir = createTestKitDir()
@@ -88,4 +92,44 @@ class ProGuardPluginTest : FreeSpec({
}
}
}
"Given a library project" - {
val project = autoClose(AndroidProject().apply {
addModule(libraryModule("lib", buildDotGradle = """
plugins {
id 'com.android.library'
id 'com.guardsquare.proguard'
}
android {
compileSdkVersion 30
buildTypes {
release {
minifyEnabled false
}
debug {}
}
}
proguard {
configurations {
release {
defaultConfiguration 'proguard-android.txt'
configuration 'proguard-project.txt'
}
}
}
""".trimIndent(),
additionalFiles = listOf(SourceFile("proguard-project.txt", "-keep class **"))))
}.create())
"When the project is assembled" - {
val result = createGradleRunner(project.rootDir, testKitDir, "assembleRelease").build()
"Then the build should succeed" {
result.task(":lib:assembleRelease")?.outcome shouldBe TaskOutcome.SUCCESS
}
}
}
})

View File

@@ -118,4 +118,145 @@ class AaptRulesTest : FreeSpec({
}
}
}
"Given a project with a configuration for a variant in a project that has caching enabled" - {
val project = autoClose(AndroidProject(gradleDotProperties = "org.gradle.caching=true").apply {
addModule(applicationModule("app", buildDotGradle = """
plugins {
id 'com.android.application'
id 'com.guardsquare.proguard'
}
android {
compileSdkVersion 30
defaultConfig {
versionCode 1
}
buildTypes {
release {
minifyEnabled false
}
}
}
proguard {
configurations {
debug {
defaultConfiguration 'proguard-android-debug.txt'
}
}
}""".trimIndent()))
}.create())
"When assembleDebug is executed" - {
val aaptRules = File("${project.moduleBuildDir("app")}/intermediates/proguard/configs/aapt_rules.pro")
// run once, then delete the aapt_rules file
val result0 = createGradleRunner(project.rootDir, testKitDir, "assembleDebug").build()
aaptRules.delete()
// running again should re-generate the rules file
val result = createGradleRunner(project.rootDir, testKitDir, "clean", "assembleDebug", "--info").build()
"The build should succeed" {
result0.task(":app:assembleDebug")?.outcome shouldBe TaskOutcome.SUCCESS
result.task(":app:assembleDebug")?.outcome shouldBe TaskOutcome.SUCCESS
}
"The rules file should be passed to ProGuard" {
result.output shouldContain "Loading configuration file ${aaptRules.absolutePath}"
}
"The the AAPT rules should be generated" {
aaptRules.shouldExist()
aaptRules.readText() shouldContain "-keep class com.example.app.MainActivity { <init>(); }"
}
}
"When bundleDebug is executed" - {
val aaptRules = File("${project.moduleBuildDir("app")}/intermediates/proguard/configs/aapt_rules.pro")
// run once, then delete the aapt_rules file
val result0 = createGradleRunner(project.rootDir, testKitDir, "clean", "bundleDebug").build()
aaptRules.delete()
// running again should re-generate the rules file
val result = createGradleRunner(project.rootDir, testKitDir, "clean", "bundleDebug", "--info").build()
"The build should succeed" {
result0.task(":app:bundleDebug")?.outcome shouldBe TaskOutcome.SUCCESS
result.task(":app:bundleDebug")?.outcome shouldBe TaskOutcome.SUCCESS
}
"The rules file should be passed to ProGuard" {
result.output shouldContain "Loading configuration file ${aaptRules.absolutePath}"
}
"The the AAPT rules should be generated" {
aaptRules.shouldExist()
aaptRules.readText() shouldContain "-keep class com.example.app.MainActivity { <init>(); }"
}
}
}
"Given a project with a flavor configuration for a variant in a project that has caching enabled" - {
val project = autoClose(AndroidProject(gradleDotProperties = "org.gradle.caching=true").apply {
addModule(applicationModule("app", buildDotGradle = """
plugins {
id 'com.android.application'
id 'com.guardsquare.proguard'
}
android {
compileSdkVersion 30
buildTypes {
release {
minifyEnabled false
}
}
flavorDimensions "version"
productFlavors {
demo {
dimension "version"
applicationIdSuffix ".demo"
versionNameSuffix "-demo"
}
full {
dimension "version"
applicationIdSuffix ".full"
versionNameSuffix "-full"
}
}
}
proguard {
configurations {
demoDebug {
defaultConfiguration 'proguard-android-debug.txt'
}
}
}""".trimIndent()))
}.create())
"When the project is evaluated" - {
val aaptRules = File("${project.moduleBuildDir("app")}/intermediates/proguard/configs/aapt_rules.pro")
// run once, then delete the aapt_rules file
val result0 = createGradleRunner(project.rootDir, testKitDir, "assembleDebug").build()
aaptRules.delete()
// running again should re-generate the rules file
val result = createGradleRunner(project.rootDir, testKitDir, "clean", "assembleDebug", "--info").build()
"The build should succeed" {
result0.task(":app:assembleDebug")?.outcome shouldBe TaskOutcome.SUCCESS
result.task(":app:assembleDebug")?.outcome shouldBe TaskOutcome.SUCCESS
}
"The rules file should be passed to ProGuard" {
result.output shouldContain "Loading configuration file ${aaptRules.absolutePath}"
}
"The the AAPT rules should be generated" {
aaptRules.shouldExist()
aaptRules.readText() shouldContain "-keep class com.example.app.MainActivity { <init>(); }"
}
}
}
})

View File

@@ -8,7 +8,10 @@
package proguard.gradle.plugin.android.dsl
import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.file.shouldExist
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import java.io.File
import org.gradle.testkit.runner.TaskOutcome.SUCCESS
import testutils.AndroidProject
import testutils.applicationModule
@@ -82,6 +85,12 @@ class FlavorsConfigurationTest : FreeSpec({
"ProGuard should not be executed for non-matching build variants" {
buildResult.task(":app:transformClassesAndResourcesWithProguardTransformForFullDebug")?.outcome shouldBe null
}
"AAPT rules should be generated" - {
val aaptRules = File("${project.moduleBuildDir("app")}/intermediates/proguard/configs/aapt_rules.pro")
aaptRules.shouldExist()
aaptRules.readText() shouldContain "-keep class com.example.app.MainActivity { <init>(); }"
}
}
}
})