Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add auto-installation for sentry-android SDK and integrations #282

Merged
merged 18 commits into from Mar 9, 2022
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 15 additions & 0 deletions buildSrc/src/main/java/Dependencies.kt
Expand Up @@ -60,4 +60,19 @@ object Samples {
const val compiler = "androidx.room:room-compiler:${version}"
const val rxjava = "androidx.room:room-rxjava2:${version}"
}

object OkHttp {
private const val version = "4.9.3"
romtsn marked this conversation as resolved.
Show resolved Hide resolved
const val okhttp = "com.squareup.okhttp3:okhttp:${version}"
}

object Timber {
private const val version = "5.0.1"
marandaneto marked this conversation as resolved.
Show resolved Hide resolved
const val timber = "com.jakewharton.timber:timber:${version}"
}

object Fragment {
private const val version = "1.3.5"
const val fragmentKtx = "androidx.fragment:fragment-ktx:${version}"
}
}
4 changes: 2 additions & 2 deletions examples/android-instrumentation-sample/build.gradle.kts
Expand Up @@ -50,8 +50,6 @@ android {
// }

dependencies {
implementation(Libs.SENTRY_ANDROID)

implementation(Samples.AndroidX.recyclerView)
implementation(Samples.AndroidX.lifecycle)
implementation(Samples.AndroidX.appcompat)
Expand All @@ -63,6 +61,8 @@ dependencies {
implementation(Samples.Room.ktx)
implementation(Samples.Room.rxjava)

implementation(Samples.Timber.timber)
implementation(Samples.Fragment.fragmentKtx)
implementation(project(":examples:android-room-lib"))

kapt(Samples.Room.compiler)
Expand Down
4 changes: 4 additions & 0 deletions examples/android-room-lib/build.gradle.kts
Expand Up @@ -21,4 +21,8 @@ dependencies {

implementation(Samples.Room.runtime)
implementation(Samples.Room.ktx)

// this is here for test purposes, to ensure that transitive dependencies are also recognized
// by our auto-installation
implementation(Samples.OkHttp.okhttp)
}
Expand Up @@ -16,6 +16,8 @@ import io.sentry.android.gradle.SentryTasksProvider.getPackageBundleTask
import io.sentry.android.gradle.SentryTasksProvider.getPackageProvider
import io.sentry.android.gradle.SentryTasksProvider.getPreBundleTask
import io.sentry.android.gradle.SentryTasksProvider.getTransformerTask
import io.sentry.android.gradle.autoinstall.installDependencies
import io.sentry.android.gradle.extensions.SentryPluginExtension
import io.sentry.android.gradle.instrumentation.SpanAddingClassVisitorFactory
import io.sentry.android.gradle.services.SentrySdkStateHolder
import io.sentry.android.gradle.tasks.SentryGenerateProguardUuidTask
Expand Down Expand Up @@ -290,6 +292,8 @@ class SentryPlugin : Plugin<Project> {
project.logger.info { "uploadSentryNativeSymbols won't be executed" }
}
}

project.installDependencies(extension)
}
}

