Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ hs_err_pid*

# Generated files
.idea/**/contentModel.xml
.idea/**/misc.xml
.idea/**/vcs.xml
.idea/**/kotlinc.xml
.idea/**/.gitignore

# Sensitive or high-churn files
.idea/**/dataSources/
Expand Down
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ plugins {
group = "app.revanced"

repositories {
gradlePluginPortal()
mavenCentral()
google()
}
Expand All @@ -21,6 +22,7 @@ dependencies {
implementation(libs.guava)
implementation(libs.kotlin)
implementation(libs.kotlin.android)
implementation(libs.shadow)

implementation(gradleApi())
implementation(gradleKotlinDsl())
Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ binary-compatibility-validator = "0.15.1"
#noinspection GradleDependency
agp = "8.2.2" # 8.3.0 causes Java verifier error: https://github.com/ReVanced/revanced-patches/issues/2818
guava = "33.2.1-jre"
shadow = "8.1.1"

[libraries]
binary-compatibility-validator = { module = "org.jetbrains.kotlinx.binary-compatibility-validator:org.jetbrains.kotlinx.binary-compatibility-validator.gradle.plugin", version.ref = "binary-compatibility-validator" }
kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
kotlin-android = { module = "org.jetbrains.kotlin.android:org.jetbrains.kotlin.android.gradle.plugin", version.ref = "kotlin" }
android-application = { module = "com.android.application:com.android.application.gradle.plugin", version.ref = "agp" }
guava = { module = "com.google.guava:guava", version.ref = "guava" }
shadow = { group = "com.github.johnrengelman", name = "shadow", version.ref = "shadow" }

[plugins]
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin"}
Expand Down
39 changes: 37 additions & 2 deletions src/main/kotlin/app/revanced/patches/gradle/PatchesPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import com.android.tools.r8.D8
import com.android.tools.r8.D8Command
import com.android.tools.r8.OutputMode
import com.android.tools.r8.utils.ArchiveResourceProvider
import com.github.jengelman.gradle.plugins.shadow.ShadowPlugin
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import kotlinx.validation.BinaryCompatibilityValidatorPlugin
import org.gradle.api.JavaVersion
import org.gradle.api.Plugin
Expand All @@ -32,6 +34,7 @@ abstract class PatchesPlugin : Plugin<Project> {
project.configureDependencies()
project.configureKotlin()
project.configureJava()
project.configureShadow()
project.configureBinaryCompatibilityValidator()
project.configureConsumeExtensions(extension)
project.configureJarTask(extension)
Expand Down Expand Up @@ -85,6 +88,13 @@ abstract class PatchesPlugin : Plugin<Project> {
}
}

/**
* Configures the shadow plugin
*/
private fun Project.configureShadow() {
pluginManager.apply(ShadowPlugin::class.java)
}

/**
* Applies the binary compatibility validator plugin to the project, because patches have a public API.
*/
Expand Down Expand Up @@ -115,12 +125,13 @@ abstract class PatchesPlugin : Plugin<Project> {
task.description = "Builds the project for Android by compiling to DEX and adding it to the patches file."
task.group = "build"

task.dependsOn(tasks["jar"])
// Should also execute all the tests like with normal `gradlew build`
task.dependsOn(tasks["build"])

task.doLast {
val workingDirectory = layout.buildDirectory.dir("revanced").get().asFile.also(File::mkdirs)

val patchesFile = tasks["jar"].outputs.files.first()
val patchesFile = tasks["shadowJar"].outputs.files.first()
val classesZipFile = workingDirectory.resolve("classes.zip")

D8Command.builder()
Expand Down Expand Up @@ -229,6 +240,11 @@ abstract class PatchesPlugin : Plugin<Project> {
private fun Project.configureJarTask(patchesExtension: PatchesExtension) {
tasks.withType(Jar::class.java).configureEach {
it.archiveExtension.set("rvp")

if (it.archiveClassifier.orNull.isNullOrEmpty()) {
it.archiveClassifier.set("thin");
}
Comment on lines +244 to +246
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think overwriting the jar is fine, (i dont remember what we discussed regarding this)

Copy link
Author

@MarkusTieger MarkusTieger Sep 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See commit message here 3aeaf4e

The task (the one without dependencies shaded in) is already disabled and never runs, though it causes an error if it has the same classifier as the other task on running a maven publish.


it.manifest.apply {
attributes["Name"] = patchesExtension.about.name
attributes["Description"] = patchesExtension.about.description
Expand All @@ -241,4 +257,23 @@ private fun Project.configureJarTask(patchesExtension: PatchesExtension) {
attributes["License"] = patchesExtension.about.license
}
}

val shade = configurations.create("shade")
configurations.getByName("compileClasspath").extendsFrom(shade)
configurations.getByName("runtimeClasspath").extendsFrom(shade)
Comment on lines +262 to +263
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please explain the purpose of these lines

Copy link
Author

@MarkusTieger MarkusTieger Jul 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created the "shade" configuration (I explained why below). By default, it does nothing.

Meaning, it isn't present in the compileClasspath (present at compile time, self explanatory I guess), and not present in runtimeClasspath (present at runtime, for a javaExec task for example).
By adding it to the compileClasspath and runtimeClasspath, "shade" acts the same way as "implementation"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would "shade" be needed in runtime or compiletime?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it wouldn't be in the compileClasspath:

e: patches/all/misc/spoof/SignatureSpoofPatch.kt:6:20 Unresolved reference 'apksig'.
e: patches/all/misc/spoof/SignatureSpoofPatch.kt:7:20 Unresolved reference 'apksig'.
e: patches/all/misc/spoof/SignatureSpoofPatch.kt:76:24 Unresolved reference 'ApkVerifier'.
e: patches/all/misc/spoof/SignatureSpoofPatch.kt:81:71 Unresolved reference 'certificate'.
e: patches/all/misc/spoof/SignatureSpoofPatch.kt:83:71 Unresolved reference 'certificate'.
e: patches/all/misc/spoof/SignatureSpoofPatch.kt:85:71 Unresolved reference 'certificate'.
e: patches/all/misc/spoof/SignatureSpoofPatch.kt:91:17 Unresolved reference 'ApkFormatException'.

You can ask the same question with any other dependency used.
For example, why is "revanced-patcher" inside the compileClasspath (via "implementation).

Answer: So Java/Kotlin knows it exists and can compile it.

For the runtimeClasspath:
This is technically only relevant if "JavaExec" is used. Here are all dependencies that are expected to be present at runtime (= when the patches are executed).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created the "shade" configuration (I explained why below)

I couldn't find the explanation below. Why did you create a separate configuration?
The plugin already adds a "shade()" API as seen here: https://gradleup.com/shadow/configuration/

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Dependencies added to the shadow configuration are not bundled into the output JAR." Thats blacklisting.

The "shade" configuration contains the ones that are getting bundled.

But I could also just use the "shadow" configuration to blacklist "revanced-patcher" and "smali" and everything else with "implementation" would get shaded in? If you prefer this way, let me know

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think using the default APIs is better. As the plugin configures the patches project, the plugin can use the shadow APIs to exclude the APIs such as patcher, library or co.

Copy link
Author

@MarkusTieger MarkusTieger Jul 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is also "guava" inside the revanced-patches build.gradle.kts? Is there any reason for it being there and not hardcoded in the plugin? Would also need to be excluded

Copy link
Author

@MarkusTieger MarkusTieger Jul 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kotlin plugin automatically adds dependencies, which shouldn't be shaded in, which makes excluding harder.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After coming back to this, i cant follow this review, can you explain why a new configuration is added again?

Comment on lines +261 to +263
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
val shade = configurations.create("shade")
configurations.getByName("compileClasspath").extendsFrom(shade)
configurations.getByName("runtimeClasspath").extendsFrom(shade)
configurations.create("shade")
.also(configurations.getByName("compileClasspath")::extendsFrom)
.also(configurations.getByName("runtimeClasspath")::extendsFrom)
}

maybe better?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the shade variable is still used on line 266 it.configurations = listOf(shade)

Also I assume the "+}" on line 264 was a mistake?


tasks.withType(ShadowJar::class.java).configureEach {
it.configurations = listOf(shade)
it.archiveClassifier.set("")

it.minimize()
it.isEnableRelocation = true
it.relocationPrefix = "app.revanced.patches"
}
tasks.named("assemble") {
it.dependsOn(tasks.named("shadowJar"))
}
tasks.named("jar") {
it.enabled = false
}
}