Skip to content

Commit

Permalink
Adds support to verbose executions (#101)
Browse files Browse the repository at this point in the history
  • Loading branch information
ubiratansoares authored Nov 7, 2023
1 parent 61b5de0 commit e09b311
Show file tree
Hide file tree
Showing 24 changed files with 348 additions and 173 deletions.
55 changes: 36 additions & 19 deletions src/main/kotlin/io/dotanuki/aaw/Injection.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,29 @@
package io.dotanuki.aaw

import com.github.ajalt.clikt.core.subcommands
import com.github.ajalt.mordant.terminal.Terminal
import io.dotanuki.aaw.core.android.AndroidArtifactAnalyser
import io.dotanuki.aaw.core.cli.AawEntrypoint
import io.dotanuki.aaw.core.logging.Logging
import io.dotanuki.aaw.features.baseline.BaselineContext
import io.dotanuki.aaw.features.baseline.GenerateCommand
import io.dotanuki.aaw.features.comparison.ArtifactsComparator
import io.dotanuki.aaw.features.comparison.CompareCommand
import io.dotanuki.aaw.features.comparison.CompareContext
import io.dotanuki.aaw.features.overview.OverviewCommand
import io.dotanuki.aaw.features.overview.OverviewContext
import io.dotanuki.aaw.features.version.VersionCommand
import io.dotanuki.aaw.features.version.VersionContext
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNamingStrategy
import net.peanuuutz.tomlkt.Toml

@OptIn(ExperimentalSerializationApi::class)
object Injection {
class Injection(
private val verboseMode: Boolean
) {

private val terminal by lazy {
Terminal()
private val loggingContext by lazy {
Logging.create(verboseMode)
}

private val jsonSerializer by lazy {
Expand All @@ -40,42 +43,56 @@ object Injection {
}
}

private val artifactAnalyser by lazy {
with(loggingContext) {
AndroidArtifactAnalyser()
}
}

private val overviewContext by lazy {
OverviewContext(terminal, jsonSerializer)
OverviewContext(jsonSerializer, artifactAnalyser)
}

private val overviewCommand by lazy {
with(overviewContext) {
OverviewCommand()
with(loggingContext) {
with(overviewContext) {
OverviewCommand()
}
}
}

private val baselineContext by lazy {
BaselineContext(terminal, tomlSerializer)
BaselineContext(tomlSerializer, artifactAnalyser)
}

private val generateCommand by lazy {
with(baselineContext) {
GenerateCommand()
with(loggingContext) {
with(baselineContext) {
GenerateCommand()
}
}
}

private val comparator by lazy {
with(loggingContext) {
ArtifactsComparator()
}
}

private val compareContext by lazy {
CompareContext(terminal, tomlSerializer, jsonSerializer)
CompareContext(tomlSerializer, jsonSerializer, artifactAnalyser, comparator)
}

private val compareCommand by lazy {
with(compareContext) {
CompareCommand()
with(loggingContext) {
with(compareContext) {
CompareCommand()
}
}
}

private val versionContext by lazy {
VersionContext(terminal)
}

private val versionCommand by lazy {
with(versionContext) {
with(loggingContext) {
VersionCommand()
}
}
Expand Down
9 changes: 7 additions & 2 deletions src/main/kotlin/io/dotanuki/aaw/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
package io.dotanuki.aaw

fun main(args: Array<String>) {
with(Injection) {
entrypoint.main(args)
val (verboseMode, filteredArguments) = when {
!args.contains("--verbose") -> Pair(false, args)
else -> Pair(true, args.toMutableList().apply { remove("--verbose") }.toTypedArray())
}

with(Injection(verboseMode)) {
entrypoint.main(filteredArguments)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,26 @@ import com.android.utils.NullLogger
import io.dotanuki.aaw.core.errors.AawError
import io.dotanuki.aaw.core.errors.ErrorAware
import io.dotanuki.aaw.core.filesystem.Unzipper
import io.dotanuki.aaw.core.logging.Logging
import java.io.ByteArrayInputStream
import java.io.File
import java.nio.file.Files
import java.nio.file.Paths
import kotlin.io.path.absolutePathString

object AndroidArtifactAnalyser {
context (Logging)
class AndroidArtifactAnalyser {

private val sdkBridge by lazy {
AndroidSDKBridge()
}

context (ErrorAware)
fun analyse(pathToTarget: String): AnalysedArtifact {
val artifact = SuppliedArtifact.from(pathToTarget)

logger.debug("Successfully identified artifact type -> ${artifact.type.name}")

return when (artifact.type) {
AndroidArtifactType.APK -> analyseApk(artifact)
AndroidArtifactType.AAB -> analyseAab(artifact)
Expand All @@ -48,6 +56,7 @@ object AndroidArtifactAnalyser {

context (ErrorAware)
private fun analyseApk(apk: SuppliedArtifact): AnalysedArtifact {
logger.debug("Starting analysis -> ${apk.filePath}")
val appInfo = retrieveAppInfoWithAapt(apk.filePath)
val parsedManifest = parseAndroidManifestFromApk(apk.filePath)

Expand All @@ -65,12 +74,17 @@ object AndroidArtifactAnalyser {
private fun parseAndroidManifestFromApk(pathToArtifact: String) =
try {
val archiveContext = Archives.open(pathToArtifact.asPath())

val manifestPath = archiveContext.archive.contentRoot.resolve("AndroidManifest.xml")
val bytesToDecode = Files.readAllBytes(manifestPath)

logger.debug("Decoding AndroidManifest.xml binary file")
val decodedXml = BinaryXmlParser.decodeXml(manifestPath.absolutePathString(), bytesToDecode)
val inputStream = ByteArrayInputStream(decodedXml)

AndroidManifestParser.parse(inputStream)
val inputStream = ByteArrayInputStream(decodedXml)
AndroidManifestParser.parse(inputStream).also {
logger.debug("Successfully parsed AndroidManifest.xml")
}
} catch (surfaced: Throwable) {
raise(AawError("Failed when reading AndroidManifest", surfaced))
}
Expand All @@ -80,12 +94,15 @@ object AndroidArtifactAnalyser {
try {
val sdkHandler = AndroidSdkHandler.getInstance(
AndroidLocationsSingleton,
AndroidSDKBridge.sdkFolder().asPath()
sdkBridge.sdkFolder.asPath()
)

val aaptInvoker = AaptInvoker(sdkHandler, NullLogger())

AndroidApplicationInfo.parseBadging(aaptInvoker.dumpBadging(pathToArtifact.asFile()))
logger.debug("Dumping application info using aapt")
AndroidApplicationInfo
.parseBadging(aaptInvoker.dumpBadging(pathToArtifact.asFile()))
.also { logger.debug("Successfully extracted application info") }
} catch (surfaced: Throwable) {
raise(AawError("Failed when invoking aapt from Android SDK", surfaced))
}
Expand All @@ -104,15 +121,17 @@ object AndroidArtifactAnalyser {

context (ErrorAware)
private fun extractUniversalApkFromBundle(artifact: SuppliedArtifact): String = try {
logger.debug("Evaluating AppBundle information")
val artifactName = artifact.filePath.split("/").last().replace(".aab", "")
val tempDir = Files.createTempDirectory("arw-$artifactName-extraction").toFile()
val apkContainerOutput = "$tempDir/$artifactName.apks"

logger.debug("Retrieving fake keystore to sign artifacts")
val keystore = ClassLoader.getSystemClassLoader().getResourceAsStream("aaw.keystore")?.readAllBytes()
ensure(keystore != null) { AawError("Failed when reading aaw.keystore") }

val keystoreFile = File("$tempDir/aaw.keystore").apply { writeBytes(keystore) }

logger.debug("Generating universal APK from AppBundle with Bundletool")
val flags = arrayOf(
"--bundle=${artifact.filePath}",
"--output=$apkContainerOutput",
Expand All @@ -131,24 +150,29 @@ object AndroidArtifactAnalyser {
val destinationFolder = "$tempDir/extracted"
Unzipper.unzip(apkContainerPath.toFile(), "$tempDir/extracted")

"$destinationFolder/universal.apk"
"$destinationFolder/universal.apk".also {
logger.debug("Successfully extracted universal APK from -> ${artifact.filePath}")
}
} catch (surfaced: Throwable) {
raise(AawError("Cannot convert universal APK from AppBundle", surfaced))
}

context (ErrorAware)
private fun locateAapt2FromSdk(): File {
logger.debug("Locating aapt2 using Android SDK installation")
val sdkHandler = AndroidSdkHandler.getInstance(
AndroidLocationsSingleton,
AndroidSDKBridge.sdkFolder().asPath()
sdkBridge.sdkFolder.asPath()
)

val buildTools = sdkHandler.getLatestBuildTool(LoggerProgressIndicatorWrapper(NullLogger()), true)
ensure(buildTools != null) {
AawError("Failed to locate build tools inside your Android SDK installation")
}

return buildTools.location.resolve(SdkConstants.FN_AAPT2).toFile()
return buildTools.location.resolve(SdkConstants.FN_AAPT2).toFile().also {
logger.debug("Found aapt2 -> $it")
}
}

private fun String.asPath() = Paths.get(this)
Expand Down
39 changes: 21 additions & 18 deletions src/main/kotlin/io/dotanuki/aaw/core/android/AndroidSDKBridge.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,39 @@

package io.dotanuki.aaw.core.android

import arrow.core.raise.ensure
import io.dotanuki.aaw.core.errors.AawError
import io.dotanuki.aaw.core.errors.ErrorAware
import io.dotanuki.aaw.core.logging.Logging

object AndroidSDKBridge {
context (Logging)
@Suppress("TooGenericExceptionThrown")
class AndroidSDKBridge {

context (ErrorAware)
fun sdkFolder(): String =
val sdkFolder: String by lazy {
with(System.getenv()) {
when {
containsKey(FIRST_OPTION) -> this[FIRST_OPTION]
containsKey(SECOND_OPTION) -> this[SECOND_OPTION]
containsKey(THIRD_OPTION) -> this[THIRD_OPTION]
else -> null
}.let { androidHome ->
ensure(androidHome != null) { AawError(CANNOT_LOCATE_ANDROID_SDK) }
androidHome
.also { logger.debug("Found Android SDK -> $it") }
?: throw RuntimeException(CANNOT_LOCATE_ANDROID_SDK)
}
}
}

private const val FIRST_OPTION = "ANDROID_HOME"
private const val SECOND_OPTION = "ANDROID_SDK_HOME"
private const val THIRD_OPTION = "ANDROID_SDK"
companion object {
private const val FIRST_OPTION = "ANDROID_HOME"
private const val SECOND_OPTION = "ANDROID_SDK_HOME"
private const val THIRD_OPTION = "ANDROID_SDK"

private val CANNOT_LOCATE_ANDROID_SDK =
"""
Could not locate your Android SDK installation folder.
Ensure that have it exposed in one of the following environment variables
$FIRST_OPTION
$SECOND_OPTION
$THIRD_OPTION
""".trimIndent()
private val CANNOT_LOCATE_ANDROID_SDK =
"""
Could not locate your Android SDK installation folder.
Ensure that have it exposed in one of the following environment variables
$FIRST_OPTION
$SECOND_OPTION
$THIRD_OPTION
""".trimIndent()
}
}
14 changes: 6 additions & 8 deletions src/main/kotlin/io/dotanuki/aaw/core/cli/ErrorReporter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,26 @@

package io.dotanuki.aaw.core.cli

import com.github.ajalt.mordant.rendering.TextColors.red
import com.github.ajalt.mordant.terminal.Terminal
import io.dotanuki.aaw.core.errors.AawError
import io.dotanuki.aaw.core.logging.Logging
import kotlin.system.exitProcess

object ErrorReporter {

var printStackTraces: Boolean = false

private val terminal by lazy { Terminal() }

context (Logging)
fun reportFailure(surfaced: AawError) {
terminal.emptyLine()
terminal.println(red(surfaced.description))
logger.newLine()
logger.error(surfaced.description)

if (printStackTraces) {
val trace = surfaced.wrapped ?: return
terminal.emptyLine()
logger.newLine()
trace.printStackTrace()
}

terminal.emptyLine()
logger.newLine()
exitProcess(ExitCodes.FAILURE)
}
}
59 changes: 59 additions & 0 deletions src/main/kotlin/io/dotanuki/aaw/core/logging/Logger.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright 2023 Dotanuki Labs
* SPDX-License-Identifier: MIT
*/

package io.dotanuki.aaw.core.logging

import com.github.ajalt.mordant.rendering.TextColors.gray
import com.github.ajalt.mordant.rendering.TextColors.red
import com.github.ajalt.mordant.rendering.Widget
import com.github.ajalt.mordant.terminal.Terminal
import io.dotanuki.aaw.core.errors.AawError

data class Logger(
private val terminal: Terminal,
private val verboseMode: Boolean
) {

init {
if (verboseMode) {
newLine()
}
}

fun newLine() {
terminal.println()
}

fun info(widget: Widget) {
terminal.println(widget)
}

fun info(message: String) {
terminal.println(message)
}

fun debug(message: String) {
if (verboseMode) {
terminal.println(gray(message))
}
}

fun error(message: String) {
terminal.println(red(message))
}

fun error(surfaced: AawError) {
terminal.println()
terminal.println(surfaced.description)

if (verboseMode) {
val trace = surfaced.wrapped ?: return
terminal.println()
trace.printStackTrace()
}

terminal.println()
}
}
Loading

0 comments on commit e09b311

Please sign in to comment.