Skip to content

Commit

Permalink
Generate proguard rules for implementations (#92)
Browse files Browse the repository at this point in the history
* Generate proguard rules for implementations

* Enable minification on fixture

* Remove `-if`s from proguard rules

We want to just keep everything
  • Loading branch information
dellisd authored May 31, 2023
1 parent abe700f commit 01ddfb9
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 58 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2023 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package app.cash.better.dynamic.features.codegen

import app.cash.better.dynamic.features.codegen.api.FeatureApi
import app.cash.better.dynamic.features.codegen.api.FeatureImplementation
import com.squareup.kotlinpoet.ClassName

fun generateProguardRules(
forApi: FeatureApi,
implementations: List<FeatureImplementation>,
): String = buildString {
val forApiClass = ClassName(forApi.packageName, forApi.className)
// Keep feature API interface itself
appendLine("-keepnames class ${forApiClass.canonicalName}")
// Keep generated implementations container looked up by reflection
appendLine("-keep class ${forApiClass.canonicalName}ImplementationsContainer { *; }")

// Keep each implementation class and its empty constructor
implementations.forEach { implementation ->
appendLine("-keep class ${implementation.qualifiedName} {")
appendLine(" public <init>();")
appendLine("}")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -593,9 +593,11 @@ class BetterDynamicFeaturesPlugin : Plugin<Project> {

task.featureImplementationReports.setFrom(featureReports.artifacts.artifactFiles)
task.generatedFilesDirectory.set(project.buildDir.resolve("betterDynamicFeatures/generatedImplementations/${androidVariant.name}"))
task.generatedProguardFile.set(project.buildDir.resolve("betterDynamicFeatures/generatedProguard/betterDynamicFeatures-${androidVariant.name}.pro"))

task.group = GROUP
}
androidVariant.proguardFiles.add(implementationsTask.flatMap { it.generatedProguardFile })

val processTask = tasks.register(
taskName("compile", androidVariant, "Implementations"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,19 @@ import okio.source
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction

abstract class TypesafeImplementationsGeneratorTask : DefaultTask() {
@get:InputFiles
abstract val featureImplementationReports: ConfigurableFileCollection

@get:OutputFile
abstract val generatedProguardFile: RegularFileProperty

@get:OutputDirectory
abstract val generatedFilesDirectory: DirectoryProperty

Expand All @@ -52,5 +57,16 @@ abstract class TypesafeImplementationsGeneratorTask : DefaultTask() {
generateImplementationsContainer(forApi = api, implementations = implementations)
fileSpec.writeTo(directory = generatedSourcesDirectory)
}

val proguardFile = generatedProguardFile.asFile.get()
if (proguardFile.exists()) {
proguardFile.delete()
proguardFile.createNewFile()
}
proguardFile.bufferedWriter().use { writer ->
collectedFeatures.forEach { (api, implementations) ->
writer.append(generateProguardRules(forApi = api, implementations))
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ android {
minSdk 24
}

buildTypes {
release {
minifyEnabled true
}
}

dynamicFeatures = [':feature']
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,47 +26,7 @@ class CodegenIntegrationTests {
@Test
fun `codegen generates code correctly`() {
val integrationRoot = File("src/test/fixtures/codegen-fixture")
integrationRoot.resolve("base/gradle.lockfile").writeText(
"""
|# This is a Gradle generated file for dependency locking.
|# Manual edits can break the build and are not advised.
|# This file is expected to be part of source control.
|androidx.activity:activity:1.0.0=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|androidx.annotation:annotation:1.1.0=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|androidx.arch.core:core-common:2.1.0=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|androidx.arch.core:core-runtime:2.0.0=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|androidx.collection:collection:1.1.0=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|androidx.core:core:1.2.0=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|androidx.customview:customview:1.0.0=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|androidx.fragment:fragment:1.1.0=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|androidx.lifecycle:lifecycle-common:2.1.0=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|androidx.lifecycle:lifecycle-livedata-core:2.0.0=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|androidx.lifecycle:lifecycle-livedata:2.0.0=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|androidx.lifecycle:lifecycle-runtime:2.1.0=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|androidx.lifecycle:lifecycle-viewmodel:2.1.0=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|androidx.loader:loader:1.0.0=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|androidx.savedstate:savedstate:1.0.0=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|androidx.versionedparcelable:versionedparcelable:1.1.0=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|androidx.viewpager:viewpager:1.0.0=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|com.google.android.gms:play-services-basement:18.1.0=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|com.google.android.gms:play-services-tasks:18.0.2=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|com.google.android.play:core-common:2.0.3=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|com.google.android.play:feature-delivery-ktx:2.1.0=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|com.google.android.play:feature-delivery:2.1.0=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|com.squareup.okhttp3:okhttp:4.11.0=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|com.squareup.okio:okio-jvm:3.2.0=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|com.squareup.okio:okio:3.2.0=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|org.jetbrains.kotlin:kotlin-stdlib-common:$KOTLIN_VERSION=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|org.jetbrains.kotlin:kotlin-stdlib-jdk7:$KOTLIN_VERSION=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|org.jetbrains.kotlin:kotlin-stdlib-jdk8:$KOTLIN_VERSION=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|org.jetbrains.kotlin:kotlin-stdlib:$KOTLIN_VERSION=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|org.jetbrains:annotations:13.0=debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|empty=
""".trimMargin(),
)
writeCommonLockfile(integrationRoot)

val gradleRunner = GradleRunner.create()
.withCommonConfiguration(integrationRoot)
Expand Down Expand Up @@ -103,7 +63,53 @@ class CodegenIntegrationTests {
@Test
fun `generated classes are in dexfiles`() {
val integrationRoot = File("src/test/fixtures/codegen-fixture")
integrationRoot.resolve("base/gradle.lockfile").writeText(
writeCommonLockfile(integrationRoot)

val gradleRunner = GradleRunner.create()
.withCommonConfiguration(integrationRoot)
.withDebug(true)
.withArguments("clean", ":base:packageDebugUniversalApk")

gradleRunner.build()

val dexContent = dexdump(integrationRoot.resolve("base/build/outputs/apk_from_bundle/debug/base-debug-universal.apk"))
assertThat(dexContent.bufferedReader().lineSequence()).containsInConsecutiveOrder(
""" Class descriptor : 'LExampleFeatureImplementationsContainer;'""",
""" Access flags : 0x0011 (PUBLIC FINAL)""",
""" Superclass : 'Ljava/lang/Object;'""",
""" Interfaces -""",
""" #0 : 'Lapp/cash/better/dynamic/features/ImplementationsContainer;'""",
)
}

@Test
fun `proguard rules for generated code are generated`() {
val integrationRoot = File("src/test/fixtures/codegen-fixture")
writeCommonLockfile(integrationRoot)

val gradleRunner = GradleRunner.create()
.withCommonConfiguration(integrationRoot)
.withDebug(true)
.withArguments("clean", ":base:bundleRelease")

gradleRunner.build()

val proguardFile = integrationRoot.resolve("base/build/betterDynamicFeatures/generatedProguard/betterDynamicFeatures-release.pro")
assertThat(proguardFile.exists()).isTrue()
assertThat(proguardFile.readText()).isEqualTo(
"""
|-keepnames class ExampleFeature
|-keep class ExampleFeatureImplementationsContainer { *; }
|-keep class ExampleImplementation {
| public <init>();
|}
|
""".trimMargin(),
)
}

private fun writeCommonLockfile(projectRoot: File) {
projectRoot.resolve("base/gradle.lockfile").writeText(
"""
|# This is a Gradle generated file for dependency locking.
|# Manual edits can break the build and are not advised.
Expand Down Expand Up @@ -144,21 +150,5 @@ class CodegenIntegrationTests {
|empty=
""".trimMargin(),
)

val gradleRunner = GradleRunner.create()
.withCommonConfiguration(integrationRoot)
.withDebug(true)
.withArguments("clean", ":base:packageDebugUniversalApk")

gradleRunner.build()

val dexContent = dexdump(integrationRoot.resolve("base/build/outputs/apk_from_bundle/debug/base-debug-universal.apk"))
assertThat(dexContent.bufferedReader().lineSequence()).containsInConsecutiveOrder(
""" Class descriptor : 'LExampleFeatureImplementationsContainer;'""",
""" Access flags : 0x0011 (PUBLIC FINAL)""",
""" Superclass : 'Ljava/lang/Object;'""",
""" Interfaces -""",
""" #0 : 'Lapp/cash/better/dynamic/features/ImplementationsContainer;'""",
)
}
}
6 changes: 6 additions & 0 deletions sample/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ android {
versionName "1.0"
}

buildTypes {
release {
minifyEnabled true
}
}

buildFeatures {
compose true
}
Expand Down

0 comments on commit 01ddfb9

Please sign in to comment.