Expand All @@ -307,6 +311,7 @@ class SentryPlugin : Plugin<Project> {
companion object {
const val SENTRY_ORG_PARAMETER = "sentryOrg"
const val SENTRY_PROJECT_PARAMETER = "sentryProject"
internal const val SENTRY_SDK_VERSION = "5.6.1"
romtsn marked this conversation as resolved.
Show resolved Hide resolved

internal val sep = File.separator

Expand Down
@@ -0,0 +1,76 @@
package io.sentry.android.gradle.autoinstall

import io.sentry.android.gradle.autoinstall.fragment.FragmentInstallStrategy
import io.sentry.android.gradle.autoinstall.fragment.FragmentInstallStrategy.Registrar.SENTRY_FRAGMENT_ID
import io.sentry.android.gradle.autoinstall.okhttp.OkHttpInstallStrategy
import io.sentry.android.gradle.autoinstall.okhttp.OkHttpInstallStrategy.Registrar.SENTRY_OKHTTP_ID
import io.sentry.android.gradle.autoinstall.timber.TimberInstallStrategy
import io.sentry.android.gradle.autoinstall.timber.TimberInstallStrategy.Registrar.SENTRY_TIMBER_ID
import io.sentry.android.gradle.extensions.SentryPluginExtension
import io.sentry.android.gradle.util.info
import org.gradle.api.Project
import org.gradle.api.artifacts.DependencySet

internal const val SENTRY_GROUP = "io.sentry"
private const val SENTRY_ANDROID_ID = "sentry-android"

private val strategies = listOf(
OkHttpInstallStrategy.Registrar,
TimberInstallStrategy.Registrar,
FragmentInstallStrategy.Registrar
)

fun Project.installDependencies(extension: SentryPluginExtension) {
val autoInstallState = AutoInstallState.register(project)
configurations.named("implementation").configure { configuration ->
marandaneto marked this conversation as resolved.
Show resolved Hide resolved
configuration.withDependencies { dependencies ->
// if autoInstallation is disabled, the autoInstallState will contain initial values
// which all default to false, hence, the integrations won't be installed as well
if (extension.autoInstallation.enabled.get()) {
var sentryVersion = dependencies.findSentryAndroidVersion()
sentryVersion = installSentrySdk(sentryVersion, dependencies, extension)

val installOkHttp = !dependencies.isModuleAvailable(SENTRY_OKHTTP_ID)
val installTimber = !dependencies.isModuleAvailable(SENTRY_TIMBER_ID)
val installFragment = !dependencies.isModuleAvailable(SENTRY_FRAGMENT_ID)
autoInstallState.get().apply {
this.sentryVersion = sentryVersion
this.installOkHttp = installOkHttp
this.installFragment = installFragment
this.installTimber = installTimber
}
}
}
}
project.dependencies.components { component ->
strategies.forEach { it.register(component, autoInstallState) }
}
}

private fun Project.installSentrySdk(
sentryVersion: String?,
dependencies: DependencySet,
extension: SentryPluginExtension
): String {
return if (sentryVersion == null) {
val userDefinedVersion = extension.autoInstallation.sentryVersion.get()
val sentryAndroidDep =
this.dependencies.create("$SENTRY_GROUP:$SENTRY_ANDROID_ID:$userDefinedVersion")
dependencies.add(sentryAndroidDep)
logger.info {
"sentry-android is successfully installed with version: $userDefinedVersion"
romtsn marked this conversation as resolved.
Show resolved Hide resolved
}
userDefinedVersion
} else {
logger.info {
"sentry-android won't be installed because it was already installed directly"
}
sentryVersion
}
}

private fun DependencySet.findSentryAndroidVersion(): String? =
find { it.group == SENTRY_GROUP && it.name == SENTRY_ANDROID_ID }?.version
romtsn marked this conversation as resolved.
Show resolved Hide resolved

private fun DependencySet.isModuleAvailable(id: String): Boolean =
any { it.group == SENTRY_GROUP && it.name == id }
@@ -0,0 +1,49 @@
@file:Suppress("UnstableApiUsage")
romtsn marked this conversation as resolved.
Show resolved Hide resolved

package io.sentry.android.gradle.autoinstall

import io.sentry.android.gradle.SentryPlugin.Companion.SENTRY_SDK_VERSION
import io.sentry.android.gradle.util.getBuildServiceName
import java.io.Serializable
import org.gradle.api.Project
import org.gradle.api.provider.Provider
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters

abstract class AutoInstallState :
BuildService<BuildServiceParameters.None>,
AutoCloseable,
Serializable {

@get:Synchronized
@set:Synchronized
var sentryVersion: String = SENTRY_SDK_VERSION

@get:Synchronized
@set:Synchronized
var installOkHttp: Boolean = false

@get:Synchronized
@set:Synchronized
var installFragment: Boolean = false

@get:Synchronized
@set:Synchronized
var installTimber: Boolean = false

override fun close() {
sentryVersion = SENTRY_SDK_VERSION
installTimber = false
installFragment = false
installOkHttp = false
}

companion object {
fun register(project: Project): Provider<AutoInstallState> {
return project.gradle.sharedServices.registerIfAbsent(
getBuildServiceName(AutoInstallState::class.java),
AutoInstallState::class.java
) {}
}
}
}
@@ -0,0 +1,8 @@
package io.sentry.android.gradle.autoinstall

import org.gradle.api.artifacts.dsl.ComponentMetadataHandler
import org.gradle.api.provider.Provider

interface InstallStrategyRegistrar {
fun register(component: ComponentMetadataHandler, autoInstallState: Provider<AutoInstallState>)
}
@@ -0,0 +1,66 @@
package io.sentry.android.gradle.autoinstall.fragment

import io.sentry.android.gradle.SentryPlugin
import io.sentry.android.gradle.autoinstall.AutoInstallState
import io.sentry.android.gradle.autoinstall.InstallStrategyRegistrar
import io.sentry.android.gradle.autoinstall.SENTRY_GROUP
import io.sentry.android.gradle.util.info
import javax.inject.Inject
import org.gradle.api.artifacts.ComponentMetadataContext
import org.gradle.api.artifacts.ComponentMetadataRule
import org.gradle.api.artifacts.dsl.ComponentMetadataHandler
import org.gradle.api.provider.Provider
import org.slf4j.Logger

// @CacheableRule
abstract class FragmentInstallStrategy @Inject constructor(
private val autoInstallState: Provider<AutoInstallState>
) : ComponentMetadataRule {

private var logger: Logger = SentryPlugin.logger

constructor(
autoInstallState: Provider<AutoInstallState>,
logger: Logger
) : this(autoInstallState) {
this.logger = logger
}

override fun execute(context: ComponentMetadataContext) {
if (!autoInstallState.get().installFragment) {
logger.info {
"$SENTRY_FRAGMENT_ID won't be installed because it was already installed directly"
}
return
}

context.details.allVariants { metadata ->
metadata.withDependencies { dependencies ->
val sentryVersion = autoInstallState.get().sentryVersion
dependencies.add("$SENTRY_GROUP:$SENTRY_FRAGMENT_ID:$sentryVersion")

logger.info {
"$SENTRY_FRAGMENT_ID is successfully installed with version: $sentryVersion"
romtsn marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}

companion object Registrar : InstallStrategyRegistrar {
private const val FRAGMENT_GROUP = "androidx.fragment"
private const val FRAGMENT_ID = "fragment"
internal const val SENTRY_FRAGMENT_ID = "sentry-android-fragment"

override fun register(
component: ComponentMetadataHandler,
autoInstallState: Provider<AutoInstallState>
) {
component.withModule(
"$FRAGMENT_GROUP:$FRAGMENT_ID",
FragmentInstallStrategy::class.java
) {
it.params(autoInstallState)
}
}
}
}
@@ -0,0 +1,75 @@
package io.sentry.android.gradle.autoinstall.okhttp

import io.sentry.android.gradle.SentryPlugin
import io.sentry.android.gradle.autoinstall.AutoInstallState
import io.sentry.android.gradle.autoinstall.InstallStrategyRegistrar
import io.sentry.android.gradle.autoinstall.SENTRY_GROUP
import io.sentry.android.gradle.util.SemVer
import io.sentry.android.gradle.util.info
import io.sentry.android.gradle.util.warn
import javax.inject.Inject
import org.gradle.api.artifacts.ComponentMetadataContext
import org.gradle.api.artifacts.ComponentMetadataRule
import org.gradle.api.artifacts.dsl.ComponentMetadataHandler
import org.gradle.api.provider.Provider
import org.slf4j.Logger

// @CacheableRule
abstract class OkHttpInstallStrategy @Inject constructor(
private val autoInstallState: Provider<AutoInstallState>
) : ComponentMetadataRule {

private var logger: Logger = SentryPlugin.logger

constructor(
autoInstallState: Provider<AutoInstallState>,
logger: Logger
) : this(autoInstallState) {
this.logger = logger
}

override fun execute(context: ComponentMetadataContext) {
romtsn marked this conversation as resolved.
Show resolved Hide resolved
if (!autoInstallState.get().installOkHttp) {
logger.info {
"sentry-android-okhttp won't be installed because it was already installed directly"
}
return
}
val semVer = SemVer.parse(context.details.id.version)
if (semVer < MIN_SUPPORTED_VERSION) {
logger.warn {
"$SENTRY_OKHTTP_ID won't be installed because the current okhttp version is " +
"lower than the minimum supported version ($MIN_SUPPORTED_VERSION)"
}
return
}

context.details.allVariants { metadata ->
metadata.withDependencies { dependencies ->
val sentryVersion = autoInstallState.get().sentryVersion
dependencies.add("$SENTRY_GROUP:$SENTRY_OKHTTP_ID:$sentryVersion")

logger.info {
"$SENTRY_OKHTTP_ID is successfully installed with version: $sentryVersion"
romtsn marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}

companion object Registrar : InstallStrategyRegistrar {
private const val OKHTTP_GROUP = "com.squareup.okhttp3"
private const val OKHTTP_ID = "okhttp"
internal const val SENTRY_OKHTTP_ID = "sentry-android-okhttp"

private val MIN_SUPPORTED_VERSION = SemVer(3, 13, 0)

override fun register(
component: ComponentMetadataHandler,
autoInstallState: Provider<AutoInstallState>
) {
component.withModule("$OKHTTP_GROUP:$OKHTTP_ID", OkHttpInstallStrategy::class.java) {
it.params(autoInstallState)
}
}
}
}