Skip to content

Commit

Permalink
Add new @pattern feature to centralize version-aware code
Browse files Browse the repository at this point in the history
That is, most of the business code should not be aware that it is being compiled
to multiple versions even when it heavily interacts with MC, preprocessor
statements should be an escape hatch, not the norm.
Similarly, code should not be forced to do `MCVer.getWindow(mc)` instead of the
much more intuitive `mc.getWindow()`, and this new preprocessor (technically remap)
feature makes this possible by defining "search and replace"-like patterns (but
smarter in that they are type-aware) in one or more central places which then
are applied all over the code base.

In a way, this is another step in the automatic back-porting process where
preprocessor statements are used when we cannot yet do something automatically.
Previously we "merely" automatically converted between different mapping, this
new feature now also allows us to automatically perform simple refactoring
tasks like changing field access to a getter+setter (e.g. `mc.getWindow()`), or
changing how a method is called (e.g. `BufferBuilder.begin`), or changing a
method call chain (e.g. `dispatcher.camera.getYaw()`), or most other
search-and-replace-like changes and any combination of those.
The only major limitation is that the replacement itself is not smart, so
arguments must be kept in same order (or be temporarily assigned to local
variables which then can be used in any order).
  • Loading branch information
Johni0702 committed Mar 14, 2021
1 parent 7c4f90e commit 3d85a00
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 4 deletions.
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,40 @@ This also has the huge advantage that the file may be edited with full IDE suppo
This feature is fully compatible with `setCoreVersion` and overwrite files will be moved generated/removed as required such that switching back and forth leaves the same result as you started out with.
The core project itself does not allow for overwrites and any present in its folder will be deleted on `setCoreVersion`.

## Patterns

The preprocessor also supports defining simple "search and replace"-like patterns (but smarter in that they are type-aware) annotated by a `@Pattern` annotation in one or more central places which then are applied all over the code base.
This allows code which would previously have to be written with preprocessor statements or as `MCVer.getWindow(mc)` all over the code base to instead now use the much more intuitive `mc.getWindow()` and be automatically converted to `mc.window` (or even a Window stub object) on remap if a pattern for that exists anywhere in the same source tree:
```java
@Pattern
private static Window getWindow(MinecraftClient mc) {
//#if MC>=11500
return mc.getWindow();
//#elseif MC>=11400
//$$ return mc.window;
//#else
//$$ return new com.replaymod.core.versions.Window(mc);
//#endif
}
```
All pattern cases should be a single line as to not mess with indentation and/or line count.
Any arguments passed to the pattern must be used in the pattern in the same order in every case (introducing in-line locals to work around that is fine).
Defining and/or applying patterns in/on Kotlin code is not yet supported.

To use this feature, you must create a `Pattern` (name may be different) annotation in your mod:
```java
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface Pattern {
}
```
and then declare it in your `build.gradle`:
```groovy
preprocess {
patternAnnotation.set("com.replaymod.gradle.remap.Pattern")
}
```

