Skip to content

Commit

Permalink
Merge branch '1.21.x' into neoforgedgh-791
Browse files Browse the repository at this point in the history
  • Loading branch information
neoforged-automation[bot] authored Dec 15, 2024
2 parents e91858f + 52733f5 commit b42bbb1
Show file tree
Hide file tree
Showing 200 changed files with 3,145 additions and 1,744 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/check-local-changes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ jobs:
- name: Gen package infos
run: ./gradlew generatePackageInfos

- name: Gen patches
run: ./gradlew :neoforge:genPatches
- name: Gen patches and ATs
run: ./gradlew :neoforge:genPatches :neoforge:generateAccessTransformers :neoforge:generateFinalizeSpawnTargets

- name: Run datagen with Gradle
run: ./gradlew :neoforge:runData :tests:runData
Expand Down
42 changes: 17 additions & 25 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import java.util.regex.Pattern

plugins {
id 'net.neoforged.gradleutils' version '3.0.0'
id 'com.diffplug.spotless' version '6.22.0' apply false
id 'dev.lukebemish.immaculate' version '0.1.6' apply false
id 'net.neoforged.licenser' version '0.7.5'
id 'neoforge.formatting-conventions'
id 'neoforge.versioning'
Expand Down Expand Up @@ -56,14 +56,14 @@ license {
}

// Put spotless here because it wants the files to live inside the project root
spotless {
java {
target rootProject.fileTree("src", {
immaculate {
workflows.named("java") {
files.from rootProject.fileTree("src", {
include "**/*.java"
})
}
format 'patches', {
target rootProject.fileTree("patches")
workflows.register("patches") {
files.from(rootProject.fileTree("patches"))

custom 'noImportChanges', { String fileContents ->
if (fileContents.contains('+import') || fileContents.contains('-import')) {
Expand All @@ -72,28 +72,22 @@ spotless {
return fileContents
}

def interfaceChange = Pattern.compile('^[-+].*(implements|(interface.*extends)).*\$', Pattern.UNIX_LINES | Pattern.MULTILINE)
custom 'noInterfaceRemoval', { String fileContents ->
def interfaceChanges = fileContents.findAll(interfaceChange)
def interfaceChange = Pattern.compile('[-+].*(implements|(interface.*extends))(.*)\\{')
custom 'noInterfaceModifications', { String fileContents ->
def interfaceChanges = fileContents.lines().filter { it.matches(interfaceChange) }.toList()
if (interfaceChanges.isEmpty()) return fileContents
String removalChange = ""
String oldInterfaces = ""
// we expect interface additions/removals in pairs of - and then +
interfaceChanges.each { String change ->
final match = change =~ interfaceChange
match.find()
final values = match.group(3).trim()
if (change.startsWith('-')) {
//Skip the - and the ending brace
int implementsIndex = change.indexOf("implements")
if (implementsIndex == -1) implementsIndex = change.indexOf("extends")
//It should never still be -1 based on our initial matching regex, but if it does fail so we can figure out why
if (implementsIndex == -1) implementsIndex = 1
removalChange = change.substring(implementsIndex, change.length() - 1).trim()
} else if (!removalChange.isEmpty() && !change.contains(removalChange)) {
throw new GradleException("Removal of interfaces via patches is not allowed!")
} else {
removalChange = ""
oldInterfaces = values
} else if (oldInterfaces != values) {
throw new GradleException("Modification of interfaces via patches is not allowed!")
}
}
if (!removalChange.isEmpty()) {
throw new GradleException("Removal of interfaces via patches is not allowed!")
}
return fileContents
}

Expand Down Expand Up @@ -152,7 +146,5 @@ spotless {
custom 'jetbrainsNullablePatches', { String fileContents ->
fileContents.replace('@javax.annotation.Nullable', '@org.jetbrains.annotations.Nullable')
}

bumpThisNumberIfACustomStepChanges(5)
}
}
2 changes: 2 additions & 0 deletions buildSrc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@ dependencies {

implementation "com.google.code.gson:gson:${gradle.parent.ext.gson_version}"
implementation "io.codechicken:DiffPatch:${gradle.parent.ext.diffpatch_version}"

implementation "org.ow2.asm:asm:${gradle.parent.ext.asm_version}"
}
31 changes: 17 additions & 14 deletions buildSrc/src/main/groovy/neoforge.formatting-conventions.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import java.util.regex.Matcher

project.plugins.apply('com.diffplug.spotless')
project.plugins.apply('dev.lukebemish.immaculate')

abstract class GeneratePackageInfos extends DefaultTask {
@InputFiles
Expand Down Expand Up @@ -36,15 +36,19 @@ final generatePackageInfos = tasks.register('generatePackageInfos', GeneratePack
it.files.from fileTree("src/main/java")
}

spotless {
java {
endWithNewline()
indentWithSpaces()
removeUnusedImports()
toggleOffOn()
// Pin version to 4.31 because of a Spotless bug https://github.com/diffplug/spotless/issues/1992
eclipse('4.31').configFile rootProject.file('codeformat/formatter-config.xml')
importOrder()
immaculate {
workflows.register('java') {
java()
trailingNewline()
noTabs()
googleFixImports()
toggleOff = 'spotless:off'
toggleOn = 'spotless:on'
eclipse {
version '3.37.0'
config = rootProject.file('codeformat/formatter-config.xml')
}


// courtesy of diffplug/spotless#240
// https://github.com/diffplug/spotless/issues/240#issuecomment-385206606
Expand All @@ -63,28 +67,27 @@ spotless {
custom 'jetbrainsNullable', { String fileContents ->
fileContents.replace('javax.annotation.Nullable', 'org.jetbrains.annotations.Nullable')
}
bumpThisNumberIfACustomStepChanges(3)
}
}

tasks.named('licenseFormat').configure {
mustRunAfter generatePackageInfos
}
tasks.named('spotlessApply').configure {
tasks.named('immaculateApply').configure {
mustRunAfter generatePackageInfos
mustRunAfter tasks.named('licenseFormat')
}

tasks.register('applyAllFormatting', Task) {
dependsOn generatePackageInfos
dependsOn tasks.named('licenseFormat')
dependsOn tasks.named('spotlessApply')
dependsOn tasks.named('immaculateApply')
group = 'verification'
}

tasks.register('checkFormatting', Task) {
dependsOn 'licenseCheck'
dependsOn 'spotlessCheck'
dependsOn 'immaculateCheck'
group = 'verification'
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package net.neoforged.neodev;

import net.neoforged.neodev.utils.FileUtils;
import net.neoforged.neodev.utils.SerializablePredicate;
import net.neoforged.neodev.utils.structure.ClassInfo;
import net.neoforged.neodev.utils.structure.ClassStructureVisitor;
import net.neoforged.neodev.utils.structure.FieldInfo;
import net.neoforged.neodev.utils.structure.MethodInfo;
import org.gradle.api.DefaultTask;
import org.gradle.api.Named;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import org.objectweb.asm.Opcodes;

import javax.annotation.Nullable;
import java.io.IOException;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
* This task is used to generate access transformers based on a set of rules defined in the buildscript.
*/
public abstract class GenerateAccessTransformers extends DefaultTask {
public static final Modifier PUBLIC = new Modifier("public", false, Opcodes.ACC_PUBLIC);
public static final Modifier PROTECTED = new Modifier("protected", false, Opcodes.ACC_PUBLIC, Opcodes.ACC_PROTECTED);

@InputFile
public abstract RegularFileProperty getInput();

@OutputFile
public abstract RegularFileProperty getAccessTransformer();

@Input
public abstract ListProperty<AtGroup> getGroups();

@TaskAction
public void exec() throws IOException {
// First we collect all classes
var targets = ClassStructureVisitor.readJar(getInput().getAsFile().get());

var groupList = getGroups().get();

List<String>[] groups = new List[groupList.size()];
for (int i = 0; i < groupList.size(); i++) {
groups[i] = new ArrayList<>();
}

// Now we check each class against each group and see if the group wants to handle it
for (ClassInfo value : targets.values()) {
for (int i = 0; i < groupList.size(); i++) {
var group = groupList.get(i);
if (group.classMatch.test(value)) {
var lastInner = value.name().lastIndexOf("$");
// Skip anonymous classes
if (lastInner >= 0 && Character.isDigit(value.name().charAt(lastInner + 1))) {
continue;
}

// fieldMatch is non-null only for field ATs
if (group.fieldMatch != null) {
for (var field : value.fields()) {
if (group.fieldMatch.test(field) && !group.modifier.test(field.access())) {
groups[i].add(group.modifier.name + " " + value.name().replace('/', '.') + " " + field.name());
}
}
}
// methodMatch is non-null only for group ATs
else if (group.methodMatch != null) {
for (var method : value.methods()) {
if (group.methodMatch.test(method) && !group.modifier.test(method.access())) {
groups[i].add(group.modifier.name + " " + value.name().replace('/', '.') + " " + method.name() + method.descriptor());
}
}
}
// If there's neither a field nor a method predicate, this is a class AT
else if (!group.modifier.test(value.access().intValue())) {
groups[i].add(group.modifier.name + " " + value.name().replace('/', '.'));

// If we AT a record we must ensure that its constructors have the same AT
if (value.hasSuperclass("java/lang/Record")) {
for (MethodInfo method : value.methods()) {
if (method.name().equals("<init>")) {
groups[i].add(group.modifier.name + " " + value.name().replace('/', '.') + " " + method.name() + method.descriptor());
}
}
}
}
}
}
}

// Dump the ATs
var text = new StringBuilder();

text.append("# This file is generated based on the rules defined in the buildscript. DO NOT modify it manually.\n# Add more rules in the buildscript and then run the generateAccessTransformers task to update this file.\n\n");

for (int i = 0; i < groups.length; i++) {
// Check if the group found no targets. If it didn't, there's probably an error in the test and it should be reported
if (groups[i].isEmpty()) {
throw new IllegalStateException("Generated AT group '" + groupList.get(i).name + "' found no entries!");
}
text.append("# ").append(groupList.get(i).name).append('\n');
text.append(groups[i].stream().sorted().collect(Collectors.joining("\n")));
text.append('\n');

if (i < groups.length - 1) text.append('\n');
}

var outFile = getAccessTransformer().getAsFile().get().toPath();
if (!Files.exists(outFile.getParent())) {
Files.createDirectories(outFile.getParent());
}

FileUtils.writeStringSafe(outFile, text.toString(), StandardCharsets.UTF_8);
}

public void classGroup(String name, Modifier modifier, SerializablePredicate<ClassInfo> match) {
getGroups().add(new AtGroup(name, modifier, match, null, null));
}

public void methodGroup(String name, Modifier modifier, SerializablePredicate<ClassInfo> targetTest, SerializablePredicate<MethodInfo> methodTest) {
getGroups().add(new AtGroup(name, modifier, targetTest, methodTest, null));
}

public void fieldGroup(String name, Modifier modifier, SerializablePredicate<ClassInfo> targetTest, SerializablePredicate<FieldInfo> fieldTest) {
getGroups().add(new AtGroup(name, modifier, targetTest, null, fieldTest));
}

public <T extends Named> SerializablePredicate<T> named(String name) {
return target -> target.getName().equals(name);
}

public SerializablePredicate<ClassInfo> classesWithSuperclass(String superClass) {
return target -> target.hasSuperclass(superClass);
}

public SerializablePredicate<ClassInfo> innerClassesOf(String parent) {
var parentFullName = parent + "$";
return target -> target.name().startsWith(parentFullName);
}

public SerializablePredicate<MethodInfo> methodsReturning(String type) {
var endMatch = ")L" + type + ";";
return methodInfo -> methodInfo.descriptor().endsWith(endMatch);
}

public SerializablePredicate<FieldInfo> fieldsOfType(SerializablePredicate<ClassInfo> type) {
return value -> type.test(value.type());
}

public <T> SerializablePredicate<T> matchAny() {
return value -> true;
}

public record AtGroup(String name, Modifier modifier, SerializablePredicate<ClassInfo> classMatch,
@Nullable SerializablePredicate<MethodInfo> methodMatch, @Nullable SerializablePredicate<FieldInfo> fieldMatch) implements Serializable {
}

public record Modifier(String name, boolean isFinal, int... validOpcodes) implements Serializable {
public boolean test(int value) {
if (isFinal && (value & Opcodes.ACC_FINAL) == 0) return false;

for (int validOpcode : validOpcodes) {
if ((value & validOpcode) != 0) {
return true;
}
}
return false;
}
}
}
Loading

0 comments on commit b42bbb1

Please sign in to comment.