From e7c3d64bf15bf84f3853e7ef699511bf72c13767 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sun, 26 Nov 2023 05:55:49 +0100 Subject: [PATCH] feat: Allow selecting first Adb device, if none supplied automatically by updating dependencies --- docs/1_usage.md | 4 +- gradle/libs.versions.toml | 4 +- .../app/revanced/cli/command/PatchCommand.kt | 164 ++++++++++-------- .../cli/command/utility/InstallCommand.kt | 19 +- .../cli/command/utility/UninstallCommand.kt | 12 +- 5 files changed, 113 insertions(+), 90 deletions(-) diff --git a/docs/1_usage.md b/docs/1_usage.md index 3b7ca4ab..075a3ade 100644 --- a/docs/1_usage.md +++ b/docs/1_usage.md @@ -116,7 +116,7 @@ ReVanced CLI is divided into the following fundamental commands: ```bash java -jar revanced-cli.jar utility uninstall \ --package-name \ - + [] ``` > [!NOTE] @@ -128,7 +128,7 @@ ReVanced CLI is divided into the following fundamental commands: ```bash java -jar revanced-cli.jar utility install \ -a input.apk \ - + [] ``` > [!NOTE] diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dfa2ffbf..f70eea16 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,10 +1,10 @@ [versions] shadow = "8.1.1" -kotlin-test = "1.9.10" +kotlin-test = "1.9.20" kotlinx-coroutines-core = "1.7.3" picocli = "4.7.3" revanced-patcher = "19.0.0" -revanced-library = "1.2.0" +revanced-library = "1.3.0" [libraries] kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin-test" } diff --git a/src/main/kotlin/app/revanced/cli/command/PatchCommand.kt b/src/main/kotlin/app/revanced/cli/command/PatchCommand.kt index a970c2f3..b319dfa4 100644 --- a/src/main/kotlin/app/revanced/cli/command/PatchCommand.kt +++ b/src/main/kotlin/app/revanced/cli/command/PatchCommand.kt @@ -18,10 +18,9 @@ import java.io.PrintWriter import java.io.StringWriter import java.util.logging.Logger - @CommandLine.Command( name = "patch", - description = ["Patch an APK file."] + description = ["Patch an APK file."], ) internal object PatchCommand : Runnable { private val logger = Logger.getLogger(PatchCommand::class.java.name) @@ -37,25 +36,25 @@ internal object PatchCommand : Runnable { @CommandLine.Option( names = ["-i", "--include"], - description = ["List of patches to include."] + description = ["List of patches to include."], ) private var includedPatches = hashSetOf() @CommandLine.Option( names = ["--ii"], - description = ["List of patches to include by their index in relation to the supplied patch bundles."] + description = ["List of patches to include by their index in relation to the supplied patch bundles."], ) private var includedPatchesByIndex = arrayOf() @CommandLine.Option( names = ["-e", "--exclude"], - description = ["List of patches to exclude."] + description = ["List of patches to exclude."], ) private var excludedPatches = hashSetOf() @CommandLine.Option( names = ["--ei"], - description = ["List of patches to exclude by their index in relation to the supplied patch bundles."] + description = ["List of patches to exclude by their index in relation to the supplied patch bundles."], ) private var excludedPatchesByIndex = arrayOf() @@ -68,14 +67,14 @@ internal object PatchCommand : Runnable { @CommandLine.Option( names = ["--exclusive"], description = ["Only include patches that are explicitly specified to be included."], - showDefaultValue = ALWAYS + showDefaultValue = ALWAYS, ) private var exclusive = false @CommandLine.Option( - names = ["-f","--force"], + names = ["-f", "--force"], description = ["Bypass compatibility checks for the supplied APK's version."], - showDefaultValue = ALWAYS + showDefaultValue = ALWAYS, ) private var force: Boolean = false @@ -91,48 +90,52 @@ internal object PatchCommand : Runnable { @CommandLine.Option( names = ["-d", "--device-serial"], - description = ["ADB device serial to install to."], + description = ["ADB device serial to install to. If not supplied, the first connected device will be used."], + fallbackValue = "", // Empty string to indicate that the first connected device should be used. + arity = "0..1", ) private var deviceSerial: String? = null @CommandLine.Option( names = ["--mount"], description = ["Install by mounting the patched APK file."], - showDefaultValue = ALWAYS + showDefaultValue = ALWAYS, ) private var mount: Boolean = false @CommandLine.Option( names = ["--keystore"], - description = ["Path to the keystore to sign the patched APK file with. " + - "Defaults to the same directory as the supplied APK file."], + description = [ + "Path to the keystore to sign the patched APK file with. " + + "Defaults to the same directory as the supplied APK file.", + ], ) private var keystoreFilePath: File? = null // key store password @CommandLine.Option( names = ["--keystore-password"], - description = ["The password of the keystore to sign the patched APK file with. Empty password by default."] + description = ["The password of the keystore to sign the patched APK file with. Empty password by default."], ) private var keyStorePassword: String? = null // Empty password by default @CommandLine.Option( names = ["--alias"], description = ["The alias of the key from the keystore to sign the patched APK file with."], - showDefaultValue = ALWAYS + showDefaultValue = ALWAYS, ) private var alias = "ReVanced Key" @CommandLine.Option( names = ["--keystore-entry-password"], - description = ["The password of the entry from the keystore for the key to sign the patched APK file with."] + description = ["The password of the entry from the keystore for the key to sign the patched APK file with."], ) private var password = "" // Empty password by default @CommandLine.Option( names = ["--signer"], description = ["The name of the signer to sign the patched APK file with."], - showDefaultValue = ALWAYS + showDefaultValue = ALWAYS, ) private var signer = "ReVanced" @@ -147,33 +150,35 @@ internal object PatchCommand : Runnable { @CommandLine.Option( names = ["-p", "--purge"], description = ["Purge the temporary resource cache directory after patching."], - showDefaultValue = ALWAYS + showDefaultValue = ALWAYS, ) private var purge: Boolean = false @CommandLine.Option( names = ["-w", "--warn"], description = ["Warn if a patch can not be found in the supplied patch bundles."], - showDefaultValue = ALWAYS + showDefaultValue = ALWAYS, ) private var warn: Boolean = false @CommandLine.Parameters( description = ["APK file to be patched."], - arity = "1..1" + arity = "1..1", ) @Suppress("unused") private fun setApk(apk: File) { - if (!apk.exists()) throw CommandLine.ParameterException( - spec.commandLine(), - "APK file ${apk.name} does not exist" - ) + if (!apk.exists()) { + throw CommandLine.ParameterException( + spec.commandLine(), + "APK file ${apk.name} does not exist", + ) + } this.apk = apk } @CommandLine.Option( names = ["-m", "--merge"], - description = ["One or more DEX files or containers to merge into the APK."] + description = ["One or more DEX files or containers to merge into the APK."], ) @Suppress("unused") private fun setIntegrations(integrations: Array) { @@ -186,7 +191,7 @@ internal object PatchCommand : Runnable { @CommandLine.Option( names = ["-b", "--patch-bundle"], description = ["One or more bundles of patches."], - required = true + required = true, ) @Suppress("unused") private fun setPatchBundles(patchBundles: Array) { @@ -198,14 +203,16 @@ internal object PatchCommand : Runnable { @CommandLine.Option( names = ["--custom-aapt2-binary"], - description = ["Path to a custom AAPT binary to compile resources with."] + description = ["Path to a custom AAPT binary to compile resources with."], ) @Suppress("unused") private fun setAaptBinaryPath(aaptBinaryPath: File) { - if (!aaptBinaryPath.exists()) throw CommandLine.ParameterException( - spec.commandLine(), - "AAPT binary ${aaptBinaryPath.name} does not exist" - ) + if (!aaptBinaryPath.exists()) { + throw CommandLine.ParameterException( + spec.commandLine(), + "AAPT binary ${aaptBinaryPath.name} does not exist", + ) + } this.aaptBinaryPath = aaptBinaryPath } @@ -213,22 +220,20 @@ internal object PatchCommand : Runnable { // region Setup val outputFilePath = outputFilePath ?: File("").absoluteFile.resolve( - "${apk.nameWithoutExtension}-patched.${apk.extension}" + "${apk.nameWithoutExtension}-patched.${apk.extension}", ) val resourceCachePath = resourceCachePath ?: outputFilePath.parentFile.resolve( - "${outputFilePath.nameWithoutExtension}-resource-cache" + "${outputFilePath.nameWithoutExtension}-resource-cache", ) val optionsFile = optionsFile ?: outputFilePath.parentFile.resolve( - "${outputFilePath.nameWithoutExtension}-options.json" + "${outputFilePath.nameWithoutExtension}-options.json", ) val keystoreFilePath = keystoreFilePath ?: outputFilePath.parentFile .resolve("${outputFilePath.nameWithoutExtension}.keystore") - val adbManager = deviceSerial?.let { serial -> AdbManager.getAdbManager(serial, mount) } - // endregion // region Load patches @@ -238,13 +243,15 @@ internal object PatchCommand : Runnable { val patches = PatchBundleLoader.Jar(*patchBundles.toTypedArray()) // Warn if a patch can not be found in the supplied patch bundles. - if (warn) patches.map { it.name }.toHashSet().let { availableNames -> - (includedPatches + excludedPatches).filter { name -> - !availableNames.contains(name) + if (warn) { + patches.map { it.name }.toHashSet().let { availableNames -> + (includedPatches + excludedPatches).filter { name -> + !availableNames.contains(name) + } + }.let { unknownPatches -> + if (unknownPatches.isEmpty()) return@let + logger.warning("Unknown input of patches:\n${unknownPatches.joinToString("\n")}") } - }.let { unknownPatches -> - if (unknownPatches.isEmpty()) return@let - logger.warning("Unknown input of patches:\n${unknownPatches.joinToString("\n")}") } // endregion @@ -255,14 +262,17 @@ internal object PatchCommand : Runnable { resourceCachePath, aaptBinaryPath?.path, resourceCachePath.absolutePath, - true - ) + true, + ), ).use { patcher -> val filteredPatches = patcher.filterPatchSelection(patches).also { patches -> logger.info("Setting patch options") - if (optionsFile.exists()) patches.setOptions(optionsFile) - else Options.serialize(patches, prettyPrint = true).let(optionsFile::writeText) + if (optionsFile.exists()) { + patches.setOptions(optionsFile) + } else { + Options.serialize(patches, prettyPrint = true).let(optionsFile::writeText) + } } // region Patch @@ -292,24 +302,29 @@ internal object PatchCommand : Runnable { ApkUtils.copyAligned(apk, this, patcherResult) } - if (!mount) ApkUtils.sign( - alignedFile, - outputFilePath, - ApkUtils.SigningOptions( - keystoreFilePath, - keyStorePassword, - alias, - password, - signer + if (!mount) { + ApkUtils.sign( + alignedFile, + outputFilePath, + ApkUtils.SigningOptions( + keystoreFilePath, + keyStorePassword, + alias, + password, + signer, + ), ) - ) - else alignedFile.renameTo(outputFilePath) + } else { + alignedFile.renameTo(outputFilePath) + } // endregion // region Install - adbManager?.install(AdbManager.Apk(outputFilePath, patcher.context.packageMetadata.packageName)) + deviceSerial?.let { serial -> + AdbManager.getAdbManager(deviceSerial = serial.ifEmpty { null }, mount) + }?.install(AdbManager.Apk(outputFilePath, patcher.context.packageMetadata.packageName)) // endregion } @@ -320,7 +335,6 @@ internal object PatchCommand : Runnable { } } - /** * Filter the patches to be added to the patcher. The filter is based on the following: * @@ -344,17 +358,20 @@ internal object PatchCommand : Runnable { it.any { version -> version == packageVersion } } ?: true - if (!matchesVersion) return@patch logger.warning( - "$patchName is incompatible with version $packageVersion. " - + "This patch is only compatible with version " - + packages.joinToString(";") { pkg -> - pkg.versions!!.joinToString(", ") - } - ) + if (!matchesVersion) { + return@patch logger.warning( + "$patchName is incompatible with version $packageVersion. " + + "This patch is only compatible with version " + + packages.joinToString(";") { pkg -> + pkg.versions!!.joinToString(", ") + }, + ) + } } ?: return@patch logger.fine( - "$patchName is incompatible with $packageName. " - + "This patch is only compatible with " - + packages.joinToString(", ") { `package` -> `package`.name }) + "$patchName is incompatible with $packageName. " + + "This patch is only compatible with " + + packages.joinToString(", ") { `package` -> `package`.name }, + ) return@let } ?: logger.fine("$patchName has no constraint on packages.") @@ -374,8 +391,11 @@ internal object PatchCommand : Runnable { } private fun purge(resourceCachePath: File) { - val result = if (resourceCachePath.deleteRecursively()) "Purged resource cache directory" - else "Failed to purge resource cache directory" + val result = if (resourceCachePath.deleteRecursively()) { + "Purged resource cache directory" + } else { + "Failed to purge resource cache directory" + } logger.info(result) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/app/revanced/cli/command/utility/InstallCommand.kt b/src/main/kotlin/app/revanced/cli/command/utility/InstallCommand.kt index cffe3d7e..85be0221 100644 --- a/src/main/kotlin/app/revanced/cli/command/utility/InstallCommand.kt +++ b/src/main/kotlin/app/revanced/cli/command/utility/InstallCommand.kt @@ -5,24 +5,23 @@ import picocli.CommandLine.* import java.io.File import java.util.logging.Logger - @Command( name = "install", - description = ["Install an APK file to devices with the supplied ADB device serials"] + description = ["Install an APK file to devices with the supplied ADB device serials"], ) internal object InstallCommand : Runnable { private val logger = Logger.getLogger(InstallCommand::class.java.name) @Parameters( - description = ["ADB device serials"], - arity = "1..*" + description = ["ADB device serials. If not supplied, the first connected device will be used."], + arity = "0..*", ) - private lateinit var deviceSerials: Array + private var deviceSerials: Array? = null @Option( names = ["-a", "--apk"], description = ["APK file to be installed"], - required = true + required = true, ) private lateinit var apk: File @@ -32,11 +31,13 @@ internal object InstallCommand : Runnable { ) private var packageName: String? = null - override fun run() = deviceSerials.forEach { deviceSerial -> - try { + override fun run() { + fun install(deviceSerial: String? = null) = try { AdbManager.getAdbManager(deviceSerial, packageName != null).install(AdbManager.Apk(apk, packageName)) } catch (e: AdbManager.DeviceNotFoundException) { logger.severe(e.toString()) } + + deviceSerials?.forEach(::install) ?: install() } -} \ No newline at end of file +} diff --git a/src/main/kotlin/app/revanced/cli/command/utility/UninstallCommand.kt b/src/main/kotlin/app/revanced/cli/command/utility/UninstallCommand.kt index 494f93e0..e69c46bd 100644 --- a/src/main/kotlin/app/revanced/cli/command/utility/UninstallCommand.kt +++ b/src/main/kotlin/app/revanced/cli/command/utility/UninstallCommand.kt @@ -14,10 +14,10 @@ internal object UninstallCommand : Runnable { private val logger = Logger.getLogger(UninstallCommand::class.java.name) @Parameters( - description = ["ADB device serials"], - arity = "1..*" + description = ["ADB device serials. If not supplied, the first connected device will be used."], + arity = "0..*" ) - private lateinit var deviceSerials: Array + private var deviceSerials: Array? = null @Option( names = ["-p", "--package-name"], @@ -33,11 +33,13 @@ internal object UninstallCommand : Runnable { ) private var unmount: Boolean = false - override fun run() = deviceSerials.forEach { deviceSerial -> - try { + override fun run() { + fun uninstall(deviceSerial: String? = null) = try { AdbManager.getAdbManager(deviceSerial, unmount).uninstall(packageName) } catch (e: AdbManager.DeviceNotFoundException) { logger.severe(e.toString()) } + + deviceSerials?.forEach { uninstall(it) } ?: uninstall() } } \ No newline at end of file