Skip to content

Commit

Permalink
Merge pull request #234 from ZacSweers/z/v5ManifestUploading
Browse files Browse the repository at this point in the history
Support for manifest processing in AGP 4.1.0-alpha04 and above
  • Loading branch information
fractalwrench authored Jul 20, 2020
2 parents 79a9f4d + 19c7853 commit 90637a7
Show file tree
Hide file tree
Showing 12 changed files with 279 additions and 47 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ local.properties
maze_output/
package-lock.json
.cxx

*.iml
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)

Support for manifest processing in AGP 4.1.0-alpha04 and above
[#234](https://github.com/bugsnag/bugsnag-android-gradle-plugin/pull/234)

Alter bugsnag tasks to support the Incremental Build API
[#230](https://github.com/bugsnag/bugsnag-android-gradle-plugin/pull/230)

Expand Down
8 changes: 6 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ buildscript {
}

dependencies {
classpath "com.android.tools.build:gradle:4.0.0" // compile against 4.0.0, but maintain compat to 3.4.0
classpath "com.android.tools.build:gradle:4.1.0-beta04" // compile against 4.1.0-beta04, 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"
}
Expand Down Expand Up @@ -42,11 +42,15 @@ repositories {

// Build dependencies
dependencies {
compileOnly "com.android.tools.build:gradle:4.0.0"
compileOnly "com.android.tools.build:gradle:4.1.0-beta04"
compile "org.apache.httpcomponents:httpclient:4.5.2"
compile "org.apache.httpcomponents:httpmime:4.5.2"
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72"

// For serialization of shared data
implementation "com.squareup.okio:okio:2.7.0"
implementation "com.squareup.moshi:moshi:1.9.3"

testCompile "com.android.tools.build:gradle:4.0.0"
testCompile "junit:junit:4.12"
testCompile "org.mockito:mockito-core:2.28.2"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,81 @@
package com.bugsnag.android.gradle

import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonReader.Token.NULL
import com.squareup.moshi.JsonWriter
import okio.buffer
import okio.sink
import okio.source
import java.io.File

data class AndroidManifestInfo(
var apiKey: String,
var versionCode: String,
var buildUUID: String,
var versionName: String
)
) {
internal fun write(file: File) {
file.sink().buffer().use {
ADAPTER.toJson(it, this)
}
}

internal companion object {
private val OPTIONS = JsonReader.Options.of(
"apiKey",
"versionCode",
"buildUUID",
"versionName"
)

@Suppress("MagicNumber") // They are indices into the OPTIONS field above
private val ADAPTER = object : JsonAdapter<AndroidManifestInfo>() {
override fun fromJson(reader: JsonReader): AndroidManifestInfo? {
if (reader.peek() == NULL) {
return reader.nextNull()
}
lateinit var apiKey: String
lateinit var versionCode: String
lateinit var buildUUID: String
lateinit var versionName: 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()
-1 -> reader.skipValue()
}
}
reader.endObject()
return AndroidManifestInfo(apiKey, versionCode, buildUUID, versionName)
}

override fun toJson(writer: JsonWriter, value: AndroidManifestInfo?) {
if (value == null) {
writer.nullValue()
return
}
writer.beginObject()
.name("apiKey")
.value(value.apiKey)
.name("versionCode")
.value(value.versionCode)
.name("buildUUID")
.value(value.buildUUID)
.name("versionName")
.value(value.versionName)
.endObject()
}

}

fun read(file: File): AndroidManifestInfo {
return file.source().buffer().use {
ADAPTER.fromJson(it) ?: error("Failed to parse manifest info.")
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.bugsnag.android.gradle

import org.gradle.api.file.RegularFileProperty

interface AndroidManifestInfoReceiver {
val manifestInfoFile: RegularFileProperty
}

internal fun AndroidManifestInfoReceiver.parseManifestInfo(): AndroidManifestInfo {
return AndroidManifestInfo.read(manifestInfoFile.asFile.get())
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ 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 @@ -56,7 +57,12 @@ class AndroidManifestParser {
}

@Throws(ParserConfigurationException::class, SAXException::class, IOException::class)
fun writeBuildUuid(manifestPath: File, buildUuid: String) {
fun writeBuildUuid(
manifestPath: File,
outputPath: File = manifestPath,
// Uniquely identify the build so that we can identify the proguard file.
buildUuid: String
) {
val root = XmlParser().parse(manifestPath)
val application = (root[TAG_APPLICATION] as NodeList)[0] as Node
val metadataTags = findMetadataTags(application)
Expand All @@ -70,7 +76,7 @@ class AndroidManifestParser {
))

// Write the manifest file
FileWriter(manifestPath).use {
FileWriter(outputPath).use {
val printer = XmlNodePrinter(PrintWriter(it))
printer.isPreserveWhitespace = true
printer.print(root)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,34 @@ import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.logging.Logger
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.io.File
import java.nio.file.Paths
import java.util.UUID
import javax.inject.Inject

abstract class BaseBugsnagManifestUuidTask(objects: ObjectFactory) : DefaultTask() {
init {
group = BugsnagPlugin.GROUP_NAME
description = "Adds a unique build UUID to AndroidManifest to link proguard mappings to crash reports"
}

@get:Input
val buildUuid: Property<String> = objects.property(String::class.java)

@get:OutputFile
val manifestInfoProvider: RegularFileProperty = objects.fileProperty()

fun writeManifestInfo(info: AndroidManifestInfo) {
info.write(manifestInfoProvider.get().asFile)
}
}

/**
* Task to add a unique build UUID to AndroidManifest.xml during the build
Expand All @@ -25,16 +46,10 @@ import java.util.UUID
* This task must be called after "process${variantName}Manifest", since it
* requires that an AndroidManifest.xml exists in `build/intermediates`.
*/
open class BugsnagManifestUuidTask : DefaultTask() {

init {
group = BugsnagPlugin.GROUP_NAME
description = "Adds a unique build UUID to AndroidManifest to link proguard mappings to crash reports"
}
open class BugsnagManifestUuidTask @Inject constructor(objects: ObjectFactory) : BaseBugsnagManifestUuidTask(objects) {

lateinit var variantOutput: ApkVariantOutput
lateinit var variant: ApkVariant
val manifestInfoProvider: Property<AndroidManifestInfo> = project.objects.property(AndroidManifestInfo::class.java)

@TaskAction
fun updateManifest() {
Expand All @@ -43,14 +58,12 @@ open class BugsnagManifestUuidTask : DefaultTask() {
project.logger.warn("Failed to find manifest at $manifestPath for $variantOutput")
}

// Uniquely identify the build so that we can identify the proguard file.
val buildUUID = UUID.randomUUID().toString()
project.logger.lifecycle("Updating manifest with build UUID: $manifestPath")

// read the manifest information and store it for subsequent tasks
val manifestParser = AndroidManifestParser()
manifestParser.writeBuildUuid(manifestPath!!, buildUUID)
manifestInfoProvider.set(manifestParser.readManifest(manifestPath, logger))
manifestParser.writeBuildUuid(manifestPath!!, buildUuid = buildUuid.get())
writeManifestInfo(manifestParser.readManifest(manifestPath, logger))
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.bugsnag.android.gradle

import com.android.Version
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.model.ObjectFactory
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import org.gradle.util.VersionNumber
import javax.inject.Inject

/**
* An AGP-4-compatible implementation of [BugsnagManifestUuidTask].
*/
open class BugsnagManifestUuidTaskV2 @Inject constructor(
objects: ObjectFactory
) : BaseBugsnagManifestUuidTask(objects) {

internal companion object {
private val MIN_AGP_VERSION: VersionNumber = VersionNumber.parse("4.1.0-alpha04")

fun isApplicable(): Boolean {
return try {
VersionNumber.parse(Version.ANDROID_GRADLE_PLUGIN_VERSION) >= MIN_AGP_VERSION
} catch (ignored: Throwable) {
// Not on a new enough AGP version, return false
false
}
}
}

// NONE because we only care about its contents, not location.
@get:PathSensitive(PathSensitivity.NONE)
@get:InputFile
val inputManifest: RegularFileProperty = objects.fileProperty()

@get:OutputFile
val outputManifest: RegularFileProperty = objects.fileProperty()

init {
group = BugsnagPlugin.GROUP_NAME
description = "Adds a unique build UUID to AndroidManifest to link proguard mappings to crash reports"
}

@TaskAction
fun updateManifest() {
val manifestParser = AndroidManifestParser()
val output = outputManifest.asFile.get()
manifestParser.writeBuildUuid(
inputManifest.asFile.get(),
outputManifest.asFile.get(),
buildUuid = buildUuid.get()
)
writeManifestInfo(manifestParser.readManifest(output, logger))
}
}
63 changes: 47 additions & 16 deletions src/main/kotlin/com.bugsnag.android.gradle/BugsnagPlugin.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
package com.bugsnag.android.gradle

import com.android.build.api.artifact.ArtifactType
import com.android.build.gradle.AppExtension
import com.android.build.gradle.AppPlugin
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.internal.dsl.BaseAppModuleExtension
import com.android.build.gradle.tasks.ExternalNativeBuildTask
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.RegularFile
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskProvider
import java.util.UUID

/**
* Gradle plugin to automatically upload ProGuard mapping files to Bugsnag.
Expand Down Expand Up @@ -84,7 +89,7 @@ class BugsnagPlugin : Plugin<Project> {
val ndkEnabled = isNdkProject(bugsnag, project.extensions.getByType(AppExtension::class.java))

// register bugsnag tasks
val manifestUuidTask = registerManifestUuidTask(project, variant, output)
val manifestInfoFile = registerManifestUuidTask(project, variant, output)
val proguardTask = when {
jvmMinificationEnabled -> registerProguardUploadTask(project, variant, output, bugsnag)
else -> null
Expand All @@ -101,27 +106,53 @@ class BugsnagPlugin : Plugin<Project> {
findAssembleBundleTasks(project, variant, output).forEach {
// give all tasks a manifest info provider to prevent reading
// the manifest more than once
val manifestInfoProvider = manifestUuidTask.get().manifestInfoProvider
proguardTask?.get()?.manifestInfoProvider = manifestInfoProvider
symbolFileTask?.get()?.manifestInfoProvider = manifestInfoProvider
releasesTask.get().manifestInfoProvider = manifestInfoProvider
proguardTask?.get()?.manifestInfoFile?.set(manifestInfoFile)
symbolFileTask?.get()?.manifestInfoFile?.set(manifestInfoFile)
releasesTask.get().manifestInfoFile.set(manifestInfoFile)
}
}
}

private fun registerManifestUuidTask(project: Project,
variant: ApkVariant,
output: ApkVariantOutput): TaskProvider<BugsnagManifestUuidTask> {
private fun registerManifestUuidTask(
project: Project,
variant: ApkVariant,
output: ApkVariantOutput
): Provider<RegularFile> {
val taskName = "processBugsnag${taskNameForOutput(output)}Manifest"
return project.tasks.register(taskName, BugsnagManifestUuidTask::class.java) {
it.variantOutput = output
it.variant = variant
val processManifest = output.processManifestProvider.getOrNull()

if (processManifest != null) {
processManifest.finalizedBy(it)
it.dependsOn(processManifest)
val buildUuidProvider = project.provider { UUID.randomUUID().toString() }
val path = "intermediates/bugsnag/manifestInfoFor${taskNameForOutput(output)}.json"
val manifestInfoOutputFile = project.layout.buildDirectory.file(path)
return if (BugsnagManifestUuidTaskV2.isApplicable()) {
val manifestUpdater = project.tasks.register(taskName, BugsnagManifestUuidTaskV2::class.java) {
it.buildUuid.set(buildUuidProvider)
it.manifestInfoProvider.set(manifestInfoOutputFile)
}
val android = project.extensions.getByType(BaseAppModuleExtension::class.java)
android.onVariants.withName(variant.name) {
onProperties {
artifacts
.use(manifestUpdater)
.wiredWithFiles(
BugsnagManifestUuidTaskV2::inputManifest,
BugsnagManifestUuidTaskV2::outputManifest
)
.toTransform(ArtifactType.MERGED_MANIFEST)
}
}
return manifestUpdater.flatMap(BaseBugsnagManifestUuidTask::manifestInfoProvider)
} else {
project.tasks.register(taskName, BugsnagManifestUuidTask::class.java) {
it.buildUuid.set(buildUuidProvider)
it.variantOutput = output
it.variant = variant
it.manifestInfoProvider.set(manifestInfoOutputFile)
val processManifest = output.processManifestProvider.orNull

if (processManifest != null) {
processManifest.finalizedBy(it)
it.dependsOn(processManifest)
}
}.flatMap(BaseBugsnagManifestUuidTask::manifestInfoProvider)
}
}

Expand Down
Loading

0 comments on commit 90637a7

Please sign in to comment.