Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/workflows/publish-sourcemap-plugin.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: release-sourcemap-plugin
on:
workflow_dispatch:
inputs:
versionName:
description: 'Version Name'
required: true

jobs:
publish:
name: Publish
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4.1.1
- name: Set up Java 17 for running Gradle
uses: actions/setup-java@v3.13.0
with:
distribution: temurin
java-version: 17

- name: Grant Permission to Execute Gradle
run: chmod +x gradlew

- name: Build with Gradle
uses: gradle/gradle-build-action@v2
with:
arguments: build

- name: Publish Sourcemap Plugin
run: |
echo "Publishing plugin ${{ github.event.inputs.versionName }}🚀🚀"
./gradlew :sourcemap-plugin:publish --no-daemon
echo "Published✅"
env:
ORG_GRADLE_PROJECT_VERSION_NAME: ${{ github.event.inputs.versionName }}
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_SECRET }}
ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.GPG_ID }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.GPG_PASSWORD }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@
.cxx
local.properties
.idea
build
5 changes: 5 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,8 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-dontwarn io.opentelemetry.contrib.disk.buffering.**
# Ensure R8 renames as much as possible for testing
-repackageclasses ''
-allowaccessmodification
-overloadaggressively
12 changes: 12 additions & 0 deletions app/src/main/java/io/middleware/android/sample/CrashHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.middleware.android.sample;

public class CrashHelper {
public void executeRiskOperation() {
// Nested call to ensure a deeper obfuscated stacktrace
performDeepNestedTask();
}

private void performDeepNestedTask() {
throw new RuntimeException("Middleware Test Crash: " + System.currentTimeMillis());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ protected void onCreate(Bundle savedInstanceState) {
}
}
});

findViewById(R.id.btn_trigger_crash).setOnClickListener(view -> {
new CrashHelper().executeRiskOperation();
});
}

private void createSession() {
Expand Down
13 changes: 12 additions & 1 deletion app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,23 @@
app:tint="#ff0000" />

<Button
android:id="@+id/force_new_session"
android:id="@+id/btn_trigger_crash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/trigger_obfuscated_crash"
android:layout_below="@id/anr_button"
android:layout_centerHorizontal="true"
android:layout_marginTop="16dp"
app:tint="#ff0000"
/>

<Button
android:id="@+id/force_new_session"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/btn_trigger_crash"
android:layout_centerHorizontal="true"
android:layout_marginTop="16dp"
android:text="@string/force_new_session"
app:tint="#ff0000" />

Expand Down
3 changes: 2 additions & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@
<string name="custom_rum_event">CUSTOM RUM EVENT</string>
<string name="application_not_responding">APPLICATION NOT RESPONDING</string>
<string name="force_new_session">FORCE NEW SESSION</string>
</resources>
<string name="trigger_obfuscated_crash">Trigger Obfuscated Crash</string>
</resources>
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ dependencyResolutionManagement {
rootProject.name = "Sample"
include ':app'
include ':sdk'
include ':sourcemap-plugin'
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.middleware.sourcemap

/**
* DSL extension exposed to the build script as `middlewareSourcemap { ... }`.
*/
class MiddlewareSourcemapExtension {

/** Middleware API key. Falls back to the MW_API_KEY environment variable. */
String apiKey

/**
* The version string attached to the uploaded mapping.
* Defaults to the variant's versionName.
*/
String appVersion

/**
* Backend URL for obtaining a pre-signed upload URL.
* Defaults to the Middleware hosted endpoint.
*/
String backendUrl = "https://app.middleware.io/api/v1/android/getSasUrl"

/**
* When true, deletes the local mapping.txt after a successful upload.
* Useful in CI to avoid accidentally shipping mapping files.
*/
boolean deleteAfterUpload = false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package io.middleware.sourcemap

import org.gradle.api.Plugin
import org.gradle.api.Project
import com.android.build.gradle.AppExtension
import com.android.build.gradle.api.ApplicationVariant

/**
* Middleware Android Source Map Plugin
*
* Automatically registers an upload task for every build variant that has
* minification (ProGuard / R8) enabled.
*
* Usage in app/build.gradle:
*
* plugins {
* id 'io.middleware.sourcemap'
* }
*
* middlewareSourcemap {
* apiKey = "YOUR_MW_API_KEY" // or set MW_API_KEY env var
* backendUrl = "https://app.middleware.io/api/v1/rum/getSasUrl"
* // appVersion defaults to versionName; override if needed:
* // appVersion = "2.0.0-beta"
* }
*/
class MiddlewareSourcemapPlugin implements Plugin<Project> {

@Override
void apply(Project project) {

// Create the extension so the user can configure the plugin in their build script.
def extension = project.extensions.create(
'middlewareSourcemap',
MiddlewareSourcemapExtension
)

// Wait until after the project is evaluated so that all variants are available.
project.afterEvaluate {

def android = project.extensions.findByType(AppExtension)
if (android == null) {
project.logger.warn(
"[MiddlewareSourcemap] 'com.android.application' plugin not found. " +
"Make sure this plugin is applied after the Android plugin."
)
return
}

android.applicationVariants.all { ApplicationVariant variant ->
registerUploadTask(project, extension, variant)
}
}
}

private static void registerUploadTask(
Project project,
MiddlewareSourcemapExtension extension,
ApplicationVariant variant
) {
// Only register for variants that produce a mapping file.
if (!variant.buildType.minifyEnabled) {
project.logger.info(
"[MiddlewareSourcemap] Skipping variant '${variant.name}' — minification disabled."
)
return
}

def variantName = variant.name.capitalize()
def taskName = "uploadMiddlewareMapping${variantName}"

def uploadTask = project.tasks.register(taskName, UploadMappingTask) { task ->
task.group = "Middleware"
task.description = "Upload ProGuard/R8 mapping for variant '${variant.name}' to Middleware."

// Wire the mapping file output from the minification task.
//
// variant.mappingFileProvider returns Provider<FileCollection>.
// Calling .map { files.singleFile } yields Provider<File> (java.io.File),
// but RegularFileProperty.set() requires Provider<RegularFile>.
// project.layout.file(Provider<File>) performs the conversion correctly.
task.mappingFile.set(
project.layout.file(
variant.mappingFileProvider.map { files -> files.singleFile }
)
)

task.apiKey.set(
extension.apiKey ?: System.getenv("MW_API_KEY") ?: ""
)
task.appVersion.set(
extension.appVersion ?: variant.versionName ?: "latest"
)
task.backendUrl.set(
extension.backendUrl ?:
"https://app.middleware.io/api/v1/rum/getSasUrl"
)
task.deleteAfterUpload.set(extension.deleteAfterUpload ?: false)

// Run after the minification task so the mapping file exists.
def minifyTaskName = "minify${variantName}WithR8"
def altMinifyTaskName = "minify${variantName}WithProguard"
[minifyTaskName, altMinifyTaskName].each { tName ->
def t = project.tasks.findByName(tName)
if (t) task.dependsOn(t)
}
}

// Optionally hook into assemble so the upload happens automatically.
def assembleTask = project.tasks.findByName("assemble${variantName}")
assembleTask?.finalizedBy(uploadTask)
}
}
Loading
Loading