feat: Allow selecting first Adb device, if none supplied automatically by updating dependencies

This commit is contained in:
oSumAtrIX 2023-11-26 05:55:49 +01:00
parent 3765957043
commit e7c3d64bf1
No known key found for this signature in database
GPG Key ID: A9B3094ACDB604B4
5 changed files with 113 additions and 90 deletions

View File

@ -116,7 +116,7 @@ ReVanced CLI is divided into the following fundamental commands:
```bash ```bash
java -jar revanced-cli.jar utility uninstall \ java -jar revanced-cli.jar utility uninstall \
--package-name <package-name> \ --package-name <package-name> \
<device-serial> [<device-serial>]
``` ```
> [!NOTE] > [!NOTE]
@ -128,7 +128,7 @@ ReVanced CLI is divided into the following fundamental commands:
```bash ```bash
java -jar revanced-cli.jar utility install \ java -jar revanced-cli.jar utility install \
-a input.apk \ -a input.apk \
<device-serial> [<device-serial>]
``` ```
> [!NOTE] > [!NOTE]

View File

@ -1,10 +1,10 @@
[versions] [versions]
shadow = "8.1.1" shadow = "8.1.1"
kotlin-test = "1.9.10" kotlin-test = "1.9.20"
kotlinx-coroutines-core = "1.7.3" kotlinx-coroutines-core = "1.7.3"
picocli = "4.7.3" picocli = "4.7.3"
revanced-patcher = "19.0.0" revanced-patcher = "19.0.0"
revanced-library = "1.2.0" revanced-library = "1.3.0"
[libraries] [libraries]
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin-test" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin-test" }

View File

