Skip to content

Commit

Permalink
Add gh workflow for testing release builds against AGP matrix (#2439)
Browse files Browse the repository at this point in the history
  • Loading branch information
romtsn committed Dec 28, 2022
1 parent 5fd37fa commit b3704c8
Show file tree
Hide file tree
Showing 17 changed files with 242 additions and 20 deletions.
44 changes: 44 additions & 0 deletions .github/workflows/agp-matrix.yml
@@ -0,0 +1,44 @@
name: AGP Matrix Sample Release

on:
push:
branches:
- main
- release/**
pull_request:

jobs:
cancel-previous-workflow:
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@b173b6ec0100793626c2d9e6b90435061f4fc3e5 # pin@0.11.0
with:
access_token: ${{ github.token }}

agp-matrix-sample-release:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
agp: ['7.3.0','7.4.0-rc01','8.0.0-alpha09']

name: AGP Matrix Sample Release - AGP ${{ matrix.agp }}
env:
VERSION_AGP: ${{ matrix.agp }}

steps:
- name: Checkout Repo
uses: actions/checkout@v2

- name: Setup Java Version
uses: actions/setup-java@v2
with:
distribution: 'temurin'
java-version: '17'

- name: Build the Release variant
uses: gradle/gradle-build-action@3fbe033aaae657f011f88f29be9e65ed26bd29ef # pin@v2
with:
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
arguments: sentry-android-integration-tests:sentry-test-agp:assembleRelease
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -9,6 +9,7 @@
### Fixes

- Use minSdk compatible `Objects` class ([#2436](https://github.com/getsentry/sentry-java/pull/2436))
- Prevent R8 from warning on missing classes, as we check for their presence at runtime ([#2439](https://github.com/getsentry/sentry-java/pull/2439))

### Features

Expand Down
4 changes: 3 additions & 1 deletion buildSrc/src/main/java/Config.kt
@@ -1,6 +1,7 @@
import java.math.BigDecimal

object Config {
val AGP = System.getenv("VERSION_AGP") ?: "7.3.0"
val kotlinVersion = "1.6.10"
val kotlinStdLib = "stdlib-jdk8"

Expand All @@ -11,7 +12,7 @@ object Config {
val composeVersion = "1.1.1"

object BuildPlugins {
val androidGradle = "com.android.tools.build:gradle:7.3.0"
val androidGradle = "com.android.tools.build:gradle:$AGP"
val kotlinGradlePlugin = "gradle-plugin"
val buildConfig = "com.github.gmazzo.buildconfig"
val buildConfigVersion = "3.0.3"
Expand Down Expand Up @@ -130,6 +131,7 @@ object Config {
val composeNavigation = "androidx.navigation:navigation-compose:$navigationVersion"
val composeActivity = "androidx.activity:activity-compose:1.4.0"
val composeFoundation = "androidx.compose.foundation:foundation:$composeVersion"
val composeUi = "androidx.compose.ui:ui:$composeVersion"
val composeFoundationLayout = "androidx.compose.foundation:foundation-layout:$composeVersion"
val composeMaterial = "androidx.compose.material3:material3:1.0.0-alpha13"

Expand Down
3 changes: 3 additions & 0 deletions gradle.properties
Expand Up @@ -6,6 +6,9 @@ org.gradle.parallel=true
# AndroidX required by AGP >= 3.6.x
android.useAndroidX=true

# Required by AGP >= 8.0.x
android.defaults.buildfeatures.buildconfig=true

# Release information
versionName=6.10.0

Expand Down
2 changes: 2 additions & 0 deletions sentry-android-core/build.gradle.kts
Expand Up @@ -108,6 +108,8 @@ dependencies {
testImplementation(projects.sentryTestSupport)
testImplementation(projects.sentryAndroidFragment)
testImplementation(projects.sentryAndroidTimber)
testImplementation(projects.sentryComposeHelper)
testImplementation(projects.sentryAndroidNdk)
testRuntimeOnly(Config.Libs.timber)
testRuntimeOnly(Config.Libs.fragment)
}
4 changes: 2 additions & 2 deletions sentry-android-core/proguard-rules.pro
Expand Up @@ -20,10 +20,10 @@

# don't warn jetbrains annotations
-dontwarn org.jetbrains.annotations.**
# don't warn about missing classes (mainly for Guardsquare's proguard).
# We are checking for their presence at runtime
# don't warn about missing classes, as we are checking for their presence at runtime
-dontwarn io.sentry.android.timber.SentryTimberIntegration
-dontwarn io.sentry.android.fragment.FragmentLifecycleIntegration
-dontwarn io.sentry.compose.gestures.ComposeGestureTargetLocator

# To ensure that stack traces is unambiguous
# https://developer.android.com/studio/build/shrink-code#decode-stack-trace
Expand Down
Expand Up @@ -41,6 +41,11 @@
@SuppressWarnings("Convert2MethodRef") // older AGP versions do not support method references
final class AndroidOptionsInitializer {

static final String SENTRY_COMPOSE_INTEGRATION_CLASS_NAME =
"io.sentry.compose.gestures.ComposeGestureTargetLocator";

static final String COMPOSE_CLASS_NAME = "androidx.compose.ui.node.Owner";

/** private ctor */
private AndroidOptionsInitializer() {}

Expand Down Expand Up @@ -143,15 +148,15 @@ static void initializeIntegrationsAndProcessors(
if (options.getGestureTargetLocators().isEmpty()) {
final List<GestureTargetLocator> gestureTargetLocators = new ArrayList<>(2);
gestureTargetLocators.add(new AndroidViewGestureTargetLocator(isAndroidXScrollViewAvailable));
try {

final boolean isComposeUpstreamAvailable =
loadClass.isClassAvailable(COMPOSE_CLASS_NAME, options);
final boolean isComposeAvailable =
(isComposeUpstreamAvailable
&& loadClass.isClassAvailable(SENTRY_COMPOSE_INTEGRATION_CLASS_NAME, options));

if (isComposeAvailable) {
gestureTargetLocators.add(new ComposeGestureTargetLocator());
} catch (NoClassDefFoundError error) {
options
.getLogger()
.log(
SentryLevel.DEBUG,
"ComposeGestureTargetLocator not available, consider adding the `sentry-compose` library.",
error);
}
options.setGestureTargetLocators(gestureTargetLocators);
}
Expand Down
21 changes: 21 additions & 0 deletions sentry-android-core/src/test/AndroidManifest.xml
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:tools="http://schemas.android.com/tools" xmlns:android="http://schemas.android.com/apk/res/android">

<uses-sdk
tools:overrideLibrary="
io.sentry.compose,
io.sentry.android.ndk,
androidx.navigation.compose,
androidx.activity.compose,
androidx.compose.animation,
androidx.lifecycle.viewmodel.compose,
androidx.compose.foundation.layout,
androidx.compose.animation.core,
androidx.compose.ui,
androidx.compose.ui.text,
androidx.compose.runtime.saveable,
androidx.compose.ui.graphics,
androidx.compose.ui.unit,
androidx.compose.ui.geometry,
androidx.compose.ui.util"/>
</manifest>
Expand Up @@ -8,12 +8,15 @@ import io.sentry.ILogger
import io.sentry.MainEventProcessor
import io.sentry.SentryOptions
import io.sentry.android.core.cache.AndroidEnvelopeCache
import io.sentry.android.core.internal.gestures.AndroidViewGestureTargetLocator
import io.sentry.android.core.internal.modules.AssetsModulesLoader
import io.sentry.android.core.internal.util.AndroidMainThreadChecker
import io.sentry.android.fragment.FragmentLifecycleIntegration
import io.sentry.android.timber.SentryTimberIntegration
import io.sentry.compose.gestures.ComposeGestureTargetLocator
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import java.io.File
Expand Down Expand Up @@ -66,7 +69,7 @@ class AndroidOptionsInitializerTest {

fun initSutWithClassLoader(
minApi: Int = 16,
classToLoad: Class<*>? = null,
classesToLoad: List<String> = emptyList(),
isFragmentAvailable: Boolean = false,
isTimberAvailable: Boolean = false
) {
Expand All @@ -89,7 +92,7 @@ class AndroidOptionsInitializerTest {
sentryOptions,
context,
buildInfo,
createClassMock(classToLoad),
createClassMock(classesToLoad),
isFragmentAvailable,
isTimberAvailable
)
Expand All @@ -101,10 +104,14 @@ class AndroidOptionsInitializerTest {
return buildInfo
}

private fun createClassMock(clazz: Class<*>?): LoadClass {
private fun createClassMock(classes: List<String>): LoadClass {
val loadClassMock = mock<LoadClass>()
whenever(loadClassMock.loadClass(any(), any())).thenReturn(clazz)
whenever(loadClassMock.isClassAvailable(any(), any<ILogger>())).thenReturn(clazz != null)
classes.forEach {
whenever(loadClassMock.loadClass(eq(it), any()))
.thenReturn(Class.forName(it, false, this::class.java.classLoader))
whenever(loadClassMock.isClassAvailable(eq(it), any<SentryOptions>()))
.thenReturn(true)
}
return loadClassMock
}
}
Expand Down Expand Up @@ -267,23 +274,23 @@ class AndroidOptionsInitializerTest {

@Test
fun `NdkIntegration will load SentryNdk class and add to the integration list`() {
fixture.initSutWithClassLoader(classToLoad = SentryNdk::class.java)
fixture.initSutWithClassLoader(classesToLoad = listOfNotNull(NdkIntegration.SENTRY_NDK_CLASS_NAME))

val actual = fixture.sentryOptions.integrations.firstOrNull { it is NdkIntegration }
assertNotNull((actual as NdkIntegration).sentryNdkClass)
}

@Test
fun `NdkIntegration won't be enabled because API is lower than 16`() {
fixture.initSutWithClassLoader(minApi = 14, classToLoad = SentryNdk::class.java)
fixture.initSutWithClassLoader(minApi = 14, classesToLoad = listOfNotNull(NdkIntegration.SENTRY_NDK_CLASS_NAME))

val actual = fixture.sentryOptions.integrations.firstOrNull { it is NdkIntegration }
assertNull((actual as NdkIntegration).sentryNdkClass)
}

@Test
fun `NdkIntegration won't be enabled, if class not found`() {
fixture.initSutWithClassLoader(classToLoad = null)
fixture.initSutWithClassLoader(classesToLoad = emptyList())

val actual = fixture.sentryOptions.integrations.firstOrNull { it is NdkIntegration }
assertNull((actual as NdkIntegration).sentryNdkClass)
Expand Down Expand Up @@ -455,4 +462,26 @@ class AndroidOptionsInitializerTest {

assertTrue { fixture.sentryOptions.mainThreadChecker is AndroidMainThreadChecker }
}

@Test
fun `does not install ComposeGestureTargetLocator, if sentry-compose is not available`() {
fixture.initSutWithClassLoader()

assertTrue { fixture.sentryOptions.gestureTargetLocators.size == 1 }
assertTrue { fixture.sentryOptions.gestureTargetLocators[0] is AndroidViewGestureTargetLocator }
}

@Test
fun `installs ComposeGestureTargetLocator, if sentry-compose is available`() {
fixture.initSutWithClassLoader(
classesToLoad = listOf(
AndroidOptionsInitializer.COMPOSE_CLASS_NAME,
AndroidOptionsInitializer.SENTRY_COMPOSE_INTEGRATION_CLASS_NAME
)
)

assertTrue { fixture.sentryOptions.gestureTargetLocators.size == 2 }
assertTrue { fixture.sentryOptions.gestureTargetLocators[0] is AndroidViewGestureTargetLocator }
assertTrue { fixture.sentryOptions.gestureTargetLocators[1] is ComposeGestureTargetLocator }
}
}
1 change: 1 addition & 0 deletions sentry-android-integration-tests/README.md
Expand Up @@ -5,3 +5,4 @@
* [App metrics test specification (yaml)](./metrics-test.yml)
* [Espresso-based benchmarks](./sentry-uitest-android-benchmark) - run within SauceLabs (see /.sauce/*.yml)
* [Espresso-based UI tests](./sentry-uitest-android) - run within SauceLabs (see /.sauce/*.yml)
* [Sample app for testing against AGP compatibility matrix](./sentry-test-agp)
@@ -0,0 +1 @@
/build
64 changes: 64 additions & 0 deletions sentry-android-integration-tests/sentry-test-agp/build.gradle.kts
@@ -0,0 +1,64 @@
plugins {
id("com.android.application")
}

android {
compileSdk = Config.Android.compileSdkVersion
namespace = "io.sentry.test.agp"

defaultConfig {
applicationId = "io.sentry.test.agp"
minSdk = Config.Android.minSdkVersionOkHttp
targetSdk = Config.Android.targetSdkVersion
versionCode = 1
versionName = "1.0"
}

buildTypes {
getByName("release") {
isMinifyEnabled = true
signingConfig = signingConfigs.getByName("debug") // to be able to run release mode
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt")
)
ndk {
abiFilters.clear()
abiFilters.add("arm64-v8a")
}
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
buildFeatures {
viewBinding = true
}
signingConfigs {
getByName("debug") {
storeFile = rootProject.file("debug.keystore")
storePassword = "android"
keyAlias = "androiddebugkey"
keyPassword = "android"
}
}

variantFilter {
if (Config.Android.shouldSkipDebugVariant(buildType.name)) {
ignore = true
}
}
}
dependencies {
// just a mix of different dependencies to test how our logic for checking classes at runtime
// works with r8
implementation(projects.sentryAndroid)
implementation(projects.sentryAndroidOkhttp)
implementation(projects.sentryAndroidFragment)
implementation(projects.sentryAndroidTimber)

implementation(Config.Libs.fragment)
implementation(Config.Libs.timber)

implementation(Config.Libs.retrofit2)
}
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity android:name=".MainActivity"/>
<meta-data android:name="io.sentry.dsn" android:value="https://1053864c67cc410aa1ffc9701bd6f93d@o447951.ingest.sentry.io/5428559" />
</application>
</manifest>
@@ -0,0 +1,17 @@
package io.sentry.test.agp;

import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
helloWorld();
}

public void helloWorld() {
System.out.println(\\_(ツ)_/¯");
}
}

0 comments on commit b3704c8

Please sign in to comment.