## License
The Preprocessor is provided under the terms of the GNU General Public License Version 3 or (at your option) any later version.
See `LICENSE.md` for the full license text.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ repositories {
dependencies {
implementation(gradleApi())
compile(localGroovy())
implementation("com.github.replaymod:remap:6073541")
implementation("com.github.replaymod:remap:e7bc082")
implementation("net.fabricmc:tiny-mappings-parser:0.2.1.13")
testImplementation("io.kotest:kotest-runner-junit5-jvm:$kotestVersion")
testImplementation("io.kotest:kotest-assertions-core-jvm:$kotestVersion")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.replaymod.gradle.preprocess

import org.gradle.api.model.ObjectFactory
import org.gradle.kotlin.dsl.mapProperty
import org.gradle.kotlin.dsl.property

open class PreprocessExtension(objects: ObjectFactory, val mcVersion: Int) {
val vars = objects.mapProperty<String, Int>().convention(mutableMapOf(
Expand All @@ -15,4 +16,5 @@ open class PreprocessExtension(objects: ObjectFactory, val mcVersion: Int) {
".mcmeta" to PreprocessTask.DEFAULT_KEYWORDS,
".cfg" to PreprocessTask.CFG_KEYWORDS
))
val patternAnnotation = objects.property<String>()
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class PreprocessPlugin : Plugin<Project> {
reverseMapping = reverseMappings
vars.convention(ext.vars)
keywords.convention(ext.keywords)
patternAnnotation.convention(ext.patternAnnotation)
}
val sourceJavaTask = project.tasks.findByName("source${name.capitalize()}Java")
(sourceJavaTask ?: project.tasks["compile${cName}Java"]).dependsOn(preprocessJava)
Expand All @@ -92,6 +93,7 @@ class PreprocessPlugin : Plugin<Project> {
reverseMapping = reverseMappings
vars.convention(ext.vars)
keywords.convention(ext.keywords)
patternAnnotation.convention(ext.patternAnnotation)
}
val kotlinConsumerTask = project.tasks.findByName("source${name.capitalize()}Kotlin")
?: project.tasks["compile${cName}Kotlin"]
Expand All @@ -108,6 +110,7 @@ class PreprocessPlugin : Plugin<Project> {
generated = preprocessedResources
vars.convention(ext.vars)
keywords.convention(ext.keywords)
patternAnnotation.convention(ext.patternAnnotation)
}
project.tasks["process${cName}Resources"].dependsOn(preprocessResources)
resources.setSrcDirs(listOf(overwriteResources, preprocessedResources))
Expand Down
29 changes: 26 additions & 3 deletions src/main/kotlin/com/replaymod/gradle/preprocess/PreprocessTask.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import org.gradle.api.file.ConfigurableFileTree
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.*
import org.gradle.api.tasks.compile.AbstractCompile
import org.gradle.kotlin.dsl.fileTree
import org.gradle.kotlin.dsl.mapProperty
import org.gradle.kotlin.dsl.property
import org.jetbrains.kotlin.backend.common.peek
import org.jetbrains.kotlin.backend.common.pop
import org.jetbrains.kotlin.backend.common.push
Expand Down Expand Up @@ -96,6 +96,10 @@ open class PreprocessTask : DefaultTask() {
@Input
val keywords = project.objects.mapProperty<String, Keywords>()

@Input
@Optional
val patternAnnotation = project.objects.property<String>()

fun source(file: Any) {
source = project.files(file)
}
Expand Down Expand Up @@ -142,6 +146,7 @@ open class PreprocessTask : DefaultTask() {
Files.createDirectories(it.parent)
})
val javaTransformer = Transformer(mappings)
javaTransformer.patternAnnotation = patternAnnotation.orNull
LOGGER.debug("Remap Classpath:")
javaTransformer.classpath = classpath.files.mapNotNull {
if (it.exists()) {
Expand All @@ -152,13 +157,31 @@ open class PreprocessTask : DefaultTask() {
}
}.toTypedArray()
val sources = mutableMapOf<String, String>()
val processedSources = mutableMapOf<String, String>()
source.flatMap { base -> project.fileTree(base).map { Pair(base, it) } }.forEach { (base, file) ->
if (file.name.endsWith(".java") || file.name.endsWith(".kt")) {
val relPath = base.toPath().relativize(file.toPath())
sources[relPath.toString()] = file.readText()
val text = file.readText()
sources[relPath.toString()] = text
val lines = text.lines()
val kws = keywords.get().entries.find { (ext, _) -> file.name.endsWith(ext) }
if (kws != null) {
processedSources[relPath.toString()] = CommentPreprocessor(vars.get()).convertSource(
kws.value,
lines,
lines.map { Pair(it, emptyList()) },
file.toString()
).joinToString("\n")
}
}
}
overwritesPath?.let { base -> project.fileTree(base).map { Pair(base, it) } }?.forEach { (base, file) ->
if (file.name.endsWith(".java") || file.name.endsWith(".kt")) {
val relPath = base.relativize(file.toPath())
processedSources[relPath.toString()] = file.readText()
}
}
mappedSources = javaTransformer.remap(sources)
mappedSources = javaTransformer.remap(sources, processedSources)
}

project.delete(outPath)
Expand Down

0 comments on commit 3d85a00

Please sign in to comment.