Move filament into the yarn repo. (#3296)

This will make further developments a lot easier.
This commit is contained in:
modmuss50 2022-09-19 18:18:02 +01:00 committed by GitHub
parent 18a44f9955
commit 1f985580c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 86391 additions and 6 deletions

View File

@ -1,2 +1,7 @@
[*.{gradle,mapping,unpick}]
indent_style = tab
[*.java]
indent_style = tab
ij_continuation_indent_size = 8
ij_java_imports_layout = $*,|,java.**,|,javax.**,|,*,|,net.fabricmc.**

View File

@ -5,7 +5,7 @@ jobs:
strategy:
matrix:
java: [17-jdk, 18-jdk]
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
container:
image: openjdk:${{ matrix.java }}
options: --user root
@ -20,3 +20,13 @@ jobs:
with:
name: Artifacts
path: build/libs/
test-build-logic:
runs-on: ubuntu-22.04
container:
image: openjdk:18-jdk
options: --user root
steps:
- uses: actions/checkout@v2
- uses: gradle/wrapper-validation-action@v1
- run: ./gradlew :filament:build

View File

@ -4,7 +4,7 @@ concurrency: ci-${{ github.ref }}
jobs:
publish:
if: ${{ github.repository_owner == 'FabricMC' }}
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
container:
image: openjdk:18-jdk
options: --user root

View File

@ -7,7 +7,7 @@ on:
jobs:
update:
if: ${{ github.event.label.name == 'update-base' }}
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- uses: FabricMC/fabric-action-scripts@v1
with:

View File

@ -24,7 +24,7 @@ plugins {
id 'de.undercouch.download' version '4.1.2'
id 'maven-publish'
id "com.diffplug.spotless" version "6.4.2"
id 'net.fabricmc.filament' version '0.3.0'
id 'net.fabricmc.filament'
}
def minecraft_version = "1.19.2"

56
filament/build.gradle Normal file
View File

@ -0,0 +1,56 @@
plugins {
id 'java-library'
id 'java-gradle-plugin'
id 'checkstyle'
}
group = 'net.fabricmc'
def properties = new Properties()
file('../gradle.properties').newInputStream().withCloseable {
properties.load(it)
}
repositories {
maven {
name "Fabric Repository"
url 'https://maven.fabricmc.net'
}
mavenCentral()
}
dependencies {
implementation "org.ow2.asm:asm:${properties.asm_version}"
implementation "org.ow2.asm:asm-tree:${properties.asm_version}"
implementation "cuchaz:enigma:$properties.enigma_version"
implementation "net.fabricmc.unpick:unpick:$properties.unpick_version"
implementation "net.fabricmc.unpick:unpick-format-utils:$properties.unpick_version"
implementation "net.fabricmc:tiny-mappings-parser:$properties.tiny_mappings_parser_version"
testImplementation platform("org.junit:junit-bom:$properties.junit_version")
testImplementation 'org.junit.jupiter:junit-jupiter'
testImplementation "org.assertj:assertj-core:$properties.assertj_version"
}
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
options.release = 17
}
test {
useJUnitPlatform()
}
checkstyle {
configFile = file('checkstyle.xml')
toolVersion = '10.3.3'
}
gradlePlugin {
plugins {
filament {
id = 'net.fabricmc.filament'
implementationClass = 'net.fabricmc.filament.FilamentGradlePlugin'
}
}
}

164
filament/checkstyle.xml Normal file
View File

@ -0,0 +1,164 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.2//EN" "http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
<module name="Checker">
<property name="charset" value="UTF-8"/>
<property name="fileExtensions" value="java"/>
<property name="localeLanguage" value="en"/>
<property name="localeCountry" value="US"/>
<module name="NewlineAtEndOfFile"/>
<!-- disallow trailing whitespace -->
<module name="RegexpSingleline">
<property name="format" value="\s+$"/>
<property name="message" value="trailing whitespace"/>
</module>
<!-- note: RegexpMultiline shows nicer messages than Regexp, but has to be outside TreeWalker -->
<!-- disallow multiple consecutive blank lines -->
<module name="RegexpMultiline">
<property name="format" value="\n[\t ]*\r?\n[\t ]*\r?\n"/>
<property name="message" value="adjacent blank lines"/>
</module>
<!-- disallow blank after { -->
<module name="RegexpMultiline">
<property name="format" value="\{[\t ]*\r?\n[\t ]*\r?\n"/>
<property name="message" value="blank line after '{'"/>
</module>
<!-- disallow blank before } -->
<module name="RegexpMultiline">
<property name="format" value="\n[\t ]*\r?\n[\t ]*\}"/>
<property name="message" value="blank line before '}'"/>
</module>
<!-- require blank before { in the same indentation level -->
<module name="RegexpMultiline">
<!-- the regex works as follows:
It matches (=fails) for \n<indentation><something>\n<same indentation><control statement>[...]{\n
while <something> is a single line comment, it'll look for a blank line one line earlier
if <something> is a space, indicating a formatting error or ' */', it'll ignore the instance
if <something> is a tab, indicating a continued line, it'll ignore the instance
<control statement> is 'if', 'do', 'while', 'for', 'try' or nothing (instance initializer block)
- first \n: with positive lookbehind (?<=\n) to move the error marker to a more reasonable place
- capture tabs for <indentation>, later referenced via \1
- remaining preceding line as a non-comment (doesn't start with '/', '//', ' ' or '\t') or multiple lines where all but the first are a single line comment with the same indentation
- new line
- <indentation> as captured earlier
- <control statement> as specified above
- { before the next new line -->
<property name="format" value="(?&lt;=\n)([\t]+)(?:[^/\r\n \t][^\r\n]*|/[^/\r\n][^\r\n]*|[^/\r\n][^\r\n]*(\r?\n\1//[^\r\n]*)+)\r?\n\1(|(if|do|while|for|try)[^\r\n]+)\{[\t ]*\r?\n"/>
<property name="message" value="missing blank line before block at same indentation level"/>
</module>
<!-- require blank after } in the same indentation level -->
<module name="RegexpMultiline">
<!-- \n<indentation>}\n<same indentation><whatever unless newline, '}' or starting with cas(e) or def(ault)> -->
<property name="format" value="(?&lt;=\n)([\t]+)\}\r?\n\1(?:[^\r\n\}cd]|c[^\r\na]|ca[^\r\ns]|d[^\r\ne]|de[^\r\nf])"/>
<property name="message" value="missing blank line after block at same indentation level"/>
</module>
<module name="TreeWalker">
<!-- Ensure all imports are ship shape -->
<module name="AvoidStarImport"/>
<module name="IllegalImport"/>
<module name="RedundantImport"/>
<module name="UnusedImports"/>
<module name="ImportOrder">
<property name="groups" value="java,javax,*,net.minecraft,net.fabricmc"/>
<property name="ordered" value="false"/><!-- the plugin orders alphabetically without considering separators.. -->
<property name="separated" value="true"/>
<property name="option" value="top"/>
<property name="sortStaticImportsAlphabetically" value="true"/>
</module>
<!-- Ensures braces are at the end of a line -->
<module name="LeftCurly"/>
<module name="RightCurly"/>
<!-- single line statements on one line, -->
<module name="NeedBraces">
<property name="tokens" value="LITERAL_IF,LITERAL_FOR,LITERAL_WHILE"/>
<property name="allowSingleLineStatement" value="true"/>
</module>
<module name="NeedBraces">
<property name="tokens" value="LITERAL_ELSE,LITERAL_DO"/>
<property name="allowSingleLineStatement" value="false"/>
</module>
<module name="EmptyLineSeparator">
<property name="allowNoEmptyLineBetweenFields" value="true"/>
<property name="allowMultipleEmptyLines" value="false"/>
<!-- exclude METHOD_DEF and VARIABLE_DEF -->
<property name="tokens" value="PACKAGE_DEF,IMPORT,STATIC_IMPORT,CLASS_DEF,INTERFACE_DEF,ENUM_DEF,STATIC_INIT,INSTANCE_INIT,CTOR_DEF"/>
</module>
<module name="OperatorWrap"/>
<module name="SeparatorWrap">
<property name="tokens" value="DOT,ELLIPSIS,AT"/>
<property name="option" value="nl"/>
</module>
<module name="SeparatorWrap">
<property name="tokens" value="COMMA,SEMI"/>
<property name="option" value="eol"/>
</module>
<module name="Indentation">
<property name="basicOffset" value="8"/>
<property name="caseIndent" value="0"/>
<property name="throwsIndent" value="8"/>
<property name="arrayInitIndent" value="8"/>
<property name="lineWrappingIndentation" value="16"/>
</module>
<module name="ParenPad"/>
<module name="NoWhitespaceBefore"/>
<module name="NoWhitespaceAfter">
<!-- allow ARRAY_INIT -->
<property name="tokens" value="AT,INC,DEC,UNARY_MINUS,UNARY_PLUS,BNOT,LNOT,DOT,ARRAY_DECLARATOR,INDEX_OP"/>
</module>
<module name="WhitespaceAfter"/>
<module name="WhitespaceAround">
<!-- Allow PLUS, MINUS, MUL, DIV as they may be more readable without spaces in some cases -->
<property name="tokens" value="ASSIGN,BAND,BAND_ASSIGN,BOR,BOR_ASSIGN,BSR,BSR_ASSIGN,BXOR,BXOR_ASSIGN,COLON,DIV_ASSIGN,DO_WHILE,EQUAL,GE,GT,LAMBDA,LAND,LCURLY,LE,LITERAL_CATCH,LITERAL_DO,LITERAL_ELSE,LITERAL_FINALLY,LITERAL_FOR,LITERAL_IF,LITERAL_RETURN,LITERAL_SWITCH,LITERAL_SYNCHRONIZED,LITERAL_TRY,LITERAL_WHILE,LOR,LT,MINUS_ASSIGN,MOD,MOD_ASSIGN,NOT_EQUAL,PLUS_ASSIGN,QUESTION,RCURLY,SL,SLIST,SL_ASSIGN,SR,SR_ASSIGN,STAR,STAR_ASSIGN,LITERAL_ASSERT,TYPE_EXTENSION_AND"/>
</module>
<module name="SingleSpaceSeparator"/>
<module name="GenericWhitespace"/>
<module name="CommentsIndentation"/>
<module name="ArrayTypeStyle"/>
<module name="DefaultComesLast">
<property name="skipIfLastAndSharedWithCase" value="true"/>
</module>
<module name="SimplifyBooleanExpression"/>
<module name="SimplifyBooleanReturn"/>
<module name="StringLiteralEquality"/>
<module name="ModifierOrder"/>
<module name="RedundantModifier"/>
<module name="AnnotationLocation"/>
<module name="MissingOverride"/>
<!-- By default this allows catch blocks with only comments -->
<module name="EmptyCatchBlock"/>
<!-- Enforce tabs -->
<module name="RegexpSinglelineJava">
<property name="format" value="^\t* ([^*]|\*[^ /])"/>
<property name="message" value="non-tab indentation"/>
</module>
<module name="OuterTypeFilename"/>
<!--<module name="InvalidJavadocPosition"/>-->
<module name="JavadocParagraph"/>
<module name="JavadocStyle"/>
<module name="AtclauseOrder">
<property name="tagOrder" value="@param,@return,@throws,@deprecated"/>
</module>
</module>
</module>

1
filament/settings.gradle Normal file
View File

@ -0,0 +1 @@
rootProject.name = 'fabric-filament'

View File

@ -0,0 +1,25 @@
package net.fabricmc.filament;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import net.fabricmc.filament.task.CombineUnpickDefinitionsTask;
import net.fabricmc.filament.task.GeneratePackageInfoMappingsTask;
import net.fabricmc.filament.task.JavadocLintTask;
import net.fabricmc.filament.task.RemapUnpickDefinitionsTask;
public final class FilamentGradlePlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getTasks().register("generatePackageInfoMappings", GeneratePackageInfoMappingsTask.class);
project.getTasks().register("javadocLint", JavadocLintTask.class);
var combineUnpickDefinitions = project.getTasks().register("combineUnpickDefinitions", CombineUnpickDefinitionsTask.class);
project.getTasks().register("remapUnpickDefinitionsIntermediary", RemapUnpickDefinitionsTask.class, task -> {
task.dependsOn(combineUnpickDefinitions);
task.getInput().set(combineUnpickDefinitions.flatMap(CombineUnpickDefinitionsTask::getOutput));
task.getSourceNamespace().set("named");
task.getTargetNamespace().set("intermediary");
});
}
}

View File

@ -0,0 +1,89 @@
package net.fabricmc.filament.task;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import javax.inject.Inject;
import daomephsta.unpick.constantmappers.datadriven.parser.v2.UnpickV2Reader;
import daomephsta.unpick.constantmappers.datadriven.parser.v2.UnpickV2Writer;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.tasks.InputDirectory;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import org.gradle.workers.WorkAction;
import org.gradle.workers.WorkParameters;
import org.gradle.workers.WorkQueue;
import org.gradle.workers.WorkerExecutor;
import net.fabricmc.filament.util.FileUtil;
import net.fabricmc.filament.util.UnpickUtil;
public abstract class CombineUnpickDefinitionsTask extends DefaultTask {
@InputDirectory
public abstract DirectoryProperty getInput();
@OutputFile
public abstract RegularFileProperty getOutput();
@Inject
protected abstract WorkerExecutor getWorkerExecutor();
@TaskAction
public void run() {
WorkQueue workQueue = getWorkerExecutor().noIsolation();
workQueue.submit(CombineAction.class, parameters -> {
parameters.getInput().set(getInput());
parameters.getOutput().set(getOutput());
});
}
public interface CombineParameters extends WorkParameters {
@InputDirectory
DirectoryProperty getInput();
@OutputFile
RegularFileProperty getOutput();
}
public abstract static class CombineAction implements WorkAction<CombineParameters> {
@Inject
public CombineAction() {
}
@Override
public void execute() {
try {
File output = getParameters().getOutput().getAsFile().get();
FileUtil.deleteIfExists(output);
UnpickV2Writer writer = new UnpickV2Writer();
// Sort inputs to get reproducible outputs (also for testing)
List<File> files = new ArrayList<>(getParameters().getInput().getAsFileTree().getFiles());
files.sort(Comparator.comparing(File::getName));
for (File file : files) {
if (!file.getName().endsWith(".unpick")) {
continue;
}
try (UnpickV2Reader reader = new UnpickV2Reader(new FileInputStream(file))) {
reader.accept(writer);
}
}
FileUtil.write(output, UnpickUtil.getLfOutput(writer));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
}

View File

@ -0,0 +1,106 @@
package net.fabricmc.filament.task;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.TaskAction;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import net.fabricmc.filament.util.FileUtil;
public class GeneratePackageInfoMappingsTask extends DefaultTask {
private final RegularFileProperty inputJar = getProject().getObjects().fileProperty();
private final Property<String> packageName = getProject().getObjects().property(String.class);
private final DirectoryProperty outputDir = getProject().getObjects().directoryProperty();
@InputFile
public RegularFileProperty getInputJar() {
return inputJar;
}
@Input
public Property<String> getPackageName() {
return packageName;
}
@OutputDirectory
public DirectoryProperty getOutputDir() {
return outputDir;
}
@TaskAction
public void run() throws IOException {
getProject().getLogger().lifecycle("Scanning {} for package-info classes", inputJar.get().getAsFile());
FileUtil.deleteDirectory(outputDir.get().getAsFile());
try (ZipFile zipFile = new ZipFile(inputJar.get().getAsFile())) {
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if (entry.getName().endsWith(".class")) {
try (InputStream is = zipFile.getInputStream(entry)) {
processEntry(entry.getName(), is);
}
}
}
}
}
private void processEntry(String name, InputStream inputStream) throws IOException {
name = name.replace(".class", "");
if (name.contains("$")) {
// Dont care about inner classes
return;
}
ClassReader classReader = new ClassReader(inputStream);
ClassNode classNode = new ClassNode();
classReader.accept(classNode, 0);
if (classNode.access != (Opcodes.ACC_ABSTRACT | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_INTERFACE)) {
// We only care about abstract synthetic interfaces, hopefully this is specific enough
return;
}
if (classNode.methods.size() > 0 || classNode.fields.size() > 0 || classNode.interfaces.size() > 0) {
// Nope cannot be a package-info
return;
}
generateMapping(name);
}
private void generateMapping(String name) throws IOException {
String inputName = name.substring(name.lastIndexOf("/") + 1);
String className = "PackageInfo" + name.substring(name.lastIndexOf("_") + 1);
String fullName = packageName.get() + className;
File mappingsFile = new File(outputDir.get().getAsFile(), className + ".mapping");
mappingsFile.getParentFile().mkdirs();
try (PrintWriter writer = new PrintWriter(new FileWriter(mappingsFile))) {
writer.printf("CLASS net/minecraft/%s %s", inputName, fullName);
writer.print('\n'); // println is platform-dependent and may produce CRLF.
}
}
}

View File

@ -0,0 +1,161 @@
package net.fabricmc.filament.task;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import javax.inject.Inject;
import cuchaz.enigma.ProgressListener;
import cuchaz.enigma.translation.mapping.EntryMapping;
import cuchaz.enigma.translation.mapping.serde.MappingParseException;
import cuchaz.enigma.translation.mapping.serde.enigma.EnigmaMappingsReader;
import cuchaz.enigma.translation.mapping.tree.EntryTree;
import cuchaz.enigma.translation.representation.entry.Entry;
import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
import cuchaz.enigma.translation.representation.entry.MethodEntry;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.FileType;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.api.tasks.InputDirectory;
import org.gradle.api.tasks.TaskAction;
import org.gradle.work.ChangeType;
import org.gradle.work.FileChange;
import org.gradle.work.Incremental;
import org.gradle.work.InputChanges;
import org.gradle.workers.WorkAction;
import org.gradle.workers.WorkParameters;
import org.gradle.workers.WorkQueue;
import org.gradle.workers.WorkerExecutor;
import net.fabricmc.filament.util.FileUtil;
public abstract class JavadocLintTask extends DefaultTask {
private static final Pattern PARAM_DOC_LINE = Pattern.compile("^@param\\s+[^<].*$");
private final DirectoryProperty mappingDirectory = getProject().getObjects().directoryProperty();
@Incremental
@InputDirectory
public DirectoryProperty getMappingDirectory() {
return mappingDirectory;
}
public JavadocLintTask() {
// Ignore outputs for up-to-date checks as there aren't any (so only inputs are checked)
getOutputs().upToDateWhen(task -> true);
}
@Inject
protected abstract WorkerExecutor getWorkerExecutor();
@TaskAction
public void run(InputChanges changes) {
WorkQueue workQueue = getWorkerExecutor().noIsolation();
workQueue.submit(LintAction.class, parameters -> {
for (FileChange change : changes.getFileChanges(mappingDirectory)) {
if (change.getChangeType() != ChangeType.REMOVED && change.getFileType() == FileType.FILE) {
parameters.getMappingFiles().from(change.getFile());
}
}
});
}
private static boolean isRegularMethodParameter(String line) {
return PARAM_DOC_LINE.matcher(line).matches();
}
private static String getFirstWord(String str) {
int i = str.indexOf(' ');
return i != -1 ? str.substring(0, i) : str;
}
private static String getFullName(EntryTree<EntryMapping> mappings, Entry<?> entry) {
String name = mappings.get(entry).targetName();
if (entry instanceof MethodEntry method) {
name += method.getDesc().toString();
}
if (entry.getParent() != null) {
name = getFullName(mappings, entry.getParent()) + '.' + name;
}
return name;
}
public interface LintParameters extends WorkParameters {
ConfigurableFileCollection getMappingFiles();
}
public abstract static class LintAction implements WorkAction<LintParameters> {
private static final Logger LOGGER = Logging.getLogger(LintAction.class);
@Inject
public LintAction() {
}
@Override
public void execute() {
try {
var files = FileUtil.toPaths(getParameters().getMappingFiles().getFiles()).toArray(new Path[0]);
EntryTree<EntryMapping> mappings = EnigmaMappingsReader.readFiles(ProgressListener.none(), files);
List<String> errors = new ArrayList<>();
mappings.getAllEntries().parallel().forEach(entry -> {
EntryMapping mapping = mappings.get(entry);
String javadoc = mapping.javadoc();
if (javadoc != null && !javadoc.isEmpty()) {
List<String> localErrors = new ArrayList<>();
if (entry instanceof LocalVariableEntry && ((LocalVariableEntry) entry).isArgument()) {
if (javadoc.endsWith(".")) {
localErrors.add("parameter javadoc ends with '.'");
}
if (Character.isUpperCase(javadoc.charAt(0))) {
String word = getFirstWord(javadoc);
// ignore single-letter "words" (like X or Z)
if (word.length() > 1) {
localErrors.add("parameter javadoc starts with uppercase word '" + word + "'");
}
}
} else if (entry instanceof MethodEntry) {
if (javadoc.lines().anyMatch(JavadocLintTask::isRegularMethodParameter)) {
localErrors.add("method javadoc contains parameter docs, which should be on the parameter itself");
}
}
// new rules can be added here in the future
if (!localErrors.isEmpty()) {
String name = getFullName(mappings, entry);
for (String error : localErrors) {
errors.add(name + ": " + error);
}
}
}
});
if (!errors.isEmpty()) {
for (String error : errors) {
LOGGER.error("lint: {}", error);
}
throw new GradleException("Found " + errors.size() + " javadoc format errors! See the log for details.");
}
} catch (IOException | MappingParseException e) {
throw new GradleException("Could not read and parse mappings", e);
}
}
}
}

View File

@ -0,0 +1,136 @@
package net.fabricmc.filament.task;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
import daomephsta.unpick.constantmappers.datadriven.parser.FieldKey;
import daomephsta.unpick.constantmappers.datadriven.parser.MethodKey;
import daomephsta.unpick.constantmappers.datadriven.parser.v2.UnpickV2Reader;
import daomephsta.unpick.constantmappers.datadriven.parser.v2.UnpickV2Remapper;
import daomephsta.unpick.constantmappers.datadriven.parser.v2.UnpickV2Writer;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
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.gradle.workers.WorkAction;
import org.gradle.workers.WorkParameters;
import org.gradle.workers.WorkQueue;
import org.gradle.workers.WorkerExecutor;
import net.fabricmc.filament.util.FileUtil;
import net.fabricmc.filament.util.UnpickUtil;
import net.fabricmc.mapping.tree.ClassDef;
import net.fabricmc.mapping.tree.FieldDef;
import net.fabricmc.mapping.tree.MethodDef;
import net.fabricmc.mapping.tree.TinyMappingFactory;
import net.fabricmc.mapping.tree.TinyTree;
public abstract class RemapUnpickDefinitionsTask extends DefaultTask {
@InputFile
public abstract RegularFileProperty getInput();
@InputFile
public abstract RegularFileProperty getMappings();
@Input
public abstract Property<String> getSourceNamespace();
@Input
public abstract Property<String> getTargetNamespace();
@OutputFile
public abstract RegularFileProperty getOutput();
@Inject
protected abstract WorkerExecutor getWorkerExecutor();
@TaskAction
public void run() {
WorkQueue workQueue = getWorkerExecutor().noIsolation();
workQueue.submit(RemapAction.class, parameters -> {
parameters.getInput().set(getInput());
parameters.getMappings().set(getMappings());
parameters.getSourceNamespace().set(getSourceNamespace());
parameters.getTargetNamespace().set(getTargetNamespace());
parameters.getOutput().set(getOutput());
});
}
public interface RemapParameters extends WorkParameters {
@InputFile
RegularFileProperty getInput();
@InputFile
RegularFileProperty getMappings();
@Input
Property<String> getSourceNamespace();
@Input
Property<String> getTargetNamespace();
@OutputFile
RegularFileProperty getOutput();
}
public abstract static class RemapAction implements WorkAction<RemapParameters> {
@Inject
public RemapAction() {
}
@Override
public void execute() {
try {
File output = getParameters().getOutput().getAsFile().get();
FileUtil.deleteIfExists(output);
Map<String, String> classMappings = new HashMap<>();
Map<MethodKey, String> methodMappings = new HashMap<>();
Map<FieldKey, String> fieldMappings = new HashMap<>();
String fromM = getParameters().getSourceNamespace().get();
String toM = getParameters().getTargetNamespace().get();
try (BufferedReader reader = new BufferedReader(new FileReader(getParameters().getMappings().getAsFile().get()))) {
TinyTree tinyTree = TinyMappingFactory.loadWithDetection(reader);
for (ClassDef classDef : tinyTree.getClasses()) {
classMappings.put(classDef.getName(fromM), classDef.getName(toM));
for (MethodDef methodDef : classDef.getMethods()) {
methodMappings.put(
new MethodKey(classDef.getName(fromM), methodDef.getName(fromM), methodDef.getDescriptor(fromM)),
methodDef.getName(toM)
);
}
for (FieldDef fieldDef : classDef.getFields()) {
fieldMappings.put(
new FieldKey(classDef.getName(fromM), fieldDef.getName(fromM)),
fieldDef.getName(toM)
);
}
}
}
try (UnpickV2Reader reader = new UnpickV2Reader(new FileInputStream(getParameters().getInput().getAsFile().get()))) {
UnpickV2Writer writer = new UnpickV2Writer();
reader.accept(new UnpickV2Remapper(classMappings, methodMappings, fieldMappings, writer));
FileUtil.write(output, UnpickUtil.getLfOutput(writer));
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
}

View File

@ -0,0 +1,49 @@
package net.fabricmc.filament.util;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
public final class FileUtil {
private FileUtil() {
}
public static Set<Path> toPaths(Iterable<File> files) {
Set<Path> set = files instanceof Collection<File> c ? new HashSet<>(c.size()) : new HashSet<>();
for (File file : files) {
set.add(file.toPath());
}
return set;
}
public static void deleteDirectory(File directory) throws IOException {
for (File child : directory.listFiles()) {
if (child.isDirectory()) {
deleteDirectory(child);
} else {
if (!child.delete()) {
throw new IOException("Could not delete file " + child.getName() + " in directory " + directory.getAbsolutePath());
}
}
}
if (!directory.delete()) {
throw new IOException("Could not delete directory " + directory.getAbsolutePath());
}
}
public static void deleteIfExists(File file) throws IOException {
Files.deleteIfExists(file.toPath());
}
public static void write(File file, String content) throws IOException {
Files.writeString(file.toPath(), content, StandardCharsets.UTF_8);
}
}

View File

@ -0,0 +1,19 @@
package net.fabricmc.filament.util;
import daomephsta.unpick.constantmappers.datadriven.parser.v2.UnpickV2Writer;
public final class UnpickUtil {
private UnpickUtil() {
}
/**
* Gets the output of the writer with all {@linkplain System#lineSeparator() system line separators}
* replaced with {@code \n}.
*
* @param writer the writer
* @return the output using LF as the line separator
*/
public static String getLfOutput(UnpickV2Writer writer) {
return writer.getOutput().replace(System.lineSeparator(), "\n");
}
}

View File

@ -0,0 +1,16 @@
The tests for Filament are mostly Gradle projects that are automatically tested using JUnit 5.
## Structure
### `/projects/sharedData`
Data files shared between tests. This includes large files like a build of Yarn mappings.
### `/projects/javadocLint`
Test project for the `javadocLint` task (`JavadocLintTask`).
### `/projects/unpickDef`
Test project for the `combineUnpickDefinitions` and `remapUnpickDefinitionsIntermediary` tasks
(`CombineUnpickDefinitionsTask` and `RemapUnpickDefinitionsTask`).

View File

@ -0,0 +1,60 @@
package net.fabricmc.filament.test;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.File;
import java.io.IOException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import net.fabricmc.filament.util.FileUtil;
class FileUtilTest {
@TempDir
protected File directory;
@Test
void deleteIfExists() throws IOException {
File file = new File(directory, "some-file.txt");
file.createNewFile();
FileUtil.deleteIfExists(file);
assertThat(file).doesNotExist();
}
@Test
void deleteIfExistsMissing() throws IOException {
File file = new File(directory, "missing-file.txt");
FileUtil.deleteIfExists(file);
assertThat(file).doesNotExist();
}
@Test
void write() throws IOException {
File file = new File(directory, "some-file.txt");
FileUtil.write(file, "Hello, world!");
assertThat(file).hasContent("Hello, world!");
}
@Test
void deleteDirectory() throws IOException {
for (int i = 0; i < 5; i++) {
File file = new File(directory, "file-" + i + ".txt");
file.createNewFile();
}
File subdirectory = new File(directory, "subdirectory");
subdirectory.mkdir();
for (int i = 0; i < 3; i++) {
File file = new File(subdirectory, "file-" + i + ".txt");
file.createNewFile();
}
FileUtil.deleteDirectory(directory);
assertThat(directory).doesNotExist();
}
}

View File

@ -0,0 +1,68 @@
package net.fabricmc.filament.test;
import static org.assertj.core.api.Assertions.assertThat;
import org.gradle.testkit.runner.BuildResult;
import org.gradle.testkit.runner.GradleRunner;
import org.gradle.testkit.runner.TaskOutcome;
import org.junit.jupiter.api.Test;
class JavadocLintTest extends ProjectTest {
private BuildResult runGradleBuild(boolean shouldSucceed) {
GradleRunner runner = GradleRunner.create()
.withPluginClasspath()
.withProjectDir(projectDirectory)
.withArguments("javadocLint");
return shouldSucceed ? runner.build() : runner.buildAndFail();
}
@Test
public void paramInMethod() {
setupProject("javadocLint", "mappings/ParamInMethod.mapping");
BuildResult result = runGradleBuild(false);
assertThat(result.task(":javadocLint").getOutcome()).isEqualTo(TaskOutcome.FAILED);
assertThat(result.getOutput()).contains("method javadoc contains parameter docs");
}
@Test
public void periodInParam() {
setupProject("javadocLint", "mappings/ParamPeriod.mapping");
BuildResult result = runGradleBuild(false);
assertThat(result.task(":javadocLint").getOutcome()).isEqualTo(TaskOutcome.FAILED);
assertThat(result.getOutput()).contains("parameter javadoc ends with '.'");
}
@Test
public void uppercaseParam() {
setupProject("javadocLint", "mappings/UppercaseParam.mapping");
BuildResult result = runGradleBuild(false);
assertThat(result.task(":javadocLint").getOutcome()).isEqualTo(TaskOutcome.FAILED);
assertThat(result.getOutput()).contains("parameter javadoc starts with uppercase word 'The'");
}
@Test
public void multipleErrors() {
setupProject(
"javadocLint",
"mappings/ParamInMethod.mapping",
"mappings/ParamPeriod.mapping",
"mappings/UppercaseParam.mapping"
);
BuildResult result = runGradleBuild(false);
assertThat(result.task(":javadocLint").getOutcome()).isEqualTo(TaskOutcome.FAILED);
assertThat(result.getOutput()).contains("Found 3 javadoc format errors");
}
@Test
public void successful() {
setupProject("javadocLint", "mappings/Successful.mapping");
BuildResult result = runGradleBuild(true);
assertThat(result.task(":javadocLint").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
}
}

View File

@ -0,0 +1,59 @@
package net.fabricmc.filament.test;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import org.junit.jupiter.api.io.TempDir;
abstract class ProjectTest {
@TempDir
protected File projectDirectory;
protected final void setupProject(String name, String... extraFiles) {
try {
copyProjectFile(name, "build.gradle");
copyProjectFile(name, "settings.gradle");
for (String file : extraFiles) {
copyProjectFile(name, file);
}
} catch (IOException e) {
throw new UncheckedIOException("Could not set up test for project " + name, e);
}
}
protected final void copyYarnV2Data(String fileName) {
try {
copyProjectFile("sharedData", "yarn-mappings-v2.tiny", fileName);
} catch (IOException e) {
throw new UncheckedIOException("Could not copy Yarn mapping data to " + fileName, e);
}
}
protected final InputStream getProjectFile(String projectName, String file) {
return ProjectTest.class.getResourceAsStream("/projects/" + projectName + '/' + file);
}
protected final String getProjectFileText(String projectName, String file) throws IOException {
try (InputStream in = getProjectFile(projectName, file)) {
return new String(in.readAllBytes(), StandardCharsets.UTF_8);
}
}
private void copyProjectFile(String projectName, String file) throws IOException {
copyProjectFile(projectName, file, file);
}
private void copyProjectFile(String projectName, String from, String to) throws IOException {
try (InputStream in = getProjectFile(projectName, from)) {
Path target = projectDirectory.toPath().resolve(to);
Files.createDirectories(target.getParent());
Files.copy(in, target);
}
}
}

View File

@ -0,0 +1,34 @@
package net.fabricmc.filament.test;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.File;
import java.io.IOException;
import org.gradle.testkit.runner.BuildResult;
import org.gradle.testkit.runner.GradleRunner;
import org.gradle.testkit.runner.TaskOutcome;
import org.junit.jupiter.api.Test;
class UnpickDefinitionsTest extends ProjectTest {
@Test
void remapUnpickDefinitionsIntermediary() throws IOException {
setupProject(
"unpickDef",
"unpick-definitions/screen_handler_slot_ids.unpick",
"unpick-definitions/set_block_state_flags.unpick"
);
copyYarnV2Data("yarn-mappings-v2.tiny");
BuildResult result = GradleRunner.create()
.withPluginClasspath()
.withProjectDir(projectDirectory)
.withArguments("remapUnpickDefinitionsIntermediary")
.build();
assertThat(result.task(":combineUnpickDefinitions").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.task(":remapUnpickDefinitionsIntermediary").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(new File(projectDirectory, "combined_definitions.unpick")).exists().hasContent(getProjectFileText("unpickDef", "expected_named.unpick"));
assertThat(new File(projectDirectory, "intermediary_definitions.unpick")).exists().hasContent(getProjectFileText("unpickDef", "expected_intermediary.unpick"));
}
}

View File

@ -0,0 +1,7 @@
plugins {
id 'net.fabricmc.filament'
}
javadocLint {
mappingDirectory = file('mappings')
}

View File

@ -0,0 +1,4 @@
CLASS a com/example/ParamInMethod
METHOD a something (Ljava/lang/String;)V
COMMENT @param name the name
ARG 1 name

View File

@ -0,0 +1,4 @@
CLASS b com/example/ParamPeriod
METHOD a something (Ljava/lang/String;)V
ARG 1 name
COMMENT the name.

View File

@ -0,0 +1,6 @@
CLASS d com/example/Successful
METHOD a something (Ljava/lang/String;)V
COMMENT This is a method that does something.
COMMENT @param <T> an unused type
ARG 1 name
COMMENT the name

View File

@ -0,0 +1,4 @@
CLASS c com/example/UppercaseParam
METHOD a something (Ljava/lang/String;)V
ARG 1 name
COMMENT The name

View File

@ -0,0 +1,10 @@
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
maven {
name = "FabricMC"
url = "https://maven.fabricmc.net"
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,13 @@
plugins {
id 'net.fabricmc.filament'
}
combineUnpickDefinitions {
input = file('unpick-definitions')
output = file('combined_definitions.unpick')
}
remapUnpickDefinitionsIntermediary {
output = file('intermediary_definitions.unpick')
mappings = file('yarn-mappings-v2.tiny')
}

View File

@ -0,0 +1,15 @@
v2
constant screen_handler_slot_ids net/minecraft/class_1703 field_30730
target_method net/minecraft/class_1703 method_30010 (IILnet/minecraft/class_1713;Lnet/minecraft/class_1657;)V
param 0 screen_handler_slot_ids
flag set_block_state_flags net/minecraft/class_2248 field_31036
flag set_block_state_flags net/minecraft/class_2248 field_31027
flag set_block_state_flags net/minecraft/class_2248 field_31028
flag set_block_state_flags net/minecraft/class_2248 field_31029
flag set_block_state_flags net/minecraft/class_2248 field_31030
flag set_block_state_flags net/minecraft/class_2248 field_31031
flag set_block_state_flags net/minecraft/class_2248 field_31032
flag set_block_state_flags net/minecraft/class_2248 field_31033
flag set_block_state_flags net/minecraft/class_2248 field_31034
target_method net/minecraft/class_1945 method_8652 (Lnet/minecraft/class_2338;Lnet/minecraft/class_2680;I)Z
param 2 set_block_state_flags

View File

@ -0,0 +1,15 @@
v2
constant screen_handler_slot_ids net/minecraft/screen/ScreenHandler EMPTY_SPACE_SLOT_INDEX
target_method net/minecraft/screen/ScreenHandler internalOnSlotClick (IILnet/minecraft/screen/slot/SlotActionType;Lnet/minecraft/entity/player/PlayerEntity;)V
param 0 screen_handler_slot_ids
flag set_block_state_flags net/minecraft/block/Block NOTIFY_ALL
flag set_block_state_flags net/minecraft/block/Block NOTIFY_NEIGHBORS
flag set_block_state_flags net/minecraft/block/Block NOTIFY_LISTENERS
flag set_block_state_flags net/minecraft/block/Block NO_REDRAW
flag set_block_state_flags net/minecraft/block/Block REDRAW_ON_MAIN_THREAD
flag set_block_state_flags net/minecraft/block/Block FORCE_STATE
flag set_block_state_flags net/minecraft/block/Block SKIP_DROPS
flag set_block_state_flags net/minecraft/block/Block MOVED
flag set_block_state_flags net/minecraft/block/Block SKIP_LIGHTING_UPDATES
target_method net/minecraft/world/ModifiableWorld setBlockState (Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;I)Z
param 2 set_block_state_flags

View File

@ -0,0 +1,10 @@
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
maven {
name = "FabricMC"
url = "https://maven.fabricmc.net"
}
}
}

View File

@ -0,0 +1,6 @@
v2
constant screen_handler_slot_ids net/minecraft/screen/ScreenHandler EMPTY_SPACE_SLOT_INDEX
target_method net/minecraft/screen/ScreenHandler internalOnSlotClick (IILnet/minecraft/screen/slot/SlotActionType;Lnet/minecraft/entity/player/PlayerEntity;)V
param 0 screen_handler_slot_ids

View File

@ -0,0 +1,14 @@
v2
flag set_block_state_flags net/minecraft/block/Block NOTIFY_ALL
flag set_block_state_flags net/minecraft/block/Block NOTIFY_NEIGHBORS
flag set_block_state_flags net/minecraft/block/Block NOTIFY_LISTENERS
flag set_block_state_flags net/minecraft/block/Block NO_REDRAW
flag set_block_state_flags net/minecraft/block/Block REDRAW_ON_MAIN_THREAD
flag set_block_state_flags net/minecraft/block/Block FORCE_STATE
flag set_block_state_flags net/minecraft/block/Block SKIP_DROPS
flag set_block_state_flags net/minecraft/block/Block MOVED
flag set_block_state_flags net/minecraft/block/Block SKIP_LIGHTING_UPDATES
target_method net/minecraft/world/ModifiableWorld setBlockState (Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;I)Z
param 2 set_block_state_flags

View File

@ -13,3 +13,8 @@ asm_version=9.3
fabric_loader_version=0.13.3
jetbrains_annotations_version=23.0.0
mappingpoet_version=0.3.0
# Build logic
tiny_mappings_parser_version=0.3.0+build.17
junit_version=5.7.1
assertj_version=3.19.0

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -14,4 +14,6 @@ if (!JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_17)) {
throw new UnsupportedOperationException("Yarn's buildscript requires Java 17 or higher.")
}
rootProject.name = "yarn"
rootProject.name = "yarn"
includeBuild 'filament'