@ -18,10 +18,9 @@ import java.io.PrintWriter
import java.io.StringWriter import java.io.StringWriter
import java.util.logging.Logger import java.util.logging.Logger
@CommandLine.Command( @CommandLine.Command(
name = "patch", name = "patch",
description = ["Patch an APK file."] description = ["Patch an APK file."],
) )
internal object PatchCommand : Runnable { internal object PatchCommand : Runnable {
private val logger = Logger.getLogger(PatchCommand::class.java.name) private val logger = Logger.getLogger(PatchCommand::class.java.name)
@ -37,25 +36,25 @@ internal object PatchCommand : Runnable {
@CommandLine.Option( @CommandLine.Option(
names = ["-i", "--include"], names = ["-i", "--include"],
description = ["List of patches to include."] description = ["List of patches to include."],
) )
private var includedPatches = hashSetOf<String>() private var includedPatches = hashSetOf<String>()
@CommandLine.Option( @CommandLine.Option(
names = ["--ii"], 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<Int>() private var includedPatchesByIndex = arrayOf<Int>()
@CommandLine.Option( @CommandLine.Option(
names = ["-e", "--exclude"], names = ["-e", "--exclude"],
description = ["List of patches to exclude."] description = ["List of patches to exclude."],
) )
private var excludedPatches = hashSetOf<String>() private var excludedPatches = hashSetOf<String>()
@CommandLine.Option( @CommandLine.Option(
names = ["--ei"], 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<Int>() private var excludedPatchesByIndex = arrayOf<Int>()
@ -68,14 +67,14 @@ internal object PatchCommand : Runnable {
@CommandLine.Option( @CommandLine.Option(
names = ["--exclusive"], names = ["--exclusive"],
description = ["Only include patches that are explicitly specified to be included."], description = ["Only include patches that are explicitly specified to be included."],
showDefaultValue = ALWAYS showDefaultValue = ALWAYS,
) )
private var exclusive = false private var exclusive = false
@CommandLine.Option( @CommandLine.Option(
names = ["-f","--force"], names = ["-f", "--force"],
description = ["Bypass compatibility checks for the supplied APK's version."], description = ["Bypass compatibility checks for the supplied APK's version."],
showDefaultValue = ALWAYS showDefaultValue = ALWAYS,
) )
private var force: Boolean = false private var force: Boolean = false
@ -91,48 +90,52 @@ internal object PatchCommand : Runnable {
@CommandLine.Option( @CommandLine.Option(
names = ["-d", "--device-serial"], 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 private var deviceSerial: String? = null
@CommandLine.Option( @CommandLine.Option(
names = ["--mount"], names = ["--mount"],
description = ["Install by mounting the patched APK file."], description = ["Install by mounting the patched APK file."],
showDefaultValue = ALWAYS showDefaultValue = ALWAYS,
) )
private var mount: Boolean = false private var mount: Boolean = false
@CommandLine.Option( @CommandLine.Option(
names = ["--keystore"], names = ["--keystore"],
description = ["Path to the keystore to sign the patched APK file with. " + description = [
"Defaults to the same directory as the supplied APK file."], "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 private var keystoreFilePath: File? = null
// key store password // key store password
@CommandLine.Option( @CommandLine.Option(
names = ["--keystore-password"], 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 private var keyStorePassword: String? = null // Empty password by default
@CommandLine.Option( @CommandLine.Option(
names = ["--alias"], names = ["--alias"],
description = ["The alias of the key from the keystore to sign the patched APK file with."], 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" private var alias = "ReVanced Key"
@CommandLine.Option( @CommandLine.Option(
names = ["--keystore-entry-password"], 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 private var password = "" // Empty password by default
@CommandLine.Option( @CommandLine.Option(
names = ["--signer"], names = ["--signer"],
description = ["The name of the signer to sign the patched APK file with."], description = ["The name of the signer to sign the patched APK file with."],
showDefaultValue = ALWAYS showDefaultValue = ALWAYS,
) )
private var signer = "ReVanced" private var signer = "ReVanced"
@ -147,33 +150,35 @@ internal object PatchCommand : Runnable {
@CommandLine.Option( @CommandLine.Option(
names = ["-p", "--purge"], names = ["-p", "--purge"],
description = ["Purge the temporary resource cache directory after patching."], description = ["Purge the temporary resource cache directory after patching."],
showDefaultValue = ALWAYS showDefaultValue = ALWAYS,
) )
private var purge: Boolean = false private var purge: Boolean = false
@CommandLine.Option( @CommandLine.Option(
names = ["-w", "--warn"], names = ["-w", "--warn"],
description = ["Warn if a patch can not be found in the supplied patch bundles."], description = ["Warn if a patch can not be found in the supplied patch bundles."],
showDefaultValue = ALWAYS showDefaultValue = ALWAYS,
) )
private var warn: Boolean = false private var warn: Boolean = false
@CommandLine.Parameters( @CommandLine.Parameters(
description = ["APK file to be patched."], description = ["APK file to be patched."],
arity = "1..1" arity = "1..1",
) )
@Suppress("unused") @Suppress("unused")
private fun setApk(apk: File) { private fun setApk(apk: File) {
if (!apk.exists()) throw CommandLine.ParameterException( if (!apk.exists()) {
spec.commandLine(), throw CommandLine.ParameterException(
"APK file ${apk.name} does not exist" spec.commandLine(),
) "APK file ${apk.name} does not exist",
)
}
this.apk = apk this.apk = apk
} }
@CommandLine.Option( @CommandLine.Option(
names = ["-m", "--merge"], 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") @Suppress("unused")
private fun setIntegrations(integrations: Array<File>) { private fun setIntegrations(integrations: Array<File>) {
@ -186,7 +191,7 @@ internal object PatchCommand : Runnable {
@CommandLine.Option( @CommandLine.Option(
names = ["-b", "--patch-bundle"], names = ["-b", "--patch-bundle"],
description = ["One or more bundles of patches."], description = ["One or more bundles of patches."],
required = true required = true,
) )
@Suppress("unused") @Suppress("unused")
private fun setPatchBundles(patchBundles: Array<File>) { private fun setPatchBundles(patchBundles: Array<File>) {
@ -198,14 +203,16 @@ internal object PatchCommand : Runnable {
@CommandLine.Option( @CommandLine.Option(
names = ["--custom-aapt2-binary"], 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") @Suppress("unused")
private fun setAaptBinaryPath(aaptBinaryPath: File) { private fun setAaptBinaryPath(aaptBinaryPath: File) {
if (!aaptBinaryPath.exists()) throw CommandLine.ParameterException( if (!aaptBinaryPath.exists()) {
spec.commandLine(), throw CommandLine.ParameterException(
"AAPT binary ${aaptBinaryPath.name} does not exist" spec.commandLine(),
) "AAPT binary ${aaptBinaryPath.name} does not exist",
)
}
this.aaptBinaryPath = aaptBinaryPath this.aaptBinaryPath = aaptBinaryPath
} }
@ -213,22 +220,20 @@ internal object PatchCommand : Runnable {
// region Setup // region Setup
val outputFilePath = outputFilePath ?: File("").absoluteFile.resolve( val outputFilePath = outputFilePath ?: File("").absoluteFile.resolve(
"${apk.nameWithoutExtension}-patched.${apk.extension}" "${apk.nameWithoutExtension}-patched.${apk.extension}",
) )
val resourceCachePath = resourceCachePath ?: outputFilePath.parentFile.resolve( val resourceCachePath = resourceCachePath ?: outputFilePath.parentFile.resolve(
"${outputFilePath.nameWithoutExtension}-resource-cache" "${outputFilePath.nameWithoutExtension}-resource-cache",
) )
val optionsFile = optionsFile ?: outputFilePath.parentFile.resolve( val optionsFile = optionsFile ?: outputFilePath.parentFile.resolve(
"${outputFilePath.nameWithoutExtension}-options.json" "${outputFilePath.nameWithoutExtension}-options.json",
) )
val keystoreFilePath = keystoreFilePath ?: outputFilePath.parentFile val keystoreFilePath = keystoreFilePath ?: outputFilePath.parentFile
.resolve("${outputFilePath.nameWithoutExtension}.keystore") .resolve("${outputFilePath.nameWithoutExtension}.keystore")
val adbManager = deviceSerial?.let { serial -> AdbManager.getAdbManager(serial, mount) }
// endregion // endregion
// region Load patches // region Load patches
@ -238,13 +243,15 @@ internal object PatchCommand : Runnable {
val patches = PatchBundleLoader.Jar(*patchBundles.toTypedArray()) val patches = PatchBundleLoader.Jar(*patchBundles.toTypedArray())
// Warn if a patch can not be found in the supplied patch bundles. // Warn if a patch can not be found in the supplied patch bundles.
if (warn) patches.map { it.name }.toHashSet().let { availableNames -> if (warn) {
(includedPatches + excludedPatches).filter { name -> patches.map { it.name }.toHashSet().let { availableNames ->
!availableNames.contains(name) (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 // endregion
@ -255,14 +262,17 @@ internal object PatchCommand : Runnable {
resourceCachePath, resourceCachePath,
aaptBinaryPath?.path, aaptBinaryPath?.path,
resourceCachePath.absolutePath, resourceCachePath.absolutePath,
true true,
) ),
).use { patcher -> ).use { patcher ->
val filteredPatches = patcher.filterPatchSelection(patches).also { patches -> val filteredPatches = patcher.filterPatchSelection(patches).also { patches ->
logger.info("Setting patch options") logger.info("Setting patch options")
if (optionsFile.exists()) patches.setOptions(optionsFile) if (optionsFile.exists()) {
else Options.serialize(patches, prettyPrint = true).let(optionsFile::writeText) patches.setOptions(optionsFile)
} else {
Options.serialize(patches, prettyPrint = true).let(optionsFile::writeText)
}
} }
// region Patch // region Patch
@ -292,24 +302,29 @@ internal object PatchCommand : Runnable {
ApkUtils.copyAligned(apk, this, patcherResult) ApkUtils.copyAligned(apk, this, patcherResult)
} }
if (!mount) ApkUtils.sign( if (!mount) {
alignedFile, ApkUtils.sign(
outputFilePath, alignedFile,
ApkUtils.SigningOptions( outputFilePath,
keystoreFilePath, ApkUtils.SigningOptions(
keyStorePassword, keystoreFilePath,
alias, keyStorePassword,
password, alias,
signer password,
signer,
),
) )
) } else {
else alignedFile.renameTo(outputFilePath) alignedFile.renameTo(outputFilePath)
}
// endregion // endregion
// region Install // 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 // 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: * 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 } it.any { version -> version == packageVersion }
} ?: true } ?: true
if (!matchesVersion) return@patch logger.warning( if (!matchesVersion) {
"$patchName is incompatible with version $packageVersion. " return@patch logger.warning(
+ "This patch is only compatible with version " "$patchName is incompatible with version $packageVersion. " +
+ packages.joinToString(";") { pkg -> "This patch is only compatible with version " +
pkg.versions!!.joinToString(", ") packages.joinToString(";") { pkg ->
} pkg.versions!!.joinToString(", ")
) },
)
}
} ?: return@patch logger.fine( } ?: return@patch logger.fine(
"$patchName is incompatible with $packageName. " "$patchName is incompatible with $packageName. " +
+ "This patch is only compatible with " "This patch is only compatible with " +
+ packages.joinToString(", ") { `package` -> `package`.name }) packages.joinToString(", ") { `package` -> `package`.name },
)
return@let return@let
} ?: logger.fine("$patchName has no constraint on packages.") } ?: logger.fine("$patchName has no constraint on packages.")
@ -374,8 +391,11 @@ internal object PatchCommand : Runnable {
} }
private fun purge(resourceCachePath: File) { private fun purge(resourceCachePath: File) {
val result = if (resourceCachePath.deleteRecursively()) "Purged resource cache directory" val result = if (resourceCachePath.deleteRecursively()) {
else "Failed to purge resource cache directory" "Purged resource cache directory"
} else {
"Failed to purge resource cache directory"
}
logger.info(result) logger.info(result)
} }
} }

View File

@ -5,24 +5,23 @@ import picocli.CommandLine.*
import java.io.File import java.io.File
import java.util.logging.Logger import java.util.logging.Logger
@Command( @Command(
name = "install", 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 { internal object InstallCommand : Runnable {
private val logger = Logger.getLogger(InstallCommand::class.java.name) private val logger = Logger.getLogger(InstallCommand::class.java.name)
@Parameters( @Parameters(
description = ["ADB device serials"], description = ["ADB device serials. If not supplied, the first connected device will be used."],
arity = "1..*" arity = "0..*",
) )
private lateinit var deviceSerials: Array<String> private var deviceSerials: Array<String>? = null
@Option( @Option(
names = ["-a", "--apk"], names = ["-a", "--apk"],
description = ["APK file to be installed"], description = ["APK file to be installed"],
required = true required = true,
) )
private lateinit var apk: File private lateinit var apk: File
@ -32,11 +31,13 @@ internal object InstallCommand : Runnable {
) )
private var packageName: String? = null private var packageName: String? = null
override fun run() = deviceSerials.forEach { deviceSerial -> override fun run() {
try { fun install(deviceSerial: String? = null) = try {
AdbManager.getAdbManager(deviceSerial, packageName != null).install(AdbManager.Apk(apk, packageName)) AdbManager.getAdbManager(deviceSerial, packageName != null).install(AdbManager.Apk(apk, packageName))
} catch (e: AdbManager.DeviceNotFoundException) { } catch (e: AdbManager.DeviceNotFoundException) {
logger.severe(e.toString()) logger.severe(e.toString())
} }
deviceSerials?.forEach(::install) ?: install()
} }
} }

View File

@ -14,10 +14,10 @@ internal object UninstallCommand : Runnable {
private val logger = Logger.getLogger(UninstallCommand::class.java.name) private val logger = Logger.getLogger(UninstallCommand::class.java.name)
@Parameters( @Parameters(
description = ["ADB device serials"], description = ["ADB device serials. If not supplied, the first connected device will be used."],
arity = "1..*" arity = "0..*"
) )
private lateinit var deviceSerials: Array<String> private var deviceSerials: Array<String>? = null
@Option( @Option(
names = ["-p", "--package-name"], names = ["-p", "--package-name"],
@ -33,11 +33,13 @@ internal object UninstallCommand : Runnable {
) )
private var unmount: Boolean = false private var unmount: Boolean = false
override fun run() = deviceSerials.forEach { deviceSerial -> override fun run() {
try { fun uninstall(deviceSerial: String? = null) = try {
AdbManager.getAdbManager(deviceSerial, unmount).uninstall(packageName) AdbManager.getAdbManager(deviceSerial, unmount).uninstall(packageName)
} catch (e: AdbManager.DeviceNotFoundException) { } catch (e: AdbManager.DeviceNotFoundException) {
logger.severe(e.toString()) logger.severe(e.toString())
} }
deviceSerials?.forEach { uninstall(it) } ?: uninstall()
} }
} }