Skip to content

Commit

Permalink
Change onReceive marker from field to annotation and read it with AGP…
Browse files Browse the repository at this point in the history
…'s ClassContext

RELNOTES=N/A
PiperOrigin-RevId: 582018651
  • Loading branch information
kuanyingchou authored and Dagger Team committed Nov 13, 2023
1 parent 6278d31 commit f946e34
Show file tree
Hide file tree
Showing 16 changed files with 63 additions and 184 deletions.
1 change: 1 addition & 0 deletions java/dagger/hilt/android/internal/BUILD
Expand Up @@ -21,6 +21,7 @@ android_library(
name = "internal",
srcs = [
"Contexts.java",
"OnReceiveBytecodeInjectionMarker.java",
"ThreadUtil.java",
],
)
Expand Down
@@ -0,0 +1,31 @@
/*
* Copyright (C) 2023 The Dagger Authors.
*
* 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 dagger.hilt.android.internal;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* The marker annotation used to denote that we need to inject super.onReceive() call
* to the @AndroidEntryPoint-annotated BroadcastReceiver's onReceive() method.
*/
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface OnReceiveBytecodeInjectionMarker { }

Expand Up @@ -21,8 +21,6 @@ import com.android.build.api.instrumentation.FramesComputationMode
import com.android.build.api.instrumentation.InstrumentationParameters
import com.android.build.api.instrumentation.InstrumentationScope
import com.android.build.api.variant.Component
import java.io.File
import org.gradle.api.Project

internal class ComponentCompatApi70Impl(private val component: Component) : ComponentCompat() {

Expand All @@ -42,7 +40,4 @@ internal class ComponentCompatApi70Impl(private val component: Component) : Comp
override fun setAsmFramesComputationMode(mode: FramesComputationMode) {
component.setAsmFramesComputationMode(mode)
}

override fun getJavaCompileClassesDir(project: Project): File =
project.layout.buildDirectory.dir("intermediates/javac/${component.name}/classes").get().asFile
}
Expand Up @@ -21,8 +21,6 @@ import com.android.build.api.instrumentation.FramesComputationMode
import com.android.build.api.instrumentation.InstrumentationParameters
import com.android.build.api.instrumentation.InstrumentationScope
import com.android.build.api.variant.Component
import java.io.File
import org.gradle.api.Project

internal class ComponentCompatApi71Impl(private val component: Component) : ComponentCompat() {

Expand All @@ -40,7 +38,4 @@ internal class ComponentCompatApi71Impl(private val component: Component) : Comp
override fun setAsmFramesComputationMode(mode: FramesComputationMode) {
component.setAsmFramesComputationMode(mode)
}

override fun getJavaCompileClassesDir(project: Project): File =
project.layout.buildDirectory.dir("intermediates/javac/${component.name}/classes").get().asFile
}
Expand Up @@ -21,8 +21,6 @@ import com.android.build.api.instrumentation.FramesComputationMode
import com.android.build.api.instrumentation.InstrumentationParameters
import com.android.build.api.instrumentation.InstrumentationScope
import com.android.build.api.variant.Component
import java.io.File
import org.gradle.api.Project

internal class ComponentCompatApi72Impl(private val component: Component) : ComponentCompat() {

Expand All @@ -44,7 +42,4 @@ internal class ComponentCompatApi72Impl(private val component: Component) : Comp
override fun setAsmFramesComputationMode(mode: FramesComputationMode) {
component.instrumentation.setAsmFramesComputationMode(mode)
}

override fun getJavaCompileClassesDir(project: Project): File =
project.layout.buildDirectory.dir("intermediates/javac/${component.name}/classes").get().asFile
}
15 changes: 0 additions & 15 deletions java/dagger/hilt/android/plugin/agp-wrapper-8-3/build.gradle

This file was deleted.

This file was deleted.

This file was deleted.

