Skip to content

Commit

Permalink
implement gradle plugin to facilitate okhttp instrumentation
Browse files Browse the repository at this point in the history
  • Loading branch information
snowp committed Dec 4, 2024
1 parent 34dae78 commit fdf3623
Show file tree
Hide file tree
Showing 23 changed files with 870 additions and 11 deletions.
36 changes: 36 additions & 0 deletions platform/jvm/capture-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
plugins {
alias(libs.plugins.kotlin)

id("dependency-license-config")
id("java-gradle-plugin")
id("maven-publish")
id("com.vanniktech.maven.publish") version "0.28.0" apply false
}

dependencies {
compileOnly("com.android.tools.build:gradle:7.4.0")
compileOnly("org.ow2.asm:asm-commons:9.4")
compileOnly("org.ow2.asm:asm-util:9.4")
}

gradlePlugin {
plugins {
create("capturePlugin") {
id = "io.bitdrift.capture.capture-plugin"
implementationClass = "io.bitdrift.capture.CapturePlugin"
}
}
}

apply {
plugin("com.vanniktech.maven.publish")
}

publishing {
repositories {
mavenLocal()
}
}

group = "io.bitdrift.capture.capture-plugin"
version = "0.1.0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// capture-sdk - bitdrift's client SDK
// Copyright Bitdrift, Inc. All rights reserved.
//
// Use of this source code is governed by a source available license that can be found in the
// LICENSE file or at:
// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt

package io.bitdrift.capture

import com.android.build.api.instrumentation.AsmClassVisitorFactory
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.AndroidComponentsExtension
import com.android.build.api.variant.Variant
import io.bitdrift.capture.CapturePlugin.Companion.sep
import org.gradle.api.Project
import java.io.File

fun AndroidComponentsExtension<*, *, *>.configure(
project: Project,
extension: BitdriftPluginExtension,
) {
// temp folder for sentry-related stuff
val tmpDir = File("${project.buildDir}${sep}tmp${sep}sentry")
tmpDir.mkdirs()

onVariants { variant ->
if (extension.instrumentation.enabled.get()) {
variant.configureInstrumentation(
SpanAddingClassVisitorFactory::class.java,
InstrumentationScope.ALL,
FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS,
) { params ->
params.tmpDir.set(tmpDir)
params.debug.set(false)
}
}
}
}

