mirror of
https://github.com/ReVanced/revanced-cli.git
synced 2026-01-11 13:56:18 +00:00
refactor: Move ReVanced Library subproject to another repository
This commit removes the subproject ReVanced Library and moves it to another repository. A monorepo turned out to be difficult to work with.
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
package app.revanced.cli.command
|
||||
|
||||
import app.revanced.patcher.PatchBundleLoader
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.options.PatchOption
|
||||
import picocli.CommandLine.*
|
||||
import picocli.CommandLine.Help.Visibility.ALWAYS
|
||||
import java.io.File
|
||||
import java.util.logging.Logger
|
||||
|
||||
|
||||
@Command(name = "list-patches", description = ["List patches from supplied patch bundles"])
|
||||
internal object ListPatchesCommand : Runnable {
|
||||
private val logger = Logger.getLogger(ListPatchesCommand::class.java.name)
|
||||
|
||||
@Parameters(
|
||||
description = ["Paths to patch bundles"], arity = "1..*"
|
||||
)
|
||||
private lateinit var patchBundles: Array<File>
|
||||
|
||||
@Option(
|
||||
names = ["-d", "--with-descriptions"], description = ["List their descriptions"], showDefaultValue = ALWAYS
|
||||
)
|
||||
private var withDescriptions: Boolean = true
|
||||
|
||||
@Option(
|
||||
names = ["-p", "--with-packages"],
|
||||
description = ["List the packages the patches are compatible with"],
|
||||
showDefaultValue = ALWAYS
|
||||
)
|
||||
private var withPackages: Boolean = false
|
||||
|
||||
@Option(
|
||||
names = ["-v", "--with-versions"],
|
||||
description = ["List the versions of the apps the patches are compatible with"],
|
||||
showDefaultValue = ALWAYS
|
||||
)
|
||||
private var withVersions: Boolean = false
|
||||
|
||||
@Option(
|
||||
names = ["-o", "--with-options"], description = ["List the options of the patches"], showDefaultValue = ALWAYS
|
||||
)
|
||||
private var withOptions: Boolean = false
|
||||
|
||||
@Option(
|
||||
names = ["-f", "--filter-package-name"], description = ["Filter patches by package name"]
|
||||
)
|
||||
private var packageName: String? = null
|
||||
|
||||
override fun run() {
|
||||
fun Patch.CompatiblePackage.buildString() = buildString {
|
||||
if (withVersions && versions != null) {
|
||||
appendLine("Package name: $name")
|
||||
appendLine("Compatible versions:")
|
||||
append(versions!!.joinToString("\n") { version -> version }.prependIndent("\t"))
|
||||
} else append("Package name: $name")
|
||||
}
|
||||
|
||||
fun PatchOption<*>.buildString() = buildString {
|
||||
appendLine("Title: $title")
|
||||
appendLine("Description: $description")
|
||||
|
||||
value?.let {
|
||||
appendLine("Key: $key")
|
||||
append("Value: $it")
|
||||
} ?: append("Key: $key")
|
||||
}
|
||||
|
||||
fun Patch<*>.buildString() = buildString {
|
||||
append("Name: $name")
|
||||
|
||||
if (withDescriptions) append("\nDescription: $description")
|
||||
|
||||
if (withOptions && options.isNotEmpty()) {
|
||||
appendLine("\nOptions:")
|
||||
append(
|
||||
options.values.joinToString("\n\n") { option ->
|
||||
option.buildString()
|
||||
}.prependIndent("\t")
|
||||
)
|
||||
}
|
||||
|
||||
if (withPackages && compatiblePackages != null) {
|
||||
appendLine("\nCompatible packages:")
|
||||
append(
|
||||
compatiblePackages!!.joinToString("\n") { it.buildString() }.prependIndent("\t")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun Patch<*>.anyPackageName(name: String) = compatiblePackages?.any { it.name == name } == true
|
||||
|
||||
val patches = PatchBundleLoader.Jar(*patchBundles)
|
||||
|
||||
val filtered = packageName?.let { patches.filter { patch -> patch.anyPackageName(it) } } ?: patches
|
||||
|
||||
if (filtered.isNotEmpty()) logger.info(filtered.joinToString("\n\n") { it.buildString() })
|
||||
}
|
||||
}
|
||||
39
src/main/kotlin/app/revanced/cli/command/MainCommand.kt
Normal file
39
src/main/kotlin/app/revanced/cli/command/MainCommand.kt
Normal file
@@ -0,0 +1,39 @@
|
||||
package app.revanced.cli.command
|
||||
|
||||
import app.revanced.cli.command.utility.UtilityCommand
|
||||
import app.revanced.library.logging.Logger
|
||||
import picocli.CommandLine
|
||||
import picocli.CommandLine.Command
|
||||
import picocli.CommandLine.IVersionProvider
|
||||
import java.util.*
|
||||
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
Logger.setDefault()
|
||||
CommandLine(MainCommand).execute(*args)
|
||||
}
|
||||
|
||||
private object CLIVersionProvider : IVersionProvider {
|
||||
override fun getVersion() = arrayOf(
|
||||
MainCommand::class.java.getResourceAsStream(
|
||||
"/app/revanced/cli/version.properties"
|
||||
)?.use { stream ->
|
||||
Properties().apply { load(stream) }.let {
|
||||
"ReVanced CLI v${it.getProperty("version")}"
|
||||
}
|
||||
} ?: "ReVanced CLI")
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "revanced-cli",
|
||||
description = ["Command line application to use ReVanced"],
|
||||
mixinStandardHelpOptions = true,
|
||||
versionProvider = CLIVersionProvider::class,
|
||||
subcommands = [
|
||||
ListPatchesCommand::class,
|
||||
PatchCommand::class,
|
||||
OptionsCommand::class,
|
||||
UtilityCommand::class,
|
||||
]
|
||||
)
|
||||
private object MainCommand
|
||||
53
src/main/kotlin/app/revanced/cli/command/OptionsCommand.kt
Normal file
53
src/main/kotlin/app/revanced/cli/command/OptionsCommand.kt
Normal file
@@ -0,0 +1,53 @@
|
||||
package app.revanced.cli.command
|
||||
|
||||
import app.revanced.library.Options
|
||||
import app.revanced.library.Options.setOptions
|
||||
import app.revanced.patcher.PatchBundleLoader
|
||||
import picocli.CommandLine
|
||||
import picocli.CommandLine.Help.Visibility.ALWAYS
|
||||
import java.io.File
|
||||
import java.util.logging.Logger
|
||||
|
||||
@CommandLine.Command(
|
||||
name = "options",
|
||||
description = ["Generate options file from patches"],
|
||||
)
|
||||
internal object OptionsCommand : Runnable {
|
||||
private val logger = Logger.getLogger(OptionsCommand::class.java.name)
|
||||
|
||||
@CommandLine.Parameters(
|
||||
description = ["Paths to patch bundles"], arity = "1..*"
|
||||
)
|
||||
private lateinit var patchBundles: Array<File>
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-p", "--path"], description = ["Path to patch options JSON file"], showDefaultValue = ALWAYS
|
||||
)
|
||||
private var filePath: File = File("options.json")
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-o", "--overwrite"], description = ["Overwrite existing options file"], showDefaultValue = ALWAYS
|
||||
)
|
||||
private var overwrite: Boolean = false
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-u", "--update"],
|
||||
description = ["Update existing options by adding missing and removing non-existent options"],
|
||||
showDefaultValue = ALWAYS
|
||||
)
|
||||
private var update: Boolean = false
|
||||
|
||||
override fun run() = try {
|
||||
PatchBundleLoader.Jar(*patchBundles).let { patches ->
|
||||
if (!filePath.exists() || overwrite) {
|
||||
if (update && filePath.exists()) patches.setOptions(filePath)
|
||||
|
||||
Options.serialize(patches, prettyPrint = true).let(filePath::writeText)
|
||||
} else throw OptionsFileAlreadyExistsException()
|
||||
}
|
||||
} catch (ex: OptionsFileAlreadyExistsException) {
|
||||
logger.severe("Options file already exists, use --overwrite to override it")
|
||||
}
|
||||
|
||||
class OptionsFileAlreadyExistsException : Exception()
|
||||
}
|
||||
331
src/main/kotlin/app/revanced/cli/command/PatchCommand.kt
Normal file
331
src/main/kotlin/app/revanced/cli/command/PatchCommand.kt
Normal file
@@ -0,0 +1,331 @@
|
||||
package app.revanced.cli.command
|
||||
|
||||
import app.revanced.library.ApkUtils
|
||||
import app.revanced.library.Options
|
||||
import app.revanced.library.Options.setOptions
|
||||
import app.revanced.library.adb.AdbManager
|
||||
import app.revanced.patcher.PatchBundleLoader
|
||||
import app.revanced.patcher.PatchSet
|
||||
import app.revanced.patcher.Patcher
|
||||
import app.revanced.patcher.PatcherOptions
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import picocli.CommandLine
|
||||
import picocli.CommandLine.Help.Visibility.ALWAYS
|
||||
import picocli.CommandLine.Model.CommandSpec
|
||||
import picocli.CommandLine.Spec
|
||||
import java.io.File
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import java.util.logging.Logger
|
||||
|
||||
|
||||
@CommandLine.Command(
|
||||
name = "patch", description = ["Patch an APK file"]
|
||||
)
|
||||
internal object PatchCommand : Runnable {
|
||||
private val logger = Logger.getLogger(PatchCommand::class.java.name)
|
||||
|
||||
@Spec
|
||||
lateinit var spec: CommandSpec // injected by picocli
|
||||
|
||||
private lateinit var apk: File
|
||||
|
||||
private var integrations = listOf<File>()
|
||||
|
||||
private var patchBundles = emptyList<File>()
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-i", "--include"], description = ["List of patches to include"]
|
||||
)
|
||||
private var includedPatches = arrayOf<String>()
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-e", "--exclude"], description = ["List of patches to exclude"]
|
||||
)
|
||||
private var excludedPatches = arrayOf<String>()
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["--options"], description = ["Path to patch options JSON file"], showDefaultValue = ALWAYS
|
||||
)
|
||||
private var optionsFile: File = File("options.json")
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["--exclusive"],
|
||||
description = ["Only include patches that are explicitly specified to be included"],
|
||||
showDefaultValue = ALWAYS
|
||||
)
|
||||
private var exclusive = false
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-f","--force"],
|
||||
description = ["Force inclusion of patches that are incompatible with the supplied APK file's version"],
|
||||
showDefaultValue = ALWAYS
|
||||
)
|
||||
private var force: Boolean = false
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-o", "--out"], description = ["Path to save the patched APK file to"], required = true
|
||||
)
|
||||
private lateinit var outputFilePath: File
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-d", "--device-serial"], description = ["ADB device serial to install to"], showDefaultValue = ALWAYS
|
||||
)
|
||||
private var deviceSerial: String? = null
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["--mount"], description = ["Install by mounting the patched APK file"], showDefaultValue = ALWAYS
|
||||
)
|
||||
private var mount: Boolean = false
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["--keystore"], description = ["Path to the keystore to sign the patched APK file with"],
|
||||
)
|
||||
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"],
|
||||
)
|
||||
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
|
||||
)
|
||||
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"]
|
||||
)
|
||||
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
|
||||
)
|
||||
private var signer = "ReVanced"
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-r", "--resource-cache"],
|
||||
description = ["Path to temporary resource cache directory"],
|
||||
showDefaultValue = ALWAYS
|
||||
)
|
||||
private var resourceCachePath = File("revanced-resource-cache")
|
||||
|
||||
private var aaptBinaryPath: File? = null
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-p", "--purge"],
|
||||
description = ["Purge the temporary resource cache directory after patching"],
|
||||
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
|
||||
)
|
||||
private var warn: Boolean = false
|
||||
|
||||
@CommandLine.Parameters(
|
||||
description = ["APK file to be patched"], 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"
|
||||
)
|
||||
this.apk = apk
|
||||
}
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-m", "--merge"], description = ["One or more DEX files or containers to merge into the APK"]
|
||||
)
|
||||
@Suppress("unused")
|
||||
private fun setIntegrations(integrations: Array<File>) {
|
||||
integrations.firstOrNull { !it.exists() }?.let {
|
||||
throw CommandLine.ParameterException(spec.commandLine(), "Integrations file ${it.name} does not exist")
|
||||
}
|
||||
this.integrations += integrations
|
||||
}
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-b", "--patch-bundle"], description = ["One or more bundles of patches"], required = true
|
||||
)
|
||||
@Suppress("unused")
|
||||
private fun setPatchBundles(patchBundles: Array<File>) {
|
||||
patchBundles.firstOrNull { !it.exists() }?.let {
|
||||
throw CommandLine.ParameterException(spec.commandLine(), "Patch bundle ${it.name} does not exist")
|
||||
}
|
||||
this.patchBundles = patchBundles.toList()
|
||||
}
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["--custom-aapt2-binary"], 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"
|
||||
)
|
||||
this.aaptBinaryPath = aaptBinaryPath
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
val adbManager = deviceSerial?.let { serial -> AdbManager.getAdbManager(serial, mount) }
|
||||
|
||||
// region Load patches
|
||||
|
||||
logger.info("Loading patches")
|
||||
|
||||
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 ->
|
||||
arrayOf(*includedPatches, *excludedPatches).filter { name ->
|
||||
!availableNames.contains(name)
|
||||
}
|
||||
}.let { unknownPatches ->
|
||||
if (unknownPatches.isEmpty()) return@let
|
||||
logger.warning("Unknown input of patches:\n${unknownPatches.joinToString("\n")}")
|
||||
}
|
||||
|
||||
logger.info("Setting patch options")
|
||||
|
||||
optionsFile.let {
|
||||
if (it.exists()) patches.setOptions(it)
|
||||
else Options.serialize(patches, prettyPrint = true).let(it::writeText)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
Patcher(
|
||||
PatcherOptions(
|
||||
apk,
|
||||
resourceCachePath,
|
||||
aaptBinaryPath?.path,
|
||||
resourceCachePath.absolutePath,
|
||||
)
|
||||
).use { patcher ->
|
||||
// region Patch
|
||||
|
||||
val patcherResult = patcher.apply {
|
||||
acceptIntegrations(integrations)
|
||||
acceptPatches(filterPatchSelection(patches))
|
||||
|
||||
// Execute patches.
|
||||
runBlocking {
|
||||
apply(false).collect { patchResult ->
|
||||
patchResult.exception?.let {
|
||||
StringWriter().use { writer ->
|
||||
it.printStackTrace(PrintWriter(writer))
|
||||
logger.severe("${patchResult.patch.name} failed:\n$writer")
|
||||
}
|
||||
} ?: logger.info("${patchResult.patch.name} succeeded")
|
||||
}
|
||||
}
|
||||
}.get()
|
||||
|
||||
// endregion
|
||||
|
||||
// region Save
|
||||
|
||||
val tempFile = resourceCachePath.resolve(apk.name).apply {
|
||||
ApkUtils.copyAligned(apk, this, patcherResult)
|
||||
}
|
||||
|
||||
val keystoreFilePath = keystoreFilePath ?: outputFilePath.absoluteFile.parentFile
|
||||
.resolve("${outputFilePath.nameWithoutExtension}.keystore")
|
||||
|
||||
if (!mount) ApkUtils.sign(
|
||||
tempFile,
|
||||
outputFilePath,
|
||||
ApkUtils.SigningOptions(
|
||||
keystoreFilePath,
|
||||
keyStorePassword,
|
||||
alias,
|
||||
password,
|
||||
signer
|
||||
)
|
||||
)
|
||||
|
||||
// endregion
|
||||
|
||||
// region Install
|
||||
|
||||
adbManager?.install(AdbManager.Apk(outputFilePath, patcher.context.packageMetadata.packageName))
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
||||
if (purge) {
|
||||
logger.info("Purging temporary files")
|
||||
purge(resourceCachePath)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filter the patches to be added to the patcher. The filter is based on the following:
|
||||
*
|
||||
* @param patches The patches to filter.
|
||||
* @return The filtered patches.
|
||||
*/
|
||||
private fun Patcher.filterPatchSelection(patches: PatchSet) = buildList {
|
||||
val packageName = context.packageMetadata.packageName
|
||||
val packageVersion = context.packageMetadata.packageVersion
|
||||
|
||||
patches.forEach patch@{ patch ->
|
||||
val patchName = patch.name!!
|
||||
|
||||
val explicitlyExcluded = excludedPatches.contains(patchName)
|
||||
if (explicitlyExcluded) return@patch logger.info("Excluding $patchName")
|
||||
|
||||
// Make sure the patch is compatible with the supplied APK files package name and version.
|
||||
patch.compatiblePackages?.let { packages ->
|
||||
packages.singleOrNull { it.name == packageName }?.let { `package` ->
|
||||
val matchesVersion = force || `package`.versions?.let {
|
||||
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.name}: ${pkg.versions!!.joinToString(", ")}"
|
||||
}
|
||||
)
|
||||
} ?: return@patch logger.fine(
|
||||
"$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.")
|
||||
|
||||
// If the patch is implicitly used, it will be only included if [exclusive] is false.
|
||||
val implicitlyIncluded = !exclusive && patch.use
|
||||
// If the patch is explicitly used, it will be included even if [exclusive] is false.
|
||||
val explicitlyIncluded = includedPatches.contains(patchName)
|
||||
|
||||
val included = implicitlyIncluded || explicitlyIncluded
|
||||
if (!included) return@patch logger.info("$patchName excluded") // Case 1.
|
||||
|
||||
logger.fine("Adding $patchName")
|
||||
|
||||
add(patch)
|
||||
}
|
||||
}
|
||||
|
||||
private fun purge(resourceCachePath: File) {
|
||||
val result = if (resourceCachePath.deleteRecursively()) "Purged resource cache directory"
|
||||
else "Failed to purge resource cache directory"
|
||||
logger.info(result)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package app.revanced.cli.command.utility
|
||||
|
||||
import app.revanced.library.adb.AdbManager
|
||||
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"]
|
||||
)
|
||||
internal object InstallCommand : Runnable {
|
||||
private val logger = Logger.getLogger(InstallCommand::class.java.name)
|
||||
|
||||
@Parameters(
|
||||
description = ["ADB device serials"], arity = "1..*"
|
||||
)
|
||||
private lateinit var deviceSerials: Array<String>
|
||||
|
||||
@Option(
|
||||
names = ["-a", "--apk"], description = ["APK file to be installed"], required = true
|
||||
)
|
||||
private lateinit var apk: File
|
||||
|
||||
@Option(
|
||||
names = ["-m", "--mount"],
|
||||
description = ["Mount the supplied APK file over the app with the supplied package name"],
|
||||
)
|
||||
private var packageName: String? = null
|
||||
|
||||
override fun run() = deviceSerials.forEach { deviceSerial ->
|
||||
try {
|
||||
AdbManager.getAdbManager(deviceSerial, packageName != null).install(AdbManager.Apk(apk, packageName))
|
||||
} catch (e: AdbManager.DeviceNotFoundException) {
|
||||
logger.severe(e.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package app.revanced.cli.command.utility
|
||||
|
||||
import app.revanced.library.adb.AdbManager
|
||||
import picocli.CommandLine.*
|
||||
import picocli.CommandLine.Help.Visibility.ALWAYS
|
||||
import java.util.logging.Logger
|
||||
|
||||
|
||||
@Command(
|
||||
name = "uninstall",
|
||||
description = ["Uninstall a patched app from the devices with the supplied ADB device serials"]
|
||||
)
|
||||
internal object UninstallCommand : Runnable {
|
||||
private val logger = Logger.getLogger(UninstallCommand::class.java.name)
|
||||
|
||||
@Parameters(description = ["ADB device serials"], arity = "1..*")
|
||||
private lateinit var deviceSerials: Array<String>
|
||||
|
||||
@Option(names = ["-p", "--package-name"], description = ["Package name of the app to uninstall"], required = true)
|
||||
private lateinit var packageName: String
|
||||
|
||||
@Option(
|
||||
names = ["-u", "--unmount"],
|
||||
description = ["Uninstall by unmounting the patched APK file"],
|
||||
showDefaultValue = ALWAYS
|
||||
)
|
||||
private var unmount: Boolean = false
|
||||
|
||||
override fun run() = deviceSerials.forEach { deviceSerial ->
|
||||
try {
|
||||
AdbManager.getAdbManager(deviceSerial, unmount).uninstall(packageName)
|
||||
} catch (e: AdbManager.DeviceNotFoundException) {
|
||||
logger.severe(e.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package app.revanced.cli.command.utility
|
||||
|
||||
import picocli.CommandLine
|
||||
|
||||
@CommandLine.Command(
|
||||
name = "utility",
|
||||
description = ["Commands for utility purposes"],
|
||||
subcommands = [InstallCommand::class, UninstallCommand::class],
|
||||
)
|
||||
internal object UtilityCommand
|
||||
1
src/main/resources/app/revanced/cli/version.properties
Normal file
1
src/main/resources/app/revanced/cli/version.properties
Normal file
@@ -0,0 +1 @@
|
||||
version=${projectVersion}
|
||||
Reference in New Issue
Block a user