-
-
Notifications
You must be signed in to change notification settings - Fork 6
fix: Add shadow plugin #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from 3 commits
ea5affe
bd48b21
fe15805
3b4db80
03a6326
3aeaf4e
b0c8a4a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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 | ||||||||||||||||
|
|
@@ -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) | ||||||||||||||||
|
|
@@ -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. | ||||||||||||||||
| */ | ||||||||||||||||
|
|
@@ -115,12 +125,16 @@ 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"]) | ||||||||||||||||
| task.dependsOn(tasks["shadowJar"]) | ||||||||||||||||
|
|
||||||||||||||||
| // Add these manually, as the task does not depend on the "jar" task (no one needs a thin jar). | ||||||||||||||||
| task.dependsOn(tasks.named("javadocJar")) | ||||||||||||||||
| task.dependsOn(tasks.named("sourcesJar")) | ||||||||||||||||
|
|
||||||||||||||||
| 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() | ||||||||||||||||
|
|
@@ -229,6 +243,13 @@ abstract class PatchesPlugin : Plugin<Project> { | |||||||||||||||
| private fun Project.configureJarTask(patchesExtension: PatchesExtension) { | ||||||||||||||||
| tasks.withType(Jar::class.java).configureEach { | ||||||||||||||||
| it.archiveExtension.set("rvp") | ||||||||||||||||
|
|
||||||||||||||||
| // JarTask includes the javadoc and sourcesjar. Be sure to only add the "-thin" filename suffix, if there isn't already one set | ||||||||||||||||
| if (it.archiveClassifier.orNull.isNullOrEmpty()) { | ||||||||||||||||
| it.archiveClassifier.set("thin") | ||||||||||||||||
|
||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // ShadowJar inherits the manifest from the JarTask, so changing it here will also change the fat jar | ||||||||||||||||
oSumAtrIX marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||
| it.manifest.apply { | ||||||||||||||||
| attributes["Name"] = patchesExtension.about.name | ||||||||||||||||
| attributes["Description"] = patchesExtension.about.description | ||||||||||||||||
|
|
@@ -241,4 +262,21 @@ 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please explain the purpose of these lines There was a problem hiding this comment. Choose a reason for hiding this commentThe 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). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why would "shade" be needed in runtime or compiletime? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If it wouldn't be in the compileClasspath: You can ask the same question with any other dependency used. Answer: So Java/Kotlin knows it exists and can compile it. For the runtimeClasspath: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I couldn't find the explanation below. Why did you create a separate configuration? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
maybe better? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the shade variable is still used on line 266 Also I assume the "+}" on line 264 was a mistake? |
||||||||||||||||
|
|
||||||||||||||||
| tasks.withType(ShadowJar::class.java).configureEach { | ||||||||||||||||
| it.configurations = listOf(shade) | ||||||||||||||||
| it.archiveExtension.set("rvp") | ||||||||||||||||
| it.archiveClassifier.set(null as String?) | ||||||||||||||||
|
|
||||||||||||||||
| it.minimize() | ||||||||||||||||
| it.isEnableRelocation = true | ||||||||||||||||
| it.relocationPrefix = "app.revanced.libs" | ||||||||||||||||
MarkusTieger marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||
| } | ||||||||||||||||
| tasks.named("assemble") { | ||||||||||||||||
| it.setDependsOn(listOf(tasks.named("shadowJar"), tasks.named("javadocJar"), tasks.named("sourcesJar"))) | ||||||||||||||||
| } | ||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we set configurations, archiveextnsion and archiveClassifier? What does "minimize", and relocation do Why does the assemble task depend on shadowJar. I thought shadowJar depends on assemble. What about javadoc and sources? Those should not be ran when assemble is ran There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
So when you run ./gradlew build, it runs the build task. The build task depends on "assemble" and "test". The assemble task itself doesn't do anything, it just depends on the required tasks iirc. By default with the java plugin on "jar", but I replaced that with shadowJar (although I could just added it instead of replaced, but there is no use for the unshaded patches jar/rvp )
Its the default behaviour of gradle. The jar task depends on the javadoc and sourcesjar task (if java.withSourcesJar() and java.withJavadocJar() is called, which is the case). Because the jar task isn't run anymore (by default), we have to add the tasks manually to not lose the behaviour.
relocation is that the libraries don't conflict. The revanced-cli for example, uses apksig without obfuscation. If they use different versions if could get into a conflict. What relocation does it, it "relocates" the dependencies with a package prefix. For example "com.android.apksig" becomes "app.revanced.libs.com.android.apksig". Shadow automatically updates the imports in the shaded jar.
By default, the output jar would have this filename "patches-5.30.0-all.jar". The configurations are set, because by default the runtimeClasspath is used by shadow. In the runtimeClasspath is everything that should be present at runtime. Everything from "runtimeOnly" or "implementation" or "api" etc. is inside the runtimeClasspath. This includes things like "revanced-patcher" which is present in runtime, which shouldn't be shaded in. I explicitly add a new configuration called "shade", which is the exact same as "implementation", but with the difference that shadow shades every dependency from it into the shaded jar. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I can't understand this. Please explain why the assemble task depends on shadowJar. The project should be built merely from running "gradlew build". Currently buildAndroid exists to build a .dex file but that's not ideal for the reason of it to work from running the build task.
shadowJar depends on the JAR task.
This could break reflection. I don't think relocation should be enabled by default. Patches projects should individually decide on that. Without relocation and obfuscation, would it cause problems when CLI and patches have apkzlib in classpath?
You can set it to an empty string "" to avoid the cast. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
No, shadow also replaces strings of classes.
It could. Because of the patches, there is a seperate classloader. If the version does mismatch, for example:
I will look into this (although I don't like the behaviour of overwriting a jar). Like I already wrote once, it could confuse people (same filename, different files).
The shadowJar task isn't run by default. "./gradlew build" runs the jar task by default, not shadowJar. So I overwrote the dependency of "jar" in the assemble task with "shadowJar". build.gradle.kts plugins {
id("java")
id("com.github.johnrengelman.shadow") version "8.1.1"
}
tasks.named("assemble") {
setDependsOn(listOf(tasks.named("shadowJar")))
}
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That assumes the strings contain the full class. Something like "cla" + "ss" + "name" wont work.
Different API versions can not really be solved however, wont a duplicate class be thrown at runtime?
You should make jar dependon shadowJar. This way we don't have to change anything anywhere and jar "turns" into "shadowJar". Perhaps it is possible to cancel jar task so it doesn't run unnecessarily but things like javadoc continue to work. Otherwise the shadowJar task should overwrite the jar just fine. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Bad example, java is smart enough to optimize the "cla" + "ss" + "name" statement into "classname". public class Test {
public static void main(String[] args) {
System.out.println("cla" + "ss" + "name");
}
}
Same with: public class Test {
private static final String PREFIX = "class";
public static void main(String[] args) {
System.out.println(PREFIX + "name");
}
}But, yes, something like
Yes, the issue is though that different classes with different versions of the same library could be loaded at the same time. If you wish, I could write you a quick example in java (with a git repo).
Yes, I can do that (with a few changes). |
||||||||||||||||
| } | ||||||||||||||||

Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about keeping everything the same around here and just making jar depend on shadow jar task? Looks simpler to me, as JAR already handles everything. Only the classifier and extension needs to be set and signing or publication configured
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The thin (not shaded) jar would be additionally built then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I run shadowJar in revanced-cli it already builds the thin jar. So what is different?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Idk. Here it doesn't.
To be honest, I don't know myself why it isn't here the case here, but in revanced-cli it is.
Feel free to test it yourself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe because of the "application" plugin:
explorer_AIRg4slSFh.mp4