private fun <T : InstrumentationParameters> Variant.configureInstrumentation(
classVisitorFactoryImplClass: Class<out AsmClassVisitorFactory<T>>,
scope: InstrumentationScope,
mode: FramesComputationMode,
instrumentationParamsConfig: (T) -> Unit,
) {
instrumentation.transformClassesWith(
classVisitorFactoryImplClass,
scope,
instrumentationParamsConfig
)
instrumentation.setAsmFramesComputationMode(mode)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// capture-sdk - bitdrift's client SDK
// Copyright Bitdrift, Inc. All rights reserved.
//
// Use of this source code is governed by a source available license that can be found in the
// LICENSE file or at:
// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt

package io.bitdrift.capture

import org.gradle.api.Project
import javax.inject.Inject

abstract class BitdriftPluginExtension @Inject constructor(project: Project) {
private val objects = project.objects

/**
* Configures the instrumentation feature.
*/
val instrumentation: InstrumentationExtension = objects.newInstance(InstrumentationExtension::class.java)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// capture-sdk - bitdrift's client SDK
// Copyright Bitdrift, Inc. All rights reserved.
//
// Use of this source code is governed by a source available license that can be found in the
// LICENSE file or at:
// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt

package io.bitdrift.capture

import com.android.build.api.variant.AndroidComponentsExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.slf4j.LoggerFactory
import java.io.File
import javax.inject.Inject

abstract class CapturePlugin @Inject constructor() : Plugin<Project> {
override fun apply(target: Project) {
val extension = target.extensions.create("bitdrift", BitdriftPluginExtension::class.java, target)

target.pluginManager.withPlugin("com.android.application") {
val androidComponentsExt =
target.extensions.getByType(AndroidComponentsExtension::class.java)

androidComponentsExt.configure(
target,
extension,
)


}
}

companion object {
internal val sep = File.separator

internal val logger by lazy {
LoggerFactory.getLogger(CapturePlugin::class.java)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// capture-sdk - bitdrift's client SDK
// Copyright Bitdrift, Inc. All rights reserved.
//
// Use of this source code is governed by a source available license that can be found in the
// LICENSE file or at:
// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt

package io.bitdrift.capture

import javax.inject.Inject
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.provider.SetProperty

enum class InstrumentationFeature(val integrationName: String) {
OKHTTP("OkHttpInstrumentation")
}

open class InstrumentationExtension @Inject constructor(objects: ObjectFactory) {

val enabled: Property<Boolean> = objects.property(Boolean::class.java)
.convention(true)

val features: SetProperty<InstrumentationFeature> = objects.setProperty(InstrumentationFeature::class.java)

val debug: Property<Boolean> = objects.property(Boolean::class.java).convention(
false
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// capture-sdk - bitdrift's client SDK
// Copyright Bitdrift, Inc. All rights reserved.
//
// Use of this source code is governed by a source available license that can be found in the
// LICENSE file or at:
// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt

package io.bitdrift.capture

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 io.bitdrift.capture.instrumentation.ClassInstrumentable
import io.bitdrift.capture.instrumentation.okhttp.OkHttpEventListener
import io.bitdrift.capture.instrumentation.toClassContext
import io.bitdrift.capture.instrumentation.util.findClassReader
import io.bitdrift.capture.instrumentation.util.findClassWriter
import io.bitdrift.capture.instrumentation.util.isMinifiedClass
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.objectweb.asm.ClassVisitor
import java.io.File

abstract class SpanAddingClassVisitorFactory : AsmClassVisitorFactory<SpanAddingClassVisitorFactory.SpanAddingParameters> {

interface SpanAddingParameters : InstrumentationParameters {
@get:Input
val debug: Property<Boolean>

@get:Internal
val tmpDir: Property<File>

@get:Internal
var _instrumentable: ClassInstrumentable?
}

private val instrumentable: ClassInstrumentable
get() {
val memoized = parameters.get()._instrumentable
if (memoized != null) {
return memoized
}

val instrumentable = ChainedInstrumentable(
listOfNotNull(
OkHttpEventListener()
)
)
CapturePlugin.logger.info(
"Instrumentable: $instrumentable"
)
parameters.get()._instrumentable = instrumentable
return instrumentable
}


override fun createClassVisitor(
classContext: ClassContext,
nextClassVisitor: ClassVisitor
): ClassVisitor {
val className = classContext.currentClassData.className

val classReader = nextClassVisitor.findClassWriter()?.findClassReader()
val isMinifiedClass = classReader?.isMinifiedClass() ?: false
if (isMinifiedClass) {
CapturePlugin.logger.info(
"$className skipped from instrumentation because it's a minified class."
)
return nextClassVisitor
}

return instrumentable.getVisitor(
classContext,
instrumentationContext.apiVersion.get(),
nextClassVisitor,
parameters = parameters.get()
)
}

override fun isInstrumentable(classData: ClassData): Boolean =
instrumentable.isInstrumentable(classData.toClassContext())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// capture-sdk - bitdrift's client SDK
// Copyright Bitdrift, Inc. All rights reserved.
//
// Use of this source code is governed by a source available license that can be found in the
// LICENSE file or at:
// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt

@file:Suppress("UnstableApiUsage")

package io.bitdrift.capture

import com.android.build.api.instrumentation.ClassContext
import io.bitdrift.capture.instrumentation.ClassInstrumentable
import java.util.LinkedList
import org.objectweb.asm.ClassVisitor

class ChainedInstrumentable(
private val instrumentables: List<ClassInstrumentable> = emptyList()
) : ClassInstrumentable {

override fun getVisitor(
instrumentableContext: ClassContext,
apiVersion: Int,
originalVisitor: ClassVisitor,
parameters: SpanAddingClassVisitorFactory.SpanAddingParameters
): ClassVisitor {
// build a chain of visitors in order they are provided
val queue = LinkedList(instrumentables)
var prevVisitor = originalVisitor
var visitor: ClassVisitor? = null
while (queue.isNotEmpty()) {
val instrumentable = queue.poll()

visitor = if (instrumentable.isInstrumentable(instrumentableContext)) {
instrumentable.getVisitor(
instrumentableContext,
apiVersion,
prevVisitor,
parameters
)
} else {
prevVisitor
}
prevVisitor = visitor
}
return visitor ?: originalVisitor
}

override fun isInstrumentable(data: ClassContext): Boolean =
instrumentables.any { it.isInstrumentable(data) }

override fun toString(): String {
return "ChainedInstrumentable(instrumentables=" +
"${instrumentables.joinToString(", ") { it.javaClass.simpleName }})"
}
}
Loading

0 comments on commit fdf3623

Please sign in to comment.