Expand Up @@ -13,7 +13,6 @@ dependencies {
implementation project(':agp-wrapper-7-0')
implementation project(':agp-wrapper-7-1')
implementation project(':agp-wrapper-7-2')
implementation project(':agp-wrapper-8-3')
compileOnly gradleApi()
compileOnly "com.android.tools.build:gradle:$agp_version"

Expand Down
Expand Up @@ -21,9 +21,6 @@ import org.gradle.api.Project
fun getAndroidComponentsExtension(project: Project): AndroidComponentsExtensionCompat {
val version = SimpleAGPVersion.ANDROID_GRADLE_PLUGIN_VERSION
return when {
version >= SimpleAGPVersion(8, 3) -> {
AndroidComponentsExtensionCompatApi83Impl(project)
}
version >= SimpleAGPVersion(7, 2) -> {
AndroidComponentsExtensionCompatApi72Impl(project)
}
Expand Down
Expand Up @@ -42,7 +42,4 @@ abstract class ComponentCompat {

/** Redeclaration of [com.android.build.api.variant.Component.setAsmFramesComputationMode] */
abstract fun setAsmFramesComputationMode(mode: FramesComputationMode)

/** Return the directory that contains the classes from JavaCompile task */
abstract fun getJavaCompileClassesDir(project: Project): File
}
Expand Up @@ -36,23 +36,18 @@ import org.objectweb.asm.Opcodes
class AndroidEntryPointClassVisitor(
private val apiVersion: Int,
nextClassVisitor: ClassVisitor,
private val additionalClasses: File
private val classContext: ClassContext
) : ClassVisitor(apiVersion, nextClassVisitor) {

interface AndroidEntryPointParams : InstrumentationParameters {
@get:Internal
val additionalClassesDir: Property<File>
}

abstract class Factory : AsmClassVisitorFactory<AndroidEntryPointParams> {
abstract class Factory : AsmClassVisitorFactory<InstrumentationParameters.None> {
override fun createClassVisitor(
classContext: ClassContext,
nextClassVisitor: ClassVisitor
): ClassVisitor {
return AndroidEntryPointClassVisitor(
apiVersion = instrumentationContext.apiVersion.get(),
nextClassVisitor = nextClassVisitor,
additionalClasses = parameters.get().additionalClassesDir.get()
classContext = classContext
)
}

Expand Down Expand Up @@ -198,34 +193,21 @@ class AndroidEntryPointClassVisitor(
}

/**
* Check if Hilt generated class is a BroadcastReceiver with the marker field which means
* Check if Hilt generated class is a BroadcastReceiver with the marker annotation which means
* a super.onReceive invocation has to be inserted in the implementation.
*/
private fun hasOnReceiveBytecodeInjectionMarker() =
findAdditionalClassFile(newSuperclassName).inputStream().use {
var hasMarker = false
ClassReader(it).accept(
object : ClassVisitor(apiVersion) {
override fun visitField(
access: Int,
name: String,
descriptor: String,
signature: String?,
value: Any?
): FieldVisitor? {
if (name == "onReceiveBytecodeInjectionMarker") {
hasMarker = true
}
return null
}
},
ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES
)
return@use hasMarker
}
private fun hasOnReceiveBytecodeInjectionMarker(): Boolean {
val newSuperclassFQName = newSuperclassName.toFQName()
return classContext.loadClassData(newSuperclassFQName)
?.classAnnotations?.contains(ON_RECEIVE_MARKER_ANNOTATION)
?: error("Cannot load class $newSuperclassFQName!")
}

private fun findAdditionalClassFile(className: String) =
File(additionalClasses, "$className.class")
/**
* Return a fully qualified name from an internal name.
* See https://asm.ow2.io/javadoc/org/objectweb/asm/Type.html#getInternalName()
*/
private fun String.toFQName() = this.replace('/', '.')

companion object {
val ANDROID_ENTRY_POINT_ANNOTATIONS = setOf(
Expand All @@ -235,5 +217,6 @@ class AndroidEntryPointClassVisitor(
const val ON_RECEIVE_METHOD_NAME = "onReceive"
const val ON_RECEIVE_METHOD_DESCRIPTOR =
"(Landroid/content/Context;Landroid/content/Intent;)V"
const val ON_RECEIVE_MARKER_ANNOTATION = "dagger.hilt.android.internal.OnReceiveBytecodeInjectionMarker"
}
}
Expand Up @@ -245,11 +245,9 @@ class HiltGradlePlugin @Inject constructor(
fun registerTransform(androidComponent: ComponentCompat) {
androidComponent.transformClassesWith(
classVisitorFactoryImplClass = AndroidEntryPointClassVisitor.Factory::class.java,
scope = InstrumentationScope.PROJECT
) { params ->
val classesDir = androidComponent.getJavaCompileClassesDir(project)
params.additionalClassesDir.set(classesDir)
}
scope = InstrumentationScope.PROJECT,
instrumentationParamsConfig = {}
)
androidComponent.setAsmFramesComputationMode(
FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS
)
Expand Down
2 changes: 1 addition & 1 deletion java/dagger/hilt/android/plugin/settings.gradle
Expand Up @@ -21,4 +21,4 @@ include ':agp-wrapper-impl'
include ':agp-wrapper-7-0'
include ':agp-wrapper-7-1'
include ':agp-wrapper-7-2'
include ':agp-wrapper-8-3'

Expand Up @@ -30,13 +30,12 @@
import androidx.room.compiler.processing.XTypeParameterElement;
import com.google.common.collect.ImmutableSet;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import dagger.hilt.android.processor.internal.AndroidClassNames;
import dagger.hilt.processor.internal.ClassNames;
import dagger.hilt.processor.internal.Processors;
import dagger.internal.codegen.xprocessing.XExecutableTypes;
import java.io.IOException;
Expand Down Expand Up @@ -69,6 +68,13 @@ public void generate() throws IOException {
.addModifiers(metadata.generatedClassModifiers())
.addMethod(onReceiveMethod());

// Add an annotation used as a marker to let the bytecode injector know this receiver
// will need to be injected with a super.onReceive call. This is only necessary if no concrete
// onReceive call is implemented in any of the super classes.
if (metadata.requiresBytecodeInjection() && !isOnReceiveImplemented(metadata.baseElement())) {
builder.addAnnotation(ClassNames.ON_RECEIVE_BYTECODE_INJECTION_MARKER);
}

JavaPoetExtKt.addOriginatingElement(builder, metadata.element());
Generators.addGeneratedBaseClassJavadoc(builder, AndroidClassNames.ANDROID_ENTRY_POINT);
Processors.addGeneratedAnnotation(builder, env, getClass());
Expand All @@ -82,20 +88,6 @@ public void generate() throws IOException {
Generators.copyLintAnnotations(metadata.element(), builder);
Generators.copySuppressAnnotations(metadata.element(), builder);

// Add an unused field used as a marker to let the bytecode injector know this receiver will
// need to be injected with a super.onReceive call. This is only necessary if no concrete
// onReceive call is implemented in any of the super classes.
if (metadata.requiresBytecodeInjection() && !isOnReceiveImplemented(metadata.baseElement())) {
builder.addField(
FieldSpec.builder(
TypeName.BOOLEAN,
"onReceiveBytecodeInjectionMarker",
Modifier.PRIVATE,
Modifier.FINAL)
.initializer("false")
.build());
}

env.getFiler()
.write(
JavaFile.builder(generatedClassName.packageName(), builder.build()).build(),
Expand Down
3 changes: 3 additions & 0 deletions java/dagger/hilt/processor/internal/ClassNames.java
Expand Up @@ -215,6 +215,9 @@ public final class ClassNames {
public static final ClassName SUPPRESS_WARNINGS = get("java.lang", "SuppressWarnings");
public static final ClassName KOTLIN_SUPPRESS = get("kotlin", "Suppress");

public static final ClassName ON_RECEIVE_BYTECODE_INJECTION_MARKER =
get("dagger.hilt.android.internal", "OnReceiveBytecodeInjectionMarker");

// Kotlin-specific class names
public static final ClassName KOTLIN_METADATA = get("kotlin", "Metadata");

Expand Down

0 comments on commit f946e34

Please sign in to comment.