Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat!: Add patch annotation processor
This commit introduces an annotation processor for patches. Patches can use the `@Patch` instead of super constructor parameters. BREAKING CHANGE: The manifest for patches has been removed, and the properties have been added to patches. Patches are now `OptionsContainer`. The `@Patch` annotation has been removed in favour of the `@Patch` annotation from the annotation processor.
- Loading branch information
Showing
29 changed files
with
708 additions
and
162 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 25 additions & 0 deletions
25
revanced-patch-annotations-processor/api/revanced-patch-annotations-processor.api
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
public abstract interface annotation class app/revanced/patcher/patch/annotations/CompatiblePackage : java/lang/annotation/Annotation { | ||
public abstract fun name ()Ljava/lang/String; | ||
public abstract fun versions ()[Ljava/lang/String; | ||
} | ||
|
||
public abstract interface annotation class app/revanced/patcher/patch/annotations/Patch : java/lang/annotation/Annotation { | ||
public abstract fun compatiblePackages ()[Lapp/revanced/patcher/patch/annotations/CompatiblePackage; | ||
public abstract fun dependencies ()[Ljava/lang/Class; | ||
public abstract fun description ()Ljava/lang/String; | ||
public abstract fun name ()Ljava/lang/String; | ||
public abstract fun requiresIntegrations ()Z | ||
public abstract fun use ()Z | ||
} | ||
|
||
public final class app/revanced/patcher/patch/annotations/processor/PatchProcessor : com/google/devtools/ksp/processing/SymbolProcessor { | ||
public fun <init> (Lcom/google/devtools/ksp/processing/CodeGenerator;Lcom/google/devtools/ksp/processing/KSPLogger;)V | ||
public fun process (Lcom/google/devtools/ksp/processing/Resolver;)Ljava/util/List; | ||
} | ||
|
||
public final class app/revanced/patcher/patch/annotations/processor/PatchProcessorProvider : com/google/devtools/ksp/processing/SymbolProcessorProvider { | ||
public fun <init> ()V | ||
public fun create (Lcom/google/devtools/ksp/processing/SymbolProcessorEnvironment;)Lapp/revanced/patcher/patch/annotations/processor/PatchProcessor; | ||
public synthetic fun create (Lcom/google/devtools/ksp/processing/SymbolProcessorEnvironment;)Lcom/google/devtools/ksp/processing/SymbolProcessor; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
plugins { | ||
kotlin("jvm") version "1.9.0" | ||
`maven-publish` | ||
alias(libs.plugins.ksp) | ||
} | ||
|
||
group = "app.revanced" | ||
|
||
dependencies { | ||
implementation(libs.symbol.processing.api) | ||
implementation(libs.kotlinpoet.ksp) | ||
implementation(project(":revanced-patcher")) | ||
|
||
testImplementation(libs.kotlin.test) | ||
testImplementation(libs.kotlin.compile.testing) | ||
} | ||
|
||
tasks { | ||
test { | ||
useJUnitPlatform() | ||
testLogging { | ||
events("PASSED", "SKIPPED", "FAILED") | ||
} | ||
} | ||
} | ||
|
||
kotlin { jvmToolchain(11) } | ||
|
||
java { | ||
withSourcesJar() | ||
} | ||
|
||
publishing { | ||
repositories { | ||
mavenLocal() | ||
maven { | ||
name = "GitHubPackages" | ||
url = uri("https://maven.pkg.github.com/revanced/revanced-patch-annotations-processor") | ||
credentials { | ||
username = System.getenv("GITHUB_ACTOR") | ||
password = System.getenv("GITHUB_TOKEN") | ||
} | ||
} | ||
} | ||
publications { | ||
create<MavenPublication>("gpr") { | ||
from(components["java"]) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
rootProject.name = "revanced-patch-annotations-processor" | ||
|
38 changes: 38 additions & 0 deletions
38
...ions-processor/src/main/kotlin/app/revanced/patcher/patch/annotations/PatchAnnotations.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package app.revanced.patcher.patch.annotations | ||
|
||
import java.lang.annotation.Inherited | ||
import kotlin.reflect.KClass | ||
|
||
/** | ||
* Annotation for [app.revanced.patcher.patch.Patch] classes. | ||
* | ||
* @param name The name of the patch. If empty, the patch will be unnamed. | ||
* @param description The description of the patch. If empty, no description will be used. | ||
* @param dependencies The patches this patch depends on. | ||
* @param compatiblePackages The packages this patch is compatible with. | ||
* @param use Whether this patch should be used. | ||
* @param requiresIntegrations Whether this patch requires integrations. | ||
*/ | ||
@Retention(AnnotationRetention.SOURCE) | ||
@Target(AnnotationTarget.CLASS) | ||
@Inherited | ||
annotation class Patch( | ||
val name: String = "", | ||
val description: String = "", | ||
val dependencies: Array<KClass<out app.revanced.patcher.patch.Patch<*>>> = [], | ||
val compatiblePackages: Array<CompatiblePackage> = [], | ||
val use: Boolean = true, | ||
// TODO: Remove this property, once integrations are coupled with patches. | ||
val requiresIntegrations: Boolean = false, | ||
) | ||
|
||
/** | ||
* A package that a [app.revanced.patcher.patch.Patch] is compatible with. | ||
* | ||
* @param name The name of the package. | ||
* @param versions The versions of the package. | ||
*/ | ||
annotation class CompatiblePackage( | ||
val name: String, | ||
val versions: Array<String> = [], | ||
) |
198 changes: 198 additions & 0 deletions
198
...cessor/src/main/kotlin/app/revanced/patcher/patch/annotations/processor/PatchProcessor.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
package app.revanced.patcher.patch.annotations.processor | ||
|
||
import app.revanced.patcher.data.BytecodeContext | ||
import app.revanced.patcher.data.ResourceContext | ||
import app.revanced.patcher.patch.BytecodePatch | ||
import app.revanced.patcher.patch.PatchOptions | ||
import app.revanced.patcher.patch.ResourcePatch | ||
import app.revanced.patcher.patch.annotations.Patch | ||
import com.google.devtools.ksp.processing.* | ||
import com.google.devtools.ksp.symbol.KSAnnotated | ||
import com.google.devtools.ksp.symbol.KSAnnotation | ||
import com.google.devtools.ksp.symbol.KSClassDeclaration | ||
import com.google.devtools.ksp.symbol.KSType | ||
import com.google.devtools.ksp.validate | ||
import com.squareup.kotlinpoet.* | ||
import com.squareup.kotlinpoet.ksp.toClassName | ||
import com.squareup.kotlinpoet.ksp.writeTo | ||
import kotlin.reflect.KClass | ||
|
||
class PatchProcessor( | ||
private val codeGenerator: CodeGenerator, | ||
private val logger: KSPLogger | ||
) : SymbolProcessor { | ||
|
||
private fun KSAnnotated.isSubclassOf(cls: KClass<*>): Boolean { | ||
if (this !is KSClassDeclaration) return false | ||
|
||
if (qualifiedName?.asString() == cls.qualifiedName) return true | ||
|
||
return superTypes.any { it.resolve().declaration.isSubclassOf(cls) } | ||
} | ||
|
||
@Suppress("UNCHECKED_CAST") | ||
override fun process(resolver: Resolver): List<KSAnnotated> { | ||
val executablePatches = buildMap { | ||
resolver.getSymbolsWithAnnotation(Patch::class.qualifiedName!!).filter { | ||
// Do not check here if Patch is super of the class, because it is expensive. | ||
// Check it later when processing. | ||
it.validate() && it.isSubclassOf(app.revanced.patcher.patch.Patch::class) | ||
}.map { | ||
it as KSClassDeclaration | ||
}.forEach { patchDeclaration -> | ||
patchDeclaration.annotations.find { | ||
it.annotationType.resolve().declaration.qualifiedName!!.asString() == Patch::class.qualifiedName!! | ||
}?.let { annotation -> | ||
fun KSAnnotation.property(name: String) = | ||
arguments.find { it.name!!.asString() == name }?.value!! | ||
|
||
val name = | ||
annotation.property("name").toString().ifEmpty { null } | ||
|
||
val description = | ||
annotation.property("description").toString().ifEmpty { null } | ||
|
||
val dependencies = | ||
(annotation.property("dependencies") as List<KSType>).ifEmpty { null } | ||
|
||
val compatiblePackages = | ||
(annotation.property("compatiblePackages") as List<KSAnnotation>).ifEmpty { null } | ||
|
||
val use = | ||
annotation.property("use") as Boolean | ||
|
||
val requiresIntegrations = | ||
annotation.property("requiresIntegrations") as Boolean | ||
|
||
// Data class for KotlinPoet | ||
data class PatchData( | ||
val name: String?, | ||
val description: String?, | ||
val dependencies: List<ClassName>?, | ||
val compatiblePackages: List<CodeBlock>?, | ||
val use: Boolean, | ||
val requiresIntegrations: Boolean | ||
) | ||
|
||
this[patchDeclaration] = PatchData( | ||
name, | ||
description, | ||
dependencies?.map { dependency -> dependency.toClassName() }, | ||
compatiblePackages?.map { | ||
val packageName = it.property("name") | ||
val packageVersions = (it.property("versions") as List<String>) | ||
.joinToString(", ") { version -> "\"$version\"" } | ||
|
||
CodeBlock.of( | ||
"%T(%S, setOf(%L))", | ||
app.revanced.patcher.patch.Patch.CompatiblePackage::class, | ||
packageName, | ||
packageVersions | ||
) | ||
}, | ||
use, | ||
requiresIntegrations | ||
) | ||
} | ||
} | ||
} | ||
|
||
// If a patch depends on another, that is annotated, the dependency should be replaced with the generated patch, | ||
// because the generated patch has all the necessary properties to invoke the super constructor, | ||
// unlike the annotated patch. | ||
val dependencyResolutionMap = buildMap { | ||
executablePatches.values.filter { it.dependencies != null }.flatMap { | ||
it.dependencies!! | ||
}.distinct().forEach { dependency -> | ||
executablePatches.keys.find { it.qualifiedName?.asString() == dependency.toString() } | ||
?.let { patch -> | ||
this[dependency] = ClassName( | ||
patch.packageName.asString(), | ||
patch.simpleName.asString() + "Generated" | ||
) | ||
} | ||
} | ||
} | ||
|
||
// kotlin poet generate a class for each patch | ||
executablePatches.forEach { (patchDeclaration, patchAnnotation) -> | ||
val isBytecodePatch = patchDeclaration.isSubclassOf(BytecodePatch::class) | ||
|
||
val superClass = if (isBytecodePatch) { | ||
BytecodePatch::class | ||
} else { | ||
ResourcePatch::class | ||
} | ||
|
||
val contextClass = if (isBytecodePatch) { | ||
BytecodeContext::class | ||
} else { | ||
ResourceContext::class | ||
} | ||
|
||
val generatedPatchClassName = ClassName( | ||
patchDeclaration.packageName.asString(), | ||
patchDeclaration.simpleName.asString() + "Generated" | ||
) | ||
|
||
FileSpec.builder(generatedPatchClassName) | ||
.addType( | ||
TypeSpec.objectBuilder(generatedPatchClassName) | ||
.superclass(superClass).apply { | ||
patchAnnotation.name?.let { name -> | ||
addSuperclassConstructorParameter("name = %S", name) | ||
} | ||
|
||
patchAnnotation.description?.let { description -> | ||
addSuperclassConstructorParameter("description = %S", description) | ||
} | ||
|
||
patchAnnotation.compatiblePackages?.let { compatiblePackages -> | ||
addSuperclassConstructorParameter( | ||
"compatiblePackages = setOf(%L)", | ||
compatiblePackages.joinToString(", ") | ||
) | ||
} | ||
|
||
patchAnnotation.dependencies?.let { dependencies -> | ||
addSuperclassConstructorParameter( | ||
"dependencies = setOf(%L)", | ||
buildList { | ||
addAll(dependencies) | ||
// Also add the source class of the generated class so that it is also executed | ||
add(patchDeclaration.toClassName()) | ||
}.joinToString(", ") { dependency -> | ||
"${(dependencyResolutionMap[dependency] ?: dependency)}::class" | ||
} | ||
) | ||
} | ||
addSuperclassConstructorParameter( | ||
"use = %L", patchAnnotation.use | ||
) | ||
|
||
addSuperclassConstructorParameter( | ||
"requiresIntegrations = %L", | ||
patchAnnotation.requiresIntegrations | ||
) | ||
} | ||
.addFunction( | ||
FunSpec.builder("execute") | ||
.addModifiers(KModifier.OVERRIDE) | ||
.addParameter("context", contextClass) | ||
.build() | ||
) | ||
.addProperty( | ||
PropertySpec.builder("options", PatchOptions::class, KModifier.OVERRIDE) | ||
.initializer("%T.options", patchDeclaration.toClassName()) | ||
.build() | ||
) | ||
.build() | ||
).build().writeTo( | ||
codeGenerator, | ||
Dependencies(false, patchDeclaration.containingFile!!) | ||
) | ||
} | ||
|
||
return emptyList() | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
...rc/main/kotlin/app/revanced/patcher/patch/annotations/processor/PatchProcessorProvider.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package app.revanced.patcher.patch.annotations.processor | ||
|
||
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment | ||
import com.google.devtools.ksp.processing.SymbolProcessorProvider | ||
|
||
class PatchProcessorProvider : SymbolProcessorProvider { | ||
override fun create(environment: SymbolProcessorEnvironment) = | ||
PatchProcessor(environment.codeGenerator, environment.logger) | ||
} |
1 change: 1 addition & 0 deletions
1
...in/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
app.revanced.patcher.patch.annotations.processor.PatchProcessorProvider |
Oops, something went wrong.