diff --git a/src/main/kotlin/app/revanced/patcher/Patcher.kt b/src/main/kotlin/app/revanced/patcher/Patcher.kt index 00c1b14..8db51c5 100644 --- a/src/main/kotlin/app/revanced/patcher/Patcher.kt +++ b/src/main/kotlin/app/revanced/patcher/Patcher.kt @@ -11,6 +11,7 @@ import app.revanced.patcher.patch.Patch import app.revanced.patcher.patch.PatchResult import app.revanced.patcher.patch.PatchResultError import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.DependencyType import app.revanced.patcher.patch.impl.BytecodePatch import app.revanced.patcher.patch.impl.ResourcePatch import app.revanced.patcher.util.ListBackedSet @@ -261,15 +262,19 @@ class Patcher(private val options: PatcherOptions) { } // recursively apply all dependency patches - patch.dependencies?.forEach { - val patchDependency = it.java - - val result = applyPatch(patchDependency, appliedPatches) + patch.dependencies.forEach { + val dependency = it.patch.java + if ( // soft dependencies must be included manually. + it.type == DependencyType.SOFT && !data.patches.any { p -> + p.patchName == dependency.patchName + } + ) return@forEach + val result = applyPatch(dependency, appliedPatches) if (result.isSuccess()) return@forEach val errorMessage = result.error()!!.cause - return PatchResultError("'$patchName' depends on '${patchDependency.patchName}' but the following error was raised: $errorMessage") + return PatchResultError("'$patchName' depends on '${dependency.patchName}' but the following error was raised: $errorMessage") } val patchInstance = patch.getDeclaredConstructor().newInstance() diff --git a/src/main/kotlin/app/revanced/patcher/extensions/AnnotationExtensions.kt b/src/main/kotlin/app/revanced/patcher/extensions/AnnotationExtensions.kt index d5dc5c6..e5cf5de 100644 --- a/src/main/kotlin/app/revanced/patcher/extensions/AnnotationExtensions.kt +++ b/src/main/kotlin/app/revanced/patcher/extensions/AnnotationExtensions.kt @@ -7,6 +7,9 @@ import app.revanced.patcher.annotation.Version import app.revanced.patcher.data.Data import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint import app.revanced.patcher.patch.Patch +import app.revanced.patcher.patch.annotations.Dependencies +import app.revanced.patcher.patch.annotations.DependencyType +import app.revanced.patcher.patch.annotations.DependsOn import kotlin.reflect.KClass /** @@ -35,19 +38,26 @@ private fun Class<*>.findAnnotationRecursively( return null } +private typealias PatchClass = Class> + object PatchExtensions { - val Class>.patchName: String get() = recursiveAnnotation(Name::class)?.name ?: this.javaClass.simpleName - val Class>.version get() = recursiveAnnotation(Version::class)?.version - val Class>.include get() = recursiveAnnotation(app.revanced.patcher.patch.annotations.Patch::class)!!.include - val Class>.description get() = recursiveAnnotation(Description::class)?.description - val Class>.dependencies get() = recursiveAnnotation(app.revanced.patcher.patch.annotations.Dependencies::class)?.dependencies - val Class>.compatiblePackages get() = recursiveAnnotation(Compatibility::class)?.compatiblePackages + val PatchClass.patchName: String get() = recursiveAnnotation(Name::class)?.name ?: this.javaClass.simpleName + val PatchClass.version get() = recursiveAnnotation(Version::class)?.version + val PatchClass.include get() = recursiveAnnotation(app.revanced.patcher.patch.annotations.Patch::class)!!.include + val PatchClass.description get() = recursiveAnnotation(Description::class)?.description + val PatchClass.dependencies get() = buildList { + recursiveAnnotation(DependsOn::class)?.let { add(PatchDependency(it.value, it.type)) } + recursiveAnnotation(Dependencies::class)?.dependencies?.forEach { add(PatchDependency(it, DependencyType.HARD)) } + }.toTypedArray() + val PatchClass.compatiblePackages get() = recursiveAnnotation(Compatibility::class)?.compatiblePackages @JvmStatic - fun Class>.dependsOn(patch: Class>): Boolean { + fun PatchClass.dependsOn(patch: PatchClass): Boolean { if (this.patchName == patch.patchName) throw IllegalArgumentException("thisval and patch may not be the same") - return this.dependencies?.any { it.java.patchName == this@dependsOn.patchName } == true + return this.dependencies.any { it.patch.java.patchName == this@dependsOn.patchName } } + + class PatchDependency internal constructor(val patch: KClass>, val type: DependencyType = DependencyType.HARD) } object MethodFingerprintExtensions { diff --git a/src/main/kotlin/app/revanced/patcher/patch/annotations/PatchAnnotation.kt b/src/main/kotlin/app/revanced/patcher/patch/annotations/PatchAnnotation.kt index ebfe5c8..b859fc6 100644 --- a/src/main/kotlin/app/revanced/patcher/patch/annotations/PatchAnnotation.kt +++ b/src/main/kotlin/app/revanced/patcher/patch/annotations/PatchAnnotation.kt @@ -19,6 +19,24 @@ annotation class Patch(val include: Boolean = true) @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) @MustBeDocumented +@Deprecated( + "Does not support new parameter 'type'", + ReplaceWith("DependsOn") +) annotation class Dependencies( val dependencies: Array>> = [] -) \ No newline at end of file +) + +/** + * Annotation for dependencies of [Patch]es . + */ +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +@MustBeDocumented +@Repeatable +annotation class DependsOn( + val value: KClass>, + val type: DependencyType = DependencyType.HARD +) + +enum class DependencyType { HARD, SOFT } \ No newline at end of file diff --git a/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt b/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt index b0ea493..9b6bf0f 100644 --- a/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt +++ b/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt @@ -9,6 +9,8 @@ import app.revanced.patcher.extensions.or import app.revanced.patcher.extensions.replaceInstruction import app.revanced.patcher.patch.PatchResult import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.DependencyType +import app.revanced.patcher.patch.annotations.DependsOn import app.revanced.patcher.patch.annotations.Patch import app.revanced.patcher.patch.impl.BytecodePatch import app.revanced.patcher.usage.resource.annotation.ExampleResourceCompatibility @@ -35,6 +37,7 @@ import org.jf.dexlib2.util.Preconditions @Description("Example demonstration of a bytecode patch.") @ExampleResourceCompatibility @Version("0.0.1") +@DependsOn(ExampleBytecodePatch::class, DependencyType.SOFT) class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) { // This function will be executed by the patcher. // You can treat it as a constructor