Skip to content

Commit

Permalink
Merge pull request #359 from bugsnag/PLAT-5694/dexguard-9
Browse files Browse the repository at this point in the history
Add support for DexGuard 9
  • Loading branch information
fractalwrench authored Jan 21, 2021
2 parents 76731fa + a129715 commit 60e3b8b
Show file tree
Hide file tree
Showing 10 changed files with 378 additions and 57 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## TBD

* Add support for DexGuard 9
[#359](https://github.com/bugsnag/bugsnag-android-gradle-plugin/pull/359)

## 5.7.1 (2021-01-18)

* Add warning in BAGP for React Native when running via react-native CLI
Expand Down
2 changes: 2 additions & 0 deletions detekt-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
<ID>ComplexCondition:BugsnagPlugin.kt$BugsnagPlugin$!jvmMinificationEnabled &amp;&amp; !ndkEnabled &amp;&amp; !unityEnabled &amp;&amp; !reactNativeEnabled</ID>
<ID>MagicNumber:BugsnagPluginExtension.kt$BugsnagPluginExtension$60000</ID>
<ID>MagicNumber:BugsnagReleasesTask.kt$BugsnagReleasesTask$200</ID>
<ID>MagicNumber:MappingFileProvider.kt$9</ID>
<ID>MaxLineLength:BugsnagManifestUuidTask.kt$BugsnagManifestUuidTask$private</ID>
<ID>ReturnCount:BugsnagPlugin.kt$BugsnagPlugin$ @Suppress("SENSELESS_COMPARISON") internal fun isUnityLibraryUploadEnabled( bugsnag: BugsnagPluginExtension, android: AppExtension ): Boolean</ID>
<ID>ReturnCount:BugsnagPlugin.kt$BugsnagPlugin$ private fun registerUploadSourceMapTask( project: Project, variant: ApkVariant, output: ApkVariantOutput, bugsnag: BugsnagPluginExtension, manifestInfoFileProvider: Provider&lt;RegularFile&gt; ): TaskProvider&lt;out BugsnagUploadJsSourceMapTask&gt;?</ID>
<ID>ReturnCount:ManifestUuidTaskV2Compat.kt$internal fun createManifestUpdateTask( bugsnag: BugsnagPluginExtension, project: Project, variantName: String ): TaskProvider&lt;BugsnagManifestUuidTaskV2&gt;?</ID>
<ID>ReturnCount:SharedObjectMappingFileFactory.kt$SharedObjectMappingFileFactory$ fun generateSoMappingFile(project: Project, params: Params): File?</ID>
<ID>SpreadOperator:BugsnagReleasesTask.kt$BugsnagReleasesTask$(*cmd)</ID>
<ID>SpreadOperator:DexguardCompat.kt$(buildDir, *path, variant.dirName, outputDir, "mapping.txt")</ID>
<ID>TooGenericExceptionCaught:BugsnagHttpClientHelper.kt$exc: Throwable</ID>
<ID>TooGenericExceptionCaught:BugsnagManifestUuidTask.kt$BugsnagManifestUuidTask$exc: Throwable</ID>
<ID>TooGenericExceptionCaught:BugsnagMultiPartUploadRequest.kt$BugsnagMultiPartUploadRequest$exc: Throwable</ID>
Expand Down
30 changes: 30 additions & 0 deletions src/main/groovy/com/bugsnag/android/gradle/GroovyCompat.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package com.bugsnag.android.gradle
import com.android.build.api.artifact.Artifacts
import com.android.build.gradle.AppExtension
import org.gradle.api.Action
import org.gradle.api.Project

import java.nio.file.Paths

/**
* Contains functions which exploit Groovy's metaprogramming to provide backwards
Expand All @@ -23,4 +26,31 @@ class GroovyCompat {
}
}
}

/**
* Retrieves the Dexguard Plugin version from either the path or the version on the DexGuard plugin.
*/
static String getDexguardVersionString(Project project) {
def dexguard = project.extensions.findByName("dexguard")

try {
if (dexguard == null) {
return null
}
if (dexguard.version != null) {
return dexguard.version
} else {
// the path value is structured like this: DexGuard-8.7.02
if (dexguard.path == null) {
return null
}
File dexguardDir = Paths.get(dexguard.path).toFile()
String normalizedDir = dexguardDir.canonicalFile.name
return normalizedDir.replace("DexGuard-", "")
}
} catch (MissingPropertyException ignored) {
// running earlier version of DexGuard, ignore missing property
return null
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity.NONE
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskProvider
import java.io.File
import javax.inject.Inject

/**
Expand Down Expand Up @@ -49,21 +50,26 @@ sealed class BugsnagGenerateProguardTask @Inject constructor(

@TaskAction
fun upload() {
val mappingFile = mappingFileProperty.singleFile
val mappingFile = resolveMappingFile()
if (mappingFile.length() == 0L) { // proguard's -dontobfuscate generates an empty mapping file
logger.warn("Bugsnag: Ignoring empty proguard file")
return
}
val archive = archiveOutputFile.asFile.get()
mappingFile.inputStream().use { stream ->
outputZipFile(stream, archive)
}
}

private fun resolveMappingFile(): File {
val mappingFile = mappingFileProperty.filter(File::exists).singleFile
if (!mappingFile.exists()) {
logger.warn("Bugsnag: Mapping file not found: $mappingFile")
if (failOnUploadError.get()) {
throw IllegalStateException("Mapping file not found: $mappingFile")
}
}
val archive = archiveOutputFile.asFile.get()
mappingFile.inputStream().use { stream ->
outputZipFile(stream, archive)
}
return mappingFile
}

companion object {
Expand Down
15 changes: 14 additions & 1 deletion src/main/kotlin/com/bugsnag/android/gradle/BugsnagPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.bugsnag.android.gradle.internal.TASK_JNI_LIBS
import com.bugsnag.android.gradle.internal.UNITY_SO_MAPPING_DIR
import com.bugsnag.android.gradle.internal.UploadRequestClient
import com.bugsnag.android.gradle.internal.computeManifestInfoOutputV1
import com.bugsnag.android.gradle.internal.getDexguardAabTaskName
import com.bugsnag.android.gradle.internal.hasDexguardPlugin
import com.bugsnag.android.gradle.internal.intermediateForGenerateJvmMapping
import com.bugsnag.android.gradle.internal.intermediateForMappingFileRequest
Expand Down Expand Up @@ -166,7 +167,7 @@ class BugsnagPlugin : Plugin<Project> {

// register bugsnag tasks
val manifestInfoFileProvider = registerManifestUuidTask(project, variant, output)
val mappingFilesProvider = createMappingFileProvider(project, variant, output, android)
val mappingFilesProvider = createMappingFileProvider(project, variant, output)

val generateProguardTaskProvider = when {
jvmMinificationEnabled -> registerGenerateProguardTask(
Expand Down Expand Up @@ -279,6 +280,18 @@ class BugsnagPlugin : Plugin<Project> {
val jvmAutoUpload = bugsnag.uploadJvmMappings.get()
variant.register(project, generateProguardTaskProvider, jvmAutoUpload)
variant.register(project, uploadProguardTaskProvider, jvmAutoUpload)

// DexGuard 9 runs as part of the bundle task supplied by AGP,
// so need to alter task dependency so that BAGP always runs
// after DexGuard
if (project.hasDexguardPlugin()) {
generateProguardTaskProvider.configure { bagpTask ->
val taskName = getDexguardAabTaskName(variant)
project.tasks.findByName(taskName)?.let { dexguardTask ->
bagpTask.mustRunAfter(dexguardTask)
}
}
}
}
if (uploadSourceMapProvider != null) {
variant.register(project, uploadSourceMapProvider, reactNativeEnabled)
Expand Down
74 changes: 30 additions & 44 deletions src/main/kotlin/com/bugsnag/android/gradle/MappingFileProvider.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
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 com.bugsnag.android.gradle.internal.findMappingFileDexguard9
import com.bugsnag.android.gradle.internal.findMappingFileDexguardLegacy
import com.bugsnag.android.gradle.internal.getDexguardMajorVersionInt
import com.bugsnag.android.gradle.internal.hasDexguardPlugin
import com.bugsnag.android.gradle.internal.hasMultipleOutputs
import org.gradle.api.Project
import org.gradle.api.file.FileCollection
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 and filters out
Expand All @@ -18,59 +17,46 @@ import java.nio.file.Paths
internal fun createMappingFileProvider(
project: Project,
variant: ApkVariant,
variantOutput: ApkVariantOutput,
android: AppExtension
variantOutput: ApkVariantOutput
): Provider<FileCollection> {
return findMappingFiles(project, variant, variantOutput, android)
return findMappingFiles(project, variant, variantOutput)
.map { files -> files.filter { it.exists() } }
}

private fun findMappingFiles(
project: Project,
variant: ApkVariant,
variantOutput: ApkVariantOutput,
android: AppExtension
variantOutput: ApkVariantOutput
): Provider<FileCollection> {
if (project.hasDexguardPlugin() && android.hasMultipleOutputs()) {
val mappingFile = findDexguardMappingFile(project, variant, variantOutput)
if (mappingFile.exists()) {
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"
)
return when {
project.hasDexguardPlugin() -> {
if (getDexguardMajorVersionInt(project) >= 9) {
project.provider {
val files = findMappingFileDexguard9(project, variant, variantOutput)
project.layout.files(files)
}
} else {
project.provider {
val file = findMappingFileDexguardLegacy(project, variant, variantOutput)
project.layout.files(file)
}
}
}
else -> {
findMappingFileAgp(variant, project)
}
}

// Use AGP supplied value, preferring the new "getMappingFileProvider" API but falling back
// to the old "mappingFile" API if necessary
return try {
variant.mappingFileProvider
} catch (exc: Throwable) {
project.provider { project.layout.files(variant.mappingFile) }
}
}

/**
* Retrieves the location of a DexGuard mapping file for the given variantOutput. The expected location for this
* is: build/outputs/mapping/<productFlavor>/<buildType>/<split>
*
* variant.mappingFile cannot currently be overridden using the AGP DSL on a per-variantOutput basis, which
* necessitates this workaround. https://issuetracker.google.com/issues/78921539
* Use AGP supplied value, preferring the new "getMappingFileProvider" API but falling back
* to the old "mappingFile" API if necessary
*/
private fun findDexguardMappingFile(
project: Project,
internal fun findMappingFileAgp(
variant: ApkVariant,
variantOutput: ApkVariantOutput
): File {
val buildDir = project.buildDir.toString()
var outputDir = variantOutput.dirName
if (variantOutput.dirName.endsWith("dpi" + File.separator)) {
outputDir = File(variantOutput.dirName).parent
if (outputDir == null) { // if only density splits enabled
outputDir = ""
}
}
return Paths.get(buildDir, "outputs", "mapping", variant.dirName, outputDir, "mapping.txt").toFile()
project: Project
) = try {
variant.mappingFileProvider
} catch (exc: Throwable) {
project.provider { project.layout.files(variant.mappingFile) }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.bugsnag.android.gradle.internal

import com.android.build.gradle.api.ApkVariant
import com.android.build.gradle.api.ApkVariantOutput
import com.bugsnag.android.gradle.GroovyCompat
import org.gradle.api.Project
import org.gradle.util.VersionNumber
import java.io.File
import java.nio.file.Paths

/**
* Finds the mapping file locations for DexGuard >=9. This can be different depending on whether it
* is a bundle or an APK.
*/
internal fun findMappingFileDexguard9(
project: Project,
variant: ApkVariant,
variantOutput: ApkVariantOutput
): List<File> {
return listOf(
findDexguardMappingFile(project, variant, variantOutput, "outputs", "dexguard", "mapping", "apk"),
findDexguardMappingFile(project, variant, variantOutput, "outputs", "dexguard", "mapping", "bundle")
)
}

/**
* Finds the mapping file location for DexGuard <9
*/
internal fun findMappingFileDexguardLegacy(
project: Project,
variant: ApkVariant,
variantOutput: ApkVariantOutput
): File {
return findDexguardMappingFile(project, variant, variantOutput, "outputs", "mapping")
}

/**
* Retrieves the location of a DexGuard mapping file for the given variantOutput.
* The expected location for this is:
* /build/outputs/mapping/<productFlavor>/<buildType>/<split>/mapping.txt
*
* variant.mappingFile cannot currently be overridden using the AGP DSL on a per variantOutput
* basis, which is why the DexGuard plugin sets a different output for its mapping files.
* see https://issuetracker.google.com/issues/78921539
*/
private fun findDexguardMappingFile(
project: Project,
variant: ApkVariant,
variantOutput: ApkVariantOutput,
vararg path: String
): File {
val buildDir = project.buildDir.toString()
var outputDir = variantOutput.dirName
if (variantOutput.dirName.endsWith("dpi" + File.separator)) {
outputDir = File(variantOutput.dirName).parent
if (outputDir == null) { // if only density splits enabled
outputDir = ""
}
}
return Paths.get(buildDir, *path, variant.dirName, outputDir, "mapping.txt").toFile()
}

/**
* Returns true if the DexGuard plugin has been applied to the project
*/
internal fun Project.hasDexguardPlugin(): Boolean {
return pluginManager.hasPlugin("dexguard")
}

/**
* Retrieves the major version of DexGuard in use in the project
*/
internal fun getDexguardMajorVersionInt(project: Project): Int {
val version = GroovyCompat.getDexguardVersionString(project) ?: ""
val versionNumber = VersionNumber.parse(version)
return versionNumber.major
}

/**
* Gets the task name for the Dexguard App Bundle task for this variant.
*/
internal fun getDexguardAabTaskName(variant: ApkVariant): String {
val buildType = variant.buildType.name.capitalize()
val flavor = variant.flavorName.capitalize()
return "dexguardAab$flavor$buildType"
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,6 @@ internal fun AppExtension.hasMultipleOutputs(): Boolean {
return outputSize > variantSize
}

/**
* Returns true if the DexGuard plugin has been applied to the project
*/
internal fun Project.hasDexguardPlugin(): Boolean {
return pluginManager.hasPlugin("dexguard")
}

/**
* Returns true if an APK variant output includes SO files for the given ABI.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.bugsnag.android.gradle

import com.android.build.gradle.api.ApkVariant
import org.gradle.api.Project
import org.gradle.api.file.FileCollection
import org.gradle.api.provider.Provider
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnitRunner

@RunWith(MockitoJUnitRunner::class)
class MappingFileProviderKtTest {

@Mock
lateinit var proj: Project

@Mock
lateinit var variant: ApkVariant

@Mock
lateinit var fileCollectionProvider: Provider<FileCollection>

@Test
fun findMappingFileAgp() {
`when`(variant.mappingFileProvider).thenReturn(fileCollectionProvider)
assertEquals(fileCollectionProvider, findMappingFileAgp(variant, proj))
}
}
Loading

0 comments on commit 60e3b8b

Please sign in to comment.