diff --git a/build.gradle b/build.gradle index c493ba3f..a169f88f 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { } dependencies { - classpath "com.android.tools.build:gradle:4.1.0-beta04" // compile against 4.1.0-beta04, but maintain compat to 3.4.0 + classpath "com.android.tools.build:gradle:4.1.0-rc01" // compile against 4.1.0-rc01, but maintain compat to 3.4.0 classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72" classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.9.1" } @@ -58,7 +58,7 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { // Build dependencies dependencies { kapt "com.squareup.moshi:moshi-kotlin-codegen:1.9.3" - compileOnly "com.android.tools.build:gradle:4.1.0-beta04" + compileOnly "com.android.tools.build:gradle:4.1.0-rc01" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72" // For uploading proguard/ndk files @@ -73,7 +73,7 @@ dependencies { // For multiple I/O use cases implementation "com.squareup.okio:okio:2.7.0" - testImplementation "com.android.tools.build:gradle:4.0.0" + testImplementation "com.android.tools.build:gradle:4.1.0-rc01" testImplementation "junit:junit:4.12" testImplementation "org.mockito:mockito-core:2.28.2" testImplementation "com.squareup.okhttp3:mockwebserver:4.8.0" diff --git a/features/abi_apk_splits.feature b/features/abi_apk_splits.feature index 1df49b58..faccff1d 100644 --- a/features/abi_apk_splits.feature +++ b/features/abi_apk_splits.feature @@ -5,58 +5,58 @@ Scenario: ABI Splits project builds successfully When I build "abi_splits" using the "standard" bugsnag config Then I should receive 16 requests - And the request 15 is valid for the Build API - And the payload field "appVersionCode" equals "2" for request 15 + And the request 0 is valid for the Build API + And the payload field "appVersionCode" equals "2" for request 0 - And the request 14 is valid for the Build API - And the payload field "appVersionCode" equals "3" for request 14 + And the request 1 is valid for the Build API + And the payload field "appVersionCode" equals "3" for request 1 - And the request 13 is valid for the Build API - And the payload field "appVersionCode" equals "4" for request 13 + And the request 2 is valid for the Build API + And the payload field "appVersionCode" equals "4" for request 2 - And the request 12 is valid for the Build API - And the payload field "appVersionCode" equals "5" for request 12 + And the request 3 is valid for the Build API + And the payload field "appVersionCode" equals "5" for request 3 - And the request 11 is valid for the Build API - And the payload field "appVersionCode" equals "6" for request 11 + And the request 4 is valid for the Build API + And the payload field "appVersionCode" equals "6" for request 4 - And the request 10 is valid for the Build API - And the payload field "appVersionCode" equals "1" for request 10 + And the request 5 is valid for the Build API + And the payload field "appVersionCode" equals "1" for request 5 - And the request 9 is valid for the Build API - And the payload field "appVersionCode" equals "7" for request 9 + And the request 6 is valid for the Build API + And the payload field "appVersionCode" equals "7" for request 6 - And the request 8 is valid for the Build API - And the payload field "appVersionCode" equals "8" for request 8 - And the payload field "appVersion" equals "1.0" for request 8 - And the payload field "apiKey" equals "TEST_API_KEY" for request 8 + And the request 7 is valid for the Build API + And the payload field "appVersionCode" equals "8" for request 7 + And the payload field "appVersion" equals "1.0" for request 7 + And the payload field "apiKey" equals "TEST_API_KEY" for request 7 - And the request 7 is valid for the Android Mapping API - And the field "versionCode" for multipart request 7 equals "2" + And the request 8 is valid for the Android Mapping API + And the field "versionCode" for multipart request 8 equals "2" - And the request 6 is valid for the Android Mapping API - And the field "versionCode" for multipart request 6 equals "3" + And the request 9 is valid for the Android Mapping API + And the field "versionCode" for multipart request 9 equals "3" - And the request 5 is valid for the Android Mapping API - And the field "versionCode" for multipart request 5 equals "4" + And the request 10 is valid for the Android Mapping API + And the field "versionCode" for multipart request 10 equals "4" - And the request 4 is valid for the Android Mapping API - And the field "versionCode" for multipart request 4 equals "5" + And the request 11 is valid for the Android Mapping API + And the field "versionCode" for multipart request 11 equals "5" - And the request 3 is valid for the Android Mapping API - And the field "versionCode" for multipart request 3 equals "6" + And the request 12 is valid for the Android Mapping API + And the field "versionCode" for multipart request 12 equals "6" - And the request 2 is valid for the Android Mapping API - And the field "versionCode" for multipart request 2 equals "1" + And the request 13 is valid for the Android Mapping API + And the field "versionCode" for multipart request 13 equals "1" - And the request 1 is valid for the Android Mapping API - And the field "versionCode" for multipart request 1 equals "7" + And the request 14 is valid for the Android Mapping API + And the field "versionCode" for multipart request 14 equals "7" - And the request 0 is valid for the Android Mapping API - And the field "versionCode" for multipart request 0 equals "8" - And the field "apiKey" for multipart request 0 equals "TEST_API_KEY" - And the field "versionName" for multipart request 0 equals "1.0" - And the field "appId" for multipart request 0 equals "com.bugsnag.android.example" + And the request 15 is valid for the Android Mapping API + And the field "versionCode" for multipart request 15 equals "8" + And the field "apiKey" for multipart request 15 equals "TEST_API_KEY" + And the field "versionName" for multipart request 15 equals "1.0" + And the field "appId" for multipart request 15 equals "com.bugsnag.android.example" @skip_agp4_1_or_higher Scenario: ABI Splits automatic upload disabled @@ -64,6 +64,7 @@ Scenario: ABI Splits automatic upload disabled Then I should receive no requests @skip_agp4_1_or_higher +@skip_agp3_5 Scenario: ABI Splits manual upload of build API When I build the "Armeabi-release" variantOutput for "abi_splits" using the "all_disabled" bugsnag config Then I should receive 1 request diff --git a/features/apk_splits_same_version.feature b/features/apk_splits_same_version.feature index a881cdfc..26a43915 100644 --- a/features/apk_splits_same_version.feature +++ b/features/apk_splits_same_version.feature @@ -4,6 +4,6 @@ Feature: Plugin integrated in project with APK splits Scenario: APK splits avoid uploading duplicate requests for same version information When I build "apk_splits" using the "standard" bugsnag config Then I should receive 2 requests - And the request 0 is valid for the Android Mapping API - And the payload field "buildUUID" equals "same-build-uuid" for request 0 - And the request 1 is valid for the Build API + And the request 1 is valid for the Android Mapping API + And the payload field "buildUUID" equals "same-build-uuid" for request 1 + And the request 0 is valid for the Build API diff --git a/features/app_bundle.feature b/features/app_bundle.feature index 58f1bcd4..4ddc8185 100644 --- a/features/app_bundle.feature +++ b/features/app_bundle.feature @@ -1,41 +1,43 @@ Feature: Generating Android app bundles +@skip_agp3_5 Scenario: Single-module default app bundles successfully When I bundle "default_app" using the "standard" bugsnag config Then I should receive 2 requests - And the request 1 is valid for the Build API - And the payload field "appVersion" equals "1.0" for request 1 - And the payload field "apiKey" equals "TEST_API_KEY" for request 1 - And the payload field "builderName" is not null for request 1 - And the payload field "buildTool" equals "gradle-android" for request 1 - And the payload field "appVersionCode" equals "1" for request 1 - And the payload field "sourceControl.provider" equals "github" for request 1 - And the payload field "sourceControl.repository" equals "https://github.com/bugsnag/bugsnag-android-gradle-plugin.git" for request 1 - And the payload field "sourceControl.revision" is not null for request 1 + And the request 0 is valid for the Build API + And the payload field "appVersion" equals "1.0" for request 0 + And the payload field "apiKey" equals "TEST_API_KEY" for request 0 + And the payload field "builderName" is not null for request 0 + And the payload field "buildTool" equals "gradle-android" for request 0 + And the payload field "appVersionCode" equals "1" for request 0 + And the payload field "sourceControl.provider" equals "github" for request 0 + And the payload field "sourceControl.repository" equals "https://github.com/bugsnag/bugsnag-android-gradle-plugin.git" for request 0 + And the payload field "sourceControl.revision" is not null for request 0 - And the payload field "metadata.os_arch" is not null for request 1 - And the payload field "metadata.os_name" is not null for request 1 - And the payload field "metadata.os_version" is not null for request 1 - And the payload field "metadata.java_version" is not null for request 1 - And the payload field "metadata.gradle_version" is not null for request 1 - And the payload field "metadata.git_version" is not null for request 1 + And the payload field "metadata.os_arch" is not null for request 0 + And the payload field "metadata.os_name" is not null for request 0 + And the payload field "metadata.os_version" is not null for request 0 + And the payload field "metadata.java_version" is not null for request 0 + And the payload field "metadata.gradle_version" is not null for request 0 + And the payload field "metadata.git_version" is not null for request 0 - And the request 0 is valid for the Android Mapping API - And the field "apiKey" for multipart request 0 equals "TEST_API_KEY" - And the field "versionCode" for multipart request 0 equals "1" - And the field "versionName" for multipart request 0 equals "1.0" - And the field "appId" for multipart request 0 equals "com.bugsnag.android.example" - And the field "overwrite" for multipart request 0 is null + And the request 1 is valid for the Android Mapping API + And the field "apiKey" for multipart request 1 equals "TEST_API_KEY" + And the field "versionCode" for multipart request 1 equals "1" + And the field "versionName" for multipart request 1 equals "1.0" + And the field "appId" for multipart request 1 equals "com.bugsnag.android.example" + And the field "overwrite" for multipart request 1 is null +@skip_agp3_5 Scenario: Bundling multiple flavors automatically When I bundle "flavors" using the "standard" bugsnag config Then I should receive 4 requests - And the request 3 is valid for the Build API - And the payload field "appVersion" equals "1.0" for request 3 - And the payload field "apiKey" equals "TEST_API_KEY" for request 3 - And the payload field "appVersionCode" equals "1" for request 3 + And the request 0 is valid for the Build API + And the payload field "appVersion" equals "1.0" for request 0 + And the payload field "apiKey" equals "TEST_API_KEY" for request 0 + And the payload field "appVersionCode" equals "1" for request 0 And the request 2 is valid for the Build API And the payload field "appVersion" equals "1.0" for request 2 @@ -48,23 +50,23 @@ Scenario: Bundling multiple flavors automatically And the field "versionName" for multipart request 1 equals "1.0" And the field "appId" for multipart request 1 equals "com.bugsnag.android.example.bar" - And the request 0 is valid for the Android Mapping API - And the field "apiKey" for multipart request 0 equals "TEST_API_KEY" - And the field "versionCode" for multipart request 0 equals "1" - And the field "versionName" for multipart request 0 equals "1.0" - And the field "appId" for multipart request 0 equals "com.bugsnag.android.example.foo" + And the request 3 is valid for the Android Mapping API + And the field "apiKey" for multipart request 3 equals "TEST_API_KEY" + And the field "versionCode" for multipart request 3 equals "1" + And the field "versionName" for multipart request 3 equals "1.0" + And the field "appId" for multipart request 3 equals "com.bugsnag.android.example.foo" Scenario: Bundling single flavor When I bundle the "Foo" variantOutput for "flavors" using the "standard" bugsnag config Then I should receive 2 requests - And the request 1 is valid for the Build API + And the request 0 is valid for the Build API - And the request 0 is valid for the Android Mapping API - And the field "apiKey" for multipart request 0 equals "TEST_API_KEY" - And the field "versionCode" for multipart request 0 equals "1" - And the field "versionName" for multipart request 0 equals "1.0" - And the field "appId" for multipart request 0 equals "com.bugsnag.android.example.foo" + And the request 1 is valid for the Android Mapping API + And the field "apiKey" for multipart request 1 equals "TEST_API_KEY" + And the field "versionCode" for multipart request 1 equals "1" + And the field "versionName" for multipart request 1 equals "1.0" + And the field "appId" for multipart request 1 equals "com.bugsnag.android.example.foo" Scenario: Auto upload disabled When I bundle "default_app" using the "all_disabled" bugsnag config diff --git a/features/default_app.feature b/features/default_app.feature index 2215724b..61c7b529 100644 --- a/features/default_app.feature +++ b/features/default_app.feature @@ -4,26 +4,26 @@ Scenario: Single-module default app builds successfully When I build "default_app" using the "standard" bugsnag config Then I should receive 2 requests - And the request 1 is valid for the Build API - And the payload field "appVersion" equals "1.0" for request 1 - And the payload field "apiKey" equals "TEST_API_KEY" for request 1 - And the payload field "builderName" is not null for request 1 - And the payload field "buildTool" equals "gradle-android" for request 1 - And the payload field "appVersionCode" equals "1" for request 1 - And the payload field "sourceControl.provider" equals "github" for request 1 - And the payload field "sourceControl.repository" equals "https://github.com/bugsnag/bugsnag-android-gradle-plugin.git" for request 1 - And the payload field "sourceControl.revision" is not null for request 1 + And the request 0 is valid for the Build API + And the payload field "appVersion" equals "1.0" for request 0 + And the payload field "apiKey" equals "TEST_API_KEY" for request 0 + And the payload field "builderName" is not null for request 0 + And the payload field "buildTool" equals "gradle-android" for request 0 + And the payload field "appVersionCode" equals "1" for request 0 + And the payload field "sourceControl.provider" equals "github" for request 0 + And the payload field "sourceControl.repository" equals "https://github.com/bugsnag/bugsnag-android-gradle-plugin.git" for request 0 + And the payload field "sourceControl.revision" is not null for request 0 - And the payload field "metadata.os_arch" is not null for request 1 - And the payload field "metadata.os_name" is not null for request 1 - And the payload field "metadata.os_version" is not null for request 1 - And the payload field "metadata.java_version" is not null for request 1 - And the payload field "metadata.gradle_version" is not null for request 1 - And the payload field "metadata.git_version" is not null for request 1 + And the payload field "metadata.os_arch" is not null for request 0 + And the payload field "metadata.os_name" is not null for request 0 + And the payload field "metadata.os_version" is not null for request 0 + And the payload field "metadata.java_version" is not null for request 0 + And the payload field "metadata.gradle_version" is not null for request 0 + And the payload field "metadata.git_version" is not null for request 0 - And the request 0 is valid for the Android Mapping API - And the field "apiKey" for multipart request 0 equals "TEST_API_KEY" - And the field "versionCode" for multipart request 0 equals "1" - And the field "versionName" for multipart request 0 equals "1.0" - And the field "appId" for multipart request 0 equals "com.bugsnag.android.example" - And the field "overwrite" for multipart request 0 is null + And the request 1 is valid for the Android Mapping API + And the field "apiKey" for multipart request 1 equals "TEST_API_KEY" + And the field "versionCode" for multipart request 1 equals "1" + And the field "versionName" for multipart request 1 equals "1.0" + And the field "appId" for multipart request 1 equals "com.bugsnag.android.example" + And the field "overwrite" for multipart request 1 is null diff --git a/features/density_abi_splits.feature b/features/density_abi_splits.feature index c12381d8..d9418aec 100644 --- a/features/density_abi_splits.feature +++ b/features/density_abi_splits.feature @@ -5,83 +5,83 @@ Scenario: Density ABI Splits project builds successfully When I build "density_abi_splits" using the "standard" bugsnag config Then I should receive 26 requests - And the request 25 is valid for the Build API - And the payload field "appVersionCode" equals "21" for request 25 + And the request 0 is valid for the Build API + And the payload field "appVersionCode" equals "21" for request 0 - And the request 24 is valid for the Build API - And the payload field "appVersionCode" equals "31" for request 24 + And the request 1 is valid for the Build API + And the payload field "appVersionCode" equals "31" for request 1 - And the request 23 is valid for the Build API - And the payload field "appVersionCode" equals "11" for request 23 + And the request 2 is valid for the Build API + And the payload field "appVersionCode" equals "11" for request 2 - And the request 22 is valid for the Build API - And the payload field "appVersionCode" equals "41" for request 22 + And the request 3 is valid for the Build API + And the payload field "appVersionCode" equals "41" for request 3 - And the request 21 is valid for the Build API - And the payload field "appVersionCode" equals "51" for request 21 + And the request 4 is valid for the Build API + And the payload field "appVersionCode" equals "51" for request 4 - And the request 20 is valid for the Build API - And the payload field "appVersionCode" equals "22" for request 20 + And the request 5 is valid for the Build API + And the payload field "appVersionCode" equals "22" for request 5 - And the request 19 is valid for the Build API - And the payload field "appVersionCode" equals "32" for request 19 + And the request 6 is valid for the Build API + And the payload field "appVersionCode" equals "32" for request 6 - And the request 18 is valid for the Build API - And the payload field "appVersionCode" equals "42" for request 18 + And the request 7 is valid for the Build API + And the payload field "appVersionCode" equals "42" for request 7 - And the request 17 is valid for the Build API - And the payload field "appVersionCode" equals "52" for request 17 + And the request 8 is valid for the Build API + And the payload field "appVersionCode" equals "52" for request 8 - And the request 16 is valid for the Build API - And the payload field "appVersionCode" equals "23" for request 16 + And the request 9 is valid for the Build API + And the payload field "appVersionCode" equals "23" for request 9 - And the request 15 is valid for the Build API - And the payload field "appVersionCode" equals "33" for request 15 + And the request 10 is valid for the Build API + And the payload field "appVersionCode" equals "33" for request 10 - And the request 14 is valid for the Build API - And the payload field "appVersionCode" equals "43" for request 14 + And the request 11 is valid for the Build API + And the payload field "appVersionCode" equals "43" for request 11 - And the request 13 is valid for the Build API - And the payload field "appVersionCode" equals "53" for request 13 + And the request 12 is valid for the Build API + And the payload field "appVersionCode" equals "53" for request 12 - And the request 12 is valid for the Android Mapping API - And the field "versionCode" for multipart request 12 equals "21" + And the request 13 is valid for the Android Mapping API + And the field "versionCode" for multipart request 13 equals "21" - And the request 11 is valid for the Android Mapping API - And the field "versionCode" for multipart request 11 equals "31" + And the request 14 is valid for the Android Mapping API + And the field "versionCode" for multipart request 14 equals "31" - And the request 10 is valid for the Android Mapping API - And the field "versionCode" for multipart request 10 equals "11" + And the request 15 is valid for the Android Mapping API + And the field "versionCode" for multipart request 15 equals "11" - And the request 9 is valid for the Android Mapping API - And the field "versionCode" for multipart request 9 equals "41" + And the request 16 is valid for the Android Mapping API + And the field "versionCode" for multipart request 16 equals "41" - And the request 8 is valid for the Android Mapping API - And the field "versionCode" for multipart request 8 equals "51" + And the request 17 is valid for the Android Mapping API + And the field "versionCode" for multipart request 17 equals "51" - And the request 7 is valid for the Android Mapping API - And the field "versionCode" for multipart request 7 equals "22" + And the request 18 is valid for the Android Mapping API + And the field "versionCode" for multipart request 18 equals "22" - And the request 6 is valid for the Android Mapping API - And the field "versionCode" for multipart request 6 equals "32" + And the request 19 is valid for the Android Mapping API + And the field "versionCode" for multipart request 19 equals "32" - And the request 5 is valid for the Android Mapping API - And the field "versionCode" for multipart request 5 equals "42" + And the request 20 is valid for the Android Mapping API + And the field "versionCode" for multipart request 20 equals "42" - And the request 4 is valid for the Android Mapping API - And the field "versionCode" for multipart request 4 equals "52" + And the request 21 is valid for the Android Mapping API + And the field "versionCode" for multipart request 21 equals "52" - And the request 3 is valid for the Android Mapping API - And the field "versionCode" for multipart request 3 equals "23" + And the request 22 is valid for the Android Mapping API + And the field "versionCode" for multipart request 22 equals "23" - And the request 2 is valid for the Android Mapping API - And the field "versionCode" for multipart request 2 equals "33" + And the request 23 is valid for the Android Mapping API + And the field "versionCode" for multipart request 23 equals "33" - And the request 1 is valid for the Android Mapping API - And the field "versionCode" for multipart request 1 equals "43" + And the request 24 is valid for the Android Mapping API + And the field "versionCode" for multipart request 24 equals "43" - And the request 0 is valid for the Android Mapping API - And the field "versionCode" for multipart request 0 equals "53" + And the request 25 is valid for the Android Mapping API + And the field "versionCode" for multipart request 25 equals "53" @skip_agp4_1_or_higher Scenario: Density ABI Splits automatic upload disabled @@ -89,6 +89,7 @@ Scenario: Density ABI Splits automatic upload disabled Then I should receive no requests @skip_agp4_1_or_higher +@skip_agp3_5 Scenario: Density ABI Splits manual upload of build API When I build the "XxxhdpiArmeabi-release" variantOutput for "density_abi_splits" using the "all_disabled" bugsnag config Then I should receive 1 request diff --git a/features/density_splits.feature b/features/density_splits.feature index 9af953be..c438a741 100644 --- a/features/density_splits.feature +++ b/features/density_splits.feature @@ -5,52 +5,52 @@ Scenario: Density Splits project builds successfully When I build "density_splits" using the "standard" bugsnag config Then I should receive 14 requests - And the request 13 is valid for the Build API - And the payload field "appVersionCode" equals "4" for request 13 + And the request 0 is valid for the Build API + And the payload field "appVersionCode" equals "4" for request 0 - And the request 12 is valid for the Build API - And the payload field "appVersionCode" equals "2" for request 12 + And the request 1 is valid for the Build API + And the payload field "appVersionCode" equals "2" for request 1 - And the request 11 is valid for the Build API - And the payload field "appVersionCode" equals "3" for request 11 + And the request 2 is valid for the Build API + And the payload field "appVersionCode" equals "3" for request 2 - And the request 10 is valid for the Build API - And the payload field "appVersionCode" equals "1" for request 10 + And the request 3 is valid for the Build API + And the payload field "appVersionCode" equals "1" for request 3 - And the request 9 is valid for the Build API - And the payload field "appVersionCode" equals "5" for request 9 + And the request 4 is valid for the Build API + And the payload field "appVersionCode" equals "5" for request 4 - And the request 8 is valid for the Build API - And the payload field "appVersionCode" equals "6" for request 8 + And the request 5 is valid for the Build API + And the payload field "appVersionCode" equals "6" for request 5 - And the request 7 is valid for the Build API - And the payload field "appVersionCode" equals "7" for request 7 - And the payload field "appVersion" equals "1.0" for request 7 - And the payload field "apiKey" equals "TEST_API_KEY" for request 7 + And the request 6 is valid for the Build API + And the payload field "appVersionCode" equals "7" for request 6 + And the payload field "appVersion" equals "1.0" for request 6 + And the payload field "apiKey" equals "TEST_API_KEY" for request 6 - And the request 6 is valid for the Android Mapping API - And the field "versionCode" for multipart request 6 equals "4" + And the request 7 is valid for the Android Mapping API + And the field "versionCode" for multipart request 7 equals "4" - And the request 5 is valid for the Android Mapping API - And the field "versionCode" for multipart request 5 equals "2" + And the request 8 is valid for the Android Mapping API + And the field "versionCode" for multipart request 8 equals "2" - And the request 4 is valid for the Android Mapping API - And the field "versionCode" for multipart request 4 equals "3" + And the request 9 is valid for the Android Mapping API + And the field "versionCode" for multipart request 9 equals "3" - And the request 3 is valid for the Android Mapping API - And the field "versionCode" for multipart request 3 equals "1" + And the request 10 is valid for the Android Mapping API + And the field "versionCode" for multipart request 10 equals "1" - And the request 2 is valid for the Android Mapping API - And the field "versionCode" for multipart request 2 equals "5" + And the request 11 is valid for the Android Mapping API + And the field "versionCode" for multipart request 11 equals "5" - And the request 1 is valid for the Android Mapping API - And the field "versionCode" for multipart request 1 equals "6" + And the request 12 is valid for the Android Mapping API + And the field "versionCode" for multipart request 12 equals "6" - And the request 0 is valid for the Android Mapping API - And the field "versionCode" for multipart request 0 equals "7" - And the field "apiKey" for multipart request 0 equals "TEST_API_KEY" - And the field "versionName" for multipart request 0 equals "1.0" - And the field "appId" for multipart request 0 equals "com.bugsnag.android.example" + And the request 13 is valid for the Android Mapping API + And the field "versionCode" for multipart request 13 equals "7" + And the field "apiKey" for multipart request 13 equals "TEST_API_KEY" + And the field "versionName" for multipart request 13 equals "1.0" + And the field "appId" for multipart request 13 equals "com.bugsnag.android.example" @skip_agp4_1_or_higher Scenario: Density Splits automatic upload disabled @@ -58,6 +58,7 @@ Scenario: Density Splits automatic upload disabled Then I should receive no requests @skip_agp4_1_or_higher +@skip_agp3_5 Scenario: Density Splits manual upload of build API When I build the "Hdpi-release" variantOutput for "density_splits" using the "all_disabled" bugsnag config Then I should receive 1 request diff --git a/features/disabled_product_flavor.feature b/features/disabled_product_flavor.feature index a733d2c0..6fb2b508 100644 --- a/features/disabled_product_flavor.feature +++ b/features/disabled_product_flavor.feature @@ -4,13 +4,13 @@ Scenario: Disabled product flavor makes no requests When I build "flavors" using the "disabled_product_flavor" bugsnag config Then I should receive 2 requests - And the request 0 is valid for the Android Mapping API - And the field "apiKey" for multipart request 0 equals "TEST_API_KEY" - And the field "versionCode" for multipart request 0 equals "1" - And the field "versionName" for multipart request 0 equals "1.0" - And the field "appId" for multipart request 0 equals "com.bugsnag.android.example.bar" + And the request 1 is valid for the Android Mapping API + And the field "apiKey" for multipart request 1 equals "TEST_API_KEY" + And the field "versionCode" for multipart request 1 equals "1" + And the field "versionName" for multipart request 1 equals "1.0" + And the field "appId" for multipart request 1 equals "com.bugsnag.android.example.bar" - And the request 1 is valid for the Build API - And the payload field "appVersion" equals "1.0" for request 1 - And the payload field "apiKey" equals "TEST_API_KEY" for request 1 - And the payload field "appVersionCode" equals "1" for request 1 + And the request 0 is valid for the Build API + And the payload field "appVersion" equals "1.0" for request 0 + And the payload field "apiKey" equals "TEST_API_KEY" for request 0 + And the payload field "appVersionCode" equals "1" for request 0 diff --git a/features/extension_properties.feature b/features/extension_properties.feature index 37fd7629..f2bb89c2 100644 --- a/features/extension_properties.feature +++ b/features/extension_properties.feature @@ -8,10 +8,10 @@ Scenario: Disable autoReportBuilds Scenario: Enable debug mapping upload When I build "debug_proguard" using the "upload_debug_enabled" bugsnag config Then I should receive 4 requests - And the request 0 is valid for the Android Mapping API And the request 1 is valid for the Android Mapping API + And the request 3 is valid for the Android Mapping API + And the request 0 is valid for the Build API And the request 2 is valid for the Build API - And the request 3 is valid for the Build API Scenario: Enable overwrite When I build "default_app" using the "overwrite_enabled" bugsnag config diff --git a/features/flavor_apk_splits.feature b/features/flavor_apk_splits.feature index 29fb25f2..9d319c47 100644 --- a/features/flavor_apk_splits.feature +++ b/features/flavor_apk_splits.feature @@ -5,47 +5,47 @@ Scenario: Flavor Density Split project builds successfully When I build "flavor_apk_splits" using the "standard" bugsnag config Then I should receive 12 requests - And the request 11 is valid for the Build API - And the payload field "appVersionCode" equals "2" for request 11 + And the request 0 is valid for the Build API + And the payload field "appVersionCode" equals "2" for request 0 - And the request 10 is valid for the Build API - And the payload field "appVersionCode" equals "1" for request 10 + And the request 1 is valid for the Build API + And the payload field "appVersionCode" equals "1" for request 1 - And the request 9 is valid for the Build API - And the payload field "appVersionCode" equals "3" for request 9 + And the request 2 is valid for the Build API + And the payload field "appVersionCode" equals "3" for request 2 - And the request 8 is valid for the Build API - And the payload field "appVersionCode" equals "2" for request 8 + And the request 6 is valid for the Build API + And the payload field "appVersionCode" equals "2" for request 6 And the request 7 is valid for the Build API And the payload field "appVersionCode" equals "1" for request 7 - And the request 6 is valid for the Build API - And the payload field "appVersionCode" equals "3" for request 6 + And the request 8 is valid for the Build API + And the payload field "appVersionCode" equals "3" for request 8 - And the request 5 is valid for the Android Mapping API - And the field "versionCode" for multipart request 5 equals "2" - And the field "appId" for multipart request 5 equals "com.bugsnag.android.example.bar" + And the request 3 is valid for the Android Mapping API + And the field "versionCode" for multipart request 3 equals "2" + And the field "appId" for multipart request 3 equals "com.bugsnag.android.example.bar" And the request 4 is valid for the Android Mapping API And the field "versionCode" for multipart request 4 equals "1" And the field "appId" for multipart request 4 equals "com.bugsnag.android.example.bar" - And the request 3 is valid for the Android Mapping API - And the field "versionCode" for multipart request 3 equals "3" - And the field "appId" for multipart request 3 equals "com.bugsnag.android.example.bar" + And the request 5 is valid for the Android Mapping API + And the field "versionCode" for multipart request 5 equals "3" + And the field "appId" for multipart request 5 equals "com.bugsnag.android.example.bar" - And the request 2 is valid for the Android Mapping API - And the field "versionCode" for multipart request 2 equals "2" - And the field "appId" for multipart request 2 equals "com.bugsnag.android.example.foo" + And the request 9 is valid for the Android Mapping API + And the field "versionCode" for multipart request 9 equals "2" + And the field "appId" for multipart request 9 equals "com.bugsnag.android.example.foo" - And the request 1 is valid for the Android Mapping API - And the field "versionCode" for multipart request 1 equals "1" - And the field "appId" for multipart request 1 equals "com.bugsnag.android.example.foo" + And the request 10 is valid for the Android Mapping API + And the field "versionCode" for multipart request 10 equals "1" + And the field "appId" for multipart request 10 equals "com.bugsnag.android.example.foo" - And the request 0 is valid for the Android Mapping API - And the field "versionCode" for multipart request 0 equals "3" - And the field "appId" for multipart request 0 equals "com.bugsnag.android.example.foo" + And the request 11 is valid for the Android Mapping API + And the field "versionCode" for multipart request 11 equals "3" + And the field "appId" for multipart request 11 equals "com.bugsnag.android.example.foo" @skip_agp4_1_or_higher Scenario: Flavor Density Split automatic upload disabled @@ -53,6 +53,7 @@ Scenario: Flavor Density Split automatic upload disabled Then I should receive no requests @skip_agp4_1_or_higher +@skip_agp3_5 Scenario: Flavor Density Split manual upload of build API When I build the "Bar-xxhdpi-release" variantOutput for "flavor_apk_splits" using the "all_disabled" bugsnag config Then I should receive 1 request diff --git a/features/flavors.feature b/features/flavors.feature index a192ef63..368bb144 100644 --- a/features/flavors.feature +++ b/features/flavors.feature @@ -4,10 +4,10 @@ Scenario: Flavors automatic upload on build When I build "flavors" using the "standard" bugsnag config Then I should receive 4 requests - And the request 3 is valid for the Build API - And the payload field "appVersion" equals "1.0" for request 3 - And the payload field "apiKey" equals "TEST_API_KEY" for request 3 - And the payload field "appVersionCode" equals "1" for request 3 + And the request 0 is valid for the Build API + And the payload field "appVersion" equals "1.0" for request 0 + And the payload field "apiKey" equals "TEST_API_KEY" for request 0 + And the payload field "appVersionCode" equals "1" for request 0 And the request 2 is valid for the Build API And the payload field "appVersion" equals "1.0" for request 2 @@ -20,16 +20,17 @@ Scenario: Flavors automatic upload on build And the field "versionName" for multipart request 1 equals "1.0" And the field "appId" for multipart request 1 equals "com.bugsnag.android.example.bar" - And the request 0 is valid for the Android Mapping API - And the field "apiKey" for multipart request 0 equals "TEST_API_KEY" - And the field "versionCode" for multipart request 0 equals "1" - And the field "versionName" for multipart request 0 equals "1.0" - And the field "appId" for multipart request 0 equals "com.bugsnag.android.example.foo" + And the request 3 is valid for the Android Mapping API + And the field "apiKey" for multipart request 3 equals "TEST_API_KEY" + And the field "versionCode" for multipart request 3 equals "1" + And the field "versionName" for multipart request 3 equals "1.0" + And the field "appId" for multipart request 3 equals "com.bugsnag.android.example.foo" Scenario: Flavors automatic upload disabled When I build "flavors" using the "all_disabled" bugsnag config Then I should receive no requests +@skip_agp3_5 Scenario: Flavors manual upload of build API When I build the "Foo-release" variantOutput for "flavors" using the "all_disabled" bugsnag config Then I should receive 1 request diff --git a/features/manual_build_uuid.feature b/features/manual_build_uuid.feature index 4bced069..16624637 100644 --- a/features/manual_build_uuid.feature +++ b/features/manual_build_uuid.feature @@ -4,27 +4,27 @@ Scenario: Build UUID excluded from request when set to false When I build "manual_build_uuid" using the "standard" bugsnag config Then I should receive 2 requests - And the request 0 is valid for the Android Mapping API - And the field "apiKey" for multipart request 0 equals "TEST_API_KEY" - And the field "versionCode" for multipart request 0 equals "1" - And the field "versionName" for multipart request 0 equals "1.0" - And the field "appId" for multipart request 0 equals "com.bugsnag.android.example" - And the field "overwrite" for multipart request 0 is null - And the field "buildUUID" for multipart request 0 equals "same-build-uuid" + And the request 1 is valid for the Android Mapping API + And the field "apiKey" for multipart request 1 equals "TEST_API_KEY" + And the field "versionCode" for multipart request 1 equals "1" + And the field "versionName" for multipart request 1 equals "1.0" + And the field "appId" for multipart request 1 equals "com.bugsnag.android.example" + And the field "overwrite" for multipart request 1 is null + And the field "buildUUID" for multipart request 1 equals "same-build-uuid" - And the request 1 is valid for the Build API - And the payload field "appVersion" equals "1.0" for request 1 - And the payload field "apiKey" equals "TEST_API_KEY" for request 1 - And the payload field "builderName" is not null for request 1 - And the payload field "buildTool" equals "gradle-android" for request 1 - And the payload field "appVersionCode" equals "1" for request 1 + And the request 0 is valid for the Build API + And the payload field "appVersion" equals "1.0" for request 0 + And the payload field "apiKey" equals "TEST_API_KEY" for request 0 + And the payload field "builderName" is not null for request 0 + And the payload field "buildTool" equals "gradle-android" for request 0 + And the payload field "appVersionCode" equals "1" for request 0 - And the payload field "sourceControl.provider" equals "github" for request 1 - And the payload field "sourceControl.repository" equals "https://github.com/bugsnag/bugsnag-android-gradle-plugin.git" for request 1 - And the payload field "sourceControl.revision" is not null for request 1 - And the payload field "metadata.os_arch" is not null for request 1 - And the payload field "metadata.os_name" is not null for request 1 - And the payload field "metadata.os_version" is not null for request 1 - And the payload field "metadata.java_version" is not null for request 1 - And the payload field "metadata.gradle_version" is not null for request 1 - And the payload field "metadata.git_version" is not null for request 1 + And the payload field "sourceControl.provider" equals "github" for request 0 + And the payload field "sourceControl.repository" equals "https://github.com/bugsnag/bugsnag-android-gradle-plugin.git" for request 0 + And the payload field "sourceControl.revision" is not null for request 0 + And the payload field "metadata.os_arch" is not null for request 0 + And the payload field "metadata.os_name" is not null for request 0 + And the payload field "metadata.os_version" is not null for request 0 + And the payload field "metadata.java_version" is not null for request 0 + And the payload field "metadata.gradle_version" is not null for request 0 + And the payload field "metadata.git_version" is not null for request 0 diff --git a/features/ndk_app_agp400.feature b/features/ndk_app_agp400.feature index f1b57ebc..0d26bcac 100644 --- a/features/ndk_app_agp400.feature +++ b/features/ndk_app_agp400.feature @@ -3,10 +3,14 @@ Feature: Plugin integrated in NDK app @requires_agp4_0_or_higher Scenario: NDK apps send requests When I build the NDK app - Then I should receive 10 requests + Then I should receive 6 requests - And the request 0 is valid for the Android Mapping API + And the request 0 is valid for the Build API + And the payload field "appVersion" equals "1.0" for request 0 And the payload field "apiKey" equals "your-api-key-here" for request 0 + And the payload field "builderName" is not null for request 0 + And the payload field "buildTool" equals "gradle-android" for request 0 + And the payload field "appVersionCode" equals "1" for request 0 And the request 1 is valid for the Android NDK Mapping API And the payload field "apiKey" equals "your-api-key-here" for request 1 @@ -16,52 +20,28 @@ Scenario: NDK apps send requests And the request 2 is valid for the Android NDK Mapping API And the payload field "apiKey" equals "your-api-key-here" for request 2 And the payload field "projectRoot" is not null for request 2 - And the payload field "arch" equals "arm64-v8a" for request 2 + And the payload field "arch" equals "armeabi-v7a" for request 2 And the request 3 is valid for the Android NDK Mapping API And the payload field "apiKey" equals "your-api-key-here" for request 3 And the payload field "projectRoot" is not null for request 3 - And the payload field "arch" equals "armeabi-v7a" for request 3 + And the payload field "arch" equals "x86" for request 3 And the request 4 is valid for the Android NDK Mapping API And the payload field "apiKey" equals "your-api-key-here" for request 4 And the payload field "projectRoot" is not null for request 4 - And the payload field "arch" equals "armeabi-v7a" for request 4 + And the payload field "arch" equals "x86_64" for request 4 - And the request 5 is valid for the Android NDK Mapping API + And the request 5 is valid for the Android Mapping API And the payload field "apiKey" equals "your-api-key-here" for request 5 - And the payload field "projectRoot" is not null for request 5 - And the payload field "arch" equals "x86" for request 5 - - And the request 6 is valid for the Android NDK Mapping API - And the payload field "apiKey" equals "your-api-key-here" for request 6 - And the payload field "projectRoot" is not null for request 6 - And the payload field "arch" equals "x86" for request 6 - - And the request 7 is valid for the Android NDK Mapping API - And the payload field "apiKey" equals "your-api-key-here" for request 7 - And the payload field "projectRoot" is not null for request 7 - And the payload field "arch" equals "x86_64" for request 7 - - And the request 8 is valid for the Android NDK Mapping API - And the payload field "apiKey" equals "your-api-key-here" for request 8 - And the payload field "projectRoot" is not null for request 8 - And the payload field "arch" equals "x86_64" for request 8 - - And the request 9 is valid for the Build API - And the payload field "appVersion" equals "1.0" for request 9 - And the payload field "apiKey" equals "your-api-key-here" for request 9 - And the payload field "builderName" is not null for request 9 - And the payload field "buildTool" equals "gradle-android" for request 9 - And the payload field "appVersionCode" equals "1" for request 9 @requires_agp4_0_or_higher Scenario: Custom projectRoot is added to payload When I set environment variable "PROJECT_ROOT" to "/repos/custom/my-app" When I build the NDK app - Then I should receive 10 requests + Then I should receive 6 requests - And the request 0 is valid for the Android Mapping API + And the request 0 is valid for the Build API And the request 1 is valid for the Android NDK Mapping API And the payload field "projectRoot" equals "/repos/custom/my-app" for request 1 @@ -75,39 +55,21 @@ Scenario: Custom projectRoot is added to payload And the request 4 is valid for the Android NDK Mapping API And the payload field "projectRoot" equals "/repos/custom/my-app" for request 4 - And the request 5 is valid for the Android NDK Mapping API - And the payload field "projectRoot" equals "/repos/custom/my-app" for request 5 - - And the request 6 is valid for the Android NDK Mapping API - And the payload field "projectRoot" equals "/repos/custom/my-app" for request 6 - - And the request 7 is valid for the Android NDK Mapping API - And the payload field "projectRoot" equals "/repos/custom/my-app" for request 7 - - And the request 8 is valid for the Android NDK Mapping API - And the payload field "projectRoot" equals "/repos/custom/my-app" for request 8 - - And the request 9 is valid for the Build API + And the request 5 is valid for the Android Mapping API # Sets a non-existent objdump location for x86 and arm64-v8a, delivery should proceed as normal for other files @requires_agp4_0_or_higher Scenario: Custom objdump location When I set environment variable "OBJDUMP_LOCATION" to "/fake/objdump" When I build the NDK app - Then I should receive 6 requests + Then I should receive 4 requests - And the request 0 is valid for the Android Mapping API + And the request 0 is valid for the Build API And the request 1 is valid for the Android NDK Mapping API And the payload field "arch" equals "armeabi-v7a" for request 1 And the request 2 is valid for the Android NDK Mapping API - And the payload field "arch" equals "armeabi-v7a" for request 2 - - And the request 3 is valid for the Android NDK Mapping API - And the payload field "arch" equals "x86_64" for request 3 - - And the request 4 is valid for the Android NDK Mapping API - And the payload field "arch" equals "x86_64" for request 4 + And the payload field "arch" equals "x86_64" for request 2 - And the request 5 is valid for the Build API + And the request 3 is valid for the Android Mapping API diff --git a/features/ndk_app_legacy.feature b/features/ndk_app_legacy.feature index 9f1853a0..29ee3cdf 100644 --- a/features/ndk_app_legacy.feature +++ b/features/ndk_app_legacy.feature @@ -5,12 +5,12 @@ Scenario: NDK apps send requests When I build the NDK app Then I should receive 6 requests - And the request 5 is valid for the Build API - And the payload field "appVersion" equals "1.0" for request 5 - And the payload field "apiKey" equals "your-api-key-here" for request 5 - And the payload field "builderName" is not null for request 5 - And the payload field "buildTool" equals "gradle-android" for request 5 - And the payload field "appVersionCode" equals "1" for request 5 + And the request 0 is valid for the Build API + And the payload field "appVersion" equals "1.0" for request 0 + And the payload field "apiKey" equals "your-api-key-here" for request 0 + And the payload field "builderName" is not null for request 0 + And the payload field "buildTool" equals "gradle-android" for request 0 + And the payload field "appVersionCode" equals "1" for request 0 And the request 1 is valid for the Android NDK Mapping API And the payload field "apiKey" equals "your-api-key-here" for request 1 @@ -32,8 +32,8 @@ Scenario: NDK apps send requests And the payload field "projectRoot" is not null for request 4 And the payload field "arch" equals "x86_64" for request 4 - And the request 0 is valid for the Android Mapping API - And the payload field "apiKey" equals "your-api-key-here" for request 0 + And the request 5 is valid for the Android Mapping API + And the payload field "apiKey" equals "your-api-key-here" for request 5 @skip_agp4_0_or_higher Scenario: Custom projectRoot is added to payload @@ -41,7 +41,7 @@ Scenario: Custom projectRoot is added to payload When I build the NDK app Then I should receive 6 requests - And the request 0 is valid for the Android Mapping API + And the request 5 is valid for the Android Mapping API And the request 1 is valid for the Android NDK Mapping API And the payload field "projectRoot" equals "/repos/custom/my-app" for request 1 @@ -55,16 +55,17 @@ Scenario: Custom projectRoot is added to payload And the request 4 is valid for the Android NDK Mapping API And the payload field "projectRoot" equals "/repos/custom/my-app" for request 4 - And the request 5 is valid for the Build API + And the request 0 is valid for the Build API # Sets a non-existent objdump location for x86 and arm64-v8a, delivery should proceed as normal for other files +@skip_agp3_6 # skip AGP 3.6 until request ordering does not matter in tests @skip_agp4_0_or_higher Scenario: Custom objdump location When I set environment variable "OBJDUMP_LOCATION" to "/fake/objdump" When I build the NDK app Then I should receive 4 requests - And the request 0 is valid for the Android Mapping API + And the request 3 is valid for the Android Mapping API And the request 1 is valid for the Android NDK Mapping API And the payload field "arch" equals "armeabi-v7a" for request 1 @@ -72,64 +73,53 @@ Scenario: Custom objdump location And the request 2 is valid for the Android NDK Mapping API And the payload field "arch" equals "x86_64" for request 2 - And the request 3 is valid for the Build API + And the request 0 is valid for the Build API +@skip_agp3_6 # skip AGP 3.6 until request ordering does not matter in tests @skip_agp4_0_or_higher Scenario: NDK app only uploads SO file matching architecture for ABI splits When I set environment variable "ABI_SPLITS" to "enabled" When I build the NDK app - Then I should receive 18 requests - - And the request 0 is valid for the Android Mapping API - And the payload field "versionCode" equals "5" for request 0 - - And the request 1 is valid for the Android Mapping API - And the payload field "versionCode" equals "4" for request 1 - - And the request 2 is valid for the Android Mapping API - And the payload field "versionCode" equals "1" for request 2 + Then I should receive 14 requests - And the request 3 is valid for the Android NDK Mapping API - And the payload field "arch" equals "x86_64" for request 3 - - And the request 4 is valid for the Android NDK Mapping API - And the payload field "arch" equals "x86" for request 4 + And the request 13 is valid for the Android Mapping API + And the payload field "versionCode" equals "5" for request 13 - And the request 5 is valid for the Android NDK Mapping API - And the payload field "arch" equals "arm64-v8a" for request 5 + And the request 12 is valid for the Android Mapping API + And the payload field "versionCode" equals "4" for request 12 - And the request 6 is valid for the Android NDK Mapping API - And the payload field "arch" equals "armeabi-v7a" for request 6 + And the request 11 is valid for the Android Mapping API + And the payload field "versionCode" equals "1" for request 11 And the request 7 is valid for the Android NDK Mapping API - And the payload field "arch" equals "x86" for request 7 + And the payload field "arch" equals "arm64-v8a" for request 7 And the request 8 is valid for the Android NDK Mapping API - And the payload field "arch" equals "x86_64" for request 8 + And the payload field "arch" equals "armeabi-v7a" for request 8 And the request 9 is valid for the Android NDK Mapping API - And the payload field "arch" equals "armeabi-v7a" for request 9 + And the payload field "arch" equals "x86" for request 9 And the request 10 is valid for the Android NDK Mapping API - And the payload field "arch" equals "arm64-v8a" for request 10 + And the payload field "arch" equals "x86_64" for request 10 - And the request 11 is valid for the Android Mapping API - And the payload field "versionCode" equals "3" for request 11 + And the request 6 is valid for the Android Mapping API + And the payload field "versionCode" equals "3" for request 6 - And the request 12 is valid for the Android Mapping API - And the payload field "versionCode" equals "2" for request 12 + And the request 5 is valid for the Android Mapping API + And the payload field "versionCode" equals "2" for request 5 - And the request 13 is valid for the Build API - And the payload field "appVersionCode" equals "5" for request 13 + And the request 4 is valid for the Build API + And the payload field "appVersionCode" equals "5" for request 4 - And the request 14 is valid for the Build API - And the payload field "appVersionCode" equals "4" for request 14 + And the request 3 is valid for the Build API + And the payload field "appVersionCode" equals "4" for request 3 - And the request 15 is valid for the Build API - And the payload field "appVersionCode" equals "1" for request 15 + And the request 2 is valid for the Build API + And the payload field "appVersionCode" equals "1" for request 2 - And the request 16 is valid for the Build API - And the payload field "appVersionCode" equals "3" for request 16 + And the request 1 is valid for the Build API + And the payload field "appVersionCode" equals "3" for request 1 - And the request 17 is valid for the Build API - And the payload field "appVersionCode" equals "2" for request 17 + And the request 0 is valid for the Build API + And the payload field "appVersionCode" equals "2" for request 0 diff --git a/features/react_native.feature b/features/react_native.feature index 98433a94..3327314d 100644 --- a/features/react_native.feature +++ b/features/react_native.feature @@ -4,15 +4,15 @@ Scenario: React Native sends requests When I build the React Native app Then I should receive 2 requests - And the request 1 is valid for the Build API - And the payload field "appVersion" equals "1.0" for request 1 - And the payload field "apiKey" equals "YOUR-API-KEY-HERE" for request 1 - And the payload field "builderName" is not null for request 1 - And the payload field "buildTool" equals "gradle-android" for request 1 - And the payload field "appVersionCode" equals "1" for request 1 + And the request 0 is valid for the Build API + And the payload field "appVersion" equals "1.0" for request 0 + And the payload field "apiKey" equals "YOUR-API-KEY-HERE" for request 0 + And the payload field "builderName" is not null for request 0 + And the payload field "buildTool" equals "gradle-android" for request 0 + And the payload field "appVersionCode" equals "1" for request 0 - And the request 0 is valid for the Android Mapping API - And the field "apiKey" for multipart request 0 equals "YOUR-API-KEY-HERE" - And the field "versionCode" for multipart request 0 equals "1" - And the field "versionName" for multipart request 0 equals "1.0" - And the field "appId" for multipart request 0 equals "com.bugsnag.android.rnapp" + And the request 1 is valid for the Android Mapping API + And the field "apiKey" for multipart request 1 equals "YOUR-API-KEY-HERE" + And the field "versionCode" for multipart request 1 equals "1" + And the field "versionName" for multipart request 1 equals "1.0" + And the field "appId" for multipart request 1 equals "com.bugsnag.android.rnapp" diff --git a/features/support/env.rb b/features/support/env.rb index 8223264f..98ff6728 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -31,6 +31,16 @@ skip_this_scenario() if is_above_or_equal_to_target(410) end +Before('@skip_agp3_5') do |scenario| + skip_this_scenario() if equals_target(350) +end + +def equals_target(target) + version = ENV["AGP_VERSION"].slice(0, 5) + version = version.gsub(".", "") + return version.to_i == target +end + def is_above_or_equal_to_target(target) version = ENV["AGP_VERSION"].slice(0, 5) version = version.gsub(".", "") diff --git a/src/main/kotlin/com/bugsnag/android/gradle/BugsnagManifestUuidTask.kt b/src/main/kotlin/com/bugsnag/android/gradle/BugsnagManifestUuidTask.kt index a5ddd5ee..e5e8eb64 100644 --- a/src/main/kotlin/com/bugsnag/android/gradle/BugsnagManifestUuidTask.kt +++ b/src/main/kotlin/com/bugsnag/android/gradle/BugsnagManifestUuidTask.kt @@ -50,6 +50,11 @@ abstract class BaseBugsnagManifestUuidTask(objects: ObjectFactory) : DefaultTask */ open class BugsnagManifestUuidTask @Inject constructor(objects: ObjectFactory) : BaseBugsnagManifestUuidTask(objects) { + companion object { + private const val ASSEMBLE_TASK = "assemble" + private const val BUNDLE_TASK = "bundle" + } + @get:Internal internal lateinit var variantOutput: ApkVariantOutput @@ -157,7 +162,7 @@ open class BugsnagManifestUuidTask @Inject constructor(objects: ObjectFactory) : private fun isRunningAssembleTask(project: Project, variant: ApkVariant, output: ApkVariantOutput): Boolean { - return isRunningTaskWithPrefix(project, variant, output, BugsnagPlugin.ASSEMBLE_TASK) + return isRunningTaskWithPrefix(project, variant, output, ASSEMBLE_TASK) } /** @@ -166,7 +171,7 @@ open class BugsnagManifestUuidTask @Inject constructor(objects: ObjectFactory) : private fun isRunningBundleTask(project: Project, variant: ApkVariant, output: ApkVariantOutput): Boolean { - return isRunningTaskWithPrefix(project, variant, output, BugsnagPlugin.BUNDLE_TASK) + return isRunningTaskWithPrefix(project, variant, output, BUNDLE_TASK) } /** @@ -178,8 +183,7 @@ open class BugsnagManifestUuidTask @Inject constructor(objects: ObjectFactory) : output: ApkVariantOutput, prefix: String): Boolean { val taskNames = HashSet() - val plugin = project.plugins.getPlugin(BugsnagPlugin::class.java) - taskNames.addAll(plugin.findTaskNamesForPrefix(variant, output, prefix)) + taskNames.addAll(findTaskNamesForPrefix(variant, output, prefix)) return project.gradle.taskGraph.allTasks.any { task -> taskNames.any { @@ -187,4 +191,34 @@ open class BugsnagManifestUuidTask @Inject constructor(objects: ObjectFactory) : } } } + + + + /** + * Finds all the task names which can be used to assemble a variant, and replaces 'assemble' with the given + * prefix. + * + * E.g. [bundle, bundleRelease, bundleFooRelease] + */ + internal fun findTaskNamesForPrefix(variant: ApkVariant, + output: ApkVariantOutput, + prefix: String): Set { + val variantName = output.name.split("-")[0].capitalize() + val assembleTask = variant.assembleProvider.orNull + + val taskNames = HashSet() + taskNames.add(prefix) + + if (assembleTask != null) { + val assembleTaskName = assembleTask.name + val buildTypeTaskName = assembleTaskName.replace(variantName, "") + val buildType = buildTypeTaskName.replace(ASSEMBLE_TASK, "") + val variantTaskName = assembleTaskName.replace(buildType, "") + + taskNames.add(assembleTaskName.replace(ASSEMBLE_TASK, prefix)) + taskNames.add(buildTypeTaskName.replace(ASSEMBLE_TASK, prefix)) + taskNames.add(variantTaskName.replace(ASSEMBLE_TASK, prefix)) + } + return taskNames + } } diff --git a/src/main/kotlin/com/bugsnag/android/gradle/BugsnagPlugin.kt b/src/main/kotlin/com/bugsnag/android/gradle/BugsnagPlugin.kt index 35ec284d..f555677a 100644 --- a/src/main/kotlin/com/bugsnag/android/gradle/BugsnagPlugin.kt +++ b/src/main/kotlin/com/bugsnag/android/gradle/BugsnagPlugin.kt @@ -5,7 +5,6 @@ import com.android.build.api.dsl.CommonExtension import com.android.build.gradle.AppExtension import com.android.build.gradle.api.ApkVariant import com.android.build.gradle.api.ApkVariantOutput -import com.android.build.gradle.api.ApplicationVariant import com.android.build.gradle.tasks.ExternalNativeBuildTask import com.bugsnag.android.gradle.BugsnagInstallJniLibsTask.Companion.resolveBugsnagArtifacts import com.bugsnag.android.gradle.internal.BugsnagHttpClientHelper @@ -13,13 +12,13 @@ import com.bugsnag.android.gradle.internal.BuildServiceBugsnagHttpClientHelper import com.bugsnag.android.gradle.internal.GradleVersions import com.bugsnag.android.gradle.internal.LegacyBugsnagHttpClientHelper import com.bugsnag.android.gradle.internal.UploadRequestClient +import com.bugsnag.android.gradle.internal.hasDexguardPlugin import com.bugsnag.android.gradle.internal.newUploadRequestClientProvider import com.bugsnag.android.gradle.internal.register import com.bugsnag.android.gradle.internal.versionNumber -import org.gradle.api.DomainObjectSet import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.Task +import org.gradle.api.file.FileCollection import org.gradle.api.file.RegularFile import org.gradle.api.provider.Provider import org.gradle.api.tasks.TaskProvider @@ -44,8 +43,6 @@ class BugsnagPlugin : Plugin { companion object { const val GROUP_NAME = "Bugsnag" private const val CLEAN_TASK = "Clean" - const val ASSEMBLE_TASK = "assemble" - const val BUNDLE_TASK = "bundle" } @Suppress("LongMethod") @@ -91,7 +88,8 @@ class BugsnagPlugin : Plugin { val taskName = computeManifestTaskNameFor(variantName) val manifestInfoOutputFile = project.computeManifestInfoOutputV2(variantName) val buildUuidProvider = project.newUuidProvider() - val manifestUpdater = project.tasks.register(taskName, BugsnagManifestUuidTaskV2::class.java) { + val manifestUpdater = project.tasks.register(taskName, + BugsnagManifestUuidTaskV2::class.java) { it.buildUuid.set(buildUuidProvider) it.manifestInfoProvider.set(manifestInfoOutputFile) } @@ -115,6 +113,7 @@ class BugsnagPlugin : Plugin { } registerBugsnagTasksForVariant( project, + android, variant, bugsnag, httpClientHelperProvider, @@ -144,7 +143,8 @@ class BugsnagPlugin : Plugin { val buildTasks = ndkTasks.filter { !it.name.contains(CLEAN_TASK) }.toSet() if (buildTasks.isNotEmpty()) { - val ndkSetupTask = BugsnagInstallJniLibsTask.register(project, "bugsnagInstallJniLibsTask") { + val ndkSetupTask = BugsnagInstallJniLibsTask.register(project, + "bugsnagInstallJniLibsTask") { val files = resolveBugsnagArtifacts(project) bugsnagArtifacts.from(files) } @@ -163,9 +163,10 @@ class BugsnagPlugin : Plugin { * * See https://sites.google.com/a/android.com/tools/tech-docs/new-build-system/user-guide#TOC-Build-Variants */ - @Suppress("LongParameterList") + @Suppress("LongParameterList", "LongMethod", "ComplexMethod") private fun registerBugsnagTasksForVariant( project: Project, + android: AppExtension, variant: ApkVariant, bugsnag: BugsnagPluginExtension, httpClientHelperProvider: Provider, @@ -173,10 +174,13 @@ class BugsnagPlugin : Plugin { proguardUploadClientProvider: Provider, ndkUploadClientProvider: Provider ) { - variant.outputs.configureEach { - val output = it as ApkVariantOutput - val jvmMinificationEnabled = variant.buildType.isMinifyEnabled || hasDexguardPlugin(project) - val ndkEnabled = isNdkUploadEnabled(bugsnag, project.extensions.getByType(AppExtension::class.java)) + variant.outputs.configureEach { output -> + check(output is ApkVariantOutput) { + "Expected variant output to be ApkVariantOutput but found ${output.javaClass}" + } + val jvmMinificationEnabled = variant.buildType.isMinifyEnabled || project.hasDexguardPlugin() + val ndkEnabled = isNdkUploadEnabled(bugsnag, + project.extensions.getByType(AppExtension::class.java)) // skip tasks for variant if JVM/NDK minification not enabled if (!jvmMinificationEnabled && !ndkEnabled) { @@ -184,48 +188,56 @@ class BugsnagPlugin : Plugin { } // register bugsnag tasks - val manifestInfoFile = registerManifestUuidTask(project, variant, output) - val proguardTask = when { + val manifestInfoFileProvider = registerManifestUuidTask(project, variant, output) + val mappingFilesProvider = createMappingFileProvider(project, variant, output, android) + + val proguardTaskProvider = when { jvmMinificationEnabled -> registerProguardUploadTask( project, - variant, output, bugsnag, - httpClientHelperProvider + httpClientHelperProvider, + manifestInfoFileProvider, + proguardUploadClientProvider, + mappingFilesProvider ) else -> null } - val symbolFileTask = when { + val symbolFileTaskProvider = when { ndkEnabled -> registerSharedObjectUploadTask( project, variant, output, bugsnag, - httpClientHelperProvider + httpClientHelperProvider, + manifestInfoFileProvider, + ndkUploadClientProvider ) else -> null } - val releasesTask = registerReleasesUploadTask(project, variant, output, bugsnag) - - // Set the manifest info provider to prevent reading - // the manifest more than once - proguardTask?.get()?.let { task -> - task.manifestInfoFile.set(manifestInfoFile) - val mappingFileProvider = createMappingFileProvider(project, variant, output) - task.mappingFileProperty.set(mappingFileProvider) - releasesTask.get().jvmMappingFileProperty.set(mappingFileProvider) - task.uploadRequestClient.set(proguardUploadClientProvider) - } - symbolFileTask?.get()?.let { task -> - val ndkSearchDirs = symbolFileTask.get().searchDirectories - releasesTask.get().ndkMappingFileProperty.from(ndkSearchDirs) - task.uploadRequestClient.set(ndkUploadClientProvider) + val releaseUploadTask = registerReleasesUploadTask( + project, + variant, + output, + bugsnag, + manifestInfoFileProvider, + releasesUploadClientProvider, + mappingFilesProvider, + symbolFileTaskProvider != null + ) + + if (shouldUploadMappings(output, bugsnag)) { + if (bugsnag.reportBuilds.get()) { + variant.register(project, releaseUploadTask) + } + if (symbolFileTaskProvider != null && isNdkUploadEnabled(bugsnag, android)) { + variant.register(project, symbolFileTaskProvider) + } + if (proguardTaskProvider != null && bugsnag.uploadJvmMappings.get()) { + variant.register(project, proguardTaskProvider) + } } - releasesTask.get().uploadRequestClient.set(releasesUploadClientProvider) - releasesTask.get().manifestInfoFile.set(manifestInfoFile) - symbolFileTask?.get()?.manifestInfoFile?.set(manifestInfoFile) - releasesTask.get().manifestInfoFile.set(manifestInfoFile) } } @@ -263,31 +275,43 @@ class BugsnagPlugin : Plugin { /** * Creates a bugsnag task to upload proguard mapping file */ + @Suppress("LongParameterList") private fun registerProguardUploadTask( project: Project, - variant: ApkVariant, output: ApkVariantOutput, bugsnag: BugsnagPluginExtension, - httpClientHelperProvider: Provider - ): TaskProvider { + httpClientHelperProvider: Provider, + manifestInfoFileProvider: Provider, + proguardUploadClientProvider: Provider, + mappingFilesProvider: Provider + ): TaskProvider { val outputName = taskNameForOutput(output) val taskName = "uploadBugsnag${outputName}Mapping" val path = "intermediates/bugsnag/requests/proguardFor${outputName}.json" val requestOutputFileProvider = project.layout.buildDirectory.file(path) - return project.tasks.register(taskName) { + + return BugsnagUploadProguardTask.register(project, taskName) { requestOutputFile.set(requestOutputFileProvider) httpClientHelper.set(httpClientHelperProvider) - addTaskToExecutionGraph(this, variant, output, project, bugsnag, bugsnag.uploadJvmMappings.get()) + manifestInfoFile.set(manifestInfoFileProvider) + uploadRequestClient.set(proguardUploadClientProvider) + + mappingFilesProvider.let { + mappingFileProperty.from(it) + } configureWith(bugsnag) } } + @Suppress("LongParameterList") private fun registerSharedObjectUploadTask( project: Project, variant: ApkVariant, output: ApkVariantOutput, bugsnag: BugsnagPluginExtension, - httpClientHelperProvider: Provider + httpClientHelperProvider: Provider, + manifestInfoFileProvider: Provider, + ndkUploadClientProvider: Provider ): TaskProvider { // Create a Bugsnag task to upload NDK mapping file(s) val outputName = taskNameForOutput(output) @@ -297,20 +321,29 @@ class BugsnagPlugin : Plugin { return BugsnagUploadNdkTask.register(project, taskName) { this.requestOutputFile.set(requestOutputFile) projectRoot.set(bugsnag.projectRoot.getOrElse(project.projectDir.toString())) - searchDirectories.from(getSearchDirectories(project, variant)) variantOutput = output objDumpPaths.set(bugsnag.objdumpPaths) httpClientHelper.set(httpClientHelperProvider) - addTaskToExecutionGraph(this, variant, output, project, bugsnag, true) + manifestInfoFile.set(manifestInfoFileProvider) + uploadRequestClient.set(ndkUploadClientProvider) configureWith(bugsnag) + variant.externalNativeBuildProviders.forEach { provider -> + searchDirectories.from(provider.map(ExternalNativeBuildTask::objFolder)) + searchDirectories.from(provider.map(ExternalNativeBuildTask::soFolder)) + } } } + @Suppress("LongParameterList") private fun registerReleasesUploadTask( project: Project, variant: ApkVariant, output: ApkVariantOutput, - bugsnag: BugsnagPluginExtension + bugsnag: BugsnagPluginExtension, + manifestInfoFileProvider: Provider, + releasesUploadClientProvider: Provider, + mappingFilesProvider: Provider?, + checkSearchDirectories: Boolean ): TaskProvider { val outputName = taskNameForOutput(output) val taskName = "bugsnagRelease${outputName}Task" @@ -327,91 +360,37 @@ class BugsnagPlugin : Plugin { metadata.set(bugsnag.metadata) builderName.set(bugsnag.builderName) gradleVersion.set(project.gradle.gradleVersion) - addTaskToExecutionGraph(this, variant, output, project, bugsnag, bugsnag.reportBuilds.get()) - configureMetadata() - } - } - - private fun addTaskToExecutionGraph(task: Task, - variant: ApkVariant, - output: ApkVariantOutput, - project: Project, - bugsnag: BugsnagPluginExtension, - autoUpload: Boolean) { - if (shouldUploadDebugMappings(output, bugsnag)) { - findAssembleBundleTasks(project, variant, output).forEach { - task.mustRunAfter(it) - - if (autoUpload) { - it.finalizedBy(task) + manifestInfoFile.set(manifestInfoFileProvider) + uploadRequestClient.set(releasesUploadClientProvider) + mappingFilesProvider?.let { + jvmMappingFileProperty.from(it) + } + if (checkSearchDirectories) { + variant.externalNativeBuildProviders.forEach { task -> + ndkMappingFileProperty.from(task.map { it.objFolder }) + ndkMappingFileProperty.from(task.map { it.soFolder }) } } + configureMetadata() } } - private fun shouldUploadDebugMappings(output: ApkVariantOutput, - bugsnag: BugsnagPluginExtension): Boolean { - return !output.name.toLowerCase().endsWith("debug") || bugsnag.uploadDebugBuildMappings.get() + private fun shouldUploadMappings( + output: ApkVariantOutput, + bugsnag: BugsnagPluginExtension + ): Boolean { + return !output.name.toLowerCase().endsWith( + "debug") || bugsnag.uploadDebugBuildMappings.get() } private fun isNdkUploadEnabled(bugsnag: BugsnagPluginExtension, - android: AppExtension): Boolean { + android: AppExtension): Boolean { val usesCmake = android.externalNativeBuild.cmake.path != null val usesNdkBuild = android.externalNativeBuild.ndkBuild.path != null val default = usesCmake || usesNdkBuild return bugsnag.uploadNdkMappings.getOrElse(default) } - /** - * Fetches all the assemble and bundle tasks in the current project that match the variant - * - * Expected behaviour: [assemble, assembleJavaExampleRelease, assembleJavaExample, assembleRelease, - * bundle, bundleJavaExampleRelease, bundleJavaExample, bundleRelease] - * - * @param output the variantOutput - * @param project the current project - * @return the assemble + bundle tasks - */ - private fun findAssembleBundleTasks(project: Project, - variant: ApkVariant, - output: ApkVariantOutput): Set { - val taskNames = HashSet() - taskNames.addAll(findTaskNamesForPrefix(variant, output, ASSEMBLE_TASK)) - taskNames.addAll(findTaskNamesForPrefix(variant, output, BUNDLE_TASK)) - - return project.tasks.filter { - taskNames.contains(it.name) - }.toSet() - } - - /** - * Finds all the task names which can be used to assemble a variant, and replaces 'assemble' with the given - * prefix. - * - * E.g. [bundle, bundleRelease, bundleFooRelease] - */ - internal fun findTaskNamesForPrefix(variant: ApkVariant, - output: ApkVariantOutput, - prefix: String): Set { - val variantName = output.name.split("-")[0].capitalize() - val assembleTask = variant.assembleProvider.orNull - - val taskNames = HashSet() - taskNames.add(prefix) - - if (assembleTask != null) { - val assembleTaskName = assembleTask.name - val buildTypeTaskName = assembleTaskName.replace(variantName, "") - val buildType = buildTypeTaskName.replace(ASSEMBLE_TASK, "") - val variantTaskName = assembleTaskName.replace(buildType, "") - - taskNames.add(assembleTaskName.replace(ASSEMBLE_TASK, prefix)) - taskNames.add(buildTypeTaskName.replace(ASSEMBLE_TASK, prefix)) - taskNames.add(variantTaskName.replace(ASSEMBLE_TASK, prefix)) - } - return taskNames - } - fun taskNameForVariant(variant: ApkVariant): String { return variant.name.capitalize() } @@ -429,39 +408,15 @@ class BugsnagPlugin : Plugin { return layout.buildDirectory.file(path) } - private fun Project.computeManifestInfoOutputV1(output: ApkVariantOutput): Provider { + private fun Project.computeManifestInfoOutputV1( + output: ApkVariantOutput): Provider { val path = "intermediates/bugsnag/manifestInfoFor${taskNameForOutput(output)}.json" return layout.buildDirectory.file(path) } private fun Project.newUuidProvider(): Provider { - val bugsnag = extensions.findByType(BugsnagPluginExtension::class.java)!! return provider { UUID.randomUUID().toString() } } - - /** - * Returns true if the DexGuard plugin has been applied to the project - */ - fun hasDexguardPlugin(project: Project): Boolean { - return project.pluginManager.hasPlugin("dexguard") - } - - /** - * Returns true if a project has configured multiple variant outputs. - * - * This calculation is based on a heuristic - the number of variantOutputs in a project must be - * greater than the number of variants. - */ - fun hasMultipleOutputs(android: AppExtension): Boolean { - val variants: DomainObjectSet = android.applicationVariants - val variantSize = variants.count() - var outputSize = 0 - - variants.forEach { variant -> - outputSize += variant.outputs.count() - } - return outputSize > variantSize - } } diff --git a/src/main/kotlin/com/bugsnag/android/gradle/BugsnagReleasesTask.kt b/src/main/kotlin/com/bugsnag/android/gradle/BugsnagReleasesTask.kt index 79c5a55a..be2f42f3 100644 --- a/src/main/kotlin/com/bugsnag/android/gradle/BugsnagReleasesTask.kt +++ b/src/main/kotlin/com/bugsnag/android/gradle/BugsnagReleasesTask.kt @@ -71,12 +71,10 @@ sealed class BugsnagReleasesTask( // should take the JVM + NDK mapping files as inputs because the manifestInfo will // not necessarily vary between different builds. it is not guaranteed that // either of these properties will be set so they are marked as optional. - @get:PathSensitive(NONE) - @get:InputFile + @get:InputFiles @get:Optional - val jvmMappingFileProperty: RegularFileProperty = objects.fileProperty() + abstract val jvmMappingFileProperty: ConfigurableFileCollection - @get:PathSensitive(NONE) @get:InputFiles @get:Optional abstract val ndkMappingFileProperty: ConfigurableFileCollection @@ -367,9 +365,12 @@ internal open class BugsnagReleasesTaskLegacy @Inject constructor( providerFactory: ProviderFactory, projectLayout: ProjectLayout ) : BugsnagReleasesTask(objects, providerFactory) { + @Suppress("DEPRECATION") // Here for backward compat + @get:InputFiles + @get:Optional + override val jvmMappingFileProperty: ConfigurableFileCollection = projectLayout.configurableFiles() @Suppress("DEPRECATION") // Here for backward compat - @get:PathSensitive(NONE) @get:InputFiles @get:Optional override val ndkMappingFileProperty: ConfigurableFileCollection = projectLayout.configurableFiles() @@ -382,7 +383,10 @@ internal open class BugsnagReleasesTaskGradle53Plus @Inject constructor( objects: ObjectFactory, providerFactory: ProviderFactory ) : BugsnagReleasesTask(objects, providerFactory) { - @get:PathSensitive(NONE) + @get:InputFiles + @get:Optional + override val jvmMappingFileProperty: ConfigurableFileCollection = objects.fileCollection() + @get:InputFiles @get:Optional override val ndkMappingFileProperty: ConfigurableFileCollection = objects.fileCollection() @@ -401,7 +405,10 @@ internal open class BugsnagReleasesTaskGradle6Plus @Inject constructor( providerFactory: ProviderFactory, private val execOperations: ExecOperations ) : BugsnagReleasesTask(objects, providerFactory) { - @get:PathSensitive(NONE) + @get:InputFiles + @get:Optional + override val jvmMappingFileProperty: ConfigurableFileCollection = objects.fileCollection() + @get:InputFiles @get:Optional override val ndkMappingFileProperty: ConfigurableFileCollection = objects.fileCollection() diff --git a/src/main/kotlin/com/bugsnag/android/gradle/BugsnagUploadNdkTask.kt b/src/main/kotlin/com/bugsnag/android/gradle/BugsnagUploadNdkTask.kt index 6ab578bb..f5820c4b 100644 --- a/src/main/kotlin/com/bugsnag/android/gradle/BugsnagUploadNdkTask.kt +++ b/src/main/kotlin/com/bugsnag/android/gradle/BugsnagUploadNdkTask.kt @@ -1,5 +1,6 @@ package com.bugsnag.android.gradle +import com.android.build.VariantOutput import com.android.build.gradle.AppExtension import com.android.build.gradle.api.ApkVariantOutput import com.bugsnag.android.gradle.Abi.Companion.findByName @@ -11,10 +12,7 @@ import com.bugsnag.android.gradle.internal.md5HashCode import com.bugsnag.android.gradle.internal.property import com.bugsnag.android.gradle.internal.register import com.bugsnag.android.gradle.internal.versionNumber -import okhttp3.RequestBody import okhttp3.RequestBody.Companion.asRequestBody -import okio.HashingSource -import okio.blackholeSink import okio.buffer import okio.gzip import okio.sink @@ -115,12 +113,47 @@ sealed class BugsnagUploadNdkTask( fun upload() { logger.lifecycle("Starting ndk upload") val searchDirs = searchDirectories.files.toList() - val files = findSharedObjectMappingFiles(project, variantOutput, searchDirs) + val files = findSharedObjectMappingFiles(variantOutput, searchDirs) logger.lifecycle("Processing shared object files") processFiles(files) requestOutputFile.asFile.get().writeText("OK") } + private fun findSharedObjectMappingFiles( + variantOutput: ApkVariantOutput, + searchDirectories: List + ): Collection { + val splitArch = variantOutput.getFilter(VariantOutput.FilterType.ABI) + return searchDirectories.flatMap { findSharedObjectFiles(it, splitArch) } + // sort SO files alphabetically by architecture for consistent request order + .toSortedSet(compareBy { it.parentFile.name }) + } + + /** + * Searches the subdirectories of a given path for SO files. These are added to a + * collection and returned if they should be uploaded by the current task. + * + * If the variantOutput is an APK split the splitArch parameter should be non-null, + * as this allows the avoidance of unnecessary uploads of all architectures for each split. + * + * @param searchDirectory The parent path to search. Each subdirectory should + * represent an architecture + * @param abiArchitecture The architecture of the ABI split, or null if this is not an APK split. + */ + private fun findSharedObjectFiles( + searchDirectory: File, + abiArchitecture: String? + ): Collection { + return if (searchDirectory.exists() && searchDirectory.isDirectory) { + searchDirectory.walkTopDown() + .onEnter { archDir -> abiArchitecture == null || archDir.name == abiArchitecture } + .filter { file -> file.extension == "so" } + .toSet() + } else { + emptySet() + } + } + private fun processFiles(files: Collection) { logger.info("Bugsnag: Found shared object files for upload: $files") diff --git a/src/main/kotlin/com/bugsnag/android/gradle/BugsnagUploadProguardTask.kt b/src/main/kotlin/com/bugsnag/android/gradle/BugsnagUploadProguardTask.kt index f92cb861..154d0690 100644 --- a/src/main/kotlin/com/bugsnag/android/gradle/BugsnagUploadProguardTask.kt +++ b/src/main/kotlin/com/bugsnag/android/gradle/BugsnagUploadProguardTask.kt @@ -1,24 +1,29 @@ package com.bugsnag.android.gradle import com.bugsnag.android.gradle.internal.BugsnagHttpClientHelper +import com.bugsnag.android.gradle.internal.GradleVersions import com.bugsnag.android.gradle.internal.UploadRequestClient import com.bugsnag.android.gradle.internal.md5HashCode import com.bugsnag.android.gradle.internal.property -import okhttp3.MultipartBody -import okhttp3.Request +import com.bugsnag.android.gradle.internal.register +import com.bugsnag.android.gradle.internal.versionNumber import okhttp3.RequestBody.Companion.asRequestBody import org.gradle.api.DefaultTask +import org.gradle.api.Project +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.ProjectLayout import org.gradle.api.file.RegularFileProperty import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.Property import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.Internal import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity.NONE import org.gradle.api.tasks.TaskAction -import java.io.IOException +import org.gradle.api.tasks.TaskProvider import javax.inject.Inject /** @@ -34,7 +39,7 @@ import javax.inject.Inject * it is usually safe to have this be the absolute last task executed during * a build. */ -open class BugsnagUploadProguardTask @Inject constructor( +sealed class BugsnagUploadProguardTask @Inject constructor( objects: ObjectFactory ) : DefaultTask(), AndroidManifestInfoReceiver, BugsnagFileUploadTask { @@ -49,9 +54,8 @@ open class BugsnagUploadProguardTask @Inject constructor( @get:Internal override val httpClientHelper: Property = objects.property() - @get:PathSensitive(NONE) - @get:InputFile - val mappingFileProperty: RegularFileProperty = objects.fileProperty() + @get:InputFiles + abstract val mappingFileProperty: ConfigurableFileCollection @get:PathSensitive(NONE) @get:InputFile @@ -77,7 +81,7 @@ open class BugsnagUploadProguardTask @Inject constructor( @TaskAction fun upload() { - val mappingFile = mappingFileProperty.asFile.get() + val mappingFile = mappingFileProperty.singleFile if (mappingFile.length() == 0L) { // proguard's -dontobfuscate generates an empty mapping file logger.warn("Bugsnag: Ignoring empty proguard file") return @@ -108,4 +112,46 @@ open class BugsnagUploadProguardTask @Inject constructor( logger.lifecycle("Bugsnag: JVM mapping file complete for $mappingFile") } + companion object { + + /** + * Registers the appropriate subtype to this [project] with the given [name] and + * [configurationAction] + */ + internal fun register( + project: Project, + name: String, + configurationAction: BugsnagUploadProguardTask.() -> Unit + ): TaskProvider { + return when { + project.gradle.versionNumber() >= GradleVersions.VERSION_5_3 -> { + project.tasks.register(name, configurationAction) + } else -> { + project.tasks.register(name, configurationAction) + } + } + } + } + +} + +/** + * Legacy [BugsnagUploadProguardTask] task that requires using [getProject] and + * [ProjectLayout.configurableFiles]. + */ +internal open class BugsnagUploadProguardTaskLegacy @Inject constructor( + objects: ObjectFactory, + projectLayout: ProjectLayout +) : BugsnagUploadProguardTask(objects) { + + @get:InputFiles + override val mappingFileProperty: ConfigurableFileCollection = projectLayout.configurableFiles() +} + +internal open class BugsnagUploadProguardTaskGradle53Plus @Inject constructor( + objects: ObjectFactory +) : BugsnagUploadProguardTask(objects) { + + @get:InputFiles + override val mappingFileProperty: ConfigurableFileCollection = objects.fileCollection() } diff --git a/src/main/kotlin/com/bugsnag/android/gradle/MappingFileProvider.kt b/src/main/kotlin/com/bugsnag/android/gradle/MappingFileProvider.kt index 6e2bdbbc..70bb7a5c 100644 --- a/src/main/kotlin/com/bugsnag/android/gradle/MappingFileProvider.kt +++ b/src/main/kotlin/com/bugsnag/android/gradle/MappingFileProvider.kt @@ -3,45 +3,39 @@ package com.bugsnag.android.gradle import com.android.build.gradle.AppExtension import com.android.build.gradle.api.ApkVariant import com.android.build.gradle.api.ApkVariantOutput -import org.gradle.api.GradleException +import com.bugsnag.android.gradle.internal.hasDexguardPlugin +import com.bugsnag.android.gradle.internal.hasMultipleOutputs import org.gradle.api.Project -import org.gradle.api.file.Directory +import org.gradle.api.file.FileCollection import org.gradle.api.file.RegularFile import org.gradle.api.provider.Provider import java.io.File import java.nio.file.Paths /** - * Creates a Provider which finds the mapping file for a given variantOutput. + * Creates a Provider which finds the mapping file for a given variantOutput and filters out + * non-existent outputs. */ -fun createMappingFileProvider(project: Project, - variant: ApkVariant, - variantOutput: ApkVariantOutput): Provider { - val fileProvider: Provider = project.provider { - val mappingFile = findMappingFile(project, variant, variantOutput) - val logger = project.logger - logger.info("Bugsnag: Using mapping file: $mappingFile") - - // If we haven't enabled proguard for this variant, or the proguard - // configuration includes -dontobfuscate, the mapping file - // will not exist (but we also won't need it). - if (mappingFile == null) { - throw IllegalStateException("Mapping file not found: $mappingFile") - } - mappingFile - } - return project.layout.file(fileProvider) +internal fun createMappingFileProvider( + project: Project, + variant: ApkVariant, + variantOutput: ApkVariantOutput, + android: AppExtension +): Provider { + return findMappingFiles(project, variant, variantOutput, android) + .map { files -> files.filter { it.exists() } } } -private fun findMappingFile(project: Project, - variant: ApkVariant, - variantOutput: ApkVariantOutput): File? { - val plugin = project.plugins.getPlugin(BugsnagPlugin::class.java) - val android = project.extensions.getByType(AppExtension::class.java) - if (plugin.hasDexguardPlugin(project) && plugin.hasMultipleOutputs(android)) { +private fun findMappingFiles( + project: Project, + variant: ApkVariant, + variantOutput: ApkVariantOutput, + android: AppExtension +): Provider { + if (project.hasDexguardPlugin() && android.hasMultipleOutputs()) { val mappingFile = findDexguardMappingFile(project, variant, variantOutput) if (mappingFile.exists()) { - return mappingFile + return project.provider { project.layout.files(mappingFile) } } else { project.logger.warn("Bugsnag: Could not find DexGuard mapping file at: $mappingFile -" + " falling back to AGP mapping file value") @@ -51,16 +45,9 @@ private fun findMappingFile(project: Project, // Use AGP supplied value, preferring the new "getMappingFileProvider" API but falling back // to the old "mappingFile" API if necessary return try { - val mappingFileProvider = variant.mappingFileProvider.orNull - - // We will warn about not finding a mapping file later, so there's no need to warn here - if (mappingFileProvider == null || mappingFileProvider.isEmpty) { - null - } else { - mappingFileProvider.singleFile - } + variant.mappingFileProvider } catch (exc: Throwable) { - variant.mappingFile + project.provider { project.layout.files(variant.mappingFile) } } } diff --git a/src/main/kotlin/com/bugsnag/android/gradle/SharedObjectMappingFileProvider.kt b/src/main/kotlin/com/bugsnag/android/gradle/SharedObjectMappingFileProvider.kt deleted file mode 100644 index 16288098..00000000 --- a/src/main/kotlin/com/bugsnag/android/gradle/SharedObjectMappingFileProvider.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.bugsnag.android.gradle - -import com.android.build.VariantOutput -import com.android.build.gradle.api.ApkVariant -import com.android.build.gradle.api.ApkVariantOutput -import com.android.build.gradle.tasks.ExternalNativeBuildTask -import org.gradle.api.Project -import org.gradle.api.file.ConfigurableFileCollection -import java.io.File - -fun getSearchDirectories(project: Project, - variant: ApkVariant): ConfigurableFileCollection { - val bugsnag = project.extensions.getByType(BugsnagPluginExtension::class.java) - val searchDirectories = bugsnag.sharedObjectPaths.get().toMutableSet() - - resolveExternalNativeBuildTasks(variant).forEach { task -> - searchDirectories.add(task.objFolder) - searchDirectories.add(task.soFolder) - } - return project.files(searchDirectories.toList()) -} - -fun findSharedObjectMappingFiles(project: Project, - variantOutput: ApkVariantOutput, - searchDirectories: List): Collection { - val symbolPath = findSymbolPath(variantOutput) - project.logger.info("Bugsnag: using symbolPath $symbolPath") - val splitArch = variantOutput.getFilter(VariantOutput.FilterType.ABI) - - return searchDirectories.flatMap { findSharedObjectFiles(it, splitArch) } - .toSet() // dedupe SO files - .toList() - .sortedBy { it.parentFile.name } // sort SO files alphabetically by architecture for consistent request order -} - -private fun findSymbolPath(variantOutput: ApkVariantOutput?): File { - val resources = variantOutput!!.processResourcesProvider.get() - return resources.property("textSymbolOutputFile") as? File - ?: throw IllegalStateException("Could not find symbol path") -} - -private fun resolveExternalNativeBuildTasks(variant: ApkVariant): Collection { - return variant.externalNativeBuildProviders.mapNotNull { it.orNull } -} - -/** - * Searches the subdirectories of a given path for SO files. These are added to a - * collection and returned if they should be uploaded by the current task. - * - * If the variantOutput is an APK split the splitArch parameter should be non-null, - * as this allows the avoidance of unnecessary uploads of all architectures for each split. - * - * @param searchDirectory The parent path to search. Each subdirectory should - * represent an architecture - * @param abiArchitecture The architecture of the ABI split, or null if this is not an APK split. - */ -private fun findSharedObjectFiles(searchDirectory: File, - abiArchitecture: String?): Collection { - val sharedObjectFiles = mutableSetOf() - if (searchDirectory.exists() && searchDirectory.isDirectory) { - searchDirectory.listFiles() - .filter { archDir -> archDir.exists() && archDir.isDirectory } - .filter { archDir -> abiArchitecture == null || archDir.name == abiArchitecture } - .forEach { - val archFiles = it.listFiles() - .filter { file -> file.name.endsWith(".so") } - sharedObjectFiles.addAll(archFiles) - } - } - return sharedObjectFiles -} - diff --git a/src/main/kotlin/com/bugsnag/android/gradle/internal/GradleUtil.kt b/src/main/kotlin/com/bugsnag/android/gradle/internal/GradleUtil.kt index d0d59136..2a8b0a9e 100644 --- a/src/main/kotlin/com/bugsnag/android/gradle/internal/GradleUtil.kt +++ b/src/main/kotlin/com/bugsnag/android/gradle/internal/GradleUtil.kt @@ -1,10 +1,20 @@ -@file:Suppress("MatchingDeclarationName") // This file contains multiple top-level members +@file:Suppress("MatchingDeclarationName", "TooManyFunctions") // This file contains multiple top-level members package com.bugsnag.android.gradle.internal -import okio.HashingSource +import com.android.build.gradle.AppExtension +import com.android.build.gradle.api.ApplicationVariant +import com.android.build.gradle.api.BaseVariant +// TODO use the new replacement when min AGP version is 4.0 +import com.android.builder.model.Version.ANDROID_GRADLE_PLUGIN_VERSION +import com.android.build.gradle.internal.api.ApplicationVariantImpl +import com.android.build.gradle.internal.scope.TaskContainer as AgpTaskContainer +import com.android.build.gradle.internal.scope.MutableTaskContainer +import okio.HashingSink import okio.blackholeSink import okio.buffer import okio.source +import org.gradle.api.DomainObjectSet +import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.invocation.Gradle import org.gradle.api.model.ObjectFactory @@ -26,14 +36,131 @@ internal object GradleVersions { internal fun Gradle.versionNumber(): VersionNumber = VersionNumber.parse(gradleVersion) +internal object AgpVersions { + // Use baseVersion to avoid any qualifiers like `-alpha06` + val CURRENT: VersionNumber = VersionNumber.parse(ANDROID_GRADLE_PLUGIN_VERSION).baseVersion + val VERSION_3_4: VersionNumber = VersionNumber.parse("3.4.0") + val VERSION_3_5: VersionNumber = VersionNumber.parse("3.5.0") + val VERSION_4_0: VersionNumber = VersionNumber.parse("4.0.0") +} + /** A fast file hash that don't load the entire file contents into memory at once. */ internal fun File.md5HashCode(): Int { - return HashingSource.md5(source()).use { hashingSource -> - hashingSource.buffer().readAll(blackholeSink()) - hashingSource.hash.hashCode() + return HashingSink.md5(blackholeSink()).use { sink -> + source().buffer().use { fileSource -> + fileSource.readAll(sink) + } + sink.hash.hashCode() + } +} + +@Suppress("SpreadOperator") +internal fun TaskProvider.dependsOn(vararg tasks: TaskProvider): TaskProvider { + if (tasks.isEmpty().not()) { + configure { it.dependsOn(*tasks) } + } + + return this +} + +/** An alternative to [BaseVariant.register] that accepts a [TaskProvider] input. */ +internal fun BaseVariant.register(project: Project, provider: TaskProvider) { + val success = when { + AgpVersions.CURRENT >= AgpVersions.VERSION_4_0 -> { + registerAgp4(provider) + } + AgpVersions.CURRENT >= AgpVersions.VERSION_3_4 -> { + registerAgp3(provider) + } + else -> false + } + + if (!success) { + registerManual(project, provider) + } +} + +@Suppress("TooGenericExceptionCaught") +private fun BaseVariant.registerAgp4(provider: TaskProvider): Boolean { + return try { + // This is of type ComponentPropertiesImpl + val componentProperties = javaClass.getField("componentProperties") + .apply { isAccessible = true } + .get(this) + val taskContainer = componentProperties.javaClass.getMethod("getTaskContainer") + .apply { isAccessible = true } + .invoke(componentProperties) as AgpTaskContainer + taskContainer.register(provider) + true + } catch (t: Throwable) { + false + } +} + +@Suppress("TooGenericExceptionCaught") +private fun BaseVariant.registerAgp3(provider: TaskProvider): Boolean { + return try { + if (this is ApplicationVariantImpl) { + variantData.taskContainer.register(provider) + } + true + } catch (t: Throwable) { + false } } +private fun AgpTaskContainer.register(provider: TaskProvider) { + assembleTask.dependsOn(provider) + bundleLibraryTask?.dependsOn(provider) + if (this is MutableTaskContainer) { + bundleTask?.dependsOn(provider) + } +} + +private fun BaseVariant.registerManual(project: Project, provider: TaskProvider) { + assembleProvider.dependsOn(provider) + val bundleName = "bundle" + assembleProvider.name.removePrefix("assemble") + project.tasks.matching { it.name == bundleName } + .configureEach { + it.dependsOn(provider) + } + + if (AgpVersions.CURRENT.major == AgpVersions.VERSION_3_5.major + && AgpVersions.CURRENT.minor == AgpVersions.VERSION_3_5.minor) { + // workaround - AGP 3.5.0 executes upload tasks before the package tasks + // so we need to manually specify that it must run after the package task + // this stops config avoidance for AGP 3.5.0 only + project.tasks.matching { it.name.contains("package") } + .configureEach { packageTask -> + provider.get().mustRunAfter(packageTask) + } + } +} + +/** + * Returns true if a project has configured multiple variant outputs. + * + * This calculation is based on a heuristic - the number of variantOutputs in a project must be + * greater than the number of variants. + */ +internal fun AppExtension.hasMultipleOutputs(): Boolean { + val variants: DomainObjectSet = applicationVariants + val variantSize = variants.count() + var outputSize = 0 + + variants.forEach { variant -> + outputSize += variant.outputs.count() + } + return outputSize > variantSize +} + +/** + * Returns true if the DexGuard plugin has been applied to the project + */ +fun Project.hasDexguardPlugin(): Boolean { + return pluginManager.hasPlugin("dexguard") +} + /* Borrowed helper functions from the Gradle Kotlin DSL. */ /**