Skip to content

Commit 2cc09a9

Browse files
authored
feat: Support GitHub repo urls with --patches argument (#71)
1 parent 0a25c51 commit 2cc09a9

6 files changed

Lines changed: 343 additions & 30 deletions

File tree

src/main/kotlin/app/morphe/cli/command/CommandUtils.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,19 @@
11
package app.morphe.cli.command
22

33
import picocli.CommandLine
4-
import kotlin.text.iterator
4+
import picocli.CommandLine.Model.CommandSpec
5+
import java.io.File
6+
7+
internal fun checkFileExistsOrIsUrl(files: Set<File>, spec: CommandSpec) : Set<File> {
8+
files.firstOrNull {
9+
!it.exists() && !it.toString().let {
10+
it.startsWith("http:/") || it.startsWith("https:/")
11+
}
12+
}?.let {
13+
throw CommandLine.ParameterException(spec.commandLine(), "${it.name} can not be found")
14+
}
15+
return files
16+
}
517

618
class OptionKeyConverter : CommandLine.ITypeConverter<String> {
719
override fun convert(value: String): String = value

src/main/kotlin/app/morphe/cli/command/ListCompatibleVersions.kt

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@ import app.morphe.patcher.patch.VersionMap
55
import app.morphe.patcher.patch.loadPatchesFromJar
66
import app.morphe.patcher.patch.mostCommonCompatibleVersions
77
import picocli.CommandLine
8+
import picocli.CommandLine.Command
9+
import picocli.CommandLine.Help.Visibility.ALWAYS
10+
import picocli.CommandLine.Model.CommandSpec
11+
import picocli.CommandLine.Option
12+
import picocli.CommandLine.Spec
813
import java.io.File
914
import java.util.logging.Logger
1015

11-
@CommandLine.Command(
16+
@Command(
1217
name = "list-versions",
1318
description = [
1419
"List the most common compatible versions of apps that are compatible " +
@@ -18,25 +23,47 @@ import java.util.logging.Logger
1823
internal class ListCompatibleVersions : Runnable {
1924
private val logger = Logger.getLogger(this::class.java.name)
2025

21-
@CommandLine.Parameters(
22-
description = ["Paths to MPP files."],
26+
@Option(
27+
names = ["--patches"],
28+
description = ["Path to a MPP file or a GitHub repo url such as https://github.com/MorpheApp/morphe-patches"],
2329
arity = "1..*",
30+
required = true
2431
)
25-
private lateinit var patchesFiles: Set<File>
32+
@Suppress("unused")
33+
private fun setPatchesFile(patchesFiles: Set<File>) {
34+
this.patchesFiles = checkFileExistsOrIsUrl(patchesFiles, spec)
35+
}
36+
private var patchesFiles = emptySet<File>()
37+
38+
@Option(
39+
names = ["--prerelease"],
40+
description = ["Fetch the latest dev pre-release instead of the stable main release from the repo provided in --patches."],
41+
showDefaultValue = ALWAYS,
42+
)
43+
private var prerelease: Boolean = false
2644

27-
@CommandLine.Option(
45+
@Option(
2846
names = ["-f", "--filter-package-names"],
2947
description = ["Filter patches by package name."],
3048
)
3149
private var packageNames: Set<String>? = null
3250

33-
@CommandLine.Option(
51+
@Option(
3452
names = ["-u", "--count-unused-patches"],
3553
description = ["Count patches that are not used by default."],
36-
showDefaultValue = CommandLine.Help.Visibility.ALWAYS,
54+
showDefaultValue = ALWAYS,
3755
)
3856
private var countUnusedPatches: Boolean = false
3957

58+
@Option(
59+
names = ["-t", "--temporary-files-path"],
60+
description = ["Path to store temporary files."],
61+
)
62+
private var temporaryFilesPath: File? = null
63+
64+
@Spec
65+
private lateinit var spec: CommandSpec
66+
4067
override fun run() {
4168
fun VersionMap.buildVersionsString(): String {
4269
if (isEmpty()) return "Any"
@@ -56,6 +83,21 @@ internal class ListCompatibleVersions : Runnable {
5683
appendLine(versions.buildVersionsString().prependIndent("\t"))
5784
}
5885

86+
val temporaryFilesPath = temporaryFilesPath ?: File("").absoluteFile.resolve("morphe-temporary-files")
87+
88+
try {
89+
patchesFiles = PatchFileResolver.resolve(
90+
patchesFiles,
91+
prerelease,
92+
temporaryFilesPath
93+
)
94+
} catch (e: IllegalArgumentException) {
95+
throw CommandLine.ParameterException(
96+
spec.commandLine(),
97+
e.message ?: "Failed to resolve patch URL"
98+
)
99+
}
100+
59101
val patches = loadPatchesFromJar(patchesFiles)
60102

61103
patches.mostCommonCompatibleVersions(

src/main/kotlin/app/morphe/cli/command/ListPatchesCommand.kt

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,19 @@
33
* https://github.com/MorpheApp/morphe-cli
44
*
55
* Original hard forked code:
6-
* https://github.com/revanced/revanced-cli
6+
* https://github.com/ReVanced/revanced-cli/tree/731865e167ee449be15fff3dde7a476faea0c2de
77
*/
88

99
package app.morphe.cli.command
1010

1111
import app.morphe.patcher.patch.Package
1212
import app.morphe.patcher.patch.Patch
1313
import app.morphe.patcher.patch.loadPatchesFromJar
14+
import picocli.CommandLine
1415
import picocli.CommandLine.Command
1516
import picocli.CommandLine.Option
17+
import picocli.CommandLine.Spec
18+
import picocli.CommandLine.Model.CommandSpec
1619
import picocli.CommandLine.Help.Visibility.ALWAYS
1720
import java.io.File
1821
import java.util.logging.Logger
@@ -28,11 +31,22 @@ internal object ListPatchesCommand : Runnable {
2831
// Patches is now flag based rather than position based
2932
@Option(
3033
names = ["--patches"],
31-
description = ["One or more paths to MPP files."],
34+
description = ["Path to a MPP file or a GitHub repo url such as https://github.com/MorpheApp/morphe-patches"],
3235
arity = "1..*",
3336
required = true
3437
)
35-
private lateinit var patchFiles: Set<File>
38+
@Suppress("unused")
39+
private fun setPatchesFile(patchesFiles: Set<File>) {
40+
this.patchesFiles = checkFileExistsOrIsUrl(patchesFiles, spec)
41+
}
42+
private var patchesFiles = emptySet<File>()
43+
44+
@Option(
45+
names = ["--prerelease"],
46+
description = ["Fetch the latest dev pre-release instead of the stable main release from the repo provided in --patches."],
47+
showDefaultValue = ALWAYS,
48+
)
49+
private var prerelease: Boolean = false
3650

3751
@Option(
3852
names = ["--out"],
@@ -47,6 +61,12 @@ internal object ListPatchesCommand : Runnable {
4761
)
4862
private var withDescriptions: Boolean = true
4963

64+
@Option(
65+
names = ["-t", "--temporary-files-path"],
66+
description = ["Path to store temporary files."],
67+
)
68+
private var temporaryFilesPath: File? = null
69+
5070
@Option(
5171
names = ["-p", "--with-packages"],
5272
description = ["List the packages the patches are compatible with."],
@@ -88,6 +108,9 @@ internal object ListPatchesCommand : Runnable {
88108
)
89109
private var packageName: String? = null
90110

111+
@Spec
112+
private lateinit var spec: CommandSpec
113+
91114
override fun run() {
92115
fun Package.buildString(): String {
93116
val (name, versions) = this
@@ -157,7 +180,23 @@ internal object ListPatchesCommand : Runnable {
157180
compatiblePackageName == name
158181
} ?: withUniversalPatches
159182

160-
val patches = loadPatchesFromJar(patchFiles).withIndex().toList()
183+
184+
val temporaryFilesPath = temporaryFilesPath ?: File("").absoluteFile.resolve("morphe-temporary-files")
185+
186+
try {
187+
patchesFiles = PatchFileResolver.resolve(
188+
patchesFiles,
189+
prerelease,
190+
temporaryFilesPath
191+
)
192+
} catch (e: IllegalArgumentException) {
193+
throw CommandLine.ParameterException(
194+
spec.commandLine(),
195+
e.message ?: "Failed to resolve patch URL"
196+
)
197+
}
198+
199+
val patches = loadPatchesFromJar(patchesFiles).withIndex().toList()
161200

162201
val filtered = packageName?.let {
163202
patches.filter { (_, patch) ->
@@ -172,7 +211,7 @@ internal object ListPatchesCommand : Runnable {
172211
val finalOutput = filtered.joinToString("\n\n") {it.buildString()}
173212

174213
if (filtered.isEmpty()) {
175-
logger.warning("No compatible patches found in: $patchFiles")
214+
logger.warning("No compatible patches found in: $patchesFiles")
176215
} else {
177216
if (outputFile == null) {
178217
logger.info(finalOutput)

src/main/kotlin/app/morphe/cli/command/OptionsCommand.kt

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import app.morphe.patcher.patch.loadPatchesFromJar
88
import kotlinx.serialization.json.Json
99
import picocli.CommandLine
1010
import picocli.CommandLine.Command
11+
import picocli.CommandLine.Option
12+
import picocli.CommandLine.Help.Visibility.ALWAYS
1113
import picocli.CommandLine.Model.CommandSpec
1214
import picocli.CommandLine.Spec
1315
import java.io.File
@@ -28,29 +30,39 @@ internal object OptionsCommand : Callable<Int> {
2830
@Spec
2931
private lateinit var spec: CommandSpec
3032

31-
@CommandLine.Option(
33+
@Option(
3234
names = ["-p", "--patches"],
33-
description = ["One or more paths to MPP files."],
35+
description = ["Path to a MPP file or a GitHub repo url such as https://github.com/MorpheApp/morphe-patches"],
3436
required = true,
3537
)
3638
@Suppress("unused")
3739
private fun setPatchesFile(patchesFiles: Set<File>) {
38-
patchesFiles.firstOrNull { !it.exists() }?.let {
39-
throw CommandLine.ParameterException(spec.commandLine(), "${it.name} can't be found")
40-
}
41-
this.patchesFiles = patchesFiles
40+
this.patchesFiles = checkFileExistsOrIsUrl(patchesFiles, spec)
4241
}
4342

4443
private var patchesFiles = emptySet<File>()
4544

46-
@CommandLine.Option(
45+
@Option(
4746
names = ["-o", "--out"],
4847
description = ["Path to the output JSON file."],
4948
required = true,
5049
)
5150
private lateinit var outputFile: File
5251

53-
@CommandLine.Option(
52+
@Option(
53+
names = ["--prerelease"],
54+
description = ["Fetch the latest dev pre-release instead of the stable main release from the repo provided in --patches."],
55+
showDefaultValue = ALWAYS,
56+
)
57+
private var prerelease: Boolean = false
58+
59+
@Option(
60+
names = ["-t", "--temporary-files-path"],
61+
description = ["Path to store temporary files."],
62+
)
63+
private var temporaryFilesPath: File? = null
64+
65+
@Option(
5466
names = ["-f", "--filter-package-name"],
5567
description = ["Filter patches by compatible package name."],
5668
)
@@ -59,6 +71,21 @@ internal object OptionsCommand : Callable<Int> {
5971
private val json = Json { prettyPrint = true }
6072

6173
override fun call(): Int {
74+
val temporaryFilesPath = temporaryFilesPath ?: File("").absoluteFile.resolve("morphe-temporary-files")
75+
76+
try {
77+
patchesFiles = PatchFileResolver.resolve(
78+
patchesFiles,
79+
prerelease,
80+
temporaryFilesPath
81+
)
82+
} catch (e: IllegalArgumentException) {
83+
throw CommandLine.ParameterException(
84+
spec.commandLine(),
85+
e.message ?: "Failed to resolve patch URL"
86+
)
87+
}
88+
6289
return try {
6390
logger.info("Loading patches")
6491

src/main/kotlin/app/morphe/cli/command/PatchCommand.kt

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* https://github.com/MorpheApp/morphe-cli
44
*
55
* Original hard forked code:
6-
* https://github.com/revanced/revanced-cli
6+
* https://github.com/ReVanced/revanced-cli/tree/731865e167ee449be15fff3dde7a476faea0c2de
77
*/
88

99
package app.morphe.cli.command
@@ -46,6 +46,7 @@ import java.io.PrintWriter
4646
import java.io.StringWriter
4747
import java.util.concurrent.Callable
4848
import java.util.logging.Logger
49+
import kotlin.collections.plus
4950

5051
@OptIn(ExperimentalSerializationApi::class)
5152
@VisibleForTesting
@@ -245,19 +246,23 @@ internal object PatchCommand : Callable<Int> {
245246

246247
@CommandLine.Option(
247248
names = ["-p", "--patches"],
248-
description = ["One or more path to MPP files."],
249+
description = ["Path to a MPP file or a GitHub repo url such as https://github.com/MorpheApp/morphe-patches"],
249250
required = true,
250251
)
251252
@Suppress("unused")
252253
private fun setPatchesFile(patchesFiles: Set<File>) {
253-
patchesFiles.firstOrNull { !it.exists() }?.let {
254-
throw CommandLine.ParameterException(spec.commandLine(), "${it.name} can't be found")
255-
}
256-
this.patchesFiles = patchesFiles
254+
this.patchesFiles = checkFileExistsOrIsUrl(patchesFiles, spec)
257255
}
258256

259257
private var patchesFiles = emptySet<File>()
260258

259+
@CommandLine.Option(
260+
names = ["--prerelease"],
261+
description = ["Fetch the latest dev pre-release instead of the stable main release from the repo provided in --patches."],
262+
showDefaultValue = ALWAYS,
263+
)
264+
private var prerelease: Boolean = false
265+
261266
@CommandLine.Option(
262267
names = ["--custom-aapt2-binary"],
263268
description = ["apktool is deprecated. This parameter has no effect and will be removed in a future release."],
@@ -411,9 +416,7 @@ internal object PatchCommand : Callable<Int> {
411416
)
412417

413418
val temporaryFilesPath =
414-
temporaryFilesPath ?: outputFilePath.parentFile.resolve(
415-
"${outputFilePath.nameWithoutExtension}-temporary-files",
416-
)
419+
temporaryFilesPath ?: File("").absoluteFile.resolve("morphe-temporary-files")
417420

418421
val keystoreFilePath =
419422
keyStoreFilePath ?: outputFilePath.parentFile
@@ -458,6 +461,15 @@ internal object PatchCommand : Callable<Int> {
458461
// The heavy Patch objects hold DEX classloaders and must not leak into finally.
459462
var patchesSnapshot: PatchBundle? = null
460463

464+
try {
465+
patchesFiles = PatchFileResolver.resolve(patchesFiles, prerelease, temporaryFilesPath)
466+
} catch (e: IllegalArgumentException) {
467+
throw CommandLine.ParameterException(
468+
spec.commandLine(),
469+
e.message ?: "Failed to resolve patch URL"
470+
)
471+
}
472+
461473
try {
462474
logger.info("Loading patches")
463475
val patches: MutableSet<Patch<*>> = loadPatchesFromJar(patchesFiles).toMutableSet()

0 commit comments

Comments
 (0)