Skip to content

Commit

Permalink
Create SnapshotsPreviewRuntimeRetentionTransform to change Previews t…
Browse files Browse the repository at this point in the history
…o runtime retention (#139)
  • Loading branch information
rbro112 authored May 17, 2024
1 parent cac521c commit 3d02e12
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.emergetools.android.gradle

import com.android.build.api.artifact.SingleArtifact
import com.android.build.api.dsl.TestExtension
import com.android.build.api.instrumentation.InstrumentationScope
import com.android.build.api.variant.AndroidTest
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import com.android.build.api.variant.ApplicationVariant
Expand All @@ -11,6 +12,7 @@ import com.android.build.api.variant.Variant
import com.android.build.gradle.internal.dsl.BaseAppModuleExtension
import com.android.build.gradle.internal.tasks.factory.dependsOn
import com.android.build.gradle.internal.utils.KOTLIN_ANDROID_PLUGIN_ID
import com.emergetools.android.gradle.instrumentation.snapshots.SnapshotsPreviewRuntimeRetentionTransformFactory
import com.emergetools.android.gradle.tasks.internal.SaveExtensionConfigTask
import com.emergetools.android.gradle.tasks.perf.GeneratePerfProject
import com.emergetools.android.gradle.tasks.perf.LocalPerfTest
Expand Down Expand Up @@ -94,11 +96,14 @@ class EmergePlugin : Plugin<Project> {
appProject: Project,
emergeExtension: EmergePluginExtension,
) {
appProject.afterEvaluate {
configureAppProjectSnapshots(
appProject = appProject,
emergeExtension = emergeExtension
)
// Only configure KSP for snapshot generation if the experimental transform is disabled
if (!emergeExtension.snapshotOptions.experimentalTransformEnabled.getOrElse(false)) {
appProject.afterEvaluate {
configureAppProjectSnapshots(
appProject = appProject,
emergeExtension = emergeExtension
)
}
}

appProject.pluginManager.withPlugin(ANDROID_APPLICATION_PLUGIN_ID) { _ ->
Expand Down Expand Up @@ -300,6 +305,20 @@ class EmergePlugin : Plugin<Project> {
variant: ApplicationVariant,
androidTest: AndroidTest,
) {
if (extension.snapshotOptions.experimentalTransformEnabled.getOrElse(false)) {
variant.instrumentation.let { instrumentation ->
instrumentation.transformClassesWith(
SnapshotsPreviewRuntimeRetentionTransformFactory::class.java,
InstrumentationScope.ALL,
) { params ->
// Force invalidate/reinstrument classes if debug option is set
if (extension.debugOptions.forceInstrumentation.getOrElse(false)) {
params.invalidate.set(System.currentTimeMillis())
}
}
}
}

val snapshotPackageTask = registerSnapshotPackageTask(appProject, variant, androidTest)
registerSnapshotLocalTask(appProject, extension, variant, androidTest, snapshotPackageTask)
registerSnapshotUploadTask(appProject, extension, variant, snapshotPackageTask)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ abstract class EmergePluginExtension @Inject constructor(objects: ObjectFactory)
action.execute(vcsOptions)
}

@get:Nested
abstract val debugOptions: DebugOptions

fun debug(action: Action<DebugOptions>) {
action.execute(debugOptions)
}

/**
* Optional inputs.
*/
Expand Down Expand Up @@ -140,5 +147,11 @@ abstract class SnapshotOptions : ProductOptions() {

abstract val experimentalInternalSnapshotsEnabled: Property<Boolean>

abstract val experimentalTransformEnabled: Property<Boolean>

abstract val apiVersion: Property<Int>
}

abstract class DebugOptions : ProductOptions() {
abstract val forceInstrumentation: Property<Boolean>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.emergetools.android.gradle.instrumentation.snapshots

import com.android.build.api.instrumentation.AsmClassVisitorFactory
import com.android.build.api.instrumentation.ClassContext
import com.android.build.api.instrumentation.ClassData
import com.android.build.api.instrumentation.InstrumentationParameters
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.objectweb.asm.AnnotationVisitor
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor
import org.slf4j.Logger
import org.slf4j.LoggerFactory

abstract class SnapshotsPreviewRuntimeRetentionTransformFactory : AsmClassVisitorFactory<SnapshotsPreviewRuntimeRetentionTransformFactory.Params> {

interface Params : InstrumentationParameters {
@get:Input
val invalidate: Property<Long>
}

companion object {
private val logger by lazy {
LoggerFactory.getLogger(SnapshotsPreviewRuntimeRetentionTransformFactory::class.java)
}
}

override fun createClassVisitor(
classContext: ClassContext,
nextClassVisitor: ClassVisitor,
): ClassVisitor {
return SnapshotsPreviewRuntimeRetentionTransform(
instrumentationContext.apiVersion.get(),
nextClassVisitor,
logger,
)
}

override fun isInstrumentable(classData: ClassData): Boolean {
// Need to instrument all classes to ensure that the annotations are visible at runtime
return true
}
}

class SnapshotsPreviewRuntimeRetentionTransform(
api: Int,
classVisitor: ClassVisitor?,
private val logger: Logger,
) : ClassVisitor(api, classVisitor) {

companion object {
const val TAG = "SnapshotRuntimePreviewClassVisitor"

const val PREVIEW_ANNOTATION_DESC = "Landroidx/compose/ui/tooling/preview/Preview;"
const val PREVIEW_CONTAINER_ANNOTATION_DESC =
"Landroidx/compose/ui/tooling/preview/Preview\$Container;"
}

override fun visitMethod(
access: Int,
name: String,
descriptor: String,
signature: String?,
exceptions: Array<String>?,
): MethodVisitor {
val mv = super.visitMethod(access, name, descriptor, signature, exceptions)

return object : MethodVisitor(api, mv) {
override fun visitAnnotation(
desc: String,
visible: Boolean,
): AnnotationVisitor? {
if (desc == PREVIEW_ANNOTATION_DESC || desc == PREVIEW_CONTAINER_ANNOTATION_DESC) {
logger.info(
"$TAG: Modifying method annotation visible at runtime to true for annotation $desc $visible"
)

// Force the annotation to be visible at runtime
return super.visitAnnotation(desc, true)
}

return super.visitAnnotation(desc, visible)
}
}
}

override fun visitAnnotation(
desc: String,
visible: Boolean,
): AnnotationVisitor? {
if (desc == PREVIEW_ANNOTATION_DESC || desc == PREVIEW_CONTAINER_ANNOTATION_DESC) {
logger.info(
"$TAG: Modifying class annotation visible at runtime to true for annotation $desc $visible"
)

// Force the annotation to be visible at runtime
return super.visitAnnotation(desc, true)
}

return super.visitAnnotation(desc, visible)
}
}

0 comments on commit 3d02e12

Please sign in to comment.