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

Populate events with dependencies metadata #396

Merged
merged 13 commits into from
Nov 2, 2022
Merged
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Features

- Populate events with dependencies metadata ([#396](https://github.com/getsentry/sentry-android-gradle-plugin/pull/396))

### Fixes

- Ignore minified classes from any instrumentation ([#389](https://github.com/getsentry/sentry-android-gradle-plugin/pull/389))
Expand Down
21 changes: 21 additions & 0 deletions buildSrc/src/main/java/Dependencies.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import BuildPluginsVersion.springBootVersion
import org.gradle.util.VersionNumber

object BuildPluginsVersion {
Expand All @@ -10,6 +11,10 @@ object BuildPluginsVersion {
// build/publications/maven
const val MAVEN_PUBLISH = "0.17.0"
const val PROGUARD = "7.1.0"

val springBootVersion = "2.7.4"
romtsn marked this conversation as resolved.
Show resolved Hide resolved
val springDependencyManagementVersion = "1.0.11.RELEASE"

// proguard does not support AGP 8 yet
fun isProguardApplicable(): Boolean = VersionNumber.parse(AGP).major < 8
}
Expand Down Expand Up @@ -83,4 +88,20 @@ object Samples {
private const val version = "1.3.5"
const val fragmentKtx = "androidx.fragment:fragment-ktx:${version}"
}

object SpringBoot {
val springBoot = "org.springframework.boot"
val springDependencyManagement = "io.spring.dependency-management"
val springBootStarter = "org.springframework.boot:spring-boot-starter:$springBootVersion"
val springBootStarterTest = "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
val springBootStarterWeb = "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
val springBootStarterWebflux = "org.springframework.boot:spring-boot-starter-webflux:$springBootVersion"
val springBootStarterAop = "org.springframework.boot:spring-boot-starter-aop:$springBootVersion"
val springBootStarterSecurity = "org.springframework.boot:spring-boot-starter-security:$springBootVersion"
val springBootStarterJdbc = "org.springframework.boot:spring-boot-starter-jdbc:$springBootVersion"
val hsqldb = "org.hsqldb:hsqldb:2.6.1"
val aspectj = "org.aspectj:aspectjweaver"
val kotlinReflect = "org.jetbrains.kotlin:kotlin-reflect"
val kotlinStdLib = "stdlib-jdk8"
}
}
51 changes: 51 additions & 0 deletions examples/spring-boot-sample/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import org.jetbrains.kotlin.config.KotlinCompilerVersion
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
id(Samples.SpringBoot.springBoot) version BuildPluginsVersion.springBootVersion
id(Samples.SpringBoot.springDependencyManagement) version BuildPluginsVersion.springDependencyManagementVersion
kotlin("jvm")
kotlin("plugin.spring") version BuildPluginsVersion.KOTLIN
id("io.sentry.android.gradle")
}

group = "io.sentry.samples.spring-boot"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_1_8
java.targetCompatibility = JavaVersion.VERSION_1_8

repositories {
mavenCentral()
}

dependencies {
implementation(Samples.SpringBoot.springBootStarterSecurity)
implementation(Samples.SpringBoot.springBootStarterWeb)
implementation(Samples.SpringBoot.springBootStarterWebflux)
implementation(Samples.SpringBoot.springBootStarterAop)
implementation(Samples.SpringBoot.aspectj)
implementation(Samples.SpringBoot.springBootStarter)
implementation(Samples.SpringBoot.kotlinReflect)
implementation(Samples.SpringBoot.springBootStarterJdbc)
implementation(kotlin(Samples.SpringBoot.kotlinStdLib, KotlinCompilerVersion.VERSION))
implementation("io.sentry:sentry-spring-boot-starter:6.5.0")
implementation("io.sentry:sentry-logback:6.5.0")

// database query tracing
implementation("io.sentry:sentry-jdbc:6.5.0")
runtimeOnly(Samples.SpringBoot.hsqldb)
testImplementation(Samples.SpringBoot.springBootStarterTest) {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
}
}

tasks.withType<Test> {
useJUnitPlatform()
}

tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}
1 change: 1 addition & 0 deletions examples/spring-boot-sample/src
131 changes: 117 additions & 14 deletions plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,20 @@ import io.sentry.android.gradle.SentryPropertiesFileProvider.getPropertiesFilePa
import io.sentry.android.gradle.SentryTasksProvider.capitalized
import io.sentry.android.gradle.SentryTasksProvider.getAssembleTaskProvider
import io.sentry.android.gradle.SentryTasksProvider.getBundleTask
import io.sentry.android.gradle.SentryTasksProvider.getLintVitalAnalyzeProvider
import io.sentry.android.gradle.SentryTasksProvider.getLintVitalReportProvider
import io.sentry.android.gradle.SentryTasksProvider.getMappingFileProvider
import io.sentry.android.gradle.SentryTasksProvider.getMergeAssetsProvider
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.getProcessResourcesProvider
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.SentryModulesService
import io.sentry.android.gradle.tasks.SentryExternalDependenciesReportTask
import io.sentry.android.gradle.tasks.SentryGenerateProguardUuidTask
import io.sentry.android.gradle.tasks.SentryUploadNativeSymbolsTask
import io.sentry.android.gradle.tasks.SentryUploadProguardMappingsTask
Expand All @@ -33,17 +37,29 @@ import io.sentry.android.gradle.util.SentryPluginUtils.withLogging
import io.sentry.android.gradle.util.detectSentryAndroidSdk
import io.sentry.android.gradle.util.info
import java.io.File
import java.util.concurrent.atomic.AtomicBoolean
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.RegularFile
import org.gradle.api.plugins.ExtraPropertiesExtension
import org.gradle.api.plugins.JavaBasePlugin
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.StopExecutionException
import org.gradle.api.tasks.TaskProvider
import org.slf4j.LoggerFactory

@Suppress("UnstableApiUsage")
class SentryPlugin : Plugin<Project> {

/**
* Since we're listening for the JavaBasePlugin, there may be multiple plugins inherting from it
* applied to the same project, e.g. Spring Boot + Kotlin Jvm, hence we only want our plugin to
* be configured only once.
*/
private val configuredForJavaProject = AtomicBoolean(false)

override fun apply(project: Project) {
if (AgpVersions.CURRENT < AgpVersions.VERSION_7_0_0) {
throw StopExecutionException(
Expand Down Expand Up @@ -172,6 +188,19 @@ class SentryPlugin : Plugin<Project> {
var transformerTaskProvider: TaskProvider<Task>? = null
var packageBundleTaskProvider: TaskProvider<Task>? = null

val mergeAssetsDependants = setOf(
getMergeAssetsProvider(variant),
// lint vital tasks scan the entire "build" folder; since we're writing our
// generated stuff in there, we put explicit dependency on them to avoid
// warnings about implicit dependency
withLogging(project.logger, "lintVitalAnalyzeTask") {
getLintVitalAnalyzeProvider(project, variant.name)
},
withLogging(project.logger, "lintVitalReportTask") {
getLintVitalReportProvider(project, variant.name)
}
)

if (isMinificationEnabled) {
preBundleTaskProvider = withLogging(project.logger, "preBundleTask") {
getPreBundleTask(project, variant.name)
Expand All @@ -193,25 +222,36 @@ class SentryPlugin : Plugin<Project> {
}

val taskSuffix = variant.name.capitalizeUS()
val sentryAssetDir =
project.layout.buildDirectory.dir(
"generated${sep}assets${sep}sentry${sep}${variant.name}"
)
androidExtension.sourceSets.getByName(variant.name).assets.srcDir(sentryAssetDir)

val reportDependenciesTask = project.registerDependenciesTask(
configurationName = "${variant.name}RuntimeClasspath",
attributeValueJar = "android-classes",
includeReport = extension.includeDependenciesReport,
output = sentryAssetDir.flatMap { dir ->
dir.file(project.provider { SENTRY_DEPENDENCIES_REPORT_OUTPUT })
},
taskSuffix = taskSuffix
)
reportDependenciesTask.setupMergeAssetsDependencies(mergeAssetsDependants)

if (isMinificationEnabled && extension.includeProguardMapping.get()) {
// Setup the task to generate a UUID asset file
val generateUuidTask = project.tasks.register(
"generateSentryProguardUuid$taskSuffix",
SentryGenerateProguardUuidTask::class.java
) {
it.outputDirectory.set(
project.file(
File(
project.buildDir,
"generated${sep}assets${sep}sentry${sep}${variant.name}"
)
)
it.output.set(
sentryAssetDir.flatMap { dir ->
dir.file(project.provider { "sentry-debug-meta.properties" })
}
)
}
getMergeAssetsProvider(variant)?.configure {
it.dependsOn(generateUuidTask)
}
generateUuidTask.setupMergeAssetsDependencies(mergeAssetsDependants)

// Setup the task that uploads the proguard mapping and UUIDs
val uploadSentryProguardMappingsTask = project.tasks.register(
Expand All @@ -224,7 +264,7 @@ class SentryPlugin : Plugin<Project> {
task.sentryProperties.set(
sentryProperties?.let { file -> project.file(file) }
)
task.uuidDirectory.set(generateUuidTask.flatMap { it.outputDirectory })
task.uuidFile.set(generateUuidTask.flatMap { it.output })
task.mappingsFiles = getMappingFileProvider(
project,
variant,
Expand All @@ -234,9 +274,6 @@ class SentryPlugin : Plugin<Project> {
task.sentryOrganization.set(sentryOrgParameter)
task.sentryProject.set(sentryProjectParameter)
}
androidExtension.sourceSets.getByName(variant.name).assets.srcDir(
generateUuidTask.flatMap { it.outputDirectory }
)

if (extension.experimentalGuardsquareSupport.get() &&
GroovyCompat.isDexguardEnabledForVariant(project, variant.name)
Expand Down Expand Up @@ -308,6 +345,71 @@ class SentryPlugin : Plugin<Project> {

project.installDependencies(extension)
}

project.plugins.withType(JavaBasePlugin::class.java) {
if (project.pluginManager.hasPlugin("com.android.application")) {
// AGP also applies JavaBasePlugin, but since we have a separate setup for it,
// we just bail here
return@withType
}
if (configuredForJavaProject.getAndSet(true)) {
logger.info { "The Sentry Gradle plugin was already configured" }
return@withType
}

val javaExtension = project.extensions.getByType(JavaPluginExtension::class.java)

val sentryResDir = project.layout.buildDirectory.dir("generated${sep}sentry")
javaExtension.sourceSets.getByName("main").resources { sourceSet ->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m Should we also allow for devs to configure the source set they want?
Maybe even support multiple and have listOf("main") as default?
This could be a separate issue as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, but what would be the usecase? All the sourcesets get packaged into the final jar anyway, and that's what we're interested in

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about people renaming the source sets because they're following some conventions or have to support some legacy code base where names are different.

sourceSet.srcDir(sentryResDir)
}

val reportDependenciesTask = project.registerDependenciesTask(
configurationName = "runtimeClasspath",
attributeValueJar = "jar",
includeReport = extension.includeDependenciesReport,
output = sentryResDir.flatMap { dir ->
dir.file(
project.provider { SENTRY_DEPENDENCIES_REPORT_OUTPUT }
)
}
)
val resourcesTask = withLogging(project.logger, "processResources") {
getProcessResourcesProvider(project)
}
resourcesTask?.configure { task -> task.dependsOn(reportDependenciesTask) }
}
}

private fun Project.registerDependenciesTask(
configurationName: String,
attributeValueJar: String,
output: Provider<RegularFile>,
includeReport: Provider<Boolean>,
taskSuffix: String = ""
): TaskProvider<out Task> {
val reportDependenciesTask = tasks.register(
"collectExternal${taskSuffix}DependenciesForSentry",
SentryExternalDependenciesReportTask::class.java
) {
it.includeReport.set(includeReport)
it.attributeValueJar.set(attributeValueJar)
it.setRuntimeConfiguration(
project.configurations.getByName(configurationName)
)
it.output.set(output)
}
return reportDependenciesTask
}

private fun TaskProvider<out Task>.setupMergeAssetsDependencies(
dependants: Set<TaskProvider<out Task>?>
) {
dependants.forEach {
it?.configure { task ->
task.dependsOn(this)
}
}
}

private fun isVariantAllowed(
Expand All @@ -325,6 +427,7 @@ class SentryPlugin : Plugin<Project> {
const val SENTRY_ORG_PARAMETER = "sentryOrg"
const val SENTRY_PROJECT_PARAMETER = "sentryProject"
internal const val SENTRY_SDK_VERSION = "6.6.0"
internal const val SENTRY_DEPENDENCIES_REPORT_OUTPUT = "sentry-external-modules.txt"

internal val sep = File.separator

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.sentry.android.gradle
import com.android.build.gradle.api.ApplicationVariant
import com.android.build.gradle.tasks.MergeSourceSetFolders
import com.android.build.gradle.tasks.PackageAndroidArtifact
import io.sentry.android.gradle.SentryTasksProvider.capitalized
import io.sentry.android.gradle.util.GroovyCompat.isDexguardAvailable
import io.sentry.android.gradle.util.SentryPluginUtils.capitalizeUS
import java.io.File
Expand Down Expand Up @@ -138,6 +139,32 @@ internal object SentryTasksProvider {
// for App Bundle it uses getPackageBundleTask
variant.packageApplicationProvider

/**
* Returns the lintVitalAnalyze task provider
*
* @return the provider if found or null otherwise
*/
@JvmStatic
fun getLintVitalAnalyzeProvider(project: Project, variantName: String) =
project.findTask(listOf("lintVitalAnalyze${variantName.capitalized}"))

/**
* Returns the lintVitalReport task provider
*
* @return the provider if found or null otherwise
*/
@JvmStatic
fun getLintVitalReportProvider(project: Project, variantName: String) =
project.findTask(listOf("lintVitalReport${variantName.capitalized}"))

/**
* Returns the processResources task provider
*
* @return the provider if found or null otherwise
*/
@JvmStatic
fun getProcessResourcesProvider(project: Project) = project.findTask(listOf("processResources"))

private fun Project.findTask(taskName: List<String>): TaskProvider<Task>? =
taskName
.mapNotNull {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,15 @@ abstract class SentryPluginExtension @Inject constructor(project: Project) {
) {
autoInstallationAction.execute(autoInstallation)
}

/**
* Disables or enables the reporting of dependencies metadata for Sentry.
* If enabled the plugin will collect external dependencies and will take care of
* uploading them to Sentry as part of events. If disabled, all the logic
* related to dependencies metadata report will be excluded.
*
* Default is enabled.
*/
val includeDependenciesReport: Property<Boolean> = objects.property(Boolean::class.java)
.convention(true)
}