Skip to content

Commit

Permalink
Merge pull request #239 from bugsnag/v5-upload-tasks
Browse files Browse the repository at this point in the history
Make upload tasks support up-to-date checks
  • Loading branch information
fractalwrench authored Jul 22, 2020
2 parents c896506 + 720677a commit d5f3e6e
Show file tree
Hide file tree
Showing 13 changed files with 225 additions and 229 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## 5.0.0 (TBD)

Make upload tasks support up-to-date checks
[#239](https://github.com/bugsnag/bugsnag-android-gradle-plugin/pull/239)

Avoid unnecessary SO mapping file uploads for ABI splits
[#238](https://github.com/bugsnag/bugsnag-android-gradle-plugin/pull/238)

Expand Down
12 changes: 3 additions & 9 deletions detekt-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,21 @@
<SmellBaseline>
<Blacklist></Blacklist>
<Whitelist>
<ID>ComplexCondition:AndroidManifestParser.kt$AndroidManifestParser$apiKey == null || versionCode == null || buildUUID == null || versionName == null</ID>
<ID>ComplexCondition:AndroidManifestParser.kt$AndroidManifestParser$apiKey == null || "" == apiKey || versionCode == null || buildUUID == null || versionName == null || applicationId == null</ID>
<ID>LongParameterList:BugsnagPlugin.kt$BugsnagPlugin$(task: Task, variant: ApkVariant, output: ApkVariantOutput, project: Project, bugsnag: BugsnagPluginExtension, autoUpload: Boolean)</ID>
<ID>MagicNumber:BugsnagMultiPartUploadRequest.kt$BugsnagMultiPartUploadRequest$200</ID>
<ID>MagicNumber:BugsnagPluginExtension.kt$BugsnagPluginExtension$60000</ID>
<ID>MagicNumber:BugsnagReleasesTask.kt$BugsnagReleasesTask$200</ID>
<ID>MagicNumber:BugsnagUploadNdkTask.kt$BugsnagUploadNdkTask.Companion$8192</ID>
<ID>MaxLineLength:AndroidManifestParser.kt$AndroidManifestParser$throw IllegalStateException("Missing apiKey/versionCode/buildUuid/versionName, required to upload to bugsnag.")</ID>
<ID>MaxLineLength:BugsnagManifestUuidTask.kt$BugsnagManifestUuidTask$private</ID>
<ID>MaxLineLength:BugsnagReleasesTask.kt$BugsnagReleasesTask$private</ID>
<ID>MaxLineLength:BugsnagUploadNdkTask.kt$BugsnagUploadNdkTask$check(!(!objDumpFile.exists() || !objDumpFile.canExecute())) { "Failed to find executable objdump at $objDumpFile" }</ID>
<ID>MaxLineLength:BugsnagUploadNdkTask.kt$BugsnagUploadNdkTask.Companion$return File("$ndkDir/toolchains/${abi.toolchainPrefix}-4.9/prebuilt/$osName/bin/${abi.objdumpPrefix}-$executable")</ID>
<ID>NestedBlockDepth:BugsnagReleasesTask.kt$BugsnagReleasesTask$deliverPayload</ID>
<ID>NestedBlockDepth:BugsnagUploadNdkTask.kt$BugsnagUploadNdkTask$findSharedObjectFiles</ID>
<ID>ReturnCount:BugsnagManifestUuidTask.kt$BugsnagManifestUuidTask$private fun getManifestOutputDir(processManifest: ManifestProcessorTask, project: Project): File?</ID>
<ID>ReturnCount:BugsnagMultiPartUploadRequest.kt$BugsnagMultiPartUploadRequest$private fun uploadToServer(project: Project, mpEntity: MultipartEntity?, bugsnag: BugsnagPluginExtension): Boolean</ID>
<ID>ReturnCount:BugsnagReleasesTask.kt$BugsnagReleasesTask$private fun deliverPayload(payload: JSONObject, manifestInfo: AndroidManifestInfo, bugsnag: BugsnagPluginExtension): Boolean</ID>
<ID>SpreadOperator:BugsnagReleasesTask.kt$BugsnagReleasesTask$(*cmd)</ID>
<ID>TooGenericExceptionCaught:BugsnagManifestUuidTask.kt$BugsnagManifestUuidTask$exc: Throwable</ID>
<ID>TooGenericExceptionCaught:BugsnagMultiPartUploadRequest.kt$BugsnagMultiPartUploadRequest$exc: Throwable</ID>
<ID>TooGenericExceptionCaught:BugsnagUploadNdkTask.kt$BugsnagUploadNdkTask$e: Exception</ID>
<ID>TooGenericExceptionCaught:BugsnagUploadNdkTask.kt$BugsnagUploadNdkTask$ex: Throwable</ID>
<ID>TooGenericExceptionCaught:BugsnagUploadProguardTask.kt$BugsnagUploadProguardTask$exc: Throwable</ID>
<ID>TooGenericExceptionCaught:MappingFileProvider.kt$exc: Throwable</ID>
<ID>TooManyFunctions:BugsnagPlugin.kt$BugsnagPlugin$BugsnagPlugin</ID>
</Whitelist>
</SmellBaseline>
9 changes: 1 addition & 8 deletions features/agp-features/fail_on_upload_error.feature
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,10 @@ Scenario: No uploads or build failures when obfuscation is disabled

Scenario: Upload failure due to empty API key
When I build the failing "default_app" using the "empty_api_key" bugsnag config
Then I should receive 1 request
And the request 0 is valid for the Build API
Then I should receive 0 requests
And the exit code equals 1

Scenario: Upload failure due to connectivity failure
When I build the failing "default_app" using the "wrong_endpoint" bugsnag config
Then I should receive 0 requests
And the exit code equals 1

Scenario: Upload failure due to missing mapping file
When I build the failing "missing_mapping_file" using the "standard" bugsnag config
Then I should receive 1 request
And the request 0 is valid for the Build API
And the exit code equals 1

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ data class AndroidManifestInfo(
var apiKey: String,
var versionCode: String,
var buildUUID: String,
var versionName: String
var versionName: String,
var applicationId: String
) {
internal fun write(file: File) {
file.sink().buffer().use {
Expand All @@ -26,7 +27,8 @@ data class AndroidManifestInfo(
"apiKey",
"versionCode",
"buildUUID",
"versionName"
"versionName",
"applicationId"
)

@Suppress("MagicNumber") // They are indices into the OPTIONS field above
Expand All @@ -39,18 +41,20 @@ data class AndroidManifestInfo(
lateinit var versionCode: String
lateinit var buildUUID: String
lateinit var versionName: String
lateinit var applicationId: String
reader.beginObject()
while (reader.hasNext()) {
when (reader.selectName(OPTIONS)) {
0 -> apiKey = reader.nextString()
1 -> versionCode = reader.nextString()
2 -> buildUUID = reader.nextString()
3 -> versionName = reader.nextString()
4 -> applicationId = reader.nextString()
-1 -> reader.skipValue()
}
}
reader.endObject()
return AndroidManifestInfo(apiKey, versionCode, buildUUID, versionName)
return AndroidManifestInfo(apiKey, versionCode, buildUUID, versionName, applicationId)
}

override fun toJson(writer: JsonWriter, value: AndroidManifestInfo?) {
Expand All @@ -67,6 +71,8 @@ data class AndroidManifestInfo(
.value(value.buildUUID)
.name("versionName")
.value(value.versionName)
.name("applicationId")
.value(value.applicationId)
.endObject()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import java.io.File
import java.io.FileWriter
import java.io.IOException
import java.io.PrintWriter
import java.util.UUID
import javax.xml.parsers.ParserConfigurationException

class AndroidManifestParser {
Expand Down Expand Up @@ -50,10 +49,19 @@ class AndroidManifestParser {
if (versionName == null) {
logger.warn("Could not find 'android:versionName' value in your AndroidManifest.xml")
}
if (apiKey == null || versionCode == null || buildUUID == null || versionName == null) {
throw IllegalStateException("Missing apiKey/versionCode/buildUuid/versionName, required to upload to bugsnag.")

// Get the application ID
val applicationId = getApplicationId(root)
if (applicationId == null) {
logger.warn("Could not find 'package' value in your AndroidManifest.xml")
}

if (apiKey == null || "" == apiKey || versionCode == null ||
buildUUID == null || versionName == null || applicationId == null) {
throw IllegalStateException("Missing apiKey/versionCode/buildUuid/versionName/package," +
" required to upload to bugsnag.")
}
return AndroidManifestInfo(apiKey, versionCode, buildUUID, versionName)
return AndroidManifestInfo(apiKey, versionCode, buildUUID, versionName, applicationId)
}

@Throws(ParserConfigurationException::class, SAXException::class, IOException::class)
Expand Down Expand Up @@ -114,6 +122,10 @@ class AndroidManifestParser {
return versionCode ?: xml.attribute(namespace.get(ATTR_VERSION_CODE)) as String?
}

private fun getApplicationId(xml: Node): String? {
return xml.attribute(ATTR_APPLICATION_ID) as String?
}

companion object {
private const val TAG_APPLICATION = "application"
private const val TAG_META_DATA = "meta-data"
Expand All @@ -123,6 +135,7 @@ class AndroidManifestParser {
private const val TAG_APP_VERSION = "com.bugsnag.android.APP_VERSION"
private const val ATTR_NAME = "name"
private const val ATTR_VALUE = "value"
private const val ATTR_APPLICATION_ID = "package"
private const val ATTR_VERSION_CODE = "versionCode"
private const val ATTR_VERSION_NAME = "versionName"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package com.bugsnag.android.gradle

import com.android.build.gradle.api.ApkVariant
import com.android.build.gradle.api.ApkVariantOutput
import org.apache.http.ParseException
import org.apache.http.client.HttpClient
import org.apache.http.client.methods.HttpPost
import org.apache.http.entity.mime.MultipartEntity
Expand All @@ -12,7 +9,6 @@ import org.apache.http.params.HttpConnectionParams
import org.apache.http.util.EntityUtils
import org.gradle.api.GradleException
import org.gradle.api.Project
import java.io.IOException

/**
* Task to upload ProGuard mapping files to Bugsnag.
Expand All @@ -29,34 +25,28 @@ import java.io.IOException
*/
class BugsnagMultiPartUploadRequest {

lateinit var variantOutput: ApkVariantOutput
lateinit var variant: ApkVariant

fun uploadMultipartEntity(project: Project,
mpEntity: MultipartEntity,
manifestInfo: AndroidManifestInfo) {
manifestInfo: AndroidManifestInfo): String {
val logger = project.logger
val bugsnag = project.extensions.getByName("bugsnag") as BugsnagPluginExtension
if (manifestInfo.apiKey == "") {
logger.warn("Skipping upload due to invalid parameters")
if (bugsnag.isFailOnUploadError) {
throw GradleException("Aborting upload due to invalid parameters")
} else {
return
}
}
val bugsnag = project.extensions.getByType(BugsnagPluginExtension::class.java)
addPropertiesToMultipartEntity(project, mpEntity, manifestInfo, bugsnag)
var uploadSuccessful = uploadToServer(project, mpEntity, bugsnag)

var response = uploadToServer(project, mpEntity, bugsnag)
var uploadSuccessful = response != null
val maxRetryCount = getRetryCount(bugsnag)
var retryCount = maxRetryCount
while (!uploadSuccessful && retryCount > 0) {
logger.warn(String.format("Retrying Bugsnag upload (%d/%d) ...",
maxRetryCount - retryCount + 1, maxRetryCount))
uploadSuccessful = uploadToServer(project, mpEntity, bugsnag)
response = uploadToServer(project, mpEntity, bugsnag)
uploadSuccessful = response != null
retryCount--
}
if (!uploadSuccessful && bugsnag.isFailOnUploadError) {
throw GradleException("Upload did not succeed")
} else {
return response!!
}
}

Expand All @@ -65,7 +55,7 @@ class BugsnagMultiPartUploadRequest {
manifestInfo: AndroidManifestInfo,
bugsnag: BugsnagPluginExtension) {
mpEntity.addPart("apiKey", StringBody(manifestInfo.apiKey))
mpEntity.addPart("appId", StringBody(variant.applicationId))
mpEntity.addPart("appId", StringBody(manifestInfo.applicationId))
mpEntity.addPart("versionCode", StringBody(manifestInfo.versionCode))
mpEntity.addPart("buildUUID", StringBody(manifestInfo.buildUUID))
mpEntity.addPart("versionName", StringBody(manifestInfo.versionName))
Expand All @@ -74,15 +64,15 @@ class BugsnagMultiPartUploadRequest {
}
val logger = project.logger
logger.debug("apiKey: ${manifestInfo.apiKey}")
logger.debug("appId: ${variant.applicationId}")
logger.debug("appId: ${manifestInfo.applicationId}")
logger.debug("versionCode: ${manifestInfo.versionCode}")
logger.debug("buildUUID: ${manifestInfo.buildUUID}")
logger.debug("versionName: ${manifestInfo.versionName}")
}

private fun uploadToServer(project: Project,
mpEntity: MultipartEntity?,
bugsnag: BugsnagPluginExtension): Boolean {
bugsnag: BugsnagPluginExtension): String? {
val logger = project.logger
logger.lifecycle("Attempting upload of mapping file to Bugsnag")

Expand All @@ -93,26 +83,21 @@ class BugsnagMultiPartUploadRequest {
val params = httpClient.params
HttpConnectionParams.setConnectionTimeout(params, bugsnag.requestTimeoutMs)
HttpConnectionParams.setSoTimeout(params, bugsnag.requestTimeoutMs)
val statusCode: Int
val responseEntity: String
try {
val response = httpClient.execute(httpPost)
statusCode = response.statusLine.statusCode
val statusCode = response.statusLine.statusCode
val entity = response.entity
responseEntity = EntityUtils.toString(entity, "utf-8")
} catch (e: IOException) {
logger.error(String.format("Bugsnag upload failed: %s", e))
return false
} catch (e: ParseException) {
logger.error(String.format("Bugsnag upload failed: %s", e))
return false
}
if (statusCode == 200) {
logger.lifecycle("Bugsnag upload successful")
return true
val responseEntity = EntityUtils.toString(entity, "utf-8")

if (statusCode != 200) {
throw IllegalStateException("Bugsnag upload failed with code $statusCode $responseEntity")
} else {
logger.lifecycle("Bugsnag upload successful")
return responseEntity
}
} catch (exc: Throwable) {
throw IllegalStateException("Bugsnag upload failed", exc)
}
logger.error(String.format("Bugsnag upload failed with code %d: %s", statusCode, responseEntity))
return false
}

/**
Expand Down
Loading

0 comments on commit d5f3e6e

Please sign in to comment.