Skip to content

Commit

Permalink
Detect AAR minSdkVersion and pass it to dx (#311)
Browse files Browse the repository at this point in the history
* Detect AAR minSdkVersion and pass it to dx

* Run dx --help first to see if it supports --min-sdk-version
  • Loading branch information
benjamin-bader committed Jul 16, 2020
1 parent 7b77b63 commit 72fcfcd
Showing 1 changed file with 61 additions and 23 deletions.
84 changes: 61 additions & 23 deletions src/main/kotlin/com/getkeepsafe/dexcount/SourceFile.kt
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,27 @@ internal class DexFile(
@JvmStatic
private fun extractDexFromAar(file: File, dxTimeoutSecs: Int): List<DexFile> {
// unzip classes.jar from the AAR
var minSdk = 13 // the default minSdkVersion of dx
val tempClasses = file.unzip { entries ->
val jarFile = entries.find { it.name.matches(Regex("classes\\.jar")) }
makeTempFile("classes.jar").also {
jarFile!!.writeTo(it)
var tempFile: File? = null

for (entry in entries) {
if (entry.name == "AndroidManifest.xml") {
val manifestText = entry.inputStream().bufferedReader().use { it.readText() }
val match = Regex("""android:minSdkVersion="(\d+)"""").find(manifestText)
if (match != null) {
minSdk = match.groupValues[1].toInt()
}
}

if (entry.name.matches(Regex("classes\\.jar"))) {
tempFile = makeTempFile("classes.jar").also {
entry.writeTo(it)
}
}
}

checkNotNull(tempFile) { "No classes.jar file found in ${file.canonicalPath}" }
}

// convert it to DEX format by using the Android dx tool
Expand All @@ -137,36 +153,34 @@ internal class DexFile(
throw Exception ("dx tool not found at " + dxExe.absolutePath)
}

// ~/android-sdk/build-tools/23.0.3/dx --dex --output=temp.dex classes.jar
val tempDex = makeTempFile("classes.dex")
// Figure out if this version of dx has the --min-sdk-version flag
val dexHelp = runProcess(dxExe, listOf("--help"), timeoutMillis = 5000L)
if (dexHelp.timedOut || dexHelp.exitCode != 1) { // dx --help seems to always exit with code 1
throw DexCountException("Unable to run dx --help (file: $dxExe)")
}

val sout = StringBuilder()
val serr = StringBuilder()
val proc = ProcessBuilder().command(
dxExe.absolutePath,
"--dex",
"--output=${tempDex.absolutePath}",
tempClasses.absolutePath)
.start()
val hasMinSdkFlag = dexHelp.stdout.contains("--min-sdk-version") || dexHelp.stderr.contains("--min-sdk-version")

val didFinish = proc.waitForProcessOutput(
stdout = sout,
stderr = serr,
timeoutMillis = TimeUnit.SECONDS.toMillis(dxTimeoutSecs.toLong()))
// ~/android-sdk/build-tools/23.0.3/dx --dex --output=temp.dex classes.jar
val tempDex = makeTempFile("classes.dex")

val exitCode = if (didFinish) proc.exitValue() else -1
proc.dispose()
val dexArgs = mutableListOf("--dex", "--output=${tempDex.absolutePath}")
if (hasMinSdkFlag) {
dexArgs += "--min-sdk-version=$minSdk"
}
dexArgs += tempClasses.absolutePath

if (!didFinish) {
val dexRun = runProcess(dxExe, dexArgs, TimeUnit.SECONDS.toMillis(dxTimeoutSecs.toLong()))
if (dexRun.timedOut) {
throw DexCountException("dx timed out after $dxTimeoutSecs seconds")
}

if (exitCode != 0) {
throw DexCountException("dx exited with exit code $exitCode\nstderr=$serr")
if (dexRun.exitCode != 0) {
throw DexCountException("dx exited with exit code ${dexRun.exitCode}\nstderr=${dexRun.stderr}")
}

if (!tempDex.exists()) {
throw DexCountException("Error converting classes.jar into classes.dex: $serr")
throw DexCountException("Error converting classes.jar into classes.dex: ${dexRun.stderr}")
}

return listOf(DexFile(tempDex, true))
Expand Down Expand Up @@ -344,6 +358,30 @@ private class StreamableZipEntry(
}
}

private data class Execution(
val timedOut: Boolean,
val exitCode: Int,
val stdout: StringBuilder,
val stderr: StringBuilder
)

private fun runProcess(exe: File, args: List<String>, timeoutMillis: Long): Execution {
val stdout = StringBuilder()
val stderr = StringBuilder()

val proc = ProcessBuilder(
exe.absolutePath,
*args.toTypedArray()
).start()

val didFinish = proc.waitForProcessOutput(stdout, stderr, timeoutMillis)
val exitCode = if (didFinish) proc.exitValue() else -1

proc.dispose()

return Execution(!didFinish, exitCode, stdout, stderr)
}

private fun Process.waitForProcessOutput(stdout: Appendable, stderr: Appendable, timeoutMillis: Long): Boolean {
val (o, e) = consumeStdout(stdout) to consumeStderr(stderr)

Expand Down

0 comments on commit 72fcfcd

Please sign in to comment.