-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
28 changed files
with
644 additions
and
533 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
44 changes: 44 additions & 0 deletions
44
build-logic/src/main/java/juuxel/adorn/gradle/DataGeneratorPlugin.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,44 @@ | ||
package juuxel.adorn.gradle; | ||
|
||
import juuxel.adorn.gradle.action.MinifyJson; | ||
import juuxel.adorn.gradle.datagen.DataGeneratorExtension; | ||
import juuxel.adorn.gradle.datagen.DeleteDuplicates; | ||
import juuxel.adorn.gradle.datagen.GenerateData; | ||
import org.gradle.api.Plugin; | ||
import org.gradle.api.Project; | ||
import org.gradle.api.plugins.JavaPluginExtension; | ||
|
||
public final class DataGeneratorPlugin implements Plugin<Project> { | ||
@Override | ||
public void apply(Project project) { | ||
var java = project.getExtensions().getByType(JavaPluginExtension.class); | ||
var extension = project.getExtensions().create("dataGenerator", DataGeneratorExtension.class, project); | ||
extension.getConfigs().register("main", config -> { | ||
config.getFiles().from(project.fileTree("src/data").filter(file -> file.getName().endsWith(".xml"))); | ||
}); | ||
|
||
var generatedResources = project.getLayout().getProjectDirectory().dir("src/main/generatedResources"); | ||
var generateMainData = project.getTasks().register("generateMainData", GenerateData.class, task -> { | ||
task.getConfigs().set(extension.getConfigs()); | ||
task.getGenerateTags().set(extension.getGenerateTags()); | ||
task.getOutput().convention(generatedResources); | ||
}); | ||
var generateData = project.getTasks().register("generateData", task -> task.dependsOn(generateMainData)); | ||
project.getTasks().named("processResources", task -> task.mustRunAfter(generateData)); | ||
project.getTasks().register("deleteDuplicateResources", DeleteDuplicates.class, task -> { | ||
task.getGenerated().convention(generatedResources); | ||
task.getMain().convention(project.getLayout().dir( | ||
java.getSourceSets() | ||
.named("main") | ||
.map(main -> main.getResources().getSrcDirs().iterator().next()) | ||
)); | ||
}); | ||
java.getSourceSets().named("main", main -> { | ||
main.getResources().srcDir(generatedResources); | ||
main.getResources().exclude(".cache"); | ||
}); | ||
project.afterEvaluate(p -> { | ||
p.getTasks().named("remapJar", task -> task.doLast(new MinifyJson())); | ||
}); | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
build-logic/src/main/java/juuxel/adorn/gradle/EmiDataGeneratorPlugin.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,20 @@ | ||
package juuxel.adorn.gradle; | ||
|
||
import juuxel.adorn.gradle.datagen.EmiDataGeneratorExtension; | ||
import juuxel.adorn.gradle.datagen.GenerateData; | ||
import juuxel.adorn.gradle.datagen.GenerateEmi; | ||
import org.gradle.api.Plugin; | ||
import org.gradle.api.Project; | ||
|
||
public final class EmiDataGeneratorPlugin implements Plugin<Project> { | ||
@Override | ||
public void apply(Project project) { | ||
project.getExtensions().create("emiDataGenerator", EmiDataGeneratorExtension.class); | ||
var generateEmi = project.getTasks().register("generateEmi", GenerateEmi.class, task -> { | ||
var generateMainData = project.getTasks().named("generateMainData", GenerateData.class); | ||
task.mustRunAfter(generateMainData); | ||
task.getOutput().convention(generateMainData.flatMap(GenerateData::getOutput)); | ||
}); | ||
project.getTasks().named("generateData", task -> task.dependsOn(generateEmi)); | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
build-logic/src/main/java/juuxel/adorn/gradle/ServiceInlinePlugin.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,14 @@ | ||
package juuxel.adorn.gradle; | ||
|
||
import juuxel.adorn.gradle.action.InlineServiceLoader; | ||
import org.gradle.api.Plugin; | ||
import org.gradle.api.Project; | ||
|
||
public final class ServiceInlinePlugin implements Plugin<Project> { | ||
@Override | ||
public void apply(Project target) { | ||
target.getTasks().named("remapJar", task -> { | ||
task.doLast(new InlineServiceLoader()); | ||
}); | ||
} | ||
} |
96 changes: 96 additions & 0 deletions
96
build-logic/src/main/java/juuxel/adorn/gradle/action/InlineServiceLoader.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,96 @@ | ||
package juuxel.adorn.gradle.action; | ||
|
||
import juuxel.adorn.gradle.asm.Asm; | ||
import juuxel.adorn.gradle.asm.InsnPattern; | ||
import juuxel.adorn.gradle.asm.MethodBodyPattern; | ||
import org.gradle.api.Action; | ||
import org.gradle.api.Task; | ||
import org.objectweb.asm.Opcodes; | ||
import org.objectweb.asm.Type; | ||
import org.objectweb.asm.tree.AnnotationNode; | ||
import org.objectweb.asm.tree.InsnNode; | ||
import org.objectweb.asm.tree.LdcInsnNode; | ||
import org.objectweb.asm.tree.MethodInsnNode; | ||
import org.objectweb.asm.tree.MethodNode; | ||
import org.objectweb.asm.tree.TypeInsnNode; | ||
|
||
import java.io.IOException; | ||
import java.io.UncheckedIOException; | ||
import java.nio.file.Files; | ||
import java.util.List; | ||
import java.util.Objects; | ||
|
||
public final class InlineServiceLoader implements Action<Task> { | ||
private static final MethodBodyPattern INLINE_PATTERN = new MethodBodyPattern( | ||
List.of( | ||
new InsnPattern<>(LdcInsnNode.class, ldc -> ldc.cst instanceof Type), | ||
new InsnPattern<>( | ||
MethodInsnNode.class, | ||
method -> method.getOpcode() == Opcodes.INVOKESTATIC | ||
&& "java/util/ServiceLoader".equals(method.owner) | ||
&& "load".equals(method.name) | ||
&& "(Ljava/lang/Class;)Ljava/util/ServiceLoader;".equals(method.desc) | ||
), | ||
new InsnPattern<>( | ||
MethodInsnNode.class, | ||
method -> method.getOpcode() == Opcodes.INVOKEVIRTUAL | ||
&& "java/util/ServiceLoader".equals(method.owner) | ||
&& "findFirst".equals(method.name) | ||
&& "()Ljava/util/Optional;".equals(method.desc) | ||
) | ||
) | ||
); | ||
|
||
@Override | ||
public void execute(Task task) { | ||
try { | ||
run(task); | ||
} catch (IOException e) { | ||
throw new UncheckedIOException(e); | ||
} | ||
} | ||
|
||
private void run(Task task) throws IOException { | ||
Asm.transformJar(task.getOutputs().getFiles().getSingleFile().toPath(), (filer, classNode) -> { | ||
List<AnnotationNode> invisibleAnnotations = Objects.requireNonNullElse(classNode.invisibleAnnotations, List.of()); | ||
if (invisibleAnnotations.stream().noneMatch(node -> "Ljuuxel/adorn/util/InlineServices;".equals(node.desc))) { | ||
// Skip classes without the anno | ||
return; | ||
} | ||
|
||
for (MethodNode method : classNode.methods) { | ||
boolean success = INLINE_PATTERN.match(method, ctx -> { | ||
var ldc = (LdcInsnNode) ctx.instructions().get(0); | ||
var type = ((Type) ldc.cst).getInternalName().replace('/', '.'); | ||
var serviceFile = filer.apply("META-INF/services/" + type); | ||
if (Files.exists(serviceFile)) { | ||
try { | ||
var serviceImpls = Files.readAllLines(serviceFile); | ||
if (serviceImpls.size() != 1) throw new IllegalArgumentException("Service file " + type + " must have exactly one provider"); | ||
var implType = serviceImpls.get(0).replace('.', '/'); | ||
ctx.replaceWith( | ||
List.of( | ||
new TypeInsnNode(Opcodes.NEW, implType), | ||
new InsnNode(Opcodes.DUP), | ||
new MethodInsnNode(Opcodes.INVOKESPECIAL, implType, "<init>", "()V"), | ||
new MethodInsnNode( | ||
Opcodes.INVOKESTATIC, | ||
"java/util/Optional", | ||
"of", | ||
"(Ljava/lang/Object;)Ljava/util/Optional;" | ||
) | ||
) | ||
); | ||
} catch (IOException e) { | ||
throw new UncheckedIOException(e); | ||
} | ||
} | ||
}); | ||
|
||
if (success) { | ||
method.maxStack++; | ||
} | ||
} | ||
}); | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
build-logic/src/main/java/juuxel/adorn/gradle/action/MinifyJson.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,38 @@ | ||
package juuxel.adorn.gradle.action; | ||
|
||
import groovy.json.JsonOutput; | ||
import groovy.json.JsonSlurper; | ||
import org.gradle.api.Action; | ||
import org.gradle.api.Task; | ||
|
||
import java.io.IOException; | ||
import java.io.UncheckedIOException; | ||
import java.net.URI; | ||
import java.nio.file.FileSystems; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.util.Map; | ||
|
||
/** | ||
* A simple action that you can add to a {@code Jar} task | ||
* that minifies all JSON files using {@link JsonSlurper} and {@link JsonOutput}. | ||
*/ | ||
public final class MinifyJson implements Action<Task> { | ||
@Override | ||
public void execute(Task task) { | ||
var jar = task.getOutputs().getFiles().getSingleFile().toPath(); | ||
try (var fs = FileSystems.newFileSystem(URI.create("jar:" + jar.toUri()), Map.of("create", false))) { | ||
for (Path root : fs.getRootDirectories()) { | ||
try (var jsons = Files.walk(root).filter(it -> Files.isRegularFile(it) && it.toString().endsWith(".json"))) { | ||
var iter = jsons.iterator(); | ||
while (iter.hasNext()) { | ||
var json = iter.next(); | ||
Files.writeString(json, JsonOutput.toJson(new JsonSlurper().parse(json))); | ||
} | ||
} | ||
} | ||
} catch (IOException e) { | ||
throw new UncheckedIOException(e); | ||
} | ||
} | ||
} |
49 changes: 49 additions & 0 deletions
49
build-logic/src/main/java/juuxel/adorn/gradle/asm/Asm.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,49 @@ | ||
package juuxel.adorn.gradle.asm; | ||
|
||
import org.objectweb.asm.ClassReader; | ||
import org.objectweb.asm.ClassWriter; | ||
import org.objectweb.asm.tree.ClassNode; | ||
|
||
import java.io.IOException; | ||
import java.net.URI; | ||
import java.nio.file.FileSystems; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.util.Map; | ||
import java.util.function.Function; | ||
|
||
public final class Asm { | ||
public static void transformJar(Path jar, Transformer transformer) throws IOException { | ||
try (var fs = FileSystems.newFileSystem(URI.create("jar:" + jar.toUri()), Map.of("create", false))) { | ||
for (Path root : fs.getRootDirectories()) { | ||
try (var classes = Files.walk(root).filter(path -> Files.isRegularFile(path) && path.toString().endsWith(".class"))) { | ||
var iter = classes.iterator(); | ||
while (iter.hasNext()) { | ||
var c = iter.next(); | ||
|
||
// Read the existing class | ||
ClassReader cr; | ||
try (var in = Files.newInputStream(c)) { | ||
cr = new ClassReader(in); | ||
} | ||
var node = new ClassNode(); | ||
cr.accept(node, 0); | ||
|
||
// Transform | ||
transformer.transform(fs::getPath, node); | ||
|
||
// Write out the new class | ||
var cw = new ClassWriter(cr, 0); | ||
node.accept(cw); | ||
Files.write(c, cw.toByteArray()); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
@FunctionalInterface | ||
public interface Transformer { | ||
void transform(Function<String, Path> filer, ClassNode classNode) throws IOException; | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
build-logic/src/main/java/juuxel/adorn/gradle/asm/InsnPattern.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,8 @@ | ||
package juuxel.adorn.gradle.asm; | ||
|
||
import org.objectweb.asm.tree.AbstractInsnNode; | ||
|
||
import java.util.function.Predicate; | ||
|
||
public record InsnPattern<T extends AbstractInsnNode>(Class<T> type, Predicate<T> filter) { | ||
} |
10 changes: 10 additions & 0 deletions
10
build-logic/src/main/java/juuxel/adorn/gradle/asm/MatchContext.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,10 @@ | ||
package juuxel.adorn.gradle.asm; | ||
|
||
import org.objectweb.asm.tree.AbstractInsnNode; | ||
|
||
import java.util.List; | ||
|
||
public interface MatchContext { | ||
List<AbstractInsnNode> instructions(); | ||
void replaceWith(List<AbstractInsnNode> instructions); | ||
} |
Oops, something